Skip to content

Commit

Permalink
✨ install_ifn_and_launch() now properly waits for versions
Browse files Browse the repository at this point in the history
  • Loading branch information
JulesFouchy committed Jan 2, 2025
1 parent 7438405 commit bbecc54
Show file tree
Hide file tree
Showing 19 changed files with 422 additions and 274 deletions.
2 changes: 1 addition & 1 deletion Lab
Submodule Lab updated 1 files
+1 −1 Cool
4 changes: 2 additions & 2 deletions src/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Task_CheckForLongPathsEnabled>());
Cool::task_manager().submit(after(500ms), // Small delay to make sure users see it pop up and it draws their attention
std::make_shared<Task_CheckForLongPathsEnabled>());
}
#endif
}
Expand Down
7 changes: 7 additions & 0 deletions src/Status.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

enum class Status {
Waiting,
Completed,
Canceled,
};
7 changes: 4 additions & 3 deletions src/Task_CheckForLongPathsEnabled.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#include "Task_CheckForLongPathsEnabled.hpp"
#include <ImGuiNotify/ImGuiNotify.hpp>
#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;
}

Expand All @@ -24,5 +25,5 @@ void Task_CheckForLongPathsEnabled::do_work()
.duration = std::nullopt,
});
}
Cool::task_manager().run_small_task_in(1s, std::make_shared<Task_CheckForLongPathsEnabled>(_notification_id));
Cool::task_manager().submit(after(1s), std::make_shared<Task_CheckForLongPathsEnabled>(_notification_id));
}
7 changes: 5 additions & 2 deletions src/Task_CheckForLongPathsEnabled.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImGuiNotify::NotificationId> _notification_id{};
Expand Down
9 changes: 7 additions & 2 deletions src/Version/Task_FetchListOfVersions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<Task_FetchListOfVersions>(_warning_notification_id));
Cool::task_manager().submit(after(message ? duration_until_reset : 1s), std::make_shared<Task_FetchListOfVersions>(_warning_notification_id));
else
version_manager()._status_of_fetch_list_of_versions.store(Status::Canceled);
}
10 changes: 7 additions & 3 deletions src/Version/Task_FetchListOfVersions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
152 changes: 77 additions & 75 deletions src/Version/Task_InstallVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -12,7 +13,7 @@
#include "miniz.h"
#include "tl/expected.hpp"

static auto download_zip(std::string const& download_url, std::atomic<float>& progression, std::atomic<bool> const& cancel)
static auto download_zip(std::string const& download_url, std::function<void(float)> const& set_progress, std::function<bool()> const& wants_to_cancel)
-> tl::expected<std::string, std::string>
{
auto cli = httplib::Client{"https://github.com"};
Expand All @@ -24,11 +25,11 @@ static auto download_zip(std::string const& download_url, std::atomic<float>& pr
cli.set_write_timeout(99999h);

auto res = cli.Get(download_url, [&](uint64_t current, uint64_t total) {
progression.store(static_cast<float>(current) / static_cast<float>(total));
return !cancel.load();
set_progress(static_cast<float>(current) / static_cast<float>(total));
return !wants_to_cancel();
});

if (cancel.load())
if (wants_to_cancel())
return "";
if (!res)
{
Expand All @@ -46,7 +47,7 @@ static auto download_zip(std::string const& download_url, std::atomic<float>& pr
return res->body;
}

static auto extract_zip(std::string const& zip, std::filesystem::path const& installation_path, std::atomic<float>& progression, std::atomic<bool> const& cancel)
static auto extract_zip(std::string const& zip, std::filesystem::path const& installation_path, std::function<void(float)> const& set_progress, std::function<bool()> const& wants_to_cancel)
-> tl::expected<void, std::string>
{
auto const file_error = [&]() {
Expand Down Expand Up @@ -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<float>(i) / static_cast<float>(files_count));
set_progress(static_cast<float>(i) / static_cast<float>(files_count));

auto file_stat = mz_zip_archive_file_stat{};
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat))
Expand Down Expand Up @@ -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;
}
}
}
38 changes: 17 additions & 21 deletions src/Version/Task_InstallVersion.hpp
Original file line number Diff line number Diff line change
@@ -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<VersionName> _version_name{};
std::optional<std::string> _download_url{};

struct DataSharedWithNotification {
std::atomic<bool> cancel{false};
std::atomic<float> download_progress{0.f};
std::atomic<float> extraction_progress{0.f};
};
std::shared_ptr<DataSharedWithNotification> _data{std::make_shared<DataSharedWithNotification>()};
std::optional<std::string> _error_message{};
};
Loading

0 comments on commit bbecc54

Please sign in to comment.