diff --git a/SerialPrograms/Source/CommonFramework/ProgramSession.cpp b/SerialPrograms/Source/CommonFramework/ProgramSession.cpp index 1f6d0ad4c0..ed0212bc11 100644 --- a/SerialPrograms/Source/CommonFramework/ProgramSession.cpp +++ b/SerialPrograms/Source/CommonFramework/ProgramSession.cpp @@ -4,6 +4,7 @@ * */ +#include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/PanicDump.h" #include "CommonFramework/GlobalSettingsPanel.h" @@ -12,6 +13,10 @@ #include "CommonFramework/Panels/ProgramDescriptor.h" #include "CommonFramework/ProgramSession.h" #include "CommonFramework/ProgramStats/StatsDatabase.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h" +#include "CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h" +#include "CommonFramework/ResourceDownload/ResourceDownloadHelpers.h" #include "Integrations/ProgramTracker.h" namespace PokemonAutomation{ @@ -80,6 +85,20 @@ void ProgramSession::report_error(const std::string& message){ push_error(message); } +void ProgramSession::report_download_error(const std::string& message){ + std::lock_guard lg(m_lock); + push_download_error(message); +} + +void ProgramSession::report_download_added(std::shared_ptr download_ptr){ + // std::lock_guard lg(m_lock); + m_listeners.run_method(&Listener::download_added, std::move(download_ptr)); +} + +void ProgramSession::report_all_downloads_done(){ + m_listeners.run_method(&Listener::all_downloads_done); +} + void ProgramSession::set_state(ProgramState state){ switch (state){ @@ -110,6 +129,11 @@ void ProgramSession::push_error(const std::string& message){ m_listeners.run_method(&Listener::error, message); } +void ProgramSession::push_download_error(const std::string& message){ + m_listeners.run_method(&Listener::download_error, message); +} + + void ProgramSession::load_historical_stats(){ // Load historical stats. std::unique_ptr stats = m_descriptor.make_stats(); @@ -253,9 +277,122 @@ void ProgramSession::run_program(){ +RequiredResourceResult ProgramSession::find_missing_resources(){ + + bool upgrade_warning = false; + + std::vector missing_resources; + for(const std::string& resource_type : m_descriptor.required_resources()){ + ResourceVersionStatus version_status = get_local_version_info(resource_type).version_status; + + 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_remote_resource_metadata_from_resource_slug(resource_type); + DownloadedResourceMetadata expected_resource = get_expected_resource_metadata_from_resource_slug(resource_type); + 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 is more updated than we expect + // warn the user to upgrade CC. + 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."); + } + + missing_resources.emplace_back(remote_resource.resource_name); + + break; + } + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "resources_to_download: Unknown enum."); + } + } + + + return RequiredResourceResult{std::move(missing_resources), upgrade_warning}; + +} + + + +bool ProgramSession::download_prereqs(CancellableScope& scope){ + + try{ + auto [missing_resources, requires_upgrade] = find_missing_resources(); + if (requires_upgrade){ + std::string warning_string = + "The program is expecting an older version of a resource than is available. " + "This likely means that your version of Computer Control is out of date. " + "We recommend that you upgrade the Computer Control program."; + report_error(warning_string); + // cout << warning_string << endl; + } + if (missing_resources.empty()){ + // cout << "required_download_list is empty. Start the program." << endl; + + return true; + } + // 1. Create a C++ standard promise to hold the final boolean result + std::promise download_promise; + std::future download_future = download_promise.get_future(); + + // ProgramMissingResourceTracker is responsible for calling report_error() if + // the download fails, or if exceptions are thrown within the download. + ProgramMissingResourceTracker missing_resource_tracker(scope, download_promise, *this); + + GlobalResourceDownloadManager& global_download_manager = GlobalResourceDownloadManager::instance(); + for (const std::string& resource_slug : missing_resources){ + // cout << download_ptr->get_name() << endl; + + auto download_ptr = global_download_manager.add_to_download_list(resource_slug); + if (!download_ptr) { + std::cerr << "Error: Null download pointer for " << resource_slug << std::endl; + continue; + } + missing_resource_tracker.add_resource(download_ptr); + report_download_added(download_ptr); + } + missing_resource_tracker.finalize_initial_batch(); + + // Block until the ProgramMissingResourceTracker calls set_value(), + // when the downloads all succeed, or one fails, or the user clicks "Stop Program" + bool success = download_future.get(); + + report_all_downloads_done(); + + return success; + + + }catch(OperationFailedException& e){ + report_error(e.message()); + }catch(InternalProgramError& e){ + report_error(e.message()); + }catch (const std::exception& e) { + std::string message = std::string(e.what()) + "Report this as an error."; + report_error(message); + }catch(...){ + report_error("show_download_prereqs_popup: Unknown exception caught. Report this as an error."); + } + + return false; + +} diff --git a/SerialPrograms/Source/CommonFramework/ProgramSession.h b/SerialPrograms/Source/CommonFramework/ProgramSession.h index feb0013952..64f98aebf4 100644 --- a/SerialPrograms/Source/CommonFramework/ProgramSession.h +++ b/SerialPrograms/Source/CommonFramework/ProgramSession.h @@ -24,6 +24,7 @@ #include "Common/Cpp/Concurrency/AsyncTask.h" #include "CommonFramework/Globals.h" //#include "CommonFramework/Logging/Logger.h" +// #include "CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h" #include "Integrations/ProgramTrackerInterfaces.h" namespace PokemonAutomation{ @@ -31,7 +32,12 @@ namespace PokemonAutomation{ class StatsTracker; class CancellableScope; class ProgramDescriptor; +class ResourceDownload; +struct RequiredResourceResult { + std::vector missing_resources; + bool requires_upgrade = false; +}; class ProgramSession : public TrackableProgram{ @@ -40,6 +46,9 @@ class ProgramSession : public TrackableProgram{ virtual void state_change(ProgramState state) = 0; virtual void stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats) = 0; virtual void error(const std::string& message) = 0; + virtual void download_error(const std::string& message) = 0; + virtual void download_added(std::shared_ptr download_ptr) = 0; + virtual void all_downloads_done() = 0; }; void add_listener(Listener& listener); @@ -61,6 +70,16 @@ class ProgramSession : public TrackableProgram{ Logger& logger(){ return m_logger; } + // - return struct that contains the list of missing resources and boolean upgrade_warning. + // - upgrade_warning: is true if we need to warn the user to upgrade CC. + // i.e. the resource that was/will be downloaded is more updated than what the program is expecting + // - a resource is considered to be missing if its ResourceVersionStatus is OUTDATED or NOT_APPLICABLE. + // - throw OperationFailedException if one of the required_resources isn't found within remote_resource_download_list + RequiredResourceResult find_missing_resources(); + + bool download_prereqs(CancellableScope& scope); + + public: // Getters @@ -100,6 +119,9 @@ class ProgramSession : public TrackableProgram{ public: void report_stats_changed(); void report_error(const std::string& message); + void report_download_error(const std::string& message); + void report_download_added(std::shared_ptr download_ptr); + void report_all_downloads_done(); protected: @@ -114,6 +136,7 @@ class ProgramSession : public TrackableProgram{ void set_state(ProgramState state); void push_stats(); void push_error(const std::string& message); + void push_download_error(const std::string& message); void load_historical_stats(); void update_historical_stats_with_current(); @@ -132,6 +155,9 @@ class ProgramSession : public TrackableProgram{ std::atomic m_timestamp; std::atomic m_state; + + // ProgramMissingResourceTracker m_missing_resource_tracker; + AsyncTask m_program_thread; // Mutex m_stats_lock; diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.cpp new file mode 100644 index 0000000000..9bd245ee5e --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.cpp @@ -0,0 +1,164 @@ +/* ProgramMissingResourceTracker + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include "CommonFramework/ProgramSession.h" +#include "ProgramMissingResourceTracker.h" + + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + +ProgramMissingResourceTracker::~ProgramMissingResourceTracker(){ + m_scope.remove_cancel_listener(*this); + for (const auto& resource : m_missing_resources){ + resource->remove_listener(*this); + } +} + +ProgramMissingResourceTracker::ProgramMissingResourceTracker(CancellableScope& scope, std::promise& p, ProgramSession& session) + : m_scope(scope) + , m_promise(p) + , m_session(session) +{ + scope.add_cancel_listener(*this); +} + +void ProgramMissingResourceTracker::add_resource(const std::shared_ptr& resource){ + m_missing_resources.insert(resource); + resource->add_listener(*this); +} + + +void ProgramMissingResourceTracker::remove_resource(const std::string& resource_slug){ + std::lock_guard lg(m_lock); + + // this requires C++20 + std::erase_if(m_missing_resources, [&](const auto& download_ptr) { + return download_ptr->get_name() == resource_slug; + }); + +} + +void ProgramMissingResourceTracker::finalize_initial_batch() { + std::lock_guard lg(m_lock); + m_is_accepting_jobs = false; + + // Handles the edge case where everything finished before we even stopped adding jobs + if (m_missing_resources.empty()) { + if (!m_failed) { + fulfill_promise(true); + } + } +} + +void ProgramMissingResourceTracker::check_if_all_downloads_done(){ + std::lock_guard lg(m_lock); + + // check if m_missing_resources is empty and that we are no longer accepting new jobs + // checking m_is_accepting_jobs covers for the case where Download 1 finishes before Download 2 is added. + // this prevents the completion event from firing prematurely + if (m_missing_resources.empty() && !m_is_accepting_jobs){ + if (!m_failed) { + fulfill_promise(true); + } + } +} + +void ProgramMissingResourceTracker::cancel_downloads(){ + std::unordered_set> downloads_to_cancel; + + // Lock briefly to safely snapshot the queue + { + std::lock_guard lg(m_lock); + downloads_to_cancel = m_missing_resources; + } + + // We must call cancel_download() outside of the lock. + // The reason is that ProgramMissingResourceTracker is a listener to ResourceDownload. + // ResourceDownload::cancel_download() will trigger ProgramMissingResourceTracker::on_download_finished, + // which then triggers ProgramMissingResourceTracker::remove_resource(), which is + // done under a lock. + // This prevents a deadlock. + for (auto& download : downloads_to_cancel) { + if (download) { + download->cancel_download(); + } + } + +} + +void ProgramMissingResourceTracker::fulfill_promise(bool value) { + std::call_once(m_fulfill_flag, [this, value]() { + m_promise.set_value(value); + }); +} + +////////////////////////// +// TrackerListener +////////////////////////// + +// void ProgramMissingResourceTracker::add_tracker_listener(TrackerListener& listener){ +// m_tracker_listeners.add(listener); +// } +// void ProgramMissingResourceTracker::remove_tracker_listener(TrackerListener& listener){ +// m_tracker_listeners.remove(listener); +// } + +// void ProgramMissingResourceTracker::report_all_downloads_finished(){ +// m_tracker_listeners.run_method(&TrackerListener::on_all_missing_downloads_finished); +// } + +// void ProgramMissingResourceTracker::report_download_failed(const std::string& resource_slug){ +// m_tracker_listeners.run_method(&TrackerListener::on_download_failed, resource_slug); +// } + +// void ProgramMissingResourceTracker::report_unexpected_exception_caught(const std::string& error_msg){ +// m_tracker_listeners.run_method(&TrackerListener::on_unexpected_exception_caught, error_msg); +// } + +///////////////////// +// for ResourceDownload::Listener +///////////////////// +void ProgramMissingResourceTracker::on_download_finished(bool success, const std::string& resource_slug){ + // cout << "ProgramMissingResourceTracker::on_download_finished " << resource_slug << endl; + remove_resource(resource_slug); + if (success){ + check_if_all_downloads_done(); + } +} +void ProgramMissingResourceTracker::on_download_failed(const std::string& resource_slug){ + m_failed = true; + std::cerr << "ProgramMissingResourceTracker::on_download_failed: Error: Download failed for " << resource_slug << ". Check your internet connection and check you have enough disk space." << std::endl; + m_session.report_download_error("Error: Download failed for " + resource_slug + ". Check your internet connection and check you have enough disk space."); + fulfill_promise(false); +} +void ProgramMissingResourceTracker::on_exception_caught(const std::string& error_msg){ + m_failed = true; + std::cerr << "ProgramMissingResourceTracker::on_download_failed: Error: " << error_msg << std::endl; + m_session.report_download_error(error_msg); + fulfill_promise(false); +} + +///////////////////// +// for Cancellable::CancelListener +///////////////////// +void ProgramMissingResourceTracker::on_cancellable_cancel( + Cancellable& cancellable, + std::exception_ptr reason +){ + m_failed = true; + cancel_downloads(); + fulfill_promise(false); + +} + + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h new file mode 100644 index 0000000000..0192b58297 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h @@ -0,0 +1,82 @@ +/* ProgramMissingResourceTracker + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_MissingResourceTracker_H +#define PokemonAutomation_MissingResourceTracker_H + +#include +#include +#include "Common/Cpp/Concurrency/Mutex.h" +// #include "Common/Cpp/ListenerSet.h" +#include "ResourceDownload.h" + +namespace PokemonAutomation{ + +class ProgramSession; + +class ProgramMissingResourceTracker : public ResourceDownload::Listener, public Cancellable::CancelListener { + +public: + ~ProgramMissingResourceTracker(); + ProgramMissingResourceTracker(CancellableScope& scope, std::promise& p, ProgramSession& session); + + // struct TrackerListener{ + // virtual void on_all_missing_downloads_finished(){} + // virtual void on_download_failed(const std::string& resource_slug){} + // virtual void on_unexpected_exception_caught(const std::string& error_msg){} + // }; + + // void add_tracker_listener(TrackerListener& listener); + // void remove_tracker_listener(TrackerListener& 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: // ResourceDownload::Listener + // Remove the corresponding Resource from m_missing_resources + // if the download was a success, run check_if_all_downloads_done() + 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: // Cancellable::CancelListener + virtual void on_cancellable_cancel( + Cancellable& cancellable, + std::exception_ptr reason + ) override; + + +public: + void add_resource(const std::shared_ptr& resource); + void remove_resource(const std::string& resource_slug); + void finalize_initial_batch(); + void check_if_all_downloads_done(); + void cancel_downloads(); + +private: + void fulfill_promise(bool value); + + +private: + CancellableScope& m_scope; + std::promise& m_promise; + ProgramSession& m_session; + + std::unordered_set> m_missing_resources; + + bool m_is_accepting_jobs = true; + Mutex m_lock; + // ListenerSet m_tracker_listeners; + + std::atomic m_failed = false; + std::once_flag m_fulfill_flag; + + +}; +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.cpp b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.cpp new file mode 100644 index 0000000000..0b76e18e53 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.cpp @@ -0,0 +1,155 @@ +/* Required Download Dialog Widget + * + * From: https://github.com/PokemonAutomation/ + * + */ + +// #include +#include +#include +// #include +// #include +// #include +// #include +// #include +// #include "CommonFramework/Logging/Logger.h" +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/ProgramSession.h" +// #include "CommonFramework/Notifications/ProgramNotifications.h" +#include "ProgramResourceDownloadWidget.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + +//////////////////////////////////////////// +// ProgramResourceDownloadRowWidget +//////////////////////////////////////////// + +ProgramResourceDownloadRowWidget::~ProgramResourceDownloadRowWidget(){ + m_value->remove_listener(*this); +} + +ProgramResourceDownloadRowWidget::ProgramResourceDownloadRowWidget(QWidget& parent, std::shared_ptr download_ptr) + : QWidget(&parent) + , m_value(download_ptr) +{ + + QHBoxLayout* mainLayout = new QHBoxLayout(this); + // Create a label for the specific task + m_resource_name = new QLabel(QString::fromStdString(download_ptr->get_name()), this); + m_resource_name->setFixedWidth(300); + mainLayout->addWidget(m_resource_name); + + // Create a label for the status + m_status_label = new QLabel("", this); + m_status_label->setFixedWidth(70); + mainLayout->addWidget(m_status_label); + + // Create the progress bar + m_progress_bar = new QProgressBar(this); + m_progress_bar->setRange(0, 100); + m_progress_bar->setValue(0); + m_progress_bar->setFixedWidth(100); + mainLayout->addWidget(m_progress_bar); + + mainLayout->setContentsMargins(0, 0, 0, 0); + + download_ptr->add_listener(*this); +} + + +void ProgramResourceDownloadRowWidget::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){ + return; + } + + // current_percent has changed. update the progress bar + m_status_label->setText(QString::fromStdString(text)); + m_progress_bar->setValue(current_percent); +} + +void ProgramResourceDownloadRowWidget::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 ProgramResourceDownloadRowWidget::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 ProgramResourceDownloadRowWidget::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); +} + +// void ProgramResourceDownloadRowWidget::on_download_failed(const std::string& resource_slug){ +// std::cerr << "ProgramResourceDownloadRowWidget::on_download_failed: Error" << std::endl; +// QMetaObject::invokeMethod(this, [resource_slug]{ +// QMessageBox box; +// box.critical(nullptr, "Error", +// QString::fromStdString("Error: Download failed for " + resource_slug + ". Check your internet connection and check you have enough disk space.")); +// }); +// } + +//////////////////////////////////////////// +// ProgramResourceDownloadTableWidget +//////////////////////////////////////////// + +ProgramResourceDownloadTableWidget::~ProgramResourceDownloadTableWidget(){ + // m_missing_resource_tracker.remove_tracker_listener(*this); + // GlobalResourceDownloadManager::instance().remove_download_listener(*this); +} +ProgramResourceDownloadTableWidget::ProgramResourceDownloadTableWidget(QWidget& parent) + : QWidget (&parent) +{ + m_layout = new QVBoxLayout(this); + + setLayout(m_layout); + + m_layout->setContentsMargins(0, 0, 0, 0); + m_layout->setSpacing(0); + +} + +void ProgramResourceDownloadTableWidget::add_download(std::shared_ptr download_ptr){ + ProgramResourceDownloadRowWidget* download_widget = new ProgramResourceDownloadRowWidget(*this, download_ptr); + + m_layout->addWidget(download_widget); + + if (m_layout->count() > 0) { + this->show(); + } +} + + +void ProgramResourceDownloadTableWidget::remove_all_downloads(){ + QLayout* current_layout = layout(); + if (!current_layout) return; + + while (current_layout->count() > 0) { + QLayoutItem* item = current_layout->takeAt(0); // Always take the current front item + if (QWidget* widget = item->widget()) { + widget->deleteLater(); + } + delete item; + } + + if (m_layout->count() == 0) { + this->hide(); + } +} + + + + +} diff --git a/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h new file mode 100644 index 0000000000..718874399c --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h @@ -0,0 +1,68 @@ +/* Required Download Dialog Widget + * + * From: https://github.com/PokemonAutomation/ + * + */ + +// NOTE: These widgets do not inherit ConfigWidget. +// So, they don't need to be added to StaticRegistrationQt + +#ifndef PokemonAutomation_ProgramResourceDownloadWidget_H +#define PokemonAutomation_ProgramResourceDownloadWidget_H + +#include +#include +#include +#include +#include "ProgramMissingResourceTracker.h" +#include "GlobalResourceDownloadManager.h" +#include "CommonFramework/ResourceDownload/ResourceDownload.h" + + +namespace PokemonAutomation{ + +class ProgramSession; + +class ProgramResourceDownloadRowWidget : public QWidget, public ResourceDownload::Listener { +public: + ~ProgramResourceDownloadRowWidget(); + ProgramResourceDownloadRowWidget(QWidget& parent, std::shared_ptr download_ptr); + +public: + 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_download_failed(const std::string& resource_slug) override; + + + void update_progress_bar(uint64_t bytes_done, uint64_t total_bytes, const std::string& text); + +private: + std::shared_ptr m_value; + QLabel* m_resource_name; + QLabel* m_status_label; + QProgressBar* m_progress_bar; + +}; + +class ProgramResourceDownloadTableWidget : public QWidget { + +public: + ~ProgramResourceDownloadTableWidget(); + ProgramResourceDownloadTableWidget(QWidget& parent); + + void add_download(std::shared_ptr download_ptr); + + void remove_all_downloads(); + +private: + QVBoxLayout* m_layout; + +}; + + + + +} +#endif diff --git a/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramSession.cpp b/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramSession.cpp index c70e0773e0..db9e6d4657 100644 --- a/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramSession.cpp +++ b/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramSession.cpp @@ -81,6 +81,20 @@ void ComputerProgramSession::internal_stop_program(){ } } void ComputerProgramSession::internal_run_program(){ + CancellableHolder download_scope; + { + WriteSpinLock lg(m_lock, PA_CURRENT_FUNCTION); + m_scope = &download_scope; + } + bool success = download_prereqs(download_scope); + { + std::lock_guard lg(program_lock()); + m_scope = nullptr; + } + if (!success){ + return; + } + GlobalSettings::instance().PERFORMANCE->REALTIME_THREAD_PRIORITY.set_on_this_thread(logger()); m_option.options().reset_state(); diff --git a/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.cpp b/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.cpp index 4622e3f3a4..b4cf6deff0 100644 --- a/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.cpp +++ b/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.cpp @@ -13,10 +13,15 @@ #include "CommonFramework/Panels/PanelTools.h" #include "CommonFramework/Panels/UI/PanelElements.h" #include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h" #include "ComputerPrograms/ComputerProgram.h" #include "ComputerPrograms/Framework/ComputerProgramOption.h" #include "ComputerProgramWidget.h" +// #include +// using std::cout; +// using std::endl; + namespace PokemonAutomation{ @@ -72,6 +77,10 @@ ComputerProgramWidget::ComputerProgramWidget( m_actions_bar = new RunnablePanelActionBar(*this, m_session.current_state()); layout->addWidget(m_actions_bar); + m_downloads_table = new ProgramResourceDownloadTableWidget(*this); + m_downloads_table->setVisible(false); + layout->addWidget(m_downloads_table); + connect( m_actions_bar, &RunnablePanelActionBar::start_clicked, this, [&](ProgramState state){ @@ -111,6 +120,10 @@ void ComputerProgramWidget::state_change(ProgramState state){ }else{ m_holder.on_busy(); } + + if(state == ProgramState::STOPPING){ + m_downloads_table->remove_all_downloads(); + } }); } void ComputerProgramWidget::stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats){ @@ -128,6 +141,29 @@ void ComputerProgramWidget::error(const std::string& message){ }); } +void ComputerProgramWidget::download_error(const std::string& message){ + if (m_popup_is_open.exchange(true)){ // only show popups if one isn't already open + return; + } + QMetaObject::invokeMethod(this, [message]{ + QMessageBox box; + box.critical(nullptr, "Error", QString::fromStdString(message)); + }); + m_popup_is_open.store(false); +} + +void ComputerProgramWidget::download_added(std::shared_ptr download_ptr){ + QMetaObject::invokeMethod(this, [this, download_ptr]{ + this->m_downloads_table->add_download(std::move(download_ptr)); + }); +} + +void ComputerProgramWidget::all_downloads_done(){ + QMetaObject::invokeMethod(this, [this]{ + this->m_downloads_table->remove_all_downloads(); + }); +} + diff --git a/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.h b/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.h index 35d2d61943..40108741fe 100644 --- a/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.h +++ b/SerialPrograms/Source/ComputerPrograms/Framework/ComputerProgramWidget.h @@ -25,6 +25,8 @@ namespace PokemonAutomation{ + class ProgramResourceDownloadTableWidget; + class ComputerProgramWidget : public QWidget, private ProgramSession::Listener{ @@ -40,6 +42,9 @@ class ComputerProgramWidget : public QWidget, private ProgramSession::Listener{ virtual void state_change(ProgramState state) override; virtual void stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats) override; virtual void error(const std::string& message) override; + virtual void download_error(const std::string& message) override; + virtual void download_added(std::shared_ptr download_ptr) override; + virtual void all_downloads_done() override; private: PanelHolder& m_holder; @@ -47,6 +52,8 @@ class ComputerProgramWidget : public QWidget, private ProgramSession::Listener{ ConfigWidget* m_options; StatsBar* m_stats_bar; RunnablePanelActionBar* m_actions_bar; + ProgramResourceDownloadTableWidget* m_downloads_table; + std::atomic m_popup_is_open{false}; }; diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp index fb5809f946..9ff2b9c4ee 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp @@ -151,6 +151,24 @@ void MultiSwitchProgramSession::internal_stop_program(){ } } void MultiSwitchProgramSession::internal_run_program(){ + CancellableHolder download_scope; + { + std::lock_guard lg(program_lock()); + if (current_state() != ProgramState::RUNNING){ + return; + } + m_scope.store(&download_scope, std::memory_order_release); + } + + bool success = download_prereqs(download_scope); + { + std::lock_guard lg(program_lock()); + m_scope.store(nullptr, std::memory_order_release); + } + if (!success){ + return; + } + auto ScopeCheck = m_sanitizer.check_scope(); m_option.options().reset_state(); diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp index a761f7f53d..2056f3ffcf 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp @@ -135,6 +135,24 @@ void SingleSwitchProgramSession::internal_stop_program(){ } } void SingleSwitchProgramSession::internal_run_program(){ + CancellableHolder scope; + { + std::lock_guard lg(program_lock()); + if (current_state() != ProgramState::RUNNING){ + return; + } + m_scope.store(&scope, std::memory_order_release); + } + bool success = download_prereqs(scope); + { + std::lock_guard lg(program_lock()); + m_scope.store(nullptr, std::memory_order_release); + } + + if (!success){ + return; + } + m_option.options().reset_state(); SleepSuppressScope sleep_scope(GlobalSettings::instance().SLEEP_SUPPRESS->PROGRAM_RUNNING); diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.cpp b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.cpp index 9100ac78c3..3ec3064d43 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.cpp @@ -16,6 +16,7 @@ #include "CommonFramework/Panels/PanelTools.h" #include "CommonFramework/Panels/UI/PanelElements.h" #include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h" #include "NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramOption.h" #include "NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.h" #include "NintendoSwitch_MultiSwitchProgramWidget.h" @@ -103,6 +104,10 @@ MultiSwitchProgramWidget2::MultiSwitchProgramWidget2( m_actions_bar = new RunnablePanelActionBar(*this, m_session.current_state()); layout->addWidget(m_actions_bar); + m_downloads_table = new ProgramResourceDownloadTableWidget(*this); + m_downloads_table->setVisible(false); + layout->addWidget(m_downloads_table); + connect( m_actions_bar, &RunnablePanelActionBar::start_clicked, this, [&](ProgramState state){ @@ -153,6 +158,10 @@ void MultiSwitchProgramWidget2::state_change(ProgramState state){ }else{ m_holder.on_busy(); } + + if(state == ProgramState::STOPPING){ + m_downloads_table->remove_all_downloads(); + } }); } void MultiSwitchProgramWidget2::stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats){ @@ -172,6 +181,31 @@ void MultiSwitchProgramWidget2::error(const std::string& message){ }); } +void MultiSwitchProgramWidget2::download_error(const std::string& message){ + if (m_popup_is_open.exchange(true)){ // only show popups if one isn't already open + return; + } + auto ScopeCheck = m_sanitizer.check_scope(); + QMetaObject::invokeMethod(this, [message]{ + QMessageBox box; + box.critical(nullptr, "Error", QString::fromStdString(message)); + }); + m_popup_is_open.store(false); +} + +void MultiSwitchProgramWidget2::download_added(std::shared_ptr download_ptr){ + QMetaObject::invokeMethod(this, [this, download_ptr]{ + this->m_downloads_table->add_download(std::move(download_ptr)); + }); +} + +void MultiSwitchProgramWidget2::all_downloads_done(){ + QMetaObject::invokeMethod(this, [this]{ + this->m_downloads_table->remove_all_downloads(); + }); +} + + void MultiSwitchProgramWidget2::redraw_options(){ auto ScopeCheck = m_sanitizer.check_scope(); QMetaObject::invokeMethod(this, [this]{ diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.h b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.h index 65bcad5f77..7201849624 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.h +++ b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_MultiSwitchProgramWidget.h @@ -25,6 +25,7 @@ namespace PokemonAutomation{ struct PanelHolder; + class ProgramResourceDownloadTableWidget; namespace NintendoSwitch{ @@ -42,6 +43,9 @@ class MultiSwitchProgramWidget2 : public QWidget, private ProgramSession::Listen virtual void state_change(ProgramState state) override; virtual void stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats) override; virtual void error(const std::string& message) override; + virtual void download_error(const std::string& message) override; + virtual void download_added(std::shared_ptr download_ptr) override; + virtual void all_downloads_done() override; virtual void redraw_options() override; @@ -52,6 +56,8 @@ class MultiSwitchProgramWidget2 : public QWidget, private ProgramSession::Listen ConfigWidget* m_options; StatsBar* m_stats_bar; RunnablePanelActionBar* m_actions_bar; + ProgramResourceDownloadTableWidget* m_downloads_table; + std::atomic m_popup_is_open{false}; LifetimeSanitizer m_sanitizer; }; diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.cpp b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.cpp index 3d5dabf931..e2aa82cbd3 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.cpp @@ -15,6 +15,7 @@ #include "CommonFramework/Panels/PanelTools.h" #include "CommonFramework/Panels/UI/PanelElements.h" #include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h" #include "NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramOption.h" #include "NintendoSwitch_SingleSwitchProgramWidget.h" @@ -106,6 +107,10 @@ SingleSwitchProgramWidget2::SingleSwitchProgramWidget2( m_actions_bar = new RunnablePanelActionBar(*this, m_session.current_state()); layout->addWidget(m_actions_bar); + m_downloads_table = new ProgramResourceDownloadTableWidget(*this); + m_downloads_table->setVisible(false); + layout->addWidget(m_downloads_table); + connect( m_actions_bar, &RunnablePanelActionBar::start_clicked, this, [&](ProgramState state){ @@ -151,6 +156,10 @@ void SingleSwitchProgramWidget2::state_change(ProgramState state){ }else{ m_holder.on_busy(); } + + if(state == ProgramState::STOPPING){ + m_downloads_table->remove_all_downloads(); + } }); } void SingleSwitchProgramWidget2::stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats){ @@ -167,7 +176,29 @@ void SingleSwitchProgramWidget2::error(const std::string& message){ box.critical(nullptr, "Error", QString::fromStdString(message)); }); } +void SingleSwitchProgramWidget2::download_error(const std::string& message){ + if (m_popup_is_open.exchange(true)){ // only show popups if one isn't already open + return; + } + + QMetaObject::invokeMethod(this, [message]{ + QMessageBox box; + box.critical(nullptr, "Error", QString::fromStdString(message)); + }); + m_popup_is_open.store(false); +} +void SingleSwitchProgramWidget2::download_added(std::shared_ptr download_ptr){ + QMetaObject::invokeMethod(this, [this, download_ptr]{ + this->m_downloads_table->add_download(std::move(download_ptr)); + }); +} + +void SingleSwitchProgramWidget2::all_downloads_done(){ + QMetaObject::invokeMethod(this, [this]{ + this->m_downloads_table->remove_all_downloads(); + }); +} diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.h b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.h index e907d76b36..30a874ddae 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.h +++ b/SerialPrograms/Source/NintendoSwitch/Framework/UI/NintendoSwitch_SingleSwitchProgramWidget.h @@ -25,6 +25,7 @@ namespace PokemonAutomation{ struct PanelHolder; + class ProgramResourceDownloadTableWidget; namespace NintendoSwitch{ @@ -42,6 +43,9 @@ class SingleSwitchProgramWidget2 : public QWidget, private ProgramSession::Liste virtual void state_change(ProgramState state) override; virtual void stats_update(const StatsTracker* current_stats, const StatsTracker* historical_stats) override; virtual void error(const std::string& message) override; + virtual void download_error(const std::string& message) override; + virtual void download_added(std::shared_ptr download_ptr) override; + virtual void all_downloads_done() override; private: PanelHolder& m_holder; @@ -50,6 +54,8 @@ class SingleSwitchProgramWidget2 : public QWidget, private ProgramSession::Liste ConfigWidget* m_options; StatsBar* m_stats_bar; RunnablePanelActionBar* m_actions_bar; + ProgramResourceDownloadTableWidget* m_downloads_table; + std::atomic m_popup_is_open{false}; }; diff --git a/SerialPrograms/Source/StaticRegistrationQt.cpp b/SerialPrograms/Source/StaticRegistrationQt.cpp index 47d1fa0d9e..14c4a2c2af 100644 --- a/SerialPrograms/Source/StaticRegistrationQt.cpp +++ b/SerialPrograms/Source/StaticRegistrationQt.cpp @@ -36,6 +36,9 @@ #include "CommonFramework/Options/QtWidget/LabelCellWidget.h" #include "CommonFramework/Notifications/EventNotificationWidget.h" +// Resource Download +#include "CommonFramework/ResourceDownload/SettingsResourceDownloadWidget.h" + // Integrations #include "Integrations/DiscordIntegrationSettingsWidget.h" @@ -99,6 +102,14 @@ void register_all_statics(){ RegisterConfigWidget(); RegisterConfigWidget(); + // Resource Download + RegisterConfigWidget(); + RegisterConfigWidget(); + RegisterConfigWidget(); + RegisterConfigWidget(); + RegisterConfigWidget(); + RegisterConfigWidget(); + // Integrations RegisterConfigWidget(); diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index ae37b1fc2c..32a1210d6c 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -498,6 +498,10 @@ file(GLOB LIBRARY_SOURCES Source/CommonFramework/ResourceDownload/DownloadThread.h Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.cpp Source/CommonFramework/ResourceDownload/GlobalResourceDownloadManager.h + Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.cpp + Source/CommonFramework/ResourceDownload/ProgramMissingResourceTracker.h + Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.cpp + Source/CommonFramework/ResourceDownload/ProgramResourceDownloadWidget.h Source/CommonFramework/ResourceDownload/ResourceDownload.cpp Source/CommonFramework/ResourceDownload/ResourceDownload.h Source/CommonFramework/ResourceDownload/ResourceDownloadHelpers.cpp