diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp index 76a3cc62b7..7adcbb9497 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp @@ -266,8 +266,13 @@ GlobalSettings::GlobalSettings() PA_ADD_OPTION(STATS_FILE); PA_ADD_OPTION(TEMP_FOLDER); PA_ADD_OPTION(THEME); - PA_ADD_OPTION(USE_PADDLE_OCR); PA_ADD_OPTION(USE_GPU_FOR_ML_INFERENCE); + + // gated behind Dev mode. see GlobalSettings::load_json + PA_ADD_OPTION(USE_PADDLE_OCR); + PA_ADD_OPTION(RESOURCE_DOWNLOAD_TABLE); + PA_ADD_OPTION(DOWNLOAD_ERROR); + PA_ADD_OPTION(WINDOW_SIZE); PA_ADD_OPTION(LOG_WINDOW_SIZE); PA_ADD_OPTION(LOG_WINDOW_STARTUP); @@ -289,6 +294,8 @@ GlobalSettings::GlobalSettings() PA_ADD_STATIC(m_advanced_options); PA_ADD_OPTION(LOG_EVERYTHING); PA_ADD_OPTION(SAVE_DEBUG_IMAGES); + + // gated behind Dev mode. see GlobalSettings::load_json PA_ADD_OPTION(SAVE_DEBUG_VIDEOS_ON_SWITCH); // PA_ADD_OPTION(NAUGHTY_MODE); // PA_ADD_OPTION(HIDE_NOTIF_DISCORD_LINK); @@ -298,6 +305,8 @@ GlobalSettings::GlobalSettings() PA_ADD_OPTION(AUDIO_PIPELINE); PA_ADD_OPTION(VIDEO_PIPELINE); PA_ADD_OPTION(COMMAND_QUEUE_LIMIT); + + // gated behind Dev mode. see GlobalSettings::load_json PA_ADD_OPTION(DEVICE_LOGGING_FLAG); PA_ADD_OPTION(ENABLE_LIFETIME_SANITIZER0); @@ -327,6 +336,8 @@ void GlobalSettings::load_json(const JsonValue& json){ ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN; USE_PADDLE_OCR.set_visibility(devmode_visibility); + RESOURCE_DOWNLOAD_TABLE.set_visibility(devmode_visibility); + DOWNLOAD_ERROR.set_visibility(devmode_visibility); SAVE_DEBUG_VIDEOS_ON_SWITCH.set_visibility(devmode_visibility); DEVICE_LOGGING_FLAG.set_visibility(devmode_visibility); @@ -451,9 +462,9 @@ void GlobalSettings::on_press(){ QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(RUNTIME_BASE_PATH()))); } - - - +void GlobalSettings::connect_row_with_download(const std::string& resource_slug, std::shared_ptr& download_ptr){ + RESOURCE_DOWNLOAD_TABLE.connect_row_with_download(resource_slug, download_ptr); +} GlobalSettings_Descriptor::GlobalSettings_Descriptor() diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h index df5a1f4bbe..774e25fd99 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h @@ -17,6 +17,8 @@ #include "Common/Cpp/Options/ButtonOption.h" #include "CommonFramework/Panels/SettingsPanel.h" #include "CommonFramework/Panels/PanelTools.h" +#include "CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h" +#include "CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h" //#include //using std::cout; @@ -37,7 +39,7 @@ class PerformanceOptions; class AudioPipelineOptions; class VideoPipelineOptions; class ErrorReportOption; - +class ResourceDownload; @@ -111,6 +113,8 @@ class GlobalSettings : public BatchOption, private ConfigOption::Listener, priva virtual void load_json(const JsonValue& json) override; virtual JsonValue to_json() const override; + void connect_row_with_download(const std::string& resource_slug, std::shared_ptr& download_ptr); + private: virtual void on_config_value_changed(void* object) override; virtual void on_press() override; @@ -125,6 +129,8 @@ class GlobalSettings : public BatchOption, private ConfigOption::Listener, priva Pimpl THEME; BooleanCheckBoxOption USE_PADDLE_OCR; BooleanCheckBoxOption USE_GPU_FOR_ML_INFERENCE; + SettingsResourceDownloadTable RESOURCE_DOWNLOAD_TABLE; + SettingsDownloadError DOWNLOAD_ERROR; Pimpl WINDOW_SIZE; Pimpl LOG_WINDOW_SIZE; BooleanCheckBoxOption LOG_WINDOW_STARTUP; diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp index bd804194a6..c5a269d5e1 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp @@ -36,7 +36,7 @@ std::shared_ptr GlobalResourceDownloadManager::add_to_download auto download_ptr = m_download_queue.emplace_back(std::make_shared(*this, std::move(resource_metadata), m_queue_lock, m_cv)); download_ptr->add_listener(*this); - // GlobalSettings::instance().connect_row_with_download(resource_slug, download_ptr); TODO: re-enable this + GlobalSettings::instance().connect_row_with_download(resource_slug, download_ptr); download_ptr->start_download(); return download_ptr; diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h index 28b447f450..ed7c413d7b 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h @@ -8,7 +8,6 @@ #define PokemonAutomation_ResourceDownloadManager_H #include -#include "Common/Cpp/Concurrency/SpinLock.h" #include "Common/Cpp/Concurrency/Mutex.h" #include "Common/Cpp/Concurrency/ConditionVariable.h" #include "Common/Cpp/ListenerSet.h" diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp index 198d75d34e..399bd4c40a 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp @@ -79,7 +79,7 @@ JsonValue fetch_resource_download_list_json_from_remote(){ JsonValue json = FileDownloader::download_json_file( logger, - "https://raw.githubusercontent.com/jw098/Packages/refs/heads/download/Resources/ResourceDownloadList.json" + "https://raw.githubusercontent.com/PokemonAutomation/Packages/refs/heads/master/Resources/ResourceDownloadList.json" ); return json; diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp new file mode 100644 index 0000000000..be0bb40ee0 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp @@ -0,0 +1,138 @@ +/* Resource Download Options + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "SettingsResourceDownloadRow.h" +#include "SettingsResourceDownloadOptions.h" + + +// #include +// using std::cout; +// using std::endl; + +namespace PokemonAutomation{ + + + +void SettingsResourceButton::add_button_listener(Listener& listener){ + m_listeners.add(listener); +} +void SettingsResourceButton::remove_button_listener(Listener& listener){ + m_listeners.remove(listener); +} + +void SettingsResourceButton::change_text(const std::string& text){ + m_listeners.run_method(&Listener::on_change_text, text); +} + + +////////////////////////////////////// +// SettingsResourceDownloadButton +////////////////////////////////////// +// SettingsResourceDownloadButton::~SettingsResourceDownloadButton(){} +SettingsResourceDownloadButton::SettingsResourceDownloadButton(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) + , m_enabled(true) +{} + +void SettingsResourceDownloadButton::set_enabled(bool enabled){ + m_enabled = enabled; + change_text(enabled ? "Download" : "Downloading..."); + set_visibility(enabled ? ConfigOptionState::ENABLED : ConfigOptionState::DISABLED); +} + + +////////////////////////////////////// +// SettingsResourceDeleteButton +////////////////////////////////////// +SettingsResourceDeleteButton::SettingsResourceDeleteButton(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) + , m_enabled(true) +{} + +void SettingsResourceDeleteButton::set_enabled(bool enabled){ + m_enabled = enabled; + change_text(enabled ? "Delete" : "Deleting..."); + set_visibility(enabled ? ConfigOptionState::ENABLED : ConfigOptionState::DISABLED); +} + +////////////////////////////////////// +// SettingsResourceCancelButton +////////////////////////////////////// +SettingsResourceCancelButton::SettingsResourceCancelButton(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) + , m_enabled(true) +{} + +void SettingsResourceCancelButton::set_enabled(bool enabled){ + m_enabled = enabled; + change_text(enabled ? "Cancel" : "Cancelling..."); + set_visibility(enabled ? ConfigOptionState::ENABLED : ConfigOptionState::DISABLED); +} +////////////////////////////////////// +// SettingsResourceProgressBar +////////////////////////////////////// +SettingsResourceProgressBar::SettingsResourceProgressBar(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) +{} + +void SettingsResourceProgressBar::add_progress_listener(Listener& listener){ + m_listeners.add(listener); +} +void SettingsResourceProgressBar::remove_progress_listener(Listener& listener){ + m_listeners.remove(listener); +} + +void SettingsResourceProgressBar::change_text(const std::string& text){ + m_listeners.run_method(&Listener::on_change_text, text); +} + +void SettingsResourceProgressBar::update_progress(uint64_t bytes_done, uint64_t total_bytes){ + m_listeners.run_method(&Listener::on_update_progress, bytes_done, total_bytes); +} + +void SettingsResourceProgressBar::reset_progress(){ + m_listeners.run_method(&Listener::on_reset_progress); +} + +//////////////////////////////// +// ResourceDownload::Listener +//////////////////////////////// +void SettingsResourceProgressBar::on_download_progress(uint64_t bytes_done, uint64_t total_bytes){ + change_text("Downloading"); + set_visibility(ConfigOptionState::ENABLED); + update_progress(bytes_done, total_bytes); +} +void SettingsResourceProgressBar::on_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){ + change_text("Unzipping"); + set_visibility(ConfigOptionState::ENABLED); + update_progress(bytes_done, total_bytes); +} +void SettingsResourceProgressBar::on_hash_progress(uint64_t bytes_done, uint64_t total_bytes){ + change_text("Verifying"); + set_visibility(ConfigOptionState::ENABLED); + update_progress(bytes_done, total_bytes); +} + +////////////////////////////////////// +// SettingsDownloadPopup +////////////////////////////////////// +SettingsDownloadPopup::SettingsDownloadPopup(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::LOCK_WHILE_RUNNING) + , row(p_row) +{} + +////////////////////////////////////// +// SettingsDownloadError +////////////////////////////////////// +SettingsDownloadError::SettingsDownloadError() + : ConfigOptionImpl(LockMode::LOCK_WHILE_RUNNING) +{} + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h new file mode 100644 index 0000000000..2e265cfc84 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h @@ -0,0 +1,131 @@ +/* Resource Download Options + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_ResourceDownloadOptions_H +#define PokemonAutomation_ResourceDownloadOptions_H + +#include "Common/Cpp/ListenerSet.h" +#include "ResourceDownload.h" + +namespace PokemonAutomation{ + +class SettingsResourceDownloadRow; + + +class SettingsResourceButton { +public: + struct Listener{ + virtual void on_change_text(const std::string& text){} + }; + + void add_button_listener(Listener& listener); + void remove_button_listener(Listener& listener); + + void change_text(const std::string& text); + +private: + ListenerSet m_listeners; + +}; + +class SettingsResourceDownloadButton : public ConfigOptionImpl, public SettingsResourceButton{ +public: + // ~SettingsResourceDownloadButton(); + SettingsResourceDownloadButton(SettingsResourceDownloadRow& p_row); + +public: + inline bool get_enabled(){ return m_enabled; } + void set_enabled(bool enabled); + +public: + SettingsResourceDownloadRow& row; + +private: + bool m_enabled; // button should be blocked during an active task. m_enabled is false when blocked + + + +}; + +class SettingsResourceDeleteButton : public ConfigOptionImpl, public SettingsResourceButton{ +public: + SettingsResourceDeleteButton(SettingsResourceDownloadRow& p_row); + +public: + inline bool get_enabled(){ return m_enabled; } + void set_enabled(bool enabled); + +public: + SettingsResourceDownloadRow& row; + +private: + bool m_enabled; +}; + +class SettingsResourceCancelButton : public ConfigOptionImpl, public SettingsResourceButton{ +public: + SettingsResourceCancelButton(SettingsResourceDownloadRow& p_row); + +public: + inline bool get_enabled(){ return m_enabled; } + void set_enabled(bool enabled); + +public: + SettingsResourceDownloadRow& row; + +private: + bool m_enabled; +}; + +class SettingsResourceProgressBar : public ConfigOptionImpl, public ResourceDownload::Listener{ +public: + SettingsResourceProgressBar(SettingsResourceDownloadRow& p_row); + +public: + struct Listener{ + virtual void on_change_text(const std::string& text){} + virtual void on_update_progress(uint64_t bytes_done, uint64_t total_bytes){} + virtual void on_reset_progress(){} + }; + + void add_progress_listener(Listener& listener); + void remove_progress_listener(Listener& listener); + + void change_text(const std::string& text); + void update_progress(uint64_t bytes_done, uint64_t total_bytes); + void reset_progress(); + +public: // ResourceDownload::Listener + virtual void on_download_progress(uint64_t bytes_done, uint64_t total_bytes) override; + virtual void on_unzip_progress(uint64_t bytes_done, uint64_t total_bytes) override; + virtual void on_hash_progress(uint64_t bytes_done, uint64_t total_bytes) override; + +public: + SettingsResourceDownloadRow& row; + +private: + ListenerSet m_listeners; + +}; + +class SettingsDownloadPopup : public ConfigOptionImpl, public ResourceDownload::Listener{ +public: + SettingsDownloadPopup(SettingsResourceDownloadRow& p_row); +public: + SettingsResourceDownloadRow& row; + +}; + +class SettingsDownloadError : public ConfigOptionImpl{ +public: + SettingsDownloadError(); + + +}; + + +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.cpp new file mode 100644 index 0000000000..5bd49776a3 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.cpp @@ -0,0 +1,526 @@ +/* Resource Download Row + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Globals.h" +#include "Common/Cpp/Containers/Pimpl.tpp" +#include "Common/Cpp/PrettyPrint.h" +#include "Common/Cpp/ListenerSet.h" +#include "Common/Cpp/Concurrency/AsyncTask.h" +#include "Common/Cpp/Filesystem.h" +#include "Common/Cpp/ScopeExit.h" +#include "CommonFramework/Options/LabelCellOption.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Logging/Logger.h" +#include "CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h" +#include "ResourceDownloadHelpers.h" +// #include "SettingsResourceDownloadTable.h" +#include "SettingsResourceDownloadRow.h" + +// #include +// #include +#include + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + namespace fs = std::filesystem; + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsResourceDownloadRow +///////////////////////////////////////////////////////////////////////////////////////////////////////// + + +std::string resource_version_to_string(ResourceVersionStatus version){ + switch(version){ + case ResourceVersionStatus::CURRENT: + return "Current"; + case ResourceVersionStatus::OUTDATED: + return "Outdated"; + case ResourceVersionStatus::NOT_APPLICABLE: + return "--"; + case ResourceVersionStatus::FUTURE_VERSION: + return "Unsupported future version.
Please update the Computer Control program."; + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "resource_version_to_string: Unknown enum."); + } +} + +std::string is_downloaded_string(bool is_downloaded){ + return is_downloaded ? "Yes" : "--"; +} + +struct SettingsResourceDownloadRow::Data{ + Data( + DownloadedResourceMetadata expected_metadata, + bool is_downloaded, + std::optional version_num, + ResourceVersionStatus version_status + ) + : m_local_metadata(expected_metadata) + , m_action_state(ActionState::READY) + , m_resource_slug(expected_metadata.resource_name) + , m_resource_name_label(LockMode::LOCK_WHILE_RUNNING, m_resource_slug) + , m_file_size(expected_metadata.size_decompressed_bytes) + , m_file_size_label(LockMode::LOCK_WHILE_RUNNING, tostr_bytes(m_file_size)) + , m_is_downloaded(is_downloaded) + , m_is_downloaded_label(LockMode::LOCK_WHILE_RUNNING, is_downloaded_string(is_downloaded)) + , m_version_num(version_num) + , m_version_status(version_status) + , m_version_status_label(LockMode::LOCK_WHILE_RUNNING, resource_version_to_string(version_status)) + {} + + + + DownloadedResourceMetadata m_local_metadata; + + ActionState m_action_state; + + std::string m_resource_slug; + LabelCellOption m_resource_name_label; + + size_t m_file_size; + LabelCellOption m_file_size_label; + + bool m_is_downloaded; + LabelCellOption m_is_downloaded_label; + + std::optional m_version_num; + ResourceVersionStatus m_version_status; + LabelCellOption m_version_status_label; + + AsyncTask m_pre_download_thread; + AsyncTask m_delete_thread; + + std::shared_ptr m_download_ptr; + + std::optional m_cached_metadata; + + Mutex m_action_state_lock; + + + ListenerSet listeners; + + + +}; + +void SettingsResourceDownloadRow::set_version_status(ResourceVersionStatus version_status){ + m_data->m_version_status = version_status; + m_data->m_version_status_label.set_text(resource_version_to_string(version_status)); +} + +std::string SettingsResourceDownloadRow::get_resource_slug(){ + return m_data->m_resource_slug; +} + +void SettingsResourceDownloadRow::set_is_downloaded(bool is_downloaded){ + m_data->m_is_downloaded = is_downloaded; + m_data->m_is_downloaded_label.set_text(is_downloaded_string(is_downloaded)); +} + +void SettingsResourceDownloadRow::update_table_label(bool success){ + set_is_downloaded(success); + set_version_status(success ? ResourceVersionStatus::CURRENT : ResourceVersionStatus::NOT_APPLICABLE); +} + + + +SettingsResourceDownloadRow::~SettingsResourceDownloadRow(){ + // cout << "~SettingsResourceDownloadRow" << endl; + if (m_data->m_download_ptr) { + m_data->m_download_ptr->remove_listener(*this); + } + m_data->m_pre_download_thread.wait_and_ignore_exceptions(); + m_data->m_delete_thread.wait_and_ignore_exceptions(); +} +SettingsResourceDownloadRow::SettingsResourceDownloadRow( + DownloadedResourceMetadata local_metadata, + bool is_downloaded, + std::optional version_num, + ResourceVersionStatus version_status +) + : StaticTableRow(local_metadata.resource_name) + , m_data(CONSTRUCT_TOKEN, local_metadata, is_downloaded, version_num, version_status) + , m_download_button(*this) + , m_delete_button(*this) + , m_cancel_button(*this) + , m_progress_bar(*this) + , m_popup(*this) +{ + PA_ADD_STATIC(m_data->m_resource_name_label); + PA_ADD_STATIC(m_data->m_file_size_label); + PA_ADD_STATIC(m_data->m_is_downloaded_label); + PA_ADD_STATIC(m_data->m_version_status_label); + + PA_ADD_STATIC(m_download_button); + PA_ADD_STATIC(m_delete_button); + PA_ADD_STATIC(m_cancel_button); + PA_ADD_STATIC(m_progress_bar); + PA_ADD_STATIC(m_popup); + + update_action_state(ActionState::READY); +} + + + +const DownloadedResourceMetadata& SettingsResourceDownloadRow::fetch_remote_metadata(){ + + if (m_data->m_cached_metadata.has_value()){ + return m_data->m_cached_metadata.value(); + } + + Logger& logger = global_logger_tagged(); + std::vector all_remote_metadata; + + try{ + all_remote_metadata = remote_resource_download_list(); + }catch(OperationFailedException&){ + std::cerr << "SettingsResourceDownloadRow::fetch_remote_metadata: Error" << endl; + throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, + "Error: Download failed. Failed to fetch the list of available downloads. Check your internet connection."); + } + + std::string resource_name = m_data->m_resource_name_label.text(); + + for (const DownloadedResourceMetadata& remote_metadata : all_remote_metadata){ + if (remote_metadata.resource_name == resource_name){ + m_data->m_cached_metadata = remote_metadata; + return m_data->m_cached_metadata.value(); + } + } + + // if corresponding remote_metadata not found + throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, + "fetch_remote_metadata: Resource no longer available for download. We recommend updating the Computer Control program."); + +} + + + +void SettingsResourceDownloadRow::ensure_remote_metadata_loaded(){ + m_data->m_pre_download_thread = GlobalThreadPools::unlimited_normal().dispatch_now_blocking( + [this]{ + try { + if (!is_given_action_state(ActionState::PRE_DOWNLOAD)){ + return; + } + + + bool success = false; + ScopeExit on_exit([&success, this]{ + if (!success){ + update_action_state(ActionState::READY); + } + }); + + // std::this_thread::sleep_for(std::chrono::seconds(1)); + std::string predownload_warning; + try { + const DownloadedResourceMetadata& remote_metadata = fetch_remote_metadata(); + predownload_warning = predownload_warning_summary(remote_metadata); + }catch(OperationFailedException& e){ + predownload_warning = e.message(); + } + // cout << "Fetched remote metadata" << endl; + // throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "testing"); + // Logger& logger = global_logger_tagged(); + // throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, "test"); + + report_metadata_fetch_finished(predownload_warning); + success = true; + + }catch(OperationFailedException&){ + // cout << "failed" << endl; + GlobalResourceDownloadManager::instance().report_download_failed(m_data->m_resource_slug); + return; + }catch(...){ + // cout << "Exception thrown in thread" << endl; + GlobalResourceDownloadManager::instance().report_unexpected_exception_caught("Error: SettingsResourceDownloadButton::ensure_remote_metadata_loaded: Unknown exception. Report this as an error."); + return; + } + + } + ); + +} + +std::string SettingsResourceDownloadRow::predownload_warning_summary(const DownloadedResourceMetadata& remote_metadata){ + + std::string predownload_warning; + + uint16_t local_version_num = m_data->m_local_metadata.version_num.value(); + + uint16_t remote_version_num = remote_metadata.version_num.value(); + size_t compressed_size = remote_metadata.size_compressed_bytes; + size_t decompressed_size = remote_metadata.size_decompressed_bytes; + + std::string disk_space_requirement = "This will require " + tostr_bytes(decompressed_size + compressed_size) + " of free space"; + + if (local_version_num < remote_version_num){ + predownload_warning = "The resource you are downloading is a more updated version than the program expects. " + "This may or may not cause issues with the programs. " + "We recommend updating the Computer Control program.
" + + disk_space_requirement; + }else if (local_version_num == remote_version_num){ + predownload_warning = "Update available.
" + disk_space_requirement; + }else if (local_version_num > remote_version_num){ + predownload_warning = "The resource you are downloading is a less updated version than the program expects. " + "Please report this as a bug.
" + + disk_space_requirement; + } + + return predownload_warning; +} + + + +void SettingsResourceDownloadRow::start_download(){ + if (!is_given_action_state(ActionState::PRE_DOWNLOAD)){ + return; + } + + update_action_state(ActionState::DOWNLOADING); + + cancel_download_thread(); // cancels old download thread + + try{ + std::shared_ptr download_ptr = GlobalResourceDownloadManager::instance().add_to_download_list(m_data->m_resource_slug); + + }catch(OperationFailedException&){ + update_action_state(ActionState::READY); + } + +} + + +void SettingsResourceDownloadRow::start_delete(){ + m_data->m_delete_thread = GlobalThreadPools::unlimited_normal().dispatch_now_blocking( + [this]{ + try { + ScopeExit on_exit([this]{ + update_table_label(false); + update_action_state(ActionState::READY); + }); + + // throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "test."); + // Logger& logger = global_logger_tagged(); + // throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, "test"); + + if (!is_given_action_state(ActionState::PRE_DELETE)){ + return; + } + update_action_state(ActionState::DELETING); + + std::string resource_name = m_data->m_local_metadata.resource_name; + + std::string resource_directory = DOWNLOADED_RESOURCE_PATH() + resource_name; + // delete directory and the old resource + fs::remove_all(Filesystem::Path(resource_directory)); + + }catch(OperationFailedException& e){ + std::cerr << e.message() << endl; + GlobalResourceDownloadManager::instance().report_unexpected_exception_caught( + "Error: SettingsResourceDownloadButton::start_delete: Unexpected OperationFailedException exception. Report this as an error."); + return; + }catch(...){ + GlobalResourceDownloadManager::instance().report_unexpected_exception_caught( + "Error: SettingsResourceDownloadButton::start_delete: Unknown exception. Report this as an error."); + return; + } + } + ); + +} + + +void SettingsResourceDownloadRow::update_action_state(ActionState state){ + std::lock_guard lock(m_data->m_action_state_lock); + { + switch (state){ + case ActionState::PRE_DOWNLOAD: + // action state can only enter the PRE_DOWNLOAD state + // if going from the READY state + if (m_data->m_action_state == ActionState::READY){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(true); + m_progress_bar.change_text("Downloading"); + m_progress_bar.set_visibility(ConfigOptionState::ENABLED); + m_data->m_action_state = state; + cout << "ActionState::PRE_DOWNLOAD" << endl; + } + break; + case ActionState::DOWNLOADING: + if (m_data->m_action_state == ActionState::PRE_DOWNLOAD || m_data->m_action_state == ActionState::PRE_CANCEL || m_data->m_action_state == ActionState::READY){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(true); + m_progress_bar.change_text("Downloading"); + m_progress_bar.set_visibility(ConfigOptionState::ENABLED); + m_data->m_action_state = state; + cout << "ActionState::DOWNLOADING" << endl; + } + break; + case ActionState::PRE_DELETE: + // action state can only enter the PRE_DELETE state + // if going from the READY state + if (m_data->m_action_state == ActionState::READY){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + m_progress_bar.reset_progress(); + m_data->m_action_state = state; + cout << "ActionState::PRE_DELETE" << endl; + } + break; + case ActionState::DELETING: + // action state can only enter the DELETING state + // if going from the PRE_DELETE state + if (m_data->m_action_state == ActionState::PRE_DELETE){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + m_progress_bar.reset_progress(); + m_data->m_action_state = state; + cout << "ActionState::DELETING" << endl; + } + break; + case ActionState::PRE_CANCEL: + // action state can only enter the PRE_CANCEL state + // if going from the DOWNLOADING state + if (m_data->m_action_state == ActionState::DOWNLOADING){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + m_progress_bar.reset_progress(); + m_data->m_action_state = state; + cout << "ActionState::PRE_CANCEL" << endl; + } + break; + case ActionState::CANCELLING: + // action state can only enter the CANCELLING state + // if going from the PRE_CANCEL state + if (m_data->m_action_state == ActionState::PRE_CANCEL){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + m_progress_bar.reset_progress(); + m_data->m_action_state = state; + cout << "ActionState::CANCELLING" << endl; + } + break; + case ActionState::READY: + m_download_button.set_enabled(true); + m_delete_button.set_enabled(true); + m_cancel_button.set_enabled(true); + m_progress_bar.change_text(""); + m_progress_bar.set_visibility(ConfigOptionState::HIDDEN); + m_progress_bar.reset_progress(); + m_data->m_action_state = state; + cout << "ActionState::READY" << endl; + break; + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "update_action_state: Unknown enum."); + } + } + + report_action_state_updated(); +} + +ActionState SettingsResourceDownloadRow::get_action_state(){ + std::lock_guard lock(m_data->m_action_state_lock); + return m_data->m_action_state; +} + +bool SettingsResourceDownloadRow::is_given_action_state(ActionState state){ + std::lock_guard lock(m_data->m_action_state_lock); + return m_data->m_action_state == state; +} + +void SettingsResourceDownloadRow::cancel_download_thread(){ + if (m_data->m_download_ptr){ // if download is active + m_data->m_download_ptr->cancel_download(); + } +} + + +void SettingsResourceDownloadRow::connect_with_download(std::shared_ptr download_ptr){ + if (m_data->m_download_ptr){ + m_data->m_download_ptr->remove_listener(*this); + } + m_data->m_download_ptr = std::move(download_ptr); + m_data->m_download_ptr->add_listener(*this); + m_data->m_download_ptr->add_listener(m_progress_bar); + update_action_state(ActionState::DOWNLOADING); +} + + +///////////////////////// +// Listener +///////////////////////// +void SettingsResourceDownloadRow::add_listener(Listener& listener){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.add(listener); +} +void SettingsResourceDownloadRow::remove_listener(Listener& listener){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.remove(listener); +} + +void SettingsResourceDownloadRow::report_download_progress(uint64_t bytes_done, uint64_t total_bytes){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.run_method(&Listener::on_download_progress, bytes_done, total_bytes); +} +void SettingsResourceDownloadRow::report_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.run_method(&Listener::on_unzip_progress, bytes_done, total_bytes); +} +void SettingsResourceDownloadRow::report_hash_progress(uint64_t bytes_done, uint64_t total_bytes){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.run_method(&Listener::on_hash_progress, bytes_done, total_bytes); +} + +void SettingsResourceDownloadRow::report_metadata_fetch_finished(const std::string& popup_message){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.run_method(&Listener::on_metadata_fetch_finished, popup_message); +} + +void SettingsResourceDownloadRow::report_action_state_updated(){ + auto scope = m_lifetime_sanitizer.check_scope(); + m_data->listeners.run_method(&Listener::on_action_state_updated); +} + + +//////////////////////////////// +// ResourceDownload::Listener +//////////////////////////////// +void SettingsResourceDownloadRow::on_download_progress(uint64_t bytes_done, uint64_t total_bytes){ + report_download_progress(bytes_done, total_bytes); +} +void SettingsResourceDownloadRow::on_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){ + report_unzip_progress(bytes_done, total_bytes); +} +void SettingsResourceDownloadRow::on_hash_progress(uint64_t bytes_done, uint64_t total_bytes){ + report_hash_progress(bytes_done, total_bytes); +} + +void SettingsResourceDownloadRow::on_download_finished(bool success, const std::string& resource_slug){ + // we can't run `download_ptr->remove_listener(*this)` in this function + // since it results in deadlock, since this function is part of the listener loop + m_data->m_download_ptr.reset(); + + update_action_state(ActionState::READY); + update_table_label(success); +} + + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.h new file mode 100644 index 0000000000..64a0a1ef0a --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.h @@ -0,0 +1,118 @@ +/* Resource Download Row + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_ResourceDownloadRow_H +#define PokemonAutomation_ResourceDownloadRow_H + +#include "Common/Cpp/Containers/Pimpl.h" +#include "Common/Cpp/LifetimeSanitizer.h" +#include "Common/Cpp/Options/StaticTableOption.h" +#include "ResourceDownload.h" +#include "SettingsResourceDownloadOptions.h" + +namespace PokemonAutomation{ + + +enum class ActionState{ + PRE_DOWNLOAD, + DOWNLOADING, + PRE_DELETE, + DELETING, + PRE_CANCEL, + CANCELLING, + READY, +}; +class SettingsResourceDownloadRow : public StaticTableRow, public ResourceDownload::Listener{ +public: + ~SettingsResourceDownloadRow(); + SettingsResourceDownloadRow( + DownloadedResourceMetadata local_metadata, + bool is_downloaded, + std::optional version_num, + ResourceVersionStatus version_status + ); + +public: + struct Listener{ + virtual void on_download_progress(uint64_t bytes_done, uint64_t total_bytes){} + virtual void on_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){} + virtual void on_hash_progress(uint64_t bytes_done, uint64_t total_bytes){} + + virtual void on_metadata_fetch_finished(const std::string& popup_message){} + + virtual void on_action_state_updated(){} + }; + + void add_listener(Listener& listener); + void remove_listener(Listener& listener); + + void report_download_progress(uint64_t bytes_done, uint64_t total_bytes); + void report_unzip_progress(uint64_t bytes_done, uint64_t total_bytes); + void report_hash_progress(uint64_t bytes_done, uint64_t total_bytes); + + void report_metadata_fetch_finished(const std::string& popup_message); + + void report_action_state_updated(); + +public: // ResourceDownload::Listener + virtual void on_download_progress(uint64_t bytes_done, uint64_t total_bytes) override; + virtual void on_unzip_progress(uint64_t bytes_done, uint64_t total_bytes) override; + virtual void on_hash_progress(uint64_t bytes_done, uint64_t total_bytes) override; + + // NOTE: this runs regardless of success or failure + virtual void on_download_finished(bool success, const std::string& resource_slug) override; + +public: + std::string get_resource_slug(); + void set_version_status(ResourceVersionStatus version_status); + void set_is_downloaded(bool is_downloaded); + void update_table_label(bool success); + + void ensure_remote_metadata_loaded(); + std::string predownload_warning_summary(const DownloadedResourceMetadata& remote_metadata); + // get the DownloadedResourceMetadata from the remote JSON, that corresponds to this button/row + const DownloadedResourceMetadata& fetch_remote_metadata(); + // DownloadedResourceMetadata initialize_local_metadata(); + + void start_download(); + + void start_delete(); + + // READY: can come from any state + // PRE_DOWNLOAD, PRE_DELETE, PRE_CANCEL: can only come from READY + // DELETING, CANCELLING: can only come from their respective PRE state + // DOWNLOADING: can come from either PRE_DOWNLOAD or PRE_CANCEL + void update_action_state(ActionState state); + + ActionState get_action_state(); + + bool is_given_action_state(ActionState state); + + void cancel_download_thread(); + + // - updates m_download_ptr to point to the given download_ptr + // - adds this object as a listener to download_ptr + void connect_with_download(std::shared_ptr download_ptr); + +private: + struct Data; + Pimpl m_data; + + SettingsResourceDownloadButton m_download_button; + SettingsResourceDeleteButton m_delete_button; + SettingsResourceCancelButton m_cancel_button; + SettingsResourceProgressBar m_progress_bar; + SettingsDownloadPopup m_popup; + + LifetimeSanitizer m_lifetime_sanitizer; + + + + +}; + +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.cpp new file mode 100644 index 0000000000..5ef91e1782 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.cpp @@ -0,0 +1,98 @@ +/* Resource Download Table + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Globals.h" +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Filesystem.h" +#include "ResourceDownload.h" +#include "SettingsResourceDownloadRow.h" +#include "SettingsResourceDownloadTable.h" + +// #include +// #include +// #include + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + +std::vector> SettingsResourceDownloadTable::get_resource_download_rows(){ + std::vector> resource_rows; + std::vector resource_list; + try{ + resource_list = expected_resource_download_list(); + }catch(FileException&){ + return {}; + } + + for (uint16_t index = 0; index < resource_list.size(); index++){ + DownloadedResourceMetadata resource = resource_list[index]; + std::string resource_name = resource.resource_name; + + auto [is_downloaded, version_status, current_version_num] = get_local_version_info(resource_name); + + resource_rows.emplace_back(std::make_unique(resource, is_downloaded, current_version_num, version_status)); + } + + return resource_rows; +} + + + + +SettingsResourceDownloadTable::~SettingsResourceDownloadTable(){ + // m_worker.wait_and_ignore_exceptions(); +} + +SettingsResourceDownloadTable::SettingsResourceDownloadTable() + : StaticTableOption("Resource Downloading:
Download resources not included in the initial download of the program.", LockMode::LOCK_WHILE_RUNNING, false) + , m_resource_rows(get_resource_download_rows()) +{ + add_resource_download_rows(); + + finish_construction(); +} +std::vector SettingsResourceDownloadTable::make_header() const{ + std::vector ret{ + "Resource", + "Size", + "Downloaded", + "Version", + "", + "", + "", + "", + }; + return ret; +} + + +void SettingsResourceDownloadTable::add_resource_download_rows(){ + for (std::unique_ptr& row_ptr : m_resource_rows){ + add_row(row_ptr.get()); + } +} + +void SettingsResourceDownloadTable::connect_row_with_download(const std::string& resource_slug, std::shared_ptr& download_ptr){ + + for (const auto& row_ptr : m_resource_rows) { + if (row_ptr->get_resource_slug() == resource_slug) { + // download_ptr->add_listener(*row_ptr); + row_ptr->connect_with_download(download_ptr); + return; + } + } + + // resource_slug not found within m_resource_rows + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "connect_row_with_download: resource_slug not found within m_resource_rows."); +} + + + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h new file mode 100644 index 0000000000..4c346f43c4 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h @@ -0,0 +1,42 @@ +/* Resource Download Table + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_ResourceDownloadTable_H +#define PokemonAutomation_ResourceDownloadTable_H + +#include "Common/Cpp/Options/StaticTableOption.h" +// #include "SettingsResourceDownloadRow.h" + +namespace PokemonAutomation{ + +class SettingsResourceDownloadRow; +class ResourceDownload; + +class SettingsResourceDownloadTable : public StaticTableOption{ +public: + ~SettingsResourceDownloadTable(); + SettingsResourceDownloadTable(); + + virtual std::vector make_header() const override; + + void connect_row_with_download(const std::string& resource_slug, std::shared_ptr& download_ptr); + +private: + std::vector> get_resource_download_rows(); + void add_resource_download_rows(); + + +private: + // must use a vector of pointers to the Row, since the Row class contains a mutex + // and so can't be moved + std::vector> m_resource_rows; + +}; + + + +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.cpp new file mode 100644 index 0000000000..4c298b4f82 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.cpp @@ -0,0 +1,484 @@ +/* Resource Download Widget + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include "CommonFramework/Logging/Logger.h" +#include "Common/Cpp/Exceptions.h" +#include "SettingsResourceDownloadWidget.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsDownloadButtonWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsDownloadButtonWidget::~SettingsDownloadButtonWidget(){ + // cout << "Destructor for SettingsDownloadButtonWidget" << endl; + // m_value.disconnect(this); + m_value.remove_button_listener(*this); +} +SettingsDownloadButtonWidget::SettingsDownloadButtonWidget(QWidget& parent, SettingsResourceDownloadButton& value) + : QWidget(&parent) + , ConfigWidget(value, *this) + , m_value(value) + , m_row(value.row) +{ + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + m_button = new QPushButton("Download", this); + // m_widget = this; + + layout->addWidget(m_button); + + // cout << "Constructor for SettingsDownloadButtonWidget" << endl; + + QFont font; + font.setBold(true); + m_button->setFont(font); + m_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QFontMetrics metrics(m_button->font()); + int minWidth = metrics.horizontalAdvance("Downloading..."); + m_button->setMinimumWidth(minWidth); + + + // when the button is clicked, runs row.update_action_state(), which updates the button state + // also, fetch json + connect( + m_button, &QPushButton::clicked, + this, [this](){ + if (!m_row.is_given_action_state(ActionState::READY)){ + return; + } + m_row.update_action_state(ActionState::PRE_DOWNLOAD); + m_row.ensure_remote_metadata_loaded(); + } + ); + + + value.add_button_listener(*this); +} + + +void SettingsDownloadButtonWidget::on_change_text(const std::string& text){ + QMetaObject::invokeMethod(this, [this, text]{ + m_button->setText(QString::fromStdString(text)); + }, Qt::QueuedConnection); +} + + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsDeleteButtonWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsDeleteButtonWidget::~SettingsDeleteButtonWidget(){ + m_value.remove_button_listener(*this); +} +SettingsDeleteButtonWidget::SettingsDeleteButtonWidget(QWidget& parent, SettingsResourceDeleteButton& value) + : QWidget(&parent) + , ConfigWidget(value, *this) + , m_value(value) + , m_row(value.row) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + m_button = new QPushButton("Delete", this); + // m_widget = m_button; + + layout->addWidget(m_button); + + QFont font; + font.setBold(true); + m_button->setFont(font); + m_button->setText("Delete"); + m_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QFontMetrics metrics(m_button->font()); + int minWidth = metrics.horizontalAdvance("Deleting..."); + m_button->setMinimumWidth(minWidth); + + + // when the button is clicked, runs row.update_action_state(), which updates the button state + // also, show the delete confirm box + connect( + m_button, &QPushButton::clicked, + this, [&](bool){ + if (!m_row.is_given_action_state(ActionState::READY)){ + return; + } + m_row.update_action_state(ActionState::PRE_DELETE); + show_delete_confirm_box(); + // cout << "Clicked Delete Button" << endl; + } + ); + + value.add_button_listener(*this); +} + +void SettingsDeleteButtonWidget::on_change_text(const std::string& text){ + QMetaObject::invokeMethod(this, [this, text]{ + m_button->setText(QString::fromStdString(text)); + }, Qt::QueuedConnection); +} + + +void SettingsDeleteButtonWidget::show_delete_confirm_box(){ + QMessageBox box; + QPushButton* yes = box.addButton(QMessageBox::Yes); + QPushButton* cancel = box.addButton("Cancel", QMessageBox::NoRole); + box.setEscapeButton(cancel); +// cout << "ok = " << ok << endl; +// cout << "skip = " << skip << endl; + + box.setTextFormat(Qt::RichText); + std::string title = "Delete"; + std::string message_body = "Are you sure you want to delete this resource?"; + + box.setWindowTitle(QString::fromStdString(title)); + box.setText(QString::fromStdString(message_body)); + +// box.open(); + + box.exec(); + + QAbstractButton* clicked = box.clickedButton(); +// cout << "clicked = " << clicked << endl; + if (clicked == yes){ + cout << "Clicked Yes to Delete" << endl; + + m_row.start_delete(); + return; + } + if (clicked == cancel){ + m_row.update_action_state(ActionState::READY); + return; + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsCancelButtonWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsCancelButtonWidget::~SettingsCancelButtonWidget(){ + m_value.remove_button_listener(*this); +} +SettingsCancelButtonWidget::SettingsCancelButtonWidget(QWidget& parent, SettingsResourceCancelButton& value) + : QWidget(&parent) + , ConfigWidget(value, *this) + , m_value(value) + , m_row(value.row) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + m_button = new QPushButton("Cancel", this); + // m_widget = m_button; + + layout->addWidget(m_button); + + QFont font; + font.setBold(true); + m_button->setFont(font); + m_button->setText("Cancel"); + m_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QFontMetrics metrics(m_button->font()); + int minWidth = metrics.horizontalAdvance("Cancelling..."); + m_button->setMinimumWidth(minWidth); + + // when the button is clicked, runs row.update_action_state(), which updates the button state + // also, set cancel state to true + connect( + m_button, &QPushButton::clicked, + this, [&](bool){ + if (!m_row.is_given_action_state(ActionState::DOWNLOADING)){ + return; + } + m_row.update_action_state(ActionState::PRE_CANCEL); + show_cancel_confirm_box(); + cout << "Clicked Cancel Button" << endl; + } + ); + + value.add_button_listener(*this); +} + +void SettingsCancelButtonWidget::on_change_text(const std::string& text){ + QMetaObject::invokeMethod(this, [this, text]{ + m_button->setText(QString::fromStdString(text)); + }, Qt::QueuedConnection); +} + +void SettingsCancelButtonWidget::show_cancel_confirm_box(){ + QMessageBox box; + QPushButton* yes = box.addButton(QMessageBox::Yes); + QPushButton* cancel = box.addButton("Cancel", QMessageBox::NoRole); + box.setEscapeButton(cancel); +// cout << "ok = " << ok << endl; +// cout << "skip = " << skip << endl; + + box.setTextFormat(Qt::RichText); + std::string title = "Cancel Download"; + std::string message_body = "Are you sure you want to cancel this download?"; + + box.setWindowTitle(QString::fromStdString(title)); + box.setText(QString::fromStdString(message_body)); + +// box.open(); + + box.exec(); + + QAbstractButton* clicked = box.clickedButton(); +// cout << "clicked = " << clicked << endl; + if (clicked == yes){ + cout << "Clicked Yes to Cancel" << endl; + + if (!m_row.is_given_action_state(ActionState::PRE_CANCEL)){ + // if the download finishes and goes back to READY state before the user clicks Yes to cancel + // then nothing happens + return; + } + + m_row.update_action_state(ActionState::CANCELLING); + + m_row.cancel_download_thread(); + + return; + } + if (clicked == cancel){ + if (!m_row.is_given_action_state(ActionState::PRE_CANCEL)){ + return; + } + + m_row.update_action_state(ActionState::DOWNLOADING); + return; + } +} + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsProgressBarWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsProgressBarWidget::~SettingsProgressBarWidget(){ + // cout << "Destructor for SettingsProgressBarWidget" << endl; + m_value.remove_progress_listener(*this); +} +SettingsProgressBarWidget::SettingsProgressBarWidget(QWidget& parent, SettingsResourceProgressBar& value) + : QWidget(&parent) + , ConfigWidget(value, *this) + , m_value(value) + // , m_row(value.row) +{ + + // 1. Instantiate the widgets + m_status_label = new QLabel("", this); + m_progress_bar = new QProgressBar(this); + + // cout << "Constructor for SettingsProgressBarWidget" << endl; + + // 2. Configure the progress bar + m_progress_bar->setRange(0, 100); + m_progress_bar->setValue(0); + m_progress_bar->setTextVisible(true); // Shows % inside the bar + + // 3. Create a horizontal layout to hold them + QHBoxLayout *layout = new QHBoxLayout(); + layout->addWidget(m_status_label); + layout->addWidget(m_progress_bar); + + this->setLayout(layout); + this->setMinimumWidth(170); + + value.add_progress_listener(*this); + + update_visibility(); +} + +void SettingsProgressBarWidget::update_visibility(){ + ConfigWidget::update_visibility(); + + switch (m_value.visibility()){ + case ConfigOptionState::ENABLED: + case ConfigOptionState::DISABLED: + m_progress_bar->setVisible(true); + break; + case ConfigOptionState::HIDDEN: + m_progress_bar->setVisible(false); + break; + } + +} + + +void SettingsProgressBarWidget::update_progress_bar(int percentage){ + m_progress_bar->setValue(percentage); +} + +void SettingsProgressBarWidget::update_progress_bar(uint64_t bytes_done, uint64_t total_bytes){ + double percent = total_bytes > 0 ? (static_cast(bytes_done) / total_bytes) * 100.0 : 0; + int current_percent = static_cast(percent); + int last_percentage = m_progress_bar->value(); + // Only update UI if integer value has changed + if (current_percent != last_percentage){ + update_progress_bar(current_percent); + } +} + +void SettingsProgressBarWidget::on_change_text(const std::string& text){ + QMetaObject::invokeMethod(this, [this, text]{ + m_status_label->setText(QString::fromStdString(text)); + }, Qt::QueuedConnection); + +} +void SettingsProgressBarWidget::on_update_progress(uint64_t bytes_done, uint64_t total_bytes){ + QMetaObject::invokeMethod(this, [this, bytes_done, total_bytes]{ + update_progress_bar(bytes_done, total_bytes); + }, Qt::QueuedConnection); +} + +void SettingsProgressBarWidget::on_reset_progress(){ + QMetaObject::invokeMethod(this, [this]{ + m_progress_bar->setValue(0); + }, Qt::QueuedConnection); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsDownloadPopupWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// +template class RegisterConfigWidget; +SettingsDownloadPopupWidget::~SettingsDownloadPopupWidget(){ + m_row.remove_listener(*this); +} +SettingsDownloadPopupWidget::SettingsDownloadPopupWidget(QWidget& parent, SettingsDownloadPopup& value) + : QWidget(&parent) + , ConfigWidget(value, *this) + // , m_value(value) + , m_row(value.row) +{ + m_row.add_listener(*this); +} + +void SettingsDownloadPopupWidget::show_download_confirm_box( + const std::string& title, + const std::string& message_body +){ + QMessageBox box; + QPushButton* ok = box.addButton(QMessageBox::Ok); + QPushButton* cancel = box.addButton("Cancel", QMessageBox::NoRole); + box.setEscapeButton(cancel); +// cout << "ok = " << ok << endl; +// cout << "skip = " << skip << endl; + + box.setTextFormat(Qt::RichText); + std::string text = message_body; + // text += make_text_url(link_url, link_text); + // text += get_changes(node); + + + box.setWindowTitle(QString::fromStdString(title)); + box.setText(QString::fromStdString(text)); + +// box.open(); + + box.exec(); + + QAbstractButton* clicked = box.clickedButton(); +// cout << "clicked = " << clicked << endl; + if (clicked == ok){ + cout << "Clicked Ok to Download" << endl; + + m_row.start_download(); + return; + } + if (clicked == cancel){ + m_row.update_action_state(ActionState::READY); + return; + } +} + +// when json has been fetched, open the update box. +// When click Ok in update box, start the download. If click cancel, re-enable the download button +void SettingsDownloadPopupWidget::on_metadata_fetch_finished(const std::string& popup_message){ + QMetaObject::invokeMethod(this, [this, popup_message]{ + show_download_confirm_box("Download", popup_message); + }, Qt::QueuedConnection); + +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsDownloadErrorWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// +template class RegisterConfigWidget; +SettingsDownloadErrorWidget::~SettingsDownloadErrorWidget(){ + GlobalResourceDownloadManager::instance().remove_download_listener(*this); +} +SettingsDownloadErrorWidget::SettingsDownloadErrorWidget(QWidget& parent, SettingsDownloadError& value) + : QWidget(&parent) + , ConfigWidget(value, *this) +{ + GlobalResourceDownloadManager::instance().add_download_listener(*this); +} + +void SettingsDownloadErrorWidget::show_download_failed_box(const std::string& resource_slug){ + std::cerr << "SettingsDownloadErrorWidget::show_download_failed_box: Error: Download failed for " << resource_slug << ". Check your internet connection and check you have enough disk space." << std::endl; + if (m_popup_is_open.exchange(true)){ // only show popups if one isn't already open + return; + } + + QMessageBox box; + box.warning(nullptr, "Error:", + QString::fromStdString("Error: Download failed for " + resource_slug + " (and others possibly as well). Check your internet connection and check you have enough disk space.")); + + m_popup_is_open.store(false); +} + +void SettingsDownloadErrorWidget::show_error_box(std::string error_msg){ + std::cerr << "SettingsDownloadErrorWidget::show_error_box: " << error_msg << std::endl; + if (m_popup_is_open.exchange(true)){ + return; + } + QMessageBox box; + box.warning(nullptr, "Error:", + QString::fromStdString(error_msg)); + + m_popup_is_open.store(false); + +} + +// GlobalResourceDownloadManager::Listener +// +void SettingsDownloadErrorWidget::on_download_failed(const std::string& resource_slug){ + QMetaObject::invokeMethod(this, [this, resource_slug]{ + show_download_failed_box(resource_slug); + }, Qt::QueuedConnection); +} +void SettingsDownloadErrorWidget::on_exception_caught(const std::string& error_msg){ + QMetaObject::invokeMethod(this, [this, error_msg]{ + show_error_box(error_msg); + }, Qt::QueuedConnection); +} + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h new file mode 100644 index 0000000000..0bf877fd3f --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h @@ -0,0 +1,162 @@ +/* Resource Download Widget + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_SettingsResourceDownloadWidget_H +#define PokemonAutomation_SettingsResourceDownloadWidget_H + +#include +#include +#include "Common/Qt/Options/ConfigWidget.h" +#include "GlobalResourceDownloadManager.h" +#include "SettingsResourceDownloadRow.h" + +namespace PokemonAutomation{ + + +class SettingsDownloadButtonWidget : public QWidget, public ConfigWidget, public SettingsResourceButton::Listener{ +public: + using ParentOption = SettingsResourceDownloadButton; + +public: + ~SettingsDownloadButtonWidget(); + SettingsDownloadButtonWidget(QWidget& parent, SettingsResourceDownloadButton& value); + + +public: // SettingsResourceButton::Listener + virtual void on_change_text(const std::string& text) override; + + +private: + SettingsResourceDownloadButton& m_value; + SettingsResourceDownloadRow& m_row; + QPushButton* m_button; + +}; + + +class SettingsDeleteButtonWidget : public QWidget, public ConfigWidget, SettingsResourceButton::Listener{ +public: + using ParentOption = SettingsResourceDeleteButton; + +public: + ~SettingsDeleteButtonWidget(); + SettingsDeleteButtonWidget(QWidget& parent, SettingsResourceDeleteButton& value); + + +public: // SettingsResourceButton::Listener + virtual void on_change_text(const std::string& text) override; + +private: + void show_delete_confirm_box(); + +private: + SettingsResourceDeleteButton& m_value; + SettingsResourceDownloadRow& m_row; + QPushButton* m_button; +}; + +class SettingsCancelButtonWidget : public QWidget, public ConfigWidget, SettingsResourceButton::Listener{ +public: + using ParentOption = SettingsResourceCancelButton; + +public: + ~SettingsCancelButtonWidget(); + SettingsCancelButtonWidget(QWidget& parent, SettingsResourceCancelButton& value); + +public: // SettingsResourceButton::Listener + virtual void on_change_text(const std::string& text) override; + +private: + void show_cancel_confirm_box(); + +private: + SettingsResourceCancelButton& m_value; + SettingsResourceDownloadRow& m_row; + QPushButton* m_button; +}; + +class SettingsProgressBarWidget : public QWidget, public ConfigWidget, public SettingsResourceProgressBar::Listener{ +public: + using ParentOption = SettingsResourceProgressBar; + +public: + ~SettingsProgressBarWidget(); + SettingsProgressBarWidget(QWidget& parent, SettingsResourceProgressBar& value); + + // Because this widget is within a table, QT doesn't respect setVisible + // so we need to override update_visibility and manually hide/show the progress_bar. + virtual void update_visibility() override; + +public: // SettingsResourceProgressBar::Listener + virtual void on_change_text(const std::string& text) override; + virtual void on_update_progress(uint64_t bytes_done, uint64_t total_bytes) override; + virtual void on_reset_progress() override; + +private: + void update_progress_bar(int percentage); + void update_progress_bar(uint64_t bytes_done, uint64_t total_bytes); + +private: + SettingsResourceProgressBar& m_value; + // SettingsResourceDownloadRow& m_row; + QLabel* m_status_label; + QProgressBar* m_progress_bar; +}; + + +class SettingsDownloadPopupWidget : public QWidget, public ConfigWidget, public SettingsResourceDownloadRow::Listener{ +public: + using ParentOption = SettingsDownloadPopup; + +public: + ~SettingsDownloadPopupWidget(); + SettingsDownloadPopupWidget(QWidget& parent, SettingsDownloadPopup& value); + +public: // SettingsResourceDownloadRow::Listener + virtual void on_metadata_fetch_finished(const std::string& popup_message) override; + +private: + void show_download_confirm_box( + const std::string& title, + const std::string& message_body + ); + +private: + // SettingsDownloadPopup& m_value; + SettingsResourceDownloadRow& m_row; + +}; +class SettingsDownloadErrorWidget : public QWidget, public ConfigWidget, public GlobalResourceDownloadManager::Listener{ +public: + using ParentOption = SettingsDownloadError; + +public: + ~SettingsDownloadErrorWidget(); + SettingsDownloadErrorWidget(QWidget& parent, SettingsDownloadError& value); + +public: // GlobalResourceDownloadManager::Listener + // virtual void on_all_downloads_finished(){} + virtual void on_download_failed(const std::string& resource_slug) override; + virtual void on_exception_caught(const std::string& error_msg) override; + +private: + // show popup for download failed. Will only show 1 pop-up at a time. + void show_download_failed_box(const std::string& resource_slug); + + // show popup for download error. Will only show 1 pop-up at a time. + void show_error_box(std::string error_msg); + +private: + std::atomic m_popup_is_open{false}; + +}; + + + + + +} +#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 95e9cc92d5..f6e5f3cf92 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -496,12 +496,20 @@ file(GLOB LIBRARY_SOURCES Source/CommonFramework/Recording/StreamRecorder.h Source/CommonFramework/ResourceDownload/DownloadThread.cpp Source/CommonFramework/ResourceDownload/DownloadThread.h - Source/CommonFramework/ResourceDownload/RequiredDownload.cpp - Source/CommonFramework/ResourceDownload/RequiredDownload.h - Source/CommonFramework/ResourceDownload/RequiredDownloadManager.cpp - Source/CommonFramework/ResourceDownload/RequiredDownloadManager.h + Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp + Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h + Source/CommonFramework/ResourceDownload/ResourceDownload.cpp + Source/CommonFramework/ResourceDownload/ResourceDownload.h Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.h + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.cpp + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.h + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.cpp + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.cpp + Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h Source/CommonFramework/Startup/NewVersionCheck.cpp Source/CommonFramework/Startup/NewVersionCheck.h Source/CommonFramework/Startup/SetupSettings.cpp