diff --git a/src/common/VersionHelper.cpp b/src/common/VersionHelper.cpp index 92f014d65c..fefdb82432 100644 --- a/src/common/VersionHelper.cpp +++ b/src/common/VersionHelper.cpp @@ -27,3 +27,14 @@ VersionHelper::VersionHelper(int major, int minor, int revision) : revision(revision) { } + +std::wstring VersionHelper::toWstring() const +{ + std::wstring result{ L"v" }; + result += std::to_wstring(major); + result += L'.'; + result += std::to_wstring(minor); + result += L'.'; + result += std::to_wstring(revision); + return result; +} diff --git a/src/common/VersionHelper.h b/src/common/VersionHelper.h index 1f575dd507..6f1f22e59c 100644 --- a/src/common/VersionHelper.h +++ b/src/common/VersionHelper.h @@ -13,4 +13,6 @@ struct VersionHelper int major; int minor; int revision; + + std::wstring toWstring() const; }; diff --git a/src/common/notifications.cpp b/src/common/notifications.cpp index e7051e423c..a15c9f672e 100644 --- a/src/common/notifications.cpp +++ b/src/common/notifications.cpp @@ -33,6 +33,11 @@ namespace constexpr std::wstring_view WIN32_AUMID = L"Microsoft.PowerToysWin32"; } +namespace localized_strings +{ + constexpr std::wstring_view SNOOZE_BUTTON = L"Snooze"; +} + static DWORD loop_thread_id() { static const DWORD thread_id = GetCurrentThreadId(); @@ -213,7 +218,14 @@ void notifications::show_toast_with_activations(std::wstring message, std::wstri toast_xml += selection_id; toast_xml += LR"(" type="selection" defaultInput=")"; toast_xml += std::to_wstring(b.durations[0].minutes); - toast_xml += LR"(">)"; + toast_xml += L'"'; + if (!b.snooze_title.empty()) + { + toast_xml += LR"( title=")"; + toast_xml += b.snooze_title; + toast_xml += L'"'; + } + toast_xml += L'>'; for (const auto& duration : b.durations) { toast_xml += LR"()"; + toast_xml += LR"( content=")"; + toast_xml += localized_strings::SNOOZE_BUTTON; + toast_xml += LR"(" />)"; } }, actions[i]); } diff --git a/src/common/notifications.h b/src/common/notifications.h index 65be031ed3..b4f57e5398 100644 --- a/src/common/notifications.h +++ b/src/common/notifications.h @@ -22,6 +22,7 @@ namespace notifications struct snooze_button { + std::wstring snooze_title; std::vector durations; }; diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index e463525e92..c102f193a3 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -28,6 +28,10 @@ namespace const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest"; const wchar_t MSIX_PACKAGE_NAME[] = L"Microsoft.PowerToys"; const wchar_t MSIX_PACKAGE_PUBLISHER[] = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; + + const wchar_t UPDATE_NOTIFY_TOAST_TAG[] = L"PTUpdateNotifyTag"; + const wchar_t UPDATE_READY_TOAST_TAG[] = L"PTUpdateReadyTag"; + const size_t MAX_DOWNLOAD_ATTEMPTS = 3; } namespace localized_strings @@ -37,12 +41,14 @@ namespace localized_strings const wchar_t UNINSTALLATION_SUCCESS[] = L"Previous version of PowerToys was uninstalled successfully."; const wchar_t UNINSTALLATION_UNKNOWN_ERROR[] = L"Error: please uninstall the previous version of PowerToys manually."; - const wchar_t GITHUB_NEW_VERSION_READY_TO_INSTALL[] = L"An update to PowerToys is ready to install."; + const wchar_t GITHUB_NEW_VERSION_READY_TO_INSTALL[] = L"An update to PowerToys is ready to install.\n"; + const wchar_t GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR[] = L"Error: couldn't download PowerToys installer. Visit our GitHub page to update.\n"; const wchar_t GITHUB_NEW_VERSION_UPDATE_NOW[] = L"Update now"; const wchar_t GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART[] = L"At next launch"; - const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to get "; + const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to update.\n"; const wchar_t GITHUB_NEW_VERSION_AGREE[] = L"Visit"; + const wchar_t GITHUB_NEW_VERSION_SNOOZE_TITLE[] = L"Click Snooze to be reminded in:"; const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D[] = L"1 day"; const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D[] = L"5 days"; } @@ -204,6 +210,17 @@ namespace updating return { std::move(path_str) }; } + std::future attempt_to_download_installer(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url) + { + namespace storage = winrt::Windows::Storage; + + auto client = create_http_client(); + auto response = co_await client.GetAsync(url); + (void)response.EnsureSuccessStatusCode(); + auto msi_installer_file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(destination.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways); + co_await response.Content().WriteToStreamAsync(msi_installer_file_stream); + } + std::future try_autoupdate(const bool download_updates_automatically) { const auto new_version = co_await get_new_github_version_info_async(); @@ -212,32 +229,58 @@ namespace updating co_return; } using namespace localized_strings; - namespace storage = winrt::Windows::Storage; + 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 += new_version->version_string; if (download_updates_automatically && !could_be_costly_connection()) { - auto client = create_http_client(); - auto response = co_await client.GetAsync(new_version->msi_download_url); - (void)response.EnsureSuccessStatusCode(); - - auto download_dst = get_pending_updates_path(); + auto installer_download_dst = get_pending_updates_path(); std::error_code _; - std::filesystem::create_directories(download_dst, _); - download_dst /= new_version->msi_filename; - auto msi_installer_file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(download_dst.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways); - co_await response.Content().WriteToStreamAsync(msi_installer_file_stream); - notifications::toast_params toast_params{ L"PTUpdateReadyTag", false }; + std::filesystem::create_directories(installer_download_dst, _); + installer_download_dst /= new_version->msi_filename; + + bool download_success = false; + for (size_t i = 0; i < MAX_DOWNLOAD_ATTEMPTS; ++i) + { + try + { + co_await attempt_to_download_installer(installer_download_dst, new_version->msi_download_url); + download_success = true; + break; + } + catch (...) + { + // reattempt to download or do nothing + } + } + if (!download_success) + { + notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; + std::wstring contents = GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR; + contents += current_version_to_next_version; + + notifications::show_toast_with_activations(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } }, std::move(toast_params)); + + co_return; + } + + notifications::toast_params toast_params{ UPDATE_READY_TOAST_TAG, false }; std::wstring new_version_ready{ GITHUB_NEW_VERSION_READY_TO_INSTALL }; - new_version_ready += L" "; - new_version_ready += new_version->version_string; - notifications::show_toast_with_activations(std::move(new_version_ready), {}, { notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://update_now/" }, notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, L"powertoys://schedule_update/" }, notifications::snooze_button{ { { GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, { GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } } } }, std::move(toast_params)); + new_version_ready += current_version_to_next_version; + + notifications::show_toast_with_activations(std::move(new_version_ready), + {}, + { notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://update_now/" }, + notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, L"powertoys://schedule_update/" }, + notifications::snooze_button{ GITHUB_NEW_VERSION_SNOOZE_TITLE, { { GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, { GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } } } }, + std::move(toast_params)); } else { - notifications::toast_params toast_params{ L"PTUpdateNotifyTag", false }; + notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT; - contents += new_version->version_string; - contents += L'.'; + contents += current_version_to_next_version; notifications::show_toast_with_activations(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } }, std::move(toast_params)); } }