From c8f71b54678a7458fb7092825d5757240b966138 Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 22 Jun 2026 23:53:45 -0700 Subject: [PATCH] Resource Downloading - Settings --- .../CommonFramework/GlobalSettingsPanel.cpp | 8 +- .../CommonFramework/GlobalSettingsPanel.h | 8 +- .../ResourceDownload/DownloadThread.cpp | 13 +- .../GlobalResourceDownloadManager.cpp | 164 +++++ .../GlobalResourceDownloadManager.h | 89 +++ .../ResourceDownload/RequiredDownload.cpp | 95 --- .../RequiredDownloadManager.cpp | 173 ------ .../RequiredDownloadManager.h | 95 --- .../ResourceDownload/ResourceDownload.cpp | 98 +++ ...{RequiredDownload.h => ResourceDownload.h} | 37 +- .../ResourceDownloadHelpers.cpp | 90 +-- .../ResourceDownloadHelpers.h | 37 +- .../SettingsResourceDownloadOptions.cpp | 50 ++ .../SettingsResourceDownloadOptions.h | 94 +++ .../SettingsResourceDownloadRow.cpp | 488 +++++++++++++++ .../SettingsResourceDownloadRow.h | 135 +++++ .../SettingsResourceDownloadTable.cpp | 104 ++++ .../SettingsResourceDownloadTable.h | 45 ++ .../SettingsResourceDownloadWidget.cpp | 559 ++++++++++++++++++ .../SettingsResourceDownloadWidget.h | 147 +++++ SerialPrograms/cmake/SourceFiles.cmake | 20 +- 21 files changed, 2112 insertions(+), 437 deletions(-) create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h delete mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.cpp delete mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.cpp delete mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.h create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.cpp rename SerialPrograms/Source/CommonFramework/ResourceDownload/{RequiredDownload.h => ResourceDownload.h} (63%) create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.h create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp index 76a3cc62b7..e3ff40a6ca 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp @@ -268,6 +268,8 @@ GlobalSettings::GlobalSettings() PA_ADD_OPTION(THEME); PA_ADD_OPTION(USE_PADDLE_OCR); PA_ADD_OPTION(USE_GPU_FOR_ML_INFERENCE); + 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); @@ -451,9 +453,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/DownloadThread.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/DownloadThread.cpp index a79fdd9c19..a83b2ba7d7 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/DownloadThread.cpp +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/DownloadThread.cpp @@ -57,6 +57,13 @@ void DownloadThread::start_download_thread(){ }); try { + + // Logger& logger = global_logger_tagged(); + // throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, + // "Test"); + // throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Test."); + + // std::this_thread::sleep_for(std::chrono::seconds(7)); run_download(m_remote_metadata); @@ -72,12 +79,12 @@ void DownloadThread::start_download_thread(){ success = false; m_hooks.report_failed(); }catch (const std::exception& e) { - std::cout << "Standard exception: " << e.what() << std::endl; + std::string error_msg = "DownloadThread::start_download_thread: " + std::string(e.what()); success = false; - m_hooks.report_exception_caught("DownloadThread::start_download_thread"); + m_hooks.report_exception_caught(error_msg.c_str()); } catch(...){ success = false; - m_hooks.report_exception_caught("DownloadThread::start_download_thread"); + m_hooks.report_exception_caught("DownloadThread::start_download_thread: Unknown exception. Report this as an error."); } } diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp new file mode 100644 index 0000000000..c5a269d5e1 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp @@ -0,0 +1,164 @@ +/* Resource Download Manager + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/ResourceDownload/ResourceDownloadHelpers.h" +#include "CommonFramework/ResourceDownload/ResourceDownload.h" +#include "GlobalResourceDownloadManager.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + +GlobalResourceDownloadManager& GlobalResourceDownloadManager::instance(){ + static GlobalResourceDownloadManager manager; + return manager; +} + + +std::shared_ptr GlobalResourceDownloadManager::add_to_download_list(const std::string& resource_slug){ + std::lock_guard lg(m_queue_lock); + auto iter = std::ranges::find_if(m_download_queue, [&](const auto& download_ptr){ + return download_ptr->get_name() == resource_slug; + }); + + + if (iter == m_download_queue.end()){ // given Resource not already within the DownloadQueue + DownloadedResourceMetadata resource_metadata = get_remote_resource_metadata_from_resource_slug(resource_slug); + 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); + download_ptr->start_download(); + + return download_ptr; + } + + // Return the existing shared_ptr found in the queue + return *iter; + + +} + +void GlobalResourceDownloadManager::remove_from_download_list(const std::string& resource_slug){ + // We need to delete the target ResourceDownloads outside of the lock + // (the reason isn't clear to me. I suspect its because GlobalResourceDownloadManager is one of the + // listeners within ResourceDownload's ListenerSet, which causes an issue during its destructor) + // This temporary vector holds ownership of the target ResourceDownloads + // while modifying m_download_queue occurs within the lock. + std::vector> to_destroy; + + { + std::lock_guard lg(m_queue_lock); + + // std::erase_if modifies the vector in-place + std::erase_if(m_download_queue, [&](auto& download_ptr) { + if (!download_ptr) return false; + + if (download_ptr->get_name() == resource_slug) { + // Move the download_ptr out of the m_download_queue and into our temporary vector + // This prevents immediate destruction of download_ptr, while + // it's still removed from m_download_queue. + to_destroy.push_back(std::move(download_ptr)); + return true; + } + return false; + }); + + m_cv.notify_all(); + } + + // The 'to_destroy' vector goes out of scope here. + // The ResourceDownload destructors run safely out of the lock. +} + +bool GlobalResourceDownloadManager::is_download_ready_to_start(const std::string& resource_slug){ + // ASSUMES: the calling thread holds the m_lock. therefore, this function doesn't lock the mutex when accessing download_queue. + // std::lock_guard lg(m_lock); + + uint16_t MAX_CONCURRENT_DOWNLOADS = 10; + + auto iter = std::ranges::find_if(m_download_queue, [&](const auto& download_ptr){ + return download_ptr->get_name() == resource_slug; + }); + + if (iter == m_download_queue.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "is_download_ready_to_start: resource_slug not found within download_queue."); + } + + uint16_t download_position = (uint16_t)std::distance(m_download_queue.begin(), iter); + + // cout << "download_position: " << std::to_string(download_position) << endl; + + return download_position < MAX_CONCURRENT_DOWNLOADS; +} + + +void GlobalResourceDownloadManager::cancel_downloads(){ + std::vector> downloads_to_cancel; + + // Lock briefly to safely snapshot the queue + { + std::lock_guard lg(m_queue_lock); + downloads_to_cancel = m_download_queue; + } + + // We must call cancel_download() outside of the lock. + // The reason is that GlobalResourceDownloadManager is a listener to ResourceDownload. + // ResourceDownload::cancel_download() will trigger GlobalResourceDownloadManager::on_download_finished, + // which then triggers GlobalResourceDownloadManager::remove_from_download_list(), which is + // done under a lock. + // This prevents a deadlock. + for (auto& download : downloads_to_cancel) { + if (download) { + download->cancel_download(); + } + } + +} + + +////////////////////////// +// Listener +////////////////////////// +void GlobalResourceDownloadManager::add_download_listener(Listener& listener){ + m_download_listeners.add(listener); +} +void GlobalResourceDownloadManager::remove_download_listener(Listener& listener){ + m_download_listeners.remove(listener); +} + +void GlobalResourceDownloadManager::report_all_downloads_finished(){ + m_download_listeners.run_method(&Listener::on_all_downloads_finished); +} +void GlobalResourceDownloadManager::report_download_failed(const std::string& resource_slug){ + m_download_listeners.run_method(&Listener::on_download_failed, resource_slug); +} +void GlobalResourceDownloadManager::report_unexpected_exception_caught(const std::string& error_msg){ + m_download_listeners.run_method(&Listener::on_exception_caught, error_msg); +} + +///////////////////// +// for ResourceDownload::Listener +///////////////////// + +void GlobalResourceDownloadManager::on_download_finished(bool success, const std::string& resource_slug){ + remove_from_download_list(resource_slug); + // check_if_all_downloads_done(); +} +void GlobalResourceDownloadManager::on_download_failed(const std::string& resource_slug){ + report_download_failed(resource_slug); +} +void GlobalResourceDownloadManager::on_exception_caught(const std::string& error_msg){ + report_unexpected_exception_caught(error_msg); +} + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h new file mode 100644 index 0000000000..28b447f450 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h @@ -0,0 +1,89 @@ +/* Resource Download Manager + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_ResourceDownloadManager_H +#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" +#include "ResourceDownload.h" + +namespace PokemonAutomation{ + +// class ResourceDownload; + + + +class GlobalResourceDownloadManager : public ResourceDownload::Listener{ +public: + struct Listener{ + virtual void on_all_downloads_finished(){} + virtual void on_download_failed(const std::string& resource_slug){} + virtual void on_exception_caught(const std::string& error_msg){} + }; + + void add_download_listener(Listener& listener); + void remove_download_listener(Listener& listener); + + void report_all_downloads_finished(); + void report_download_failed(const std::string& resource_slug); + void report_unexpected_exception_caught(const std::string& error_msg); + +public: + static GlobalResourceDownloadManager& instance(); + +public: // ResourceDownload::Listener + virtual void on_download_finished(bool success, const std::string& resource_slug) override; + virtual void on_download_failed(const std::string& resource_slug) override; + virtual void on_exception_caught(const std::string& error_msg) override; + + +public: + // check if the download queue already has the resource + // if not, add the download to the back of the download queue + // and start downloading it + // Also, add this manager as a listener to the download + std::shared_ptr add_to_download_list(const std::string& resource_slug); + + void remove_from_download_list(const std::string& resource_slug); + + // return true if given resource_slug's position in m_download_queue is less than MAX_CONCURRENT_DOWNLOADS + // ASSUMES: the calling thread holds the m_lock. therefore, this function doesn't lock the mutex when accessing m_download_queue. + bool is_download_ready_to_start(const std::string& resource_slug); + + + void cancel_downloads(); + + + +private: + + // queue of downloads + // ResourceDownload must be a pointer for several reasons: + // - it contains a Mutex/CV, and so can't be moved unless it's a pointer. + // Objects can't be added to a vector if they can't be moved/copied. + // - Other objects hold references to ResourceDownload. We will need to + // remove items in the middle of the vector. This forces the vector to shift + // all subsequent elements forward in memory to fill the gap. This shifting + // breaks (invalidates) any references or iterators pointing to elements + // at or after the deletion point. + std::vector> m_download_queue; + + Mutex m_queue_lock; + ConditionVariable m_cv; + + ListenerSet m_download_listeners; + + +}; + + + +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.cpp deleted file mode 100644 index af1d1d8bef..0000000000 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* Required Download - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include "CommonFramework/GlobalSettingsPanel.h" -#include "RequiredDownloadManager.h" -#include "RequiredDownload.h" - -#include -using std::cout; -using std::endl; - -namespace PokemonAutomation{ - - -RequiredDownload::RequiredDownload(RequiredDownloadManager& download_manager, DownloadedResourceMetadata resource_metadata, Mutex& lock, ConditionVariable& cv) - : m_download_manager(download_manager) - , m_resource_metadata(resource_metadata) - , m_name(resource_metadata.resource_name) - , m_lock(lock) - , m_cv(cv) - , m_download_thread(initialize_download_thread()) -{} - -DownloadThread RequiredDownload::initialize_download_thread(){ - - DownloadThread::Hooks generic_row_hooks{ - .is_ready_to_start = [this] { return m_download_manager.is_download_ready_to_start(m_name); }, - .on_finished = [this](bool success) { - // GlobalSettings::instance().update_resource_download_row_status(m_index, success); - on_download_finished(); - }, - .report_failed = [this] { report_download_failed(); }, - .report_exception_caught = [this](const char* context) { report_unexpected_exception_caught(context); }, - .report_download_progress = [this](uint64_t bytes_done, uint64_t total_bytes) { report_download_progress(bytes_done, total_bytes); }, - .report_hash_progress = [this](uint64_t bytes_done, uint64_t total_bytes) { report_hash_progress(bytes_done, total_bytes); }, - .report_unzip_progress = [this](uint64_t bytes_done, uint64_t total_bytes) { report_unzip_progress(bytes_done, total_bytes); } - }; - - return DownloadThread{generic_row_hooks, m_resource_metadata, m_lock, m_cv}; - -} - - - -void RequiredDownload::start_download(){ - m_download_thread.start_download_thread(); -} - -void RequiredDownload::cancel_download(){ - m_download_thread.cancel(); - // m_parent_session.remove_from_download_list(m_index); -} - - -void RequiredDownload::on_download_finished(){ - m_download_manager.remove_from_download_list(m_name); - m_download_manager.check_if_all_downloads_done(); -} - -void RequiredDownload::report_download_failed(){ - m_download_manager.report_download_failed(); - // m_listeners.run_method(&Listener::on_download_failed); -} - - -void RequiredDownload::report_unexpected_exception_caught(const std::string& function_name){ - m_download_manager.report_unexpected_exception_caught(function_name); -} - -////////////////////////// -// Listeners -////////////////////////// -void RequiredDownload::add_listener(Listener& listener){ - m_listeners.add(listener); -} -void RequiredDownload::remove_listener(Listener& listener){ - m_listeners.remove(listener); -} - - -void RequiredDownload::report_download_progress(uint64_t bytes_done, uint64_t total_bytes){ - m_listeners.run_method(&Listener::on_download_progress, bytes_done, total_bytes); -} -void RequiredDownload::report_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){ - m_listeners.run_method(&Listener::on_unzip_progress, bytes_done, total_bytes); -} -void RequiredDownload::report_hash_progress(uint64_t bytes_done, uint64_t total_bytes){ - m_listeners.run_method(&Listener::on_hash_progress, bytes_done, total_bytes); -} - -} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.cpp deleted file mode 100644 index db4bcd462c..0000000000 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/* Required Download Manager - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include "Common/Cpp/Exceptions.h" -#include "CommonFramework/ResourceDownload/ResourceDownloadHelpers.h" -#include "CommonFramework/ResourceDownload/RequiredDownload.h" -#include "RequiredDownloadManager.h" - -#include -using std::cout; -using std::endl; - -namespace PokemonAutomation{ - - -RequiredDownloadManager::RequiredDownloadManager(const std::vector& required_resources) - : m_required_resources(required_resources) -{} - -void RequiredDownloadManager::initialize_required_downloads(){ - // const std::vector& required_resources = {"PokemonSV/AreaZero", "PaddleOCR" }; - auto [downloads, upgrade_needed] = find_resources_to_download(m_required_resources); - - // If an exception happened above, state variables below are never touched - - std::vector download_queue; - // Prevent multiple reallocations - download_queue.reserve(downloads.size()); - for (auto download : downloads){ // (re-)initialize m_download_queue - download_queue.push_back(download->get_name()); - } - m_download_queue = std::move(download_queue); - m_required_downloads = std::move(downloads); - m_upgrade_warning = upgrade_needed; - m_resource_list_initialized = true; -} - -RequiredResourceResult RequiredDownloadManager::find_resources_to_download(const std::vector& required_resources){ - std::vector local_resources = local_resource_download_list(); - std::vector remote_resources = remote_resource_download_list(); - - bool upgrade_warning = false; - - // find out version status for each required resource type, if outdated, or not downloaded, - // check the version available for download in the remote list and add the corresponding - // remote_resource to the required_download_list - std::vector> required_download_list; - for(const std::string& resource_type : required_resources){ - DownloadedResourceMetadata expected_resource = get_resource_metadata_from_resource_type(resource_type, local_resources); - ResourceVersionStatus version_status = get_version_status(expected_resource); - - switch(version_status){ - case ResourceVersionStatus::CURRENT: - // we have this resource, check the next one - continue; - case ResourceVersionStatus::FUTURE_VERSION: - // we have this resource, check the next one - // however, the resource that was downloaded is more updated than what the program is expecting - // warn the user to upgrade CC. - upgrade_warning = true; - continue; - case ResourceVersionStatus::OUTDATED: - case ResourceVersionStatus::NOT_APPLICABLE:{ - // we don't have the resource. check remote to see if correct version is available. - DownloadedResourceMetadata remote_resource = get_resource_metadata_from_resource_type(resource_type, remote_resources); - uint16_t expected_version_num = expected_resource.version_num.value(); - uint16_t remote_version_num = remote_resource.version_num.value(); - - if (expected_version_num == remote_version_num){ - // remote version matches what we expect - // add the resource to the download list - // m_required_downloads.emplace_back(RequiredDownload{remote_resource}); - required_download_list.emplace_back(std::make_shared(*this, remote_resource, m_queue_lock, m_cv)); - - }else if (expected_version_num < remote_version_num){ - // remote version is more updated than we expect - // warn the user to upgrade CC. - // regardless, add the resource to the download list - // m_required_downloads.emplace_back(RequiredDownload{remote_resource}); - required_download_list.emplace_back(std::make_shared(*this, remote_resource, m_queue_lock, m_cv)); - upgrade_warning = true; - - }else if (expected_version_num > remote_version_num){ - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "resources_to_download: expected_version_num > remote_version_num. This shouldn't happen."); - } - break; - } - default: - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "resources_to_download: Unknown enum."); - } - } - - // m_upgrade_warning = upgrade_warning; - // m_resource_list_initialized = true; - - return RequiredResourceResult{std::move(required_download_list), upgrade_warning}; - -} - -bool RequiredDownloadManager::get_upgrade_warning(){ - if (!m_resource_list_initialized){ - initialize_required_downloads(); - } - - return m_upgrade_warning; -} - -const std::vector>& RequiredDownloadManager::get_required_downloads(){ - if (!m_resource_list_initialized){ - initialize_required_downloads(); - } - - return m_required_downloads; -} - - -void RequiredDownloadManager::remove_from_download_list(const std::string& resource_slug){ - std::lock_guard lg(m_queue_lock); - - // this requires C++20 - std::erase(m_download_queue, resource_slug); - m_cv.notify_all(); -} - -bool RequiredDownloadManager::is_download_ready_to_start(const std::string& resource_slug){ - // ASSUMES: the calling thread holds the m_lock. therefore, this function doesn't lock the mutex when accessing download_queue. - // std::lock_guard lg(m_lock); - - uint16_t MAX_CONCURRENT_DOWNLOADS = 10; - return is_resource_ready_in_queue(MAX_CONCURRENT_DOWNLOADS, resource_slug, m_download_queue); -} - -void RequiredDownloadManager::check_if_all_downloads_done(){ - std::lock_guard lg(m_queue_lock); - - // we don't add to the download queue, - // so when the queue is empty, we must be done - if (m_download_queue.empty()){ - report_all_downloads_finished(); - } -} - -void RequiredDownloadManager::cancel_downloads(){ - for (auto download : m_required_downloads){ - download->cancel_download(); - } -} - -////////////////////////// -// Listeners -////////////////////////// -void RequiredDownloadManager::add_download_listener(DownloadListener& listener){ - m_download_listeners.add(listener); -} -void RequiredDownloadManager::remove_download_listener(DownloadListener& listener){ - m_download_listeners.remove(listener); -} - -void RequiredDownloadManager::report_all_downloads_finished(){ - m_download_listeners.run_method(&DownloadListener::on_all_downloads_finished); -} -void RequiredDownloadManager::report_download_failed(){ - m_download_listeners.run_method(&DownloadListener::on_download_failed); -} -void RequiredDownloadManager::report_unexpected_exception_caught(const std::string& function_name){ - m_download_listeners.run_method(&DownloadListener::on_exception_caught, function_name); -} - -} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.h deleted file mode 100644 index cc1ac13954..0000000000 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownloadManager.h +++ /dev/null @@ -1,95 +0,0 @@ -/* Required Download Manager - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_RequiredDownloadManager_H -#define PokemonAutomation_RequiredDownloadManager_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" -// #include "CommonFramework/ResourceDownload/RequiredDownload.h" - -namespace PokemonAutomation{ - -class RequiredDownload; - -struct RequiredResourceResult { - std::vector> downloads; - bool requires_upgrade = false; -}; - -class RequiredDownloadManager { -public: - struct DownloadListener{ - virtual void on_all_downloads_finished(){} - virtual void on_download_failed(){} - virtual void on_exception_caught(const std::string& function_name){} - }; - - void add_download_listener(DownloadListener& listener); - void remove_download_listener(DownloadListener& listener); - - void report_all_downloads_finished(); - void report_download_failed(); - void report_unexpected_exception_caught(const std::string& function_name); - -public: - RequiredDownloadManager(const std::vector& required_resources); - -public: - void initialize_required_downloads(); - - bool get_upgrade_warning(); - - const std::vector>& get_required_downloads(); - - void remove_from_download_list(const std::string& resource_slug); - - // return true if given resource_slug's position in m_download_queue is less than MAX_CONCURRENT_DOWNLOADS - // ASSUMES: the calling thread holds the m_lock. therefore, this function doesn't lock the mutex when accessing m_download_queue. - bool is_download_ready_to_start(const std::string& resource_slug); - - // check if the m_download_queue is empty - // if so, trigger report_all_downloads_finished() - void check_if_all_downloads_done(); - - void cancel_downloads(); - -private: - // find the list of resources that we need, but are not already downloaded. - // return struct that contains required_downloads and upgrade_warning. - // required_downloads: the list of required downloads - // upgrade_warning: is true if we need to warn the user to upgrade CC - RequiredResourceResult find_resources_to_download(const std::vector& required_resources); - -private: - std::vector m_required_resources; - - bool m_resource_list_initialized{false}; - - bool m_upgrade_warning; - - // RequiredDownload must be a pointer because it contains a Mutex/CV, - // and so can't be moved unless it's a pointer. - // Objects can't be added to a vector if they can't be moved/copied. - // also, it being a pointer allows it to be forward declared. - // this is needed since RequiredDownload also holds a reference to this Session - std::vector> m_required_downloads; - - // queue of downloads - // each download is represented by its slug - std::vector m_download_queue; - - Mutex m_queue_lock; - ConditionVariable m_cv; - - ListenerSet m_download_listeners; -}; - -} -#endif diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.cpp new file mode 100644 index 0000000000..c88088106e --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.cpp @@ -0,0 +1,98 @@ +/* Required Download + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include "CommonFramework/GlobalSettingsPanel.h" +#include "GlobalResourceDownloadManager.h" +#include "ResourceDownload.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + +ResourceDownload::ResourceDownload(GlobalResourceDownloadManager& download_manager, DownloadedResourceMetadata resource_metadata, Mutex& lock, ConditionVariable& cv) + : m_resource_metadata(resource_metadata) + , m_name(resource_metadata.resource_name) + , m_lock(lock) + , m_cv(cv) + , m_download_thread(initialize_download_thread()) +{ + // start_download(); +} + +DownloadThread ResourceDownload::initialize_download_thread(){ + + DownloadThread::Hooks generic_row_hooks{ + .is_ready_to_start = [this] { return GlobalResourceDownloadManager::instance().is_download_ready_to_start(m_name); }, + .on_finished = [this](bool success) { + report_download_finished(success); + }, + .report_failed = [this] { report_download_failed(); }, + .report_exception_caught = [this](const char* error_msg) { report_unexpected_exception_caught(error_msg); }, + .report_download_progress = [this](uint64_t bytes_done, uint64_t total_bytes) { report_download_progress(bytes_done, total_bytes); }, + .report_hash_progress = [this](uint64_t bytes_done, uint64_t total_bytes) { report_hash_progress(bytes_done, total_bytes); }, + .report_unzip_progress = [this](uint64_t bytes_done, uint64_t total_bytes) { report_unzip_progress(bytes_done, total_bytes); } + }; + + return DownloadThread{generic_row_hooks, m_resource_metadata, m_lock, m_cv}; + +} + + + +void ResourceDownload::start_download(){ + cout << "start_download" << endl; + m_download_thread.start_download_thread(); +} + +void ResourceDownload::cancel_download(){ + m_download_thread.cancel(); + // m_parent_session.remove_from_download_list(m_index); +} + + + +////////////////////////// +// Listeners +////////////////////////// +void ResourceDownload::add_listener(Listener& listener){ + m_listeners.add(listener); +} +void ResourceDownload::remove_listener(Listener& listener){ + m_listeners.remove(listener); +} + +// void ResourceDownload::report_download_started(){ +// m_listeners.run_method(&Listener::on_download_started); +// } + +void ResourceDownload::report_download_progress(uint64_t bytes_done, uint64_t total_bytes){ + m_listeners.run_method(&Listener::on_download_progress, bytes_done, total_bytes); +} +void ResourceDownload::report_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){ + m_listeners.run_method(&Listener::on_unzip_progress, bytes_done, total_bytes); +} +void ResourceDownload::report_hash_progress(uint64_t bytes_done, uint64_t total_bytes){ + m_listeners.run_method(&Listener::on_hash_progress, bytes_done, total_bytes); +} + +void ResourceDownload::report_download_finished(bool success){ + m_listeners.run_method(&Listener::on_download_finished, success, m_name); + +} + +void ResourceDownload::report_download_failed(){ + m_listeners.run_method(&Listener::on_download_failed, m_name); +} + +void ResourceDownload::report_unexpected_exception_caught(const std::string& error_msg){ + m_listeners.run_method(&Listener::on_exception_caught, error_msg); +} + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.h similarity index 63% rename from SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.h rename to SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.h index b69c228d22..2f47132fe9 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.h +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.h @@ -4,8 +4,8 @@ * */ -#ifndef PokemonAutomation_RequiredDownload_H -#define PokemonAutomation_RequiredDownload_H +#ifndef PokemonAutomation_ResourceDownload_H +#define PokemonAutomation_ResourceDownload_H #include // #include "ComputerPrograms/Framework/ComputerProgramSession.h" @@ -19,48 +19,53 @@ namespace PokemonAutomation{ -class RequiredDownloadManager; +class GlobalResourceDownloadManager; -class RequiredDownload { +class ResourceDownload { public: - RequiredDownload(RequiredDownloadManager& download_manager, DownloadedResourceMetadata resource_metadata, Mutex& lock, ConditionVariable& cv); + ResourceDownload(GlobalResourceDownloadManager& download_manager, DownloadedResourceMetadata resource_metadata, Mutex& lock, ConditionVariable& cv); public: inline std::string get_name() const { return m_name; } public: struct Listener{ + // virtual void on_download_started(){} 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_exception_caught(const std::string& function_name){} - virtual void on_download_failed(){} + virtual void on_download_finished(bool success, const std::string& resource_slug){} + virtual void on_download_failed(const std::string& resource_slug){} + virtual void on_exception_caught(const std::string& error_msg){} }; void add_listener(Listener& listener); void remove_listener(Listener& listener); + // void report_download_started(); + 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); + + // NOTE: this runs regardless of success or failure + void report_download_finished(bool success); + + void report_download_failed(); + + void report_unexpected_exception_caught(const std::string& error_msg); + void start_download(); void cancel_download(); - void on_download_finished(); // bool is_download_ready_to_start(); // void remove_self_from_download_queue(); - void report_unexpected_exception_caught(const std::string& function_name); - void report_download_failed(); - - 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); - private: DownloadThread initialize_download_thread(); private: - RequiredDownloadManager& m_download_manager; DownloadedResourceMetadata m_resource_metadata; std::string m_name; diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp index 59c287e9a5..198d75d34e 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp @@ -64,13 +64,13 @@ std::vector deserialize_resource_list_json(const Jso } -const std::vector& local_resource_download_list(){ - // cout << "local_resource_download_list" << endl; - static std::vector local_resources = deserialize_resource_list_json( +const std::vector& expected_resource_download_list(){ + // cout << "expected_resource_download_list" << endl; + static std::vector expected_resources = deserialize_resource_list_json( load_json_file(RESOURCE_PATH() + "ResourceDownloadList.json") ); - return local_resources; + return expected_resources; } @@ -113,21 +113,6 @@ std::optional get_resource_version_num(Filesystem::Path folder_path){ } -ResourceVersionStatus get_version_status(DownloadedResourceMetadata& expected_resource){ - uint16_t expected_version_num = expected_resource.version_num.value(); - - Filesystem::Path filepath{DOWNLOADED_RESOURCE_PATH() + expected_resource.resource_name}; - bool is_downloaded = std::filesystem::is_directory(filepath); - std::optional current_version_num; // default nullopt - if (is_downloaded){ - current_version_num = get_resource_version_num(filepath); - } - - ResourceVersionStatus version_status = compare_version_num(expected_version_num, current_version_num); - - return version_status; -} - ResourceVersionStatus compare_version_num(uint16_t expected_version_num, std::optional current_version_num){ if (!current_version_num.has_value()){ return ResourceVersionStatus::NOT_APPLICABLE; @@ -142,40 +127,79 @@ ResourceVersionStatus compare_version_num(uint16_t expected_version_num, std::op } } +ResourceVersionInfo get_local_version_info(const std::string& target_resource_slug){ + DownloadedResourceMetadata expected_resource = get_expected_resource_metadata_from_resource_slug(target_resource_slug); + uint16_t expected_version_num = expected_resource.version_num.value(); + + Filesystem::Path filepath{DOWNLOADED_RESOURCE_PATH() + expected_resource.resource_name}; + bool is_downloaded = std::filesystem::is_directory(filepath); + std::optional current_version_num; // default nullopt + if (is_downloaded){ + current_version_num = get_resource_version_num(filepath); + } + + ResourceVersionStatus version_status = compare_version_num(expected_version_num, current_version_num); + + return ResourceVersionInfo{ + .is_downloaded = is_downloaded, + .version_status = version_status, + .current_version_num = current_version_num + }; +} + -DownloadedResourceMetadata get_resource_metadata_from_resource_type(const std::string& target_resource_type, const std::vector& resource_list){ +DownloadedResourceMetadata get_resource_metadata_from_resource_type(const std::string& target_resource_slug, const std::vector& resource_list){ for (uint16_t index = 0; index < resource_list.size(); index++){ const DownloadedResourceMetadata& metadata = resource_list[index]; - if (metadata.resource_name == target_resource_type){ + if (metadata.resource_name == target_resource_slug){ return metadata; } } - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "get_resource_metadata_from_resource_type: Unable to find resource_type within resource_list."); + Logger& logger = global_logger_tagged(); + throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, + "get_resource_metadata_from_resource_type: Unable to find target_resource_slug within resource_list."); } -bool is_resource_ready_in_queue(uint16_t max_concurrent_downloads, const std::string& resource_slug, const std::vector& download_queue){ - // ASSUMES: the calling thread holds the m_lock. therefore, this function doesn't lock the mutex when accessing download_queue. - // std::lock_guard lg(m_lock); - auto it = std::find(download_queue.begin(), download_queue.end(), resource_slug); - if (it == download_queue.end()){ - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "is_download_ready_to_start: resource_slug not found within download_queue."); - } +DownloadedResourceMetadata get_remote_resource_metadata_from_resource_slug(const std::string& target_resource_slug){ + Logger& logger = global_logger_tagged(); + std::vector remote_resources; - uint16_t download_position = (uint16_t)std::distance(download_queue.begin(), it); + // Step 1: Attempt to fetch the list of available downloads + try{ + remote_resources = remote_resource_download_list(); + }catch(OperationFailedException&){ + std::cerr << "get_remote_resource_metadata_from_resource_slug: 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."); + } - // cout << "download_position: " << std::to_string(download_position) << endl; + // Step 2: Attempt to extract metadata for the specific slug + try{ + return get_resource_metadata_from_resource_type(target_resource_slug, remote_resources); + }catch(OperationFailedException&){ + std::cerr << "get_remote_resource_metadata_from_resource_slug: Error" << endl; + throw_and_log(logger, ErrorReport::NO_ERROR_REPORT, + "get_remote_resource_metadata_from_resource_slug: Unable to find " + target_resource_slug + " within resource_list. " + "Likely caused by resource being no longer available for download. We recommend updating the Computer Control program."); + } +} - return download_position < max_concurrent_downloads; +DownloadedResourceMetadata get_expected_resource_metadata_from_resource_slug(const std::string& target_resource_slug){ + try{ + return get_resource_metadata_from_resource_type(target_resource_slug, expected_resource_download_list()); + }catch(OperationFailedException&){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "get_expected_resource_metadata_from_resource_slug: Unable to find target_resource_slug within resource_list. This shouldn't happen."); + } } const std::unordered_set& all_resource_names(){ static std::unordered_set names = [](){ std::unordered_set resource_names; - for (const DownloadedResourceMetadata& resource : local_resource_download_list()){ + for (const DownloadedResourceMetadata& resource : expected_resource_download_list()){ resource_names.insert(resource.resource_name); } return resource_names; diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.h index b6100ada90..27b1cfc685 100644 --- a/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.h +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.h @@ -39,29 +39,38 @@ enum class ResourceVersionStatus{ }; +struct ResourceVersionInfo{ + bool is_downloaded; + ResourceVersionStatus version_status; + std::optional current_version_num; +}; + // there are three lists: -// - local_resource_download_list(): List of resources with the version numbers that the programs expect. from the local ResourceDownloadList.json -// - list of resources downloaded locally. use get_version_status() to determine ResourceVersionStatus relative to the expected resource version number in the local list. +// - expected_resource_download_list(): List of resources with the version numbers that the programs expect. from the local ResourceDownloadList.json +// - list of resources downloaded locally. use get_local_version_info() to determine ResourceVersionStatus relative to the expected resource version number. // - remote_resource_download_list(): list of remote resources. the remote version numbers may or may not match the local list. from the remote ResourceDownloadList.json -const std::vector& local_resource_download_list(); +const std::vector& expected_resource_download_list(); + const std::vector& remote_resource_download_list(); -std::optional get_resource_version_num(Filesystem::Path folder_path); -// for the given expected_resource, it tries to find its corresponding file downloaded locally. -// it then returns the ResourceVersionStatus, which is the result of comparing the expected version number -// (from the expected_resource) to the actual version number (from the downloaded file). -// expected_resource is one of the items from local_resource_download_list(), which is a list of -// resources with the version numbers that the programs expect (from the local ResourceDownloadList.json). -ResourceVersionStatus get_version_status(DownloadedResourceMetadata& expected_resource); -ResourceVersionStatus compare_version_num(uint16_t expected_version_num, std::optional current_version_num); +// - This returns the version information for a resource that has been downloaded locally. +// - This returns a struct containing a boolean representing whether the resource has been downloaded, +// the resource's version status (e.g. CURRENT, OUTDATED, NOT_APPLICABLE etc.), and its version number +// - for the version status, it compares the version of the locally downloaded file to the +// corresponding expected_resource from expected_resource_download_list() +ResourceVersionInfo get_local_version_info(const std::string& target_resource_slug); -// ASSUMES: given resource_list has every resource_type within it -DownloadedResourceMetadata get_resource_metadata_from_resource_type(const std::string& target_resource_type, const std::vector& resource_list); +// - throws OperationFailedException if target_resource_slug isn't found within remote_resource_download_list +// this would indicate that CC is out of date. +// - also throws OperationFailedException if Internet is not turned on. +DownloadedResourceMetadata get_remote_resource_metadata_from_resource_slug(const std::string& target_resource_slug); +// ASSUMES: given target_resource_slug is listed within expected_resource_download_list(). +// PanelInstance::validate_resource_list() should ensure that target_resource_slug is valid. +DownloadedResourceMetadata get_expected_resource_metadata_from_resource_slug(const std::string& target_resource_slug); -bool is_resource_ready_in_queue(uint16_t max_concurrent_downloads, const std::string& resource_slug, const std::vector& download_queue); const std::unordered_set& all_resource_names(); diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp new file mode 100644 index 0000000000..1dce848211 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.cpp @@ -0,0 +1,50 @@ +/* Resource Download Row + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "SettingsResourceDownloadRow.h" +#include "SettingsResourceDownloadOptions.h" + +// #include + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + +// SettingsResourceDownloadButton::~SettingsResourceDownloadButton(){} +SettingsResourceDownloadButton::SettingsResourceDownloadButton(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) + , m_enabled(true) +{} + + +SettingsResourceDeleteButton::SettingsResourceDeleteButton(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) + , m_enabled(true) +{} + + +SettingsResourceCancelButton::SettingsResourceCancelButton(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) + , m_enabled(true) +{} + + +SettingsResourceProgressBar::SettingsResourceProgressBar(SettingsResourceDownloadRow& p_row) + : ConfigOptionImpl(LockMode::UNLOCK_WHILE_RUNNING) + , row(p_row) +{} + + +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..69e76f7e49 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadOptions.h @@ -0,0 +1,94 @@ +/* Resource Download Row + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_ResourceDownloadOptions_H +#define PokemonAutomation_ResourceDownloadOptions_H + +// #include "Common/Cpp/Containers/Pimpl.h" +// #include "Common/Cpp/Concurrency/AsyncTask.h" +// #include "Common/Cpp/Options/StaticTableOption.h" +// #include "ResourceDownloadHelpers.h" + + +namespace PokemonAutomation{ + +class SettingsResourceDownloadRow; + + +class SettingsResourceDownloadButton : public ConfigOptionImpl{ +public: + // ~SettingsResourceDownloadButton(); + SettingsResourceDownloadButton(SettingsResourceDownloadRow& p_row); + +public: + inline bool get_enabled(){ return m_enabled; } + inline void set_enabled(bool enabled){ + m_enabled = 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: + SettingsResourceDeleteButton(SettingsResourceDownloadRow& p_row); + +public: + inline bool get_enabled(){ return m_enabled; } + inline void set_enabled(bool enabled){ + m_enabled = enabled; + } + +public: + SettingsResourceDownloadRow& row; + +private: + bool m_enabled; +}; + +class SettingsResourceCancelButton : public ConfigOptionImpl{ +public: + SettingsResourceCancelButton(SettingsResourceDownloadRow& p_row); + +public: + inline bool get_enabled(){ return m_enabled; } + inline void set_enabled(bool enabled){ + m_enabled = enabled; + } + +public: + SettingsResourceDownloadRow& row; + +private: + bool m_enabled; +}; + +class SettingsResourceProgressBar : public ConfigOptionImpl{ +public: + SettingsResourceProgressBar(SettingsResourceDownloadRow& p_row); + + 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..cd96852384 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.cpp @@ -0,0 +1,488 @@ +/* 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/Exceptions.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Logging/Logger.h" +#include "CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h" +#include "Common/Cpp/Filesystem.h" +#include "CommonFramework/Options/LabelCellOption.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( + std::string& resource_name, + size_t file_size, + bool is_downloaded, + std::optional version_num, + ResourceVersionStatus version_status + ) + : m_resource_name(LockMode::LOCK_WHILE_RUNNING, resource_name) + , m_file_size(file_size) + , m_file_size_label(LockMode::LOCK_WHILE_RUNNING, tostr_bytes(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)) + {} + + ListenerSet listeners; + + LabelCellOption m_resource_name; + + 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; + + +}; + +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)); +} + + +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_download_ptr) { + m_download_ptr->remove_listener(*this); + } + m_pre_download_thread.wait_and_ignore_exceptions(); + m_delete_thread.wait_and_ignore_exceptions(); +} +SettingsResourceDownloadRow::SettingsResourceDownloadRow( + std::string resource_slug, + DownloadedResourceMetadata local_metadata, + bool is_downloaded, + std::optional version_num, + ResourceVersionStatus version_status +) + : StaticTableRow(local_metadata.resource_name) + , m_action_state(ActionState::READY) + , m_resource_slug(resource_slug) + , m_local_metadata(local_metadata) + , m_data(CONSTRUCT_TOKEN, local_metadata.resource_name, local_metadata.size_decompressed_bytes, is_downloaded, version_num, version_status) + , m_download_button(*this) + , m_delete_button(*this) + , m_cancel_button(*this) + , m_progress_bar(*this) +{ + PA_ADD_STATIC(m_data->m_resource_name); + 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); +} + + + +const DownloadedResourceMetadata& SettingsResourceDownloadRow::fetch_remote_metadata(){ + + if (m_cached_metadata.has_value()){ + return 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.text(); + + for (const DownloadedResourceMetadata& remote_metadata : all_remote_metadata){ + if (remote_metadata.resource_name == resource_name){ + m_cached_metadata = remote_metadata; + return 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_pre_download_thread = GlobalThreadPools::unlimited_normal().dispatch_now_blocking( + [this]{ + try { + if (!is_given_action_state(ActionState::PRE_DOWNLOAD)){ + return; + } + + // 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"); + + // update_action_state(ActionState::READY); + report_metadata_fetch_finished(predownload_warning); + + }catch(OperationFailedException&){ + // cout << "failed" << endl; + // update_table_label(false); + update_action_state(ActionState::READY); + GlobalResourceDownloadManager::instance().report_download_failed(m_resource_slug); + return; + }catch(...){ + // update_table_label(false); + update_action_state(ActionState::READY); + // 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_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_resource_slug); + + }catch(OperationFailedException&){ + update_action_state(ActionState::READY); + } + +} + + +void SettingsResourceDownloadRow::start_delete(){ + m_delete_thread = GlobalThreadPools::unlimited_normal().dispatch_now_blocking( + [this]{ + try { + + // 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_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)); + + // update the table labels + set_is_downloaded(false); + set_version_status(ResourceVersionStatus::NOT_APPLICABLE); + + update_action_state(ActionState::READY); + }catch(OperationFailedException& e){ + std::cerr << e.message() << endl; + update_action_state(ActionState::READY); + GlobalResourceDownloadManager::instance().report_unexpected_exception_caught( + "Error: SettingsResourceDownloadButton::start_delete: Unexpected OperationFailedException exception. Report this as an error."); + return; + }catch(...){ + update_action_state(ActionState::READY); + 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_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_action_state == ActionState::READY){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(true); + m_action_state = state; + cout << "ActionState::PRE_DOWNLOAD" << endl; + } + break; + case ActionState::DOWNLOADING: + if (m_action_state == ActionState::PRE_DOWNLOAD || m_action_state == ActionState::PRE_CANCEL || m_action_state == ActionState::READY){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(true); + 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_action_state == ActionState::READY){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + 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_action_state == ActionState::PRE_DELETE){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + 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_action_state == ActionState::DOWNLOADING){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + 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_action_state == ActionState::PRE_CANCEL){ + m_download_button.set_enabled(false); + m_delete_button.set_enabled(false); + m_cancel_button.set_enabled(false); + 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_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_action_state_lock); + return m_action_state; +} + +bool SettingsResourceDownloadRow::is_given_action_state(ActionState state){ + std::lock_guard lock(m_action_state_lock); + return m_action_state == state; +} + +void SettingsResourceDownloadRow::cancel_download_thread(){ + if (m_download_ptr){ // if download is active + m_download_ptr->cancel_download(); + } +} + + +void SettingsResourceDownloadRow::connect_with_download(std::shared_ptr download_ptr){ + if (m_download_ptr){ + m_download_ptr->remove_listener(*this); + } + m_download_ptr = std::move(download_ptr); + m_download_ptr->add_listener(*this); + 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_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..459da9e2f1 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadRow.h @@ -0,0 +1,135 @@ +/* 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/Concurrency/AsyncTask.h" +// #include "Common/Cpp/Concurrency/ConditionVariable.h" +#include "Common/Cpp/LifetimeSanitizer.h" +// #include "CommonFramework/Tools/GlobalThreadPools.h" +#include "Common/Cpp/Options/StaticTableOption.h" +#include "ResourceDownloadHelpers.h" +#include "ResourceDownload.h" +#include "SettingsResourceDownloadOptions.h" +// #include + +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( + std::string resource_slug, + 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(){ return m_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: + ActionState m_action_state; + std::string m_resource_slug; + DownloadedResourceMetadata m_local_metadata; + struct Data; + Pimpl m_data; + + SettingsResourceDownloadButton m_download_button; + SettingsResourceDeleteButton m_delete_button; + SettingsResourceCancelButton m_cancel_button; + SettingsResourceProgressBar m_progress_bar; + + AsyncTask m_pre_download_thread; + AsyncTask m_delete_thread; + + std::optional m_cached_metadata; + + std::shared_ptr m_download_ptr; + + Mutex m_action_state_lock; + + 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..84d9505cd9 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.cpp @@ -0,0 +1,104 @@ +/* Resource Download Table + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Globals.h" +#include "Common/Cpp/Exceptions.h" +// #include "CommonFramework/Logging/Logger.h" +// #include "CommonFramework/Tools/GlobalThreadPools.h" +// #include "CommonFramework/Tools/FileDownloader.h" +// #include "CommonFramework/Exceptions/OperationFailedException.h" +// #include "Common/Cpp/Json/JsonArray.h" +// #include "Common/Cpp/Json/JsonObject.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_name, 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..307deca070 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadTable.h @@ -0,0 +1,45 @@ +/* Resource Download Table + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_ResourceDownloadTable_H +#define PokemonAutomation_ResourceDownloadTable_H + +#include +#include "Common/Cpp/Concurrency/AsyncTask.h" +#include "Common/Cpp/Concurrency/Mutex.h" +#include "Common/Cpp/Concurrency/ConditionVariable.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: + // we need to keep a handle on each Row, so that we can edit m_is_downloaded_label later on. + 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..5c4d05fed5 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.cpp @@ -0,0 +1,559 @@ +/* Resource Download Widget + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include +#include +#include "CommonFramework/Logging/Logger.h" +#include "Common/Cpp/Exceptions.h" + +#include "CommonFramework/Notifications/ProgramNotifications.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_row.remove_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); + + // Button should be disabled when in the middle of downloading + // this status is stored within SettingsResourceDownloadButton::m_enabled + // when the button is clicked, m_enabled is set to false + // when te download is done, m_enabled is set back to true + // the UI is updated to reflect the status of m_enabled, by using update_UI_state + + + // update the UI based on m_enabled, when the button is constructed + update_UI_state(); + + // 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(); + } + ); + + + m_row.add_listener(*this); +} + + +void SettingsDownloadButtonWidget::update_UI_state(){ + if (m_value.get_enabled()){ + m_button->setEnabled(true); + m_button->setText("Download"); + }else{ + m_button->setEnabled(false); + if (m_row.is_given_action_state(ActionState::PRE_DOWNLOAD) + || m_row.is_given_action_state(ActionState::DOWNLOADING)) + { + m_button->setText("Downloading..."); + } + } +} + + +void SettingsDownloadButtonWidget::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 SettingsDownloadButtonWidget::on_metadata_fetch_finished(const std::string& popup_message){ + QMetaObject::invokeMethod(this, [this, popup_message]{ + show_download_confirm_box("Download", popup_message); + }, Qt::QueuedConnection); + +} + + +void SettingsDownloadButtonWidget::on_action_state_updated(){ + QMetaObject::invokeMethod(this, [this]{ + update_UI_state(); + }, Qt::QueuedConnection); +} + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsDeleteButtonWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsDeleteButtonWidget::~SettingsDeleteButtonWidget(){ + m_row.remove_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); + + + // update the UI based on m_enabled, when the button is constructed + update_UI_state(); + + // 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; + } + ); + + m_row.add_listener(*this); +} + + +void SettingsDeleteButtonWidget::update_UI_state(){ + if (m_value.get_enabled()){ + m_button->setEnabled(true); + m_button->setText("Delete"); + }else{ + m_button->setEnabled(false); + if (m_row.is_given_action_state(ActionState::PRE_DELETE) + || m_row.is_given_action_state(ActionState::DELETING) + ){ + m_button->setText("Deleting..."); + } + } +} + + +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; + } +} + +// when action_state_updated, update the UI state to match +void SettingsDeleteButtonWidget::on_action_state_updated(){ + QMetaObject::invokeMethod(this, [this]{ + update_UI_state(); + }, Qt::QueuedConnection); + +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsCancelButtonWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsCancelButtonWidget::~SettingsCancelButtonWidget(){ + m_row.remove_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); + + // update the UI based on m_enabled, when the button is constructed + update_UI_state(); + + // 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; + } + ); + + m_row.add_listener(*this); + +} + +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; + } +} + + +void SettingsCancelButtonWidget::update_UI_state(){ + if (m_value.get_enabled()){ + m_button->setEnabled(true); + m_button->setText("Cancel"); + }else{ + m_button->setEnabled(false); + if (m_row.is_given_action_state(ActionState::PRE_CANCEL) + || m_row.is_given_action_state(ActionState::CANCELLING) + ){ + m_button->setText("Cancelling..."); + } + } +} + +// when action_state_updated, update the UI state to match +void SettingsCancelButtonWidget::on_action_state_updated(){ + QMetaObject::invokeMethod(this, [this]{ + update_UI_state(); + }, Qt::QueuedConnection); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// SettingsProgressBarWidget +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +template class RegisterConfigWidget; +SettingsProgressBarWidget::~SettingsProgressBarWidget(){ + // cout << "Destructor for SettingsProgressBarWidget" << endl; + m_row.remove_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 + m_progress_bar->hide(); + + // 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); + + m_row.add_listener(*this); +} + + +void SettingsProgressBarWidget::update_UI_state(){ + ActionState state = m_row.get_action_state(); + switch (state){ + case ActionState::PRE_DOWNLOAD: + case ActionState::DOWNLOADING: + m_status_label->setText("Downloading"); + if (m_progress_bar->isHidden()) { + m_progress_bar->show(); + } + break; + case ActionState::PRE_DELETE: + case ActionState::DELETING: + // m_status_label->setText(""); + // m_progress_bar->hide(); + m_progress_bar->setValue(0); + break; + case ActionState::PRE_CANCEL: + case ActionState::CANCELLING: + // m_status_label->setText(""); + // m_progress_bar->hide(); + m_progress_bar->setValue(0); + break; + case ActionState::READY: + m_status_label->setText(""); + m_progress_bar->hide(); + m_progress_bar->setValue(0); + break; + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "update_UI_state: Unknown enum."); + } +} + +void SettingsProgressBarWidget::update_progress_bar(int percentage, const std::string& text){ + if (m_progress_bar->isHidden()) { + m_progress_bar->show(); // Make it visible when progress starts + } + m_status_label->setText(QString::fromStdString(text)); + m_progress_bar->setValue(percentage); +} + +void SettingsProgressBarWidget::update_progress_bar(uint64_t bytes_done, uint64_t total_bytes, const std::string& text){ + 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, text); + } +} + +void SettingsProgressBarWidget::on_download_progress(uint64_t bytes_done, uint64_t total_bytes){ + QMetaObject::invokeMethod(this, [this, bytes_done, total_bytes]{ + update_progress_bar(bytes_done, total_bytes, "Downloading"); + }, Qt::QueuedConnection); + +} +void SettingsProgressBarWidget::on_unzip_progress(uint64_t bytes_done, uint64_t total_bytes){ + QMetaObject::invokeMethod(this, [this, bytes_done, total_bytes]{ + update_progress_bar(bytes_done, total_bytes, "Unzipping"); + }, Qt::QueuedConnection); +} +void SettingsProgressBarWidget::on_hash_progress(uint64_t bytes_done, uint64_t total_bytes){ + QMetaObject::invokeMethod(this, [this, bytes_done, total_bytes]{ + update_progress_bar(bytes_done, total_bytes, "Verifying"); + }, Qt::QueuedConnection); +} +// when action_state_updated, update the UI state to match +void SettingsProgressBarWidget::on_action_state_updated(){ + QMetaObject::invokeMethod(this, [this]{ + update_UI_state(); + }, 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..82f09766d9 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h @@ -0,0 +1,147 @@ +/* Resource Download Widget + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_SettingsResourceDownloadWidget_H +#define PokemonAutomation_SettingsResourceDownloadWidget_H + +#include +#include +#include +#include "Common/Qt/Options/ConfigWidget.h" +// #include "SettingsResourceDownloadTable.h" +#include "GlobalResourceDownloadManager.h" +#include "SettingsResourceDownloadRow.h" + +namespace PokemonAutomation{ + +// class SettingsResourceDownloadButton; + +class SettingsDownloadButtonWidget : public QWidget, public ConfigWidget, public SettingsResourceDownloadRow::Listener{ + // Q_OBJECT +public: + using ParentOption = SettingsResourceDownloadButton; + +public: + ~SettingsDownloadButtonWidget(); + SettingsDownloadButtonWidget(QWidget& parent, SettingsResourceDownloadButton& value); + + virtual void on_metadata_fetch_finished(const std::string& popup_message) override; + virtual void on_action_state_updated() override; + +private: + void update_UI_state(); + void show_download_confirm_box( + const std::string& title, + const std::string& message_body + ); + +private: + SettingsResourceDownloadButton& m_value; + SettingsResourceDownloadRow& m_row; + QPushButton* m_button; + +}; + +void show_error_box(std::string function_name); + + +class SettingsDeleteButtonWidget : public QWidget, public ConfigWidget, public SettingsResourceDownloadRow::Listener{ +public: + using ParentOption = SettingsResourceDeleteButton; + +public: + ~SettingsDeleteButtonWidget(); + SettingsDeleteButtonWidget(QWidget& parent, SettingsResourceDeleteButton& value); + + virtual void on_action_state_updated() override; + +private: + void update_UI_state(); + void show_delete_confirm_box(); + +private: + SettingsResourceDeleteButton& m_value; + SettingsResourceDownloadRow& m_row; + QPushButton* m_button; +}; + +class SettingsCancelButtonWidget : public QWidget, public ConfigWidget, public SettingsResourceDownloadRow::Listener{ +public: + using ParentOption = SettingsResourceCancelButton; + +public: + ~SettingsCancelButtonWidget(); + SettingsCancelButtonWidget(QWidget& parent, SettingsResourceCancelButton& value); + + virtual void on_action_state_updated() override; + +private: + void update_UI_state(); + void show_cancel_confirm_box(); + +private: + SettingsResourceCancelButton& m_value; + SettingsResourceDownloadRow& m_row; + QPushButton* m_button; +}; + +class SettingsProgressBarWidget : public QWidget, public ConfigWidget, public SettingsResourceDownloadRow::Listener{ +public: + using ParentOption = SettingsResourceProgressBar; + +public: + ~SettingsProgressBarWidget(); + SettingsProgressBarWidget(QWidget& parent, SettingsResourceProgressBar& value); + + 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; + + virtual void on_action_state_updated() override; + +private: + void update_UI_state(); + void update_progress_bar(int percentage, const std::string& text); + void update_progress_bar(uint64_t bytes_done, uint64_t total_bytes, const std::string& text); + +private: + // SettingsResourceProgressBar& m_value; + SettingsResourceDownloadRow& m_row; + QLabel* m_status_label; + QProgressBar* m_progress_bar; +}; + +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 2924fe9269..076ebddfe8 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -496,12 +496,24 @@ 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/ProgramMissingResourceTracker.cpp + Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h + Source/CommonFramework/ResourceDownload/ResourceDownload.cpp + Source/CommonFramework/ResourceDownload/ResourceDownload.h + Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.cpp + Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.h + Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp + Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.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