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..bd804194a6 --- /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); TODO: re-enable this + 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..b71daacec7 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.cpp @@ -0,0 +1,101 @@ +/* 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(){ + m_download_thread.cancel(); +} + +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 62% rename from SerialPrograms/Source/CommonFramework/ResourceDownload/RequiredDownload.h rename to SerialPrograms/Source/CommonFramework/ResourceDownload/ResourceDownload.h index b69c228d22..db24d7e97b 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,54 @@ namespace PokemonAutomation{ -class RequiredDownloadManager; +class GlobalResourceDownloadManager; -class RequiredDownload { +class ResourceDownload { public: - RequiredDownload(RequiredDownloadManager& download_manager, DownloadedResourceMetadata resource_metadata, Mutex& lock, ConditionVariable& cv); + ~ResourceDownload(); + 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();