From bbecc54a525bb1ab0b2fdf8b74bacc799084b964 Mon Sep 17 00:00:00 2001 From: Jules Fouchy Date: Thu, 2 Jan 2025 15:56:52 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20install=5Fifn=5Fand=5Flaunch()=20no?= =?UTF-8?q?w=20properly=20waits=20for=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lab | 2 +- src/App.cpp | 4 +- src/Status.hpp | 7 + src/Task_CheckForLongPathsEnabled.cpp | 7 +- src/Task_CheckForLongPathsEnabled.hpp | 7 +- src/Version/Task_FetchListOfVersions.cpp | 9 +- src/Version/Task_FetchListOfVersions.hpp | 10 +- src/Version/Task_InstallVersion.cpp | 152 +++++------ src/Version/Task_InstallVersion.hpp | 38 ++- src/Version/Task_LaunchVersion.cpp | 48 ++++ src/Version/Task_LaunchVersion.hpp | 28 +++ ...ask_WaitForDownloadUrlToInstallVersion.cpp | 43 ---- ...ask_WaitForDownloadUrlToInstallVersion.hpp | 23 -- src/Version/VersionManager.cpp | 238 +++++++++++++----- src/Version/VersionManager.hpp | 38 ++- src/Version/VersionRef.cpp | 20 ++ src/Version/VersionRef.hpp | 4 +- src/Version/launch.cpp | 13 - src/Version/launch.hpp | 5 - 19 files changed, 422 insertions(+), 274 deletions(-) create mode 100644 src/Status.hpp create mode 100644 src/Version/Task_LaunchVersion.cpp create mode 100644 src/Version/Task_LaunchVersion.hpp delete mode 100644 src/Version/Task_WaitForDownloadUrlToInstallVersion.cpp delete mode 100644 src/Version/Task_WaitForDownloadUrlToInstallVersion.hpp create mode 100644 src/Version/VersionRef.cpp delete mode 100644 src/Version/launch.cpp delete mode 100644 src/Version/launch.hpp diff --git a/Lab b/Lab index 1ad2a87..9970957 160000 --- a/Lab +++ b/Lab @@ -1 +1 @@ -Subproject commit 1ad2a87f78fdc0ffee75d55fb0c8bd8201a1ae23 +Subproject commit 99709570227728dfd96e1724e0e8d9a8450b0880 diff --git a/src/App.cpp b/src/App.cpp index 59e48a0..afc1199 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -15,8 +15,8 @@ App::App(Cool::WindowManager& windows, Cool::ViewsManager& /* views */) #if defined(_WIN32) if (_project_manager.has_some_projects()) // Don't show it the first time users open the launcher after installing it, because we don't want to scare them with something that might look like a virus { - Cool::task_manager().run_small_task_in(500ms, // Small delay to make sure users see it pop up and it draws their attention - std::make_shared()); + Cool::task_manager().submit(after(500ms), // Small delay to make sure users see it pop up and it draws their attention + std::make_shared()); } #endif } diff --git a/src/Status.hpp b/src/Status.hpp new file mode 100644 index 0000000..093e30e --- /dev/null +++ b/src/Status.hpp @@ -0,0 +1,7 @@ +#pragma once + +enum class Status { + Waiting, + Completed, + Canceled, +}; \ No newline at end of file diff --git a/src/Task_CheckForLongPathsEnabled.cpp b/src/Task_CheckForLongPathsEnabled.cpp index baa7045..6c96c40 100644 --- a/src/Task_CheckForLongPathsEnabled.cpp +++ b/src/Task_CheckForLongPathsEnabled.cpp @@ -1,13 +1,14 @@ #include "Task_CheckForLongPathsEnabled.hpp" +#include #include "Cool/EnableLongPaths/EnableLongPaths.hpp" #include "Cool/Task/TaskManager.hpp" -void Task_CheckForLongPathsEnabled::do_work() +void Task_CheckForLongPathsEnabled::execute() { if (Cool::has_long_paths_enabled()) { if (_notification_id.has_value()) - ImGuiNotify::close_after_small_delay(*_notification_id); + ImGuiNotify::close_immediately(*_notification_id); return; } @@ -24,5 +25,5 @@ void Task_CheckForLongPathsEnabled::do_work() .duration = std::nullopt, }); } - Cool::task_manager().run_small_task_in(1s, std::make_shared(_notification_id)); + Cool::task_manager().submit(after(1s), std::make_shared(_notification_id)); } \ No newline at end of file diff --git a/src/Task_CheckForLongPathsEnabled.hpp b/src/Task_CheckForLongPathsEnabled.hpp index 68dd2a5..52a1c17 100644 --- a/src/Task_CheckForLongPathsEnabled.hpp +++ b/src/Task_CheckForLongPathsEnabled.hpp @@ -9,10 +9,13 @@ class Task_CheckForLongPathsEnabled : public Cool::Task { : _notification_id{notification_id} {} - void do_work() override; + auto name() const -> std::string override { return "Checking if Long Paths are enabled in the Windows settings"; } + +private: + void execute() override; + auto is_quick_task() const -> bool override { return true; } void cancel() override {} auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return false; } - auto name() const -> std::string override { return "Checking if Long Paths are enabled in the Windows settings"; } private: std::optional _notification_id{}; diff --git a/src/Version/Task_FetchListOfVersions.cpp b/src/Version/Task_FetchListOfVersions.cpp index b527e5c..7ee6725 100644 --- a/src/Version/Task_FetchListOfVersions.cpp +++ b/src/Version/Task_FetchListOfVersions.cpp @@ -2,6 +2,7 @@ #include "Cool/DebugOptions/DebugOptions.h" #include "Cool/Log/ToUser.h" #include "Cool/Task/TaskManager.hpp" +#include "Status.hpp" #include "VersionManager.hpp" #include "nlohmann/json.hpp" @@ -18,7 +19,7 @@ static auto zip_name_for_current_os() -> std::string #endif } -void Task_FetchListOfVersions::do_work() +void Task_FetchListOfVersions::execute() { auto cli = httplib::Client{"https://api.github.com"}; cli.set_follow_location(true); @@ -73,6 +74,8 @@ void Task_FetchListOfVersions::do_work() Cool::Log::ToUser::error("Fetch list of versions", e.what()); } + version_manager()._status_of_fetch_list_of_versions.store(Status::Completed); + if (_warning_notification_id.has_value()) ImGuiNotify::close_immediately(*_warning_notification_id); } @@ -124,5 +127,7 @@ void Task_FetchListOfVersions::handle_error(httplib::Result const& res) ImGuiNotify::change(*_warning_notification_id, notification); if (!res || message) // Only retry if we failed because we don't have an Internet connection, or because we hit the max number of requests to Github. There is no point in retrying if the service is unavailable, it's probably not gonna get fixed soon, and if we make too many requests to their API, Github will block us - Cool::task_manager().submit_in(message ? duration_until_reset : 1s, std::make_shared(_warning_notification_id)); + Cool::task_manager().submit(after(message ? duration_until_reset : 1s), std::make_shared(_warning_notification_id)); + else + version_manager()._status_of_fetch_list_of_versions.store(Status::Canceled); } \ No newline at end of file diff --git a/src/Version/Task_FetchListOfVersions.hpp b/src/Version/Task_FetchListOfVersions.hpp index 6071a10..2007378 100644 --- a/src/Version/Task_FetchListOfVersions.hpp +++ b/src/Version/Task_FetchListOfVersions.hpp @@ -9,11 +9,15 @@ class Task_FetchListOfVersions : public Cool::Task { : _warning_notification_id{warning_notification_id} {} - void do_work() override; - void cancel() override { _cancel.store(true); } - auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return false; } auto name() const -> std::string override { return "Fetching the list of versions that are available online"; } +private: + void execute() override; + + auto is_quick_task() const -> bool override { return false; } + auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return false; } + void cancel() override { _cancel.store(true); } + private: void handle_error(httplib::Result const& res); diff --git a/src/Version/Task_InstallVersion.cpp b/src/Version/Task_InstallVersion.cpp index bb3a259..d3e3561 100644 --- a/src/Version/Task_InstallVersion.cpp +++ b/src/Version/Task_InstallVersion.cpp @@ -4,6 +4,7 @@ #include "Cool/File/File.h" #include "Cool/ImGui/ImGuiExtras.h" #include "Cool/Log/ToUser.h" +#include "Cool/Task/TaskManager.hpp" #include "ImGuiNotify/ImGuiNotify.hpp" #include "Version.hpp" #include "VersionManager.hpp" @@ -12,7 +13,7 @@ #include "miniz.h" #include "tl/expected.hpp" -static auto download_zip(std::string const& download_url, std::atomic& progression, std::atomic const& cancel) +static auto download_zip(std::string const& download_url, std::function const& set_progress, std::function const& wants_to_cancel) -> tl::expected { auto cli = httplib::Client{"https://github.com"}; @@ -24,11 +25,11 @@ static auto download_zip(std::string const& download_url, std::atomic& pr cli.set_write_timeout(99999h); auto res = cli.Get(download_url, [&](uint64_t current, uint64_t total) { - progression.store(static_cast(current) / static_cast(total)); - return !cancel.load(); + set_progress(static_cast(current) / static_cast(total)); + return !wants_to_cancel(); }); - if (cancel.load()) + if (wants_to_cancel()) return ""; if (!res) { @@ -46,7 +47,7 @@ static auto download_zip(std::string const& download_url, std::atomic& pr return res->body; } -static auto extract_zip(std::string const& zip, std::filesystem::path const& installation_path, std::atomic& progression, std::atomic const& cancel) +static auto extract_zip(std::string const& zip, std::filesystem::path const& installation_path, std::function const& set_progress, std::function const& wants_to_cancel) -> tl::expected { auto const file_error = [&]() { @@ -75,9 +76,9 @@ static auto extract_zip(std::string const& zip, std::filesystem::path const& ins auto const files_count = mz_zip_reader_get_num_files(&zip_archive); for (mz_uint i = 0; i < files_count; ++i) { - if (cancel.load()) + if (wants_to_cancel()) break; - progression.store(static_cast(i) / static_cast(files_count)); + set_progress(static_cast(i) / static_cast(files_count)); auto file_stat = mz_zip_archive_file_stat{}; if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) @@ -137,104 +138,105 @@ static auto make_file_executable(std::filesystem::path const& path) -> tl::expec return {}; } -Task_InstallVersion::Task_InstallVersion(VersionName version_name, std::string download_url, ImGuiNotify::NotificationId notification_id) - : _version_name{std::move(version_name)} - , _download_url{std::move(download_url)} - , _notification_id{notification_id} +void Task_InstallVersion::on_submit() { - ImGuiNotify::change( - _notification_id, - { - .type = ImGuiNotify::Type::Info, - .title = name(), - .custom_imgui_content = [data = _data]() { - Cool::ImGuiExtras::disabled_if(data->cancel.load(), "", [&]() { - ImGui::ProgressBar(data->download_progress.load() * 0.9f + 0.1f * data->extraction_progress.load()); - if (ImGui::Button("Cancel")) - data->cancel.store(true); - }); - }, - .duration = std::nullopt, - .is_closable = false, - } - ); -} + Cool::TaskWithProgressBar::on_submit(); -void Task_InstallVersion::on_success() -{ - version_manager().set_installation_status(_version_name, InstallationStatus::Installed); - ImGuiNotify::change( - _notification_id, - { - .type = ImGuiNotify::Type::Success, - .title = fmt::format("Installed {}", _version_name.as_string()), - .content = "Success", - } - ); + if (_version_name.has_value()) + version_manager().set_installation_status(*_version_name, InstallationStatus::Installing); } -void Task_InstallVersion::on_version_not_installed() +auto Task_InstallVersion::text_in_notification_while_waiting_to_execute() const -> std::string { - version_manager().set_installation_status(_version_name, InstallationStatus::NotInstalled); - Cool::File::remove_folder(installation_path(_version_name)); // Cleanup any files that we might have started to extract from the zip + if (version_manager().status_of_fetch_list_of_versions() == Status::Completed) + return Cool::TaskWithProgressBar::text_in_notification_while_waiting_to_execute(); + return "Waiting to connect to the Internet"; } -void Task_InstallVersion::on_cancel() +auto Task_InstallVersion::notification_after_execution_completes() const -> ImGuiNotify::Notification { - on_version_not_installed(); - ImGuiNotify::close_immediately(_notification_id); + if (!_error_message.has_value()) + return Cool::TaskWithProgressBar::notification_after_execution_completes(); + + return ImGuiNotify::Notification{ + .type = ImGuiNotify::Type::Error, + .title = name(), + .content = *_error_message, + .duration = std::nullopt, + }; } -void Task_InstallVersion::on_error(std::string const& error_message) +void Task_InstallVersion::cleanup(bool has_been_canceled) { - on_version_not_installed(); - ImGuiNotify::change( - _notification_id, - { - .type = ImGuiNotify::Type::Error, - .title = fmt::format("Installation failed ({})", _version_name.as_string()), - .content = error_message, - .duration = std::nullopt, - } - ); -} + Cool::TaskWithProgressBar::cleanup(has_been_canceled); -void Task_InstallVersion::do_work() -{ - version_manager().set_installation_status(_version_name, InstallationStatus::Installing); // TODO(Launcher) should be done in constructor + if (!_version_name.has_value()) + return; - auto const zip = download_zip(_download_url, _data->download_progress, _data->cancel); - if (_data->cancel.load()) + if (has_been_canceled || _error_message.has_value()) { - on_cancel(); - return; + version_manager().set_installation_status(*_version_name, InstallationStatus::NotInstalled); + Cool::File::remove_folder(installation_path(*_version_name)); // Cleanup any files that we might have started to extract from the zip } - if (!zip.has_value()) + else { - on_error(zip.error()); - return; + version_manager().set_installation_status(*_version_name, InstallationStatus::Installed); } +} + +void Task_InstallVersion::execute() +{ + // Find version name and/or download url if necessary + if (!_version_name.has_value()) { - auto const success = extract_zip(*zip, installation_path(_version_name), _data->extraction_progress, _data->cancel); - if (_data->cancel.load()) + auto const* const version = version_manager().latest_version(); + if (!version || !version->download_url.has_value()) { - on_cancel(); + _error_message = "Didn't find any version to install"; return; } - if (!success.has_value()) + _version_name = version->name; + _download_url = version->download_url; + version_manager().set_installation_status(*_version_name, InstallationStatus::Installing); + } + if (!_download_url.has_value()) + { + auto const* const version = version_manager().find(*_version_name); + if (!version || !version->download_url.has_value()) { - on_error(success.error()); + _error_message = "This version is not available online"; return; } + _download_url = version->download_url; } + + // Download zip + auto const zip = download_zip(*_download_url, [&](float progress) { set_progress(0.9f * progress); }, [&]() { return cancel_requested(); }); + if (cancel_requested()) + return; + if (!zip.has_value()) { - auto const success = make_file_executable(executable_path(_version_name)); + _error_message = zip.error(); + return; + } + + { // Extract zip + auto const success = extract_zip(*zip, installation_path(*_version_name), [&](float progress) { set_progress(0.9f + 0.1f * progress); }, [&]() { return cancel_requested(); }); + if (cancel_requested()) + return; if (!success.has_value()) { - on_error(success.error()); + _error_message = success.error(); return; } } - on_success(); + { // Make file executable + auto const success = make_file_executable(executable_path(*_version_name)); + if (!success.has_value()) + { + _error_message = success.error(); + return; + } + } } diff --git a/src/Version/Task_InstallVersion.hpp b/src/Version/Task_InstallVersion.hpp index 0fe52e3..3c97007 100644 --- a/src/Version/Task_InstallVersion.hpp +++ b/src/Version/Task_InstallVersion.hpp @@ -1,32 +1,28 @@ #pragma once -#include "Cool/Task/Task.hpp" +#include "Cool/Task/TaskWithProgressBar.hpp" #include "ImGuiNotify/ImGuiNotify.hpp" #include "VersionName.hpp" -class Task_InstallVersion : public Cool::Task { +class Task_InstallVersion : public Cool::TaskWithProgressBar { public: - Task_InstallVersion(VersionName version_name, std::string download_url, ImGuiNotify::NotificationId notification_id); - - void do_work() override; - void cancel() override { _data->cancel.store(true); } - auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return true; } - auto name() const -> std::string override { return fmt::format("Installing {}", _version_name.as_string()); } + Task_InstallVersion() = default; // Will install the latest version, because we don't specify a version name + explicit Task_InstallVersion(VersionName version_name) + : _version_name{std::move(version_name)} + {} + auto name() const -> std::string override { return fmt::format("Installing {}", _version_name ? _version_name->as_string() : "latest version"); } private: - void on_success(); - void on_cancel(); - void on_error(std::string const& error_message); - void on_version_not_installed(); + void on_submit() override; + void execute() override; + void cleanup(bool has_been_canceled) override; + + auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return true; } + auto text_in_notification_while_waiting_to_execute() const -> std::string override; + auto notification_after_execution_completes() const -> ImGuiNotify::Notification override; private: - VersionName _version_name; - std::string _download_url{}; - ImGuiNotify::NotificationId _notification_id{}; + std::optional _version_name{}; + std::optional _download_url{}; - struct DataSharedWithNotification { - std::atomic cancel{false}; - std::atomic download_progress{0.f}; - std::atomic extraction_progress{0.f}; - }; - std::shared_ptr _data{std::make_shared()}; + std::optional _error_message{}; }; diff --git a/src/Version/Task_LaunchVersion.cpp b/src/Version/Task_LaunchVersion.cpp new file mode 100644 index 0000000..309b43b --- /dev/null +++ b/src/Version/Task_LaunchVersion.cpp @@ -0,0 +1,48 @@ +#include "Task_LaunchVersion.hpp" +#include "Cool/AppManager/close_application.hpp" +#include "Cool/Task/TaskManager.hpp" +#include "Cool/spawn_process.hpp" +#include "VersionManager.hpp" +#include "installation_path.hpp" + +Task_LaunchVersion::Task_LaunchVersion(VersionRef version_ref, std::optional project_file_path) + : Cool::Task{reg::generate_uuid() /* give a unique id to this task, so that we can cancel it */} + , _version_ref{std::move(version_ref)} + , _project_file_path{std::move(project_file_path)} +{} + +void Task_LaunchVersion::on_submit() +{ + _notification_id = ImGuiNotify::send({ + .type = ImGuiNotify::Type::Info, + .title = name(), + .content = fmt::format("Waiting for {} to install", as_string(_version_ref)), + .custom_imgui_content = [task_id = owner_id()]() { + if (ImGui::Button("Cancel")) + Cool::task_manager().cancel_all(task_id); + }, + .duration = std::nullopt, + .is_closable = false, + }); +} + +void Task_LaunchVersion::cleanup(bool /* has_been_canceled */) +{ + ImGuiNotify::close_immediately(_notification_id); +} + +void Task_LaunchVersion::execute() +{ + auto const* const version = version_manager().find_installed_version(_version_ref); + if (!version || version->installation_status != InstallationStatus::Installed) + { + // TODO(Launcher) unexpected error, version got uninstalled before we could launch, please try again + return; + } + + Cool::spawn_process( + executable_path(version->name), + _project_file_path.has_value() ? std::vector{_project_file_path->string()} : std::vector{} + ); + Cool::close_application_if_all_tasks_are_done(); // If some installations are still in progress we want to keep the launcher open so that they can finish. And once they are done, if we were to close the launcher it would feel weird for the user that it suddenly closed, so we just keep it open. +} \ No newline at end of file diff --git a/src/Version/Task_LaunchVersion.hpp b/src/Version/Task_LaunchVersion.hpp new file mode 100644 index 0000000..421c7b2 --- /dev/null +++ b/src/Version/Task_LaunchVersion.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include "Cool/File/File.h" +#include "Cool/Task/Task.hpp" +#include "VersionRef.hpp" + +/// NB: the version must be installed before execute() is called +class Task_LaunchVersion : public Cool::Task { +public: + explicit Task_LaunchVersion(VersionRef version_ref, std::optional project_file_path); + + auto name() const -> std::string override { return fmt::format("Launching {}", _project_file_path ? fmt::format("\"{}\"", Cool::File::file_name_without_extension(*_project_file_path)) : "a new project"); } + +private: + void on_submit() override; + void execute() override; + void cleanup(bool has_been_canceled) override; + + auto is_quick_task() const -> bool override { return true; } + void cancel() override {} + auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return false; } + +private: + VersionRef _version_ref; + std::optional _project_file_path{}; + ImGuiNotify::NotificationId _notification_id{}; +}; \ No newline at end of file diff --git a/src/Version/Task_WaitForDownloadUrlToInstallVersion.cpp b/src/Version/Task_WaitForDownloadUrlToInstallVersion.cpp deleted file mode 100644 index b9dcce7..0000000 --- a/src/Version/Task_WaitForDownloadUrlToInstallVersion.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "Task_WaitForDownloadUrlToInstallVersion.hpp" -#include -#include -#include "Cool/Task/TaskManager.hpp" -#include "Task_InstallVersion.hpp" -#include "VersionManager.hpp" - -Task_WaitForDownloadUrlToInstallVersion::Task_WaitForDownloadUrlToInstallVersion(VersionName version_name, ImGuiNotify::NotificationId notification_id, std::shared_ptr> do_cancel) - : _version_name{std::move(version_name)} - , _notification_id{notification_id} - , _cancel{std::move(do_cancel)} -{ - if (_notification_id == ImGuiNotify::NotificationId{}) - { - _notification_id = ImGuiNotify::send({ - .type = ImGuiNotify::Type::Info, - .title = name(), - .content = "You are offline\nWaiting for an Internet connection", - .custom_imgui_content = [cancel = _cancel]() { - if (ImGui::Button("Cancel")) - cancel->store(true); - }, - .duration = std::nullopt, - .is_closable = false, - }); - } -} - -void Task_WaitForDownloadUrlToInstallVersion::do_work() -{ - if (_cancel->load()) - { - ImGuiNotify::close_immediately(_notification_id); - return; - } - - version_manager().with_version_found(_version_name, [&](Version const& version) { - if (version.download_url.has_value()) - Cool::task_manager().submit(std::make_shared(version.name, *version.download_url, _notification_id)); - else - Cool::task_manager().run_small_task_in(100ms, std::make_shared(_version_name, _notification_id, _cancel)); - }); -} \ No newline at end of file diff --git a/src/Version/Task_WaitForDownloadUrlToInstallVersion.hpp b/src/Version/Task_WaitForDownloadUrlToInstallVersion.hpp deleted file mode 100644 index 805f1d0..0000000 --- a/src/Version/Task_WaitForDownloadUrlToInstallVersion.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Cool/Task/Task.hpp" -#include "ImGuiNotify/ImGuiNotify.hpp" -#include "VersionName.hpp" - -class Task_WaitForDownloadUrlToInstallVersion : public Cool::Task { -public: - explicit Task_WaitForDownloadUrlToInstallVersion(VersionName version_name, ImGuiNotify::NotificationId = {}, std::shared_ptr> = std::make_unique>(false)); - - void do_work() override; - void cancel() override - { - _cancel->store(true); - ImGuiNotify::close_immediately(_notification_id); - } - auto needs_user_confirmation_to_cancel_when_closing_app() const -> bool override { return true; } - auto name() const -> std::string override { return fmt::format("Installing {}", _version_name.as_string()); } - -private: - VersionName _version_name; - ImGuiNotify::NotificationId _notification_id{}; - std::shared_ptr> _cancel; -}; diff --git a/src/Version/VersionManager.cpp b/src/Version/VersionManager.cpp index b313065..0b937e7 100644 --- a/src/Version/VersionManager.cpp +++ b/src/Version/VersionManager.cpp @@ -2,25 +2,24 @@ #include #include #include -#include #include -#include #include #include #include #include "Cool/ImGui/ImGuiExtras.h" #include "Cool/ImGui/ImGuiExtras_dropdown.hpp" #include "Cool/Task/TaskManager.hpp" +#include "Cool/Task/WaitToExecuteTask.hpp" #include "Path.hpp" #include "Task_FetchListOfVersions.hpp" -#include "Task_WaitForDownloadUrlToInstallVersion.hpp" +#include "Task_InstallVersion.hpp" +#include "Task_LaunchVersion.hpp" #include "Version.hpp" #include "VersionName.hpp" #include "VersionRef.hpp" #include "fmt/format.h" #include "handle_error.hpp" #include "installation_path.hpp" -#include "launch.hpp" static auto get_all_locally_installed_versions(std::vector& versions) -> std::optional { @@ -68,53 +67,104 @@ VersionManager::VersionManager() Cool::task_manager().submit(std::make_shared()); } -void VersionManager::install_ifn_and_launch(VersionRef const& version_ref, std::optional const& project_file_path) +class WaitToExecuteTask_HasFetchedListOfVersions : public Cool::WaitToExecuteTask { +public: + auto wants_to_execute() -> bool override { return version_manager().status_of_fetch_list_of_versions() == Status::Completed; } + auto wants_to_cancel() -> bool override { return version_manager().status_of_fetch_list_of_versions() == Status::Canceled; } +}; + +static auto after_has_fetched_list_of_versions() -> std::shared_ptr { - auto lock = std::shared_lock{_mutex}; + return std::make_shared(); +} - auto const* const version = std::visit( +auto VersionManager::after_version_installed(VersionRef const& version_ref) -> std::shared_ptr +{ + auto const after_latest_version_installed = [&]() { + if (_status_of_fetch_list_of_versions.load() == Status::Completed) + { + auto const* const latest_version = latest_version_with_download_url_no_locking(); + if (!latest_version) + { + // TODO(Launcher) error, should not happen + } + auto const install_task = get_install_task_or_create_and_submit_it(latest_version->name); + return after(install_task); + } + else if (has_at_least_one_version_installed()) + { + // We don't want to wait, use whatever version is available + return after_nothing(); + } + else + { + auto const task_install_latest_version = std::make_shared(); // TODO(Launcher) When this task starts executing, it should register itself as an installing task to the version manager + Cool::task_manager().submit(after_has_fetched_list_of_versions(), task_install_latest_version); + return after(task_install_latest_version); + } + }; + // TODO(Launcher) lock + return std::visit( wcam::overloaded{ - [&](LatestVersion) { - return latest_version_no_locking(); + [&](LatestVersion) -> std::shared_ptr { + return after_latest_version_installed(); }, - [&](LatestInstalledVersion) { - return latest_installed_version_no_locking(); + [&](LatestInstalledVersion) -> std::shared_ptr { + if (has_at_least_one_version_installed()) + return after_nothing(); + + auto const install_task = get_latest_installing_version_if_any(); + if (!install_task) + return after_latest_version_installed(); + else + return after(install_task); }, - [&](VersionName const& name) { - return find_no_locking(name); + [&](VersionName const& version_name) -> std::shared_ptr { + if (is_installed(version_name)) + return after_nothing(); + auto const install_task = get_install_task_or_create_and_submit_it(version_name); + return after(install_task); } }, version_ref ); +} - if (!version) - { - // TODO(Launcher) handle error - return; - } +auto VersionManager::get_install_task_or_create_and_submit_it(VersionName const& version_name) -> std::shared_ptr +{ + auto const it = _install_tasks.find(version_name); + if (it != _install_tasks.end()) + return it->second; + + auto const install_task = std::make_shared(version_name); + Cool::task_manager().submit(after_has_fetched_list_of_versions(), install_task); + _install_tasks.insert(std::make_pair(version_name, install_task)); + return install_task; +} +auto VersionManager::get_latest_installing_version_if_any() const -> std::shared_ptr +{ + auto res = std::shared_ptr{}; + auto ver_name = std::optional{}; + for (auto const& [version_name, task] : _install_tasks) { - auto lock2 = std::unique_lock{_project_to_launch_after_version_installed_mutex}; - if (version->installation_status == InstallationStatus::Installed) - { - launch(version->name, project_file_path); - } - else + if (task->has_been_canceled() || task->has_been_executed()) + continue; + if (!ver_name || *ver_name < version_name) { - if (version->installation_status == InstallationStatus::NotInstalled) - install(*version); - _project_to_launch_after_version_installed[version->name] = // Must be done after calling install() so that the Launch notification will be above the Install one - { - project_file_path, - ImGuiNotify::send({ - .type = ImGuiNotify::Type::Info, - .title = "Launch", - .content = fmt::format("Waiting for {} to install before we can launch the project", version->name.as_string()), - .duration = std::nullopt, - }) - }; + ver_name = version_name; + res = task; } } + return res; +} + +void VersionManager::install_ifn_and_launch(VersionRef const& version_ref, std::optional const& project_file_path) +{ + Cool::task_manager().submit( + after_version_installed(version_ref), + std::make_shared(version_ref, project_file_path) + ); } void VersionManager::install(Version const& version) @@ -124,7 +174,7 @@ void VersionManager::install(Version const& version) assert(false); return; } - Cool::task_manager().run_small_task_in(0s, std::make_shared(version.name)); + Cool::task_manager().submit(after_has_fetched_list_of_versions(), std::make_shared(version.name)); } void VersionManager::uninstall(Version& version) @@ -138,6 +188,12 @@ void VersionManager::uninstall(Version& version) version.installation_status = InstallationStatus::NotInstalled; } +auto VersionManager::find(VersionName const& name) const -> Version const* +{ + // auto lock = std::unique_lock{_mutex}; + return find_no_locking(name); +} + auto VersionManager::find_no_locking(VersionName const& name) -> Version* { auto const it = std::find_if(_versions.begin(), _versions.end(), [&](Version const& version) { @@ -148,9 +204,37 @@ auto VersionManager::find_no_locking(VersionName const& name) -> Version* return &*it; } +auto VersionManager::find_no_locking(VersionName const& name) const -> Version const* +{ + auto const it = std::find_if(_versions.begin(), _versions.end(), [&](Version const& version) { + return version.name == name; + }); + if (it == _versions.end()) + return nullptr; + return &*it; +} + +auto VersionManager::find_installed_version(VersionRef const& version_ref) const -> Version const* +{ + return std::visit( + wcam::overloaded{ + [&](LatestVersion) { + return latest_installed_version_no_locking(); + }, + [&](LatestInstalledVersion) { + return latest_installed_version_no_locking(); + }, + [&](VersionName const& name) { + return find(name); + } + }, + version_ref + ); +} + void VersionManager::with_version_found(VersionName const& name, std::function const& callback) { - auto lock = std::unique_lock{_mutex}; + // auto lock = std::unique_lock{_mutex}; auto* const version = find_no_locking(name); if (version == nullptr) @@ -164,7 +248,7 @@ void VersionManager::with_version_found(VersionName const& name, std::function const& callback) { - auto lock = std::unique_lock{_mutex}; + // auto lock = std::unique_lock{_mutex}; auto* version = find_no_locking(name); if (version == nullptr) @@ -177,6 +261,15 @@ void VersionManager::with_version_found_or_created(VersionName const& name, std: callback(*version); } +auto VersionManager::has_at_least_one_version_installed() const -> bool +{ + // auto lock = std::shared_lock{_mutex}; + + return std::any_of(_versions.begin(), _versions.end(), [&](Version const& version) { + return version.installation_status == InstallationStatus::Installed; + }); +} + void VersionManager::set_download_url(VersionName const& name, std::string download_url) { with_version_found_or_created(name, [&](Version& version) { @@ -187,41 +280,41 @@ void VersionManager::set_download_url(VersionName const& name, std::string downl void VersionManager::set_installation_status(VersionName const& name, InstallationStatus installation_status) { - with_version_found(name, [&](Version& version) { + with_version_found_or_created(name, [&](Version& version) { version.installation_status = installation_status; }); - if (installation_status == InstallationStatus::Installed) - { - auto lock = std::unique_lock{_project_to_launch_after_version_installed_mutex}; - auto const it = _project_to_launch_after_version_installed.find(name); - if (it != _project_to_launch_after_version_installed.end()) - { - ImGuiNotify::close_immediately(it->second.notification_id); - launch(name, it->second.path); - } - } - else if (installation_status == InstallationStatus::NotInstalled) + if (installation_status == InstallationStatus::Installed || installation_status == InstallationStatus::NotInstalled) { - // Installation has been canceled, so also cancel the project that was supposed to launch afterwards - auto lock = std::unique_lock{_project_to_launch_after_version_installed_mutex}; - auto const it = _project_to_launch_after_version_installed.find(name); - if (it != _project_to_launch_after_version_installed.end()) - { - ImGuiNotify::close_immediately(it->second.notification_id); - _project_to_launch_after_version_installed.erase(it); - } + auto const it = _install_tasks.find(name); + if (it != _install_tasks.end()) + _install_tasks.erase(it); } } -auto VersionManager::latest_version_no_locking() -> Version* +auto VersionManager::is_installed(VersionName const& version_name) const -> bool +{ + auto const* const version = find(version_name); + if (!version) + return false; + return version->installation_status == InstallationStatus::Installed; +} + +auto VersionManager::latest_version() const -> Version const* +{ + // auto lock = std::unique_lock{_mutex}; + return latest_version_no_locking(); +} + +auto VersionManager::latest_version_no_locking() const -> Version const* { if (_versions.empty()) return nullptr; return &_versions.front(); } -auto VersionManager::latest_installed_version_no_locking() -> Version* +auto VersionManager::latest_installed_version_no_locking() const -> Version const* { + // Versions are sorted from latest to oldest so the first one we find will be the latest auto const it = std::find_if(_versions.begin(), _versions.end(), [](Version const& version) { return version.installation_status == InstallationStatus::Installed; }); @@ -230,9 +323,20 @@ auto VersionManager::latest_installed_version_no_locking() -> Version* return &*it; } +auto VersionManager::latest_version_with_download_url_no_locking() const -> Version const* +{ + // Versions are sorted from latest to oldest so the first one we find will be the latest + auto const it = std::find_if(_versions.begin(), _versions.end(), [](Version const& version) { + return version.download_url.has_value(); + }); + if (it == _versions.end()) + return nullptr; + return &*it; +} + void VersionManager::imgui_manage_versions() { - auto lock = std::unique_lock{_mutex}; + // auto lock = std::unique_lock{_mutex}; for (auto& version : _versions) { @@ -251,17 +355,17 @@ void VersionManager::imgui_manage_versions() } } -static auto label(VersionRef const& ref) -> std::string +auto VersionManager::label(VersionRef const& ref) const -> std::string { return std::visit( wcam::overloaded{ [&](LatestVersion) { - auto const* const version = version_manager().latest_version_no_locking(); + auto const* const version = latest_version_no_locking(); return fmt::format("Latest ({})", version ? version->name.as_string() : "None"); // return _label.c_str(); }, [&](LatestInstalledVersion) { - auto const* const version = version_manager().latest_installed_version_no_locking(); + auto const* const version = latest_installed_version_no_locking(); return fmt::format("Latest Installed ({})", version ? version->name.as_string() : "None"); // return _label.c_str(); }, @@ -275,7 +379,7 @@ static auto label(VersionRef const& ref) -> std::string void VersionManager::imgui_versions_dropdown(VersionRef& ref) { - auto lock = std::shared_lock{_mutex}; + // auto lock = std::shared_lock{_mutex}; class DropdownEntry_VersionRef { public: @@ -304,7 +408,7 @@ void VersionManager::imgui_versions_dropdown(VersionRef& ref) return _label.c_str(); }, [](VersionName const& name) { - return name.as_string().c_str(); // TODO(Launcher) indicate if this is installed or not, with a small icon + return name.as_string().c_str(); // TODO(Launcher2) indicate if this is installed or not, with a small icon } }, _value diff --git a/src/Version/VersionManager.hpp b/src/Version/VersionManager.hpp index c60184b..5e3e1fb 100644 --- a/src/Version/VersionManager.hpp +++ b/src/Version/VersionManager.hpp @@ -4,6 +4,9 @@ #include #include #include +#include "Cool/Task/Task.hpp" +#include "Cool/Task/WaitToExecuteTask.hpp" +#include "Status.hpp" #include "Version.hpp" #include "VersionName.hpp" #include "VersionRef.hpp" @@ -17,35 +20,44 @@ class VersionManager { void imgui_manage_versions(); void imgui_versions_dropdown(VersionRef&); - auto latest_version_no_locking() -> Version*; - auto latest_installed_version_no_locking() -> Version*; + auto find(VersionName const& name) const -> Version const*; + auto find_installed_version(VersionRef const&) const -> Version const*; + auto latest_version() const -> Version const*; + auto status_of_fetch_list_of_versions() const -> Status { return _status_of_fetch_list_of_versions.load(); } + auto is_installed(VersionName const&) const -> bool; private: - auto find_no_locking(VersionName const& name) -> Version*; + auto find_no_locking(VersionName const&) -> Version*; + auto find_no_locking(VersionName const&) const -> Version const*; + auto latest_version_no_locking() const -> Version const*; + auto latest_installed_version_no_locking() const -> Version const*; + auto latest_version_with_download_url_no_locking() const -> Version const*; + auto label(VersionRef const&) const -> std::string; + void with_version_found(VersionName const& name, std::function const& callback); void with_version_found_or_created(VersionName const& name, std::function const& callback); + auto has_at_least_one_version_installed() const -> bool; + auto get_latest_installing_version_if_any() const -> std::shared_ptr; void install(Version const&); void uninstall(Version&); + auto after_version_installed(VersionRef const& version_ref) -> std::shared_ptr; + auto get_install_task_or_create_and_submit_it(VersionName const&) -> std::shared_ptr; + private: friend class Task_FetchListOfVersions; - friend class Task_WaitForDownloadUrlToInstallVersion; friend class Task_InstallVersion; void set_download_url(VersionName const&, std::string download_url); void set_installation_status(VersionName const&, InstallationStatus); private: - std::vector _versions{}; // Sorted, from latest to oldest version - mutable std::shared_mutex _mutex{}; - - struct ProjectToLaunch { - std::optional path{}; - ImGuiNotify::NotificationId notification_id{}; - }; - std::map _project_to_launch_after_version_installed{}; - mutable std::mutex _project_to_launch_after_version_installed_mutex{}; + std::vector _versions{}; // Sorted, from latest to oldest version + // mutable std::shared_mutex _mutex{}; + + std::atomic _status_of_fetch_list_of_versions{Status::Waiting}; + std::map> _install_tasks{}; }; inline auto version_manager() -> VersionManager& diff --git a/src/Version/VersionRef.cpp b/src/Version/VersionRef.cpp new file mode 100644 index 0000000..ce93864 --- /dev/null +++ b/src/Version/VersionRef.cpp @@ -0,0 +1,20 @@ +#include "VersionRef.hpp" +#include "wcam/src/overloaded.hpp" + +auto as_string(VersionRef const& version_ref) -> std::string +{ + return std::visit( + wcam::overloaded{ + [&](LatestVersion) { + return "latest version"s; + }, + [&](LatestInstalledVersion) { + return "latest installed version"s; + }, + [](VersionName const& name) { + return name.as_string(); + } + }, + version_ref + ); +} \ No newline at end of file diff --git a/src/Version/VersionRef.hpp b/src/Version/VersionRef.hpp index 70299ae..cffb84e 100644 --- a/src/Version/VersionRef.hpp +++ b/src/Version/VersionRef.hpp @@ -11,4 +11,6 @@ struct LatestInstalledVersion { using VersionRef = std::variant< LatestVersion, LatestInstalledVersion, - VersionName>; \ No newline at end of file + VersionName>; + +auto as_string(VersionRef const&) -> std::string; \ No newline at end of file diff --git a/src/Version/launch.cpp b/src/Version/launch.cpp deleted file mode 100644 index 364ed8d..0000000 --- a/src/Version/launch.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "launch.hpp" -#include "Cool/AppManager/close_application.hpp" -#include "Cool/spawn_process.hpp" -#include "installation_path.hpp" - -void launch(VersionName const& version_name, std::optional const& project_file_path) -{ - Cool::spawn_process( - executable_path(version_name), - project_file_path.has_value() ? std::vector{project_file_path->string()} : std::vector{} - ); - Cool::close_application_if_all_tasks_are_done(); // If some installations are still in progress we want to keep the launcher open so that they can finish. And once they are done, if we were to close the launcher it would feel weird for the user that it suddenly closed, so we just keep it open. -} \ No newline at end of file diff --git a/src/Version/launch.hpp b/src/Version/launch.hpp deleted file mode 100644 index 8fcecf7..0000000 --- a/src/Version/launch.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "VersionName.hpp" - -/// NB: The version needs to be already installed -void launch(VersionName const&, std::optional const& project_file_path); \ No newline at end of file