From 84932eb9da8f9ede4e93959d918f567b1e414e2e Mon Sep 17 00:00:00 2001 From: Andrey Nekrasov Date: Thu, 10 Dec 2020 19:05:43 +0300 Subject: [PATCH] updating: add support for prereleases (#8296) --- src/action_runner/action_runner.vcxproj | 1 + src/common/common.cpp | 2 +- src/common/updating/notifications.cpp | 8 +- src/common/updating/updating.cpp | 123 +++++++++++++++--------- src/common/updating/updating.h | 13 +-- src/runner/runner.vcxproj | 1 + src/runner/settings_window.cpp | 11 ++- src/runner/update_utils.cpp | 15 ++- src/runner/update_utils.h | 4 +- 9 files changed, 114 insertions(+), 64 deletions(-) diff --git a/src/action_runner/action_runner.vcxproj b/src/action_runner/action_runner.vcxproj index 9d683c07b3..edfff183f7 100644 --- a/src/action_runner/action_runner.vcxproj +++ b/src/action_runner/action_runner.vcxproj @@ -31,6 +31,7 @@ action_runner + Application true diff --git a/src/common/common.cpp b/src/common/common.cpp index 74c4c5e6c8..ce32d257e5 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -49,7 +49,7 @@ bool is_system_window(HWND hwnd, const char* class_name) int run_message_loop(const bool until_idle, const std::optional timeout_seconds) { - MSG msg; + MSG msg{}; bool stop = false; UINT_PTR timerId = 0; if (timeout_seconds.has_value()) diff --git a/src/common/updating/notifications.cpp b/src/common/updating/notifications.cpp index 623217001b..d27f3f4bb0 100644 --- a/src/common/updating/notifications.cpp +++ b/src/common/updating/notifications.cpp @@ -18,7 +18,7 @@ namespace updating { auto current_version_to_next_version = VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring(); current_version_to_next_version += L" -> "; - current_version_to_next_version += info.version_string; + current_version_to_next_version += info.version.toWstring(); return current_version_to_next_version; } @@ -54,7 +54,7 @@ namespace updating remove_toasts(UPDATING_PROCESS_TOAST_TAG); progress_bar_params progress_bar_params; - std::wstring progress_title{ info.version_string }; + std::wstring progress_title{ info.version.toWstring() }; progress_title += L' '; progress_title += strings.DOWNLOAD_IN_PROGRESS; @@ -134,7 +134,7 @@ namespace updating { progress_bar_params progress_bar_params; - std::wstring progress_title{ info.version_string }; + std::wstring progress_title{ info.version.toWstring() }; progress_title += L' '; progress_title += progress < 1 ? strings.DOWNLOAD_IN_PROGRESS : strings.DOWNLOAD_COMPLETE; progress_bar_params.progress_title = progress_title; @@ -142,4 +142,4 @@ namespace updating update_toast_progress_bar(UPDATING_PROCESS_TOAST_TAG, progress_bar_params); } } -} \ No newline at end of file +} diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index 18e9a303ed..ab200989e6 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -22,13 +22,54 @@ namespace // Strings in this namespace should not be localized { const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest"; + const wchar_t ALL_RELEASES_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases"; const size_t MAX_DOWNLOAD_ATTEMPTS = 3; } namespace updating { - std::future> get_new_github_version_info_async(const notifications::strings& strings) + std::optional extract_version_from_release_object(const json::JsonObject& release_object) + { + try + { + return VersionHelper{ winrt::to_string(release_object.GetNamedString(L"tag_name")) }; + } + catch (...) + { + } + return std::nullopt; + } + + std::pair extract_installer_asset_download_info(const json::JsonObject& release_object) + { + const std::wstring_view required_architecture = get_architecture_string(get_current_architecture()); + constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN; + // Desc-sorted by its priority + const std::array asset_extensions = { L".exe", L".msi" }; + for (const auto asset_extension : asset_extensions) + { + for (auto asset_elem : release_object.GetNamedArray(L"assets")) + { + auto asset{ asset_elem.GetObjectW() }; + std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str(); + std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower); + + const bool extension_matched = filename_lower.ends_with(asset_extension); + const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos; + const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos; + const bool asset_matched = extension_matched && architecture_matched && filename_matched; + if (extension_matched && architecture_matched && filename_matched) + { + return std::make_pair(Uri{ asset.GetNamedString(L"browser_download_url") }, std::move(filename_lower)); + } + } + } + + throw std::runtime_error("Release object doesn't have the required asset"); + } + + std::future> get_new_github_version_info_async(const notifications::strings& strings, const bool prerelease) { // If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates. if (VERSION_MAJOR == 0 && VERSION_MINOR == 0) @@ -38,46 +79,53 @@ namespace updating try { http::HttpClient client; - const auto body = co_await client.request(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT }); - auto json_body = json::JsonValue::Parse(body).GetObjectW(); - auto new_version = json_body.GetNamedString(L"tag_name"); - winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") }; - - VersionHelper github_version(winrt::to_string(new_version)); - VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION); + json::JsonObject release_object; + const VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION); + VersionHelper github_version = current_version; + if (prerelease) + { + const auto body = co_await client.request(Uri{ ALL_RELEASES_ENDPOINT }); + for (const auto& json : json::JsonValue::Parse(body).GetArray()) + { + auto potential_release_object = json.GetObjectW(); + const bool is_prerelease = potential_release_object.GetNamedBoolean(L"prerelease", false); + auto extracted_version = extract_version_from_release_object(potential_release_object); + if (!is_prerelease || !extracted_version || *extracted_version <= github_version) + { + continue; + } + // Do not break, since https://developer.github.com/v3/repos/releases/#list-releases + // doesn't specify the order in which release object appear + github_version = std::move(*extracted_version); + release_object = std::move(potential_release_object); + } + } + else + { + const auto body = co_await client.request(Uri{ LATEST_RELEASE_ENDPOINT }); + release_object = json::JsonValue::Parse(body).GetObjectW(); + if (auto extracted_version = extract_version_from_release_object(release_object)) + { + github_version = *extracted_version; + } + } if (github_version <= current_version) { co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_UP_TO_DATE); } - const std::wstring_view required_architecture = get_architecture_string(get_current_architecture()); - constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN; - // Desc-sorted by its priority - const std::array asset_extensions = { L".exe", L".msi" }; - for (const auto asset_extension : asset_extensions) - { - for (auto asset_elem : json_body.GetNamedArray(L"assets")) - { - auto asset{ asset_elem.GetObjectW() }; - std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str(); - std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower); - - const bool extension_matched = filename_lower.ends_with(asset_extension); - const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos; - const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos; - if (extension_matched && architecture_matched && filename_matched) - { - winrt::Windows::Foundation::Uri msi_download_url{ asset.GetNamedString(L"browser_download_url") }; - co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str(), std::move(msi_download_url), std::move(filename_lower) }; - } - } - } + Uri release_page_url{ release_object.GetNamedString(L"html_url") }; + auto installer_download_url = extract_installer_asset_download_info(release_object); + co_return new_version_download_info{ std::move(release_page_url), + std::move(github_version), + std::move(installer_download_url.first), + std::move(installer_download_url.second) }; } catch (...) { - co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_CHECK_ERROR); } + co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_CHECK_ERROR); } bool could_be_costly_connection() @@ -142,19 +190,6 @@ namespace updating } } - std::future check_new_version_available(const notifications::strings& strings) - { - auto new_version = co_await get_new_github_version_info_async(strings); - if (!new_version) - { - updating::notifications::show_unavailable(strings, std::move(new_version.error())); - co_return VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring(); - } - - updating::notifications::show_available(new_version.value(), strings); - co_return new_version->version_string; - } - std::future download_update(const notifications::strings& strings) { const auto new_version = co_await get_new_github_version_info_async(strings); diff --git a/src/common/updating/updating.h b/src/common/updating/updating.h index a519a2ec01..384bea5f50 100644 --- a/src/common/updating/updating.h +++ b/src/common/updating/updating.h @@ -5,26 +5,27 @@ #include #include #include +#include #include "notifications.h" #include "../VersionHelper.h" namespace updating { + using winrt::Windows::Foundation::Uri; struct new_version_download_info { - winrt::Windows::Foundation::Uri release_page_uri = nullptr; - std::wstring version_string; - winrt::Windows::Foundation::Uri installer_download_url = nullptr; + Uri release_page_uri = nullptr; + VersionHelper version{ 0, 0, 0 }; + Uri installer_download_url = nullptr; std::wstring installer_filename; }; std::future try_autoupdate(const bool download_updates_automatically, const notifications::strings&); std::filesystem::path get_pending_updates_path(); - - std::future check_new_version_available(const notifications::strings&); std::future download_update(const notifications::strings&); + std::future> get_new_github_version_info_async(const notifications::strings& strings, const bool prerelease = false); // non-localized constexpr inline std::wstring_view INSTALLER_FILENAME_PATTERN = L"powertoyssetup"; -} \ No newline at end of file +} diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 3cec6e9805..8749ddaeb8 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -22,6 +22,7 @@ 10.0.17134.0 + Application diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 9006069f32..ba02ba529c 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -81,13 +81,14 @@ std::optional dispatch_json_action_to_module(const json::JsonObjec } else if (action == L"check_for_updates") { - std::wstring latestVersion = check_for_updates(); - VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION); - bool isRunningLatest = latestVersion.compare(current_version.toWstring()) == 0; + auto new_version_info = check_for_updates(); + const VersionHelper latestVersion = + new_version_info ? new_version_info->version : + VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }; json::JsonObject json; - json.SetNamedValue(L"version", json::JsonValue::CreateStringValue(latestVersion)); - json.SetNamedValue(L"isVersionLatest", json::JsonValue::CreateBooleanValue(isRunningLatest)); + json.SetNamedValue(L"version", json::JsonValue::CreateStringValue(latestVersion.toWstring())); + json.SetNamedValue(L"isVersionLatest", json::JsonValue::CreateBooleanValue(!new_version_info)); result.emplace(json.Stringify()); } diff --git a/src/runner/update_utils.cpp b/src/runner/update_utils.cpp index 6e5f97f338..8820c71c5b 100644 --- a/src/runner/update_utils.cpp +++ b/src/runner/update_utils.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -74,17 +75,25 @@ void github_update_worker() } } -std::wstring check_for_updates() +std::optional check_for_updates() { try { - return updating::check_new_version_available(Strings).get(); + const auto new_version = updating::get_new_github_version_info_async(Strings).get(); + if (!new_version) + { + updating::notifications::show_unavailable(Strings, std::move(new_version.error())); + return std::nullopt; + } + + updating::notifications::show_available(new_version.value(), Strings); + return std::move(new_version.value()); } catch (...) { // Couldn't autoupdate - return std::wstring(); } + return std::nullopt; } bool launch_pending_update() diff --git a/src/runner/update_utils.h b/src/runner/update_utils.h index 22dc719313..dd6da24f8b 100644 --- a/src/runner/update_utils.h +++ b/src/runner/update_utils.h @@ -1,6 +1,8 @@ #pragma once +#include + bool start_msi_uninstallation_sequence(); void github_update_worker(); -std::wstring check_for_updates(); +std::optional check_for_updates(); bool launch_pending_update(); \ No newline at end of file