diff --git a/src/common/UnitTests-CommonLib/UnitTestsVersionHelper.cpp b/src/common/UnitTests-CommonLib/UnitTestsVersionHelper.cpp index 7d6f54024c..4d7a9a8d52 100644 --- a/src/common/UnitTests-CommonLib/UnitTestsVersionHelper.cpp +++ b/src/common/UnitTests-CommonLib/UnitTestsVersionHelper.cpp @@ -43,79 +43,82 @@ namespace UnitTestsVersionHelper } TEST_METHOD (stringConstructorShouldProperlyInitializationVersionNumbers) { - VersionHelper sut("v0.12.3"); - - Assert::AreEqual(0ull, sut.major); - Assert::AreEqual(12ull, sut.minor); - Assert::AreEqual(3ull, sut.revision); + auto sut = VersionHelper::fromString("v0.12.3"); + Assert::IsTrue(sut.has_value()); + Assert::AreEqual(0ull, sut->major); + Assert::AreEqual(12ull, sut->minor); + Assert::AreEqual(3ull, sut->revision); } TEST_METHOD (stringConstructorShouldProperlyInitializationWithDifferentVersionNumbers) { - VersionHelper sut("v2.25.1"); + auto sut = VersionHelper::fromString(L"v2.25.1"); + Assert::IsTrue(sut.has_value()); - Assert::AreEqual(2ull, sut.major); - Assert::AreEqual(25ull, sut.minor); - Assert::AreEqual(1ull, sut.revision); + Assert::AreEqual(2ull, sut->major); + Assert::AreEqual(25ull, sut->minor); + Assert::AreEqual(1ull, sut->revision); } TEST_METHOD (emptyStringNotAccepted) { - Assert::ExpectException([] { - VersionHelper sut(""); - }); + auto sut = VersionHelper::fromString(""); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (invalidStringNotAccepted1) { - Assert::ExpectException([] { - VersionHelper sut("v2a."); - }); + auto sut = VersionHelper::fromString(L"v2a."); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (invalidStringNotAccepted2) { - Assert::ExpectException([] { - VersionHelper sut("12abc2vv.0"); - }); + auto sut = VersionHelper::fromString(L"12abc2vv.0"); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (invalidStringNotAccepted3) { - Assert::ExpectException([] { - VersionHelper sut("123"); - }); + auto sut = VersionHelper::fromString("123"); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (invalidStringNotAccepted4) { - Assert::ExpectException([] { - VersionHelper sut("v1v2v3"); - }); + auto sut = VersionHelper::fromString(L"v1v2v3"); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (invalidStringNotAccepted5) { - Assert::ExpectException([] { - VersionHelper sut("v.1.2.3v"); - }); + auto sut = VersionHelper::fromString("v.1.2.3v"); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (partialVersionStringNotAccepted1) { - Assert::ExpectException([] { - VersionHelper sut("v1."); - }); + auto sut = VersionHelper::fromString(L"v1."); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (partialVersionStringNotAccepted2) { - Assert::ExpectException([] { - VersionHelper sut("v1.2"); - }); + auto sut = VersionHelper::fromString("v1.2"); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (partialVersionStringNotAccepted3) { - Assert::ExpectException([] { - VersionHelper sut("v1.2."); - }); + auto sut = VersionHelper::fromString(L"v1.2."); + + Assert::IsFalse(sut.has_value()); } TEST_METHOD (parsedWithoutLeadingV) { VersionHelper expected{ 12ull, 13ull, 111ull }; - VersionHelper actual("12.13.111"); - Assert::AreEqual(actual, expected); + auto actual = VersionHelper::fromString(L"12.13.111"); + + Assert::IsTrue(actual.has_value()); + Assert::AreEqual(*actual, expected); } TEST_METHOD (whenMajorVersionIsGreaterComparisonOperatorShouldReturnProperValue) { diff --git a/src/common/updating/pch.h b/src/common/updating/pch.h index 6b484c4eea..824630e52a 100644 --- a/src/common/updating/pch.h +++ b/src/common/updating/pch.h @@ -35,3 +35,4 @@ #endif //PCH_H +namespace fs = std::filesystem; diff --git a/src/common/updating/updateState.cpp b/src/common/updating/updateState.cpp index 40e6330576..3217321dca 100644 --- a/src/common/updating/updateState.cpp +++ b/src/common/updating/updateState.cpp @@ -3,12 +3,15 @@ #include #include +#include +#include #include namespace { const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\UpdateState.json"; const wchar_t UPDATE_STATE_MUTEX[] = L"Local\\PowerToysRunnerUpdateStateMutex"; + const VersionHelper CURRENT_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION); } UpdateState deserialize(const json::JsonObject& json) @@ -34,19 +37,40 @@ json::JsonObject serialize(const UpdateState& state) json.SetNamedValue(L"state", json::value(static_cast(state.state))); json.SetNamedValue(L"downloadedInstallerFilename", json::value(state.downloadedInstallerFilename)); + json.SetNamedValue(L"updateStateFileVersion", json::value(CURRENT_VERSION.toWstring())); + return json; } +bool IsOldFileVersion(const std::wstring_view fileVersion) +{ + if (fileVersion == L"") + { + return true; + } + + const auto parsedVer = VersionHelper::fromString(fileVersion); + return !parsedVer.has_value() || *parsedVer != CURRENT_VERSION; +} + UpdateState UpdateState::read() { const auto filename = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME; std::optional json; + wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) }; + auto lock = mutex.acquire(); + json = json::from_file(filename); + + if (json.has_value() && !IsOldFileVersion(json->GetNamedString(L"updateStateFileVersion", L"").c_str())) { - wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) }; - auto lock = mutex.acquire(); - json = json::from_file(filename); + return deserialize(*json); + } + else + { + std::error_code _; + fs::remove(filename, _); + return UpdateState{}; } - return json ? deserialize(*json) : UpdateState{}; } void UpdateState::store(std::function stateModifier) diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index 35883b8f73..8798209b4d 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -36,14 +36,7 @@ namespace updating 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; + return VersionHelper::fromString(release_object.GetNamedString(L"tag_name")); } std::pair extract_installer_asset_download_info(const json::JsonObject& release_object) diff --git a/src/common/utils/string_utils.h b/src/common/utils/string_utils.h index e509cdf8ac..27bf64b15b 100644 --- a/src/common/utils/string_utils.h +++ b/src/common/utils/string_utils.h @@ -22,28 +22,34 @@ struct default_trim_arg }; template -inline std::basic_string_view left_trim(std::basic_string_view s, const std::basic_string_view chars_to_trim = default_trim_arg::value) +inline std::basic_string_view left_trim(std::basic_string_view s, + const std::basic_string_view chars_to_trim = default_trim_arg::value) { s.remove_prefix(std::min(s.find_first_not_of(chars_to_trim), size(s))); return s; } template -inline std::basic_string_view right_trim(std::basic_string_view s, const std::basic_string_view chars_to_trim = default_trim_arg::value) +inline std::basic_string_view right_trim(std::basic_string_view s, + const std::basic_string_view chars_to_trim = default_trim_arg::value) { s.remove_suffix(std::min(size(s) - s.find_last_not_of(chars_to_trim) - 1, size(s))); return s; } template -inline std::basic_string_view trim(std::basic_string_view s, const std::basic_string_view chars_to_trim = default_trim_arg::value) +inline std::basic_string_view trim(std::basic_string_view s, + const std::basic_string_view chars_to_trim = default_trim_arg::value) { return left_trim(right_trim(s, chars_to_trim), chars_to_trim); } -inline void replace_chars(std::string& s, const std::string_view chars_to_replace, const char replacement_char) +template +inline void replace_chars(std::basic_string& s, + const std::basic_string_view chars_to_replace, + const CharT replacement_char) { - for (const char c : chars_to_replace) + for (const CharT c : chars_to_replace) { std::replace(begin(s), end(s), c, replacement_char); } diff --git a/src/common/version/helper.cpp b/src/common/version/helper.cpp index 0e1b477126..4a27fbed08 100644 --- a/src/common/version/helper.cpp +++ b/src/common/version/helper.cpp @@ -5,23 +5,6 @@ #include #include -VersionHelper::VersionHelper(std::string str) -{ - // Remove whitespaces chars and a leading 'v' - str = left_trim(trim(str), "v"); - // Replace '.' with spaces - replace_chars(str, ".", ' '); - - std::istringstream ss{ str }; - ss >> major; - ss >> minor; - ss >> revision; - if (ss.fail() || !ss.eof()) - { - throw std::logic_error("VersionHelper: couldn't parse the supplied version string"); - } -} - VersionHelper::VersionHelper(const size_t major, const size_t minor, const size_t revision) : major{ major }, minor{ minor }, @@ -29,6 +12,60 @@ VersionHelper::VersionHelper(const size_t major, const size_t minor, const size_ { } +template +struct Constants; + +template<> +struct Constants +{ + static inline const char* V = "v"; + static inline const char* DOT = "."; + static inline const char SPACE = ' '; +}; + +template<> +struct Constants +{ + static inline const wchar_t* V = L"v"; + static inline const wchar_t* DOT = L"."; + static inline const wchar_t SPACE = L' '; +}; + +template +std::optional fromString(std::basic_string_view str) +{ + try + { + str = left_trim(trim(str), Constants::V); + std::basic_string spacedStr{ str }; + replace_chars(spacedStr, Constants::DOT, Constants::SPACE); + + std::basic_istringstream ss{ spacedStr }; + VersionHelper result{ 0, 0, 0 }; + ss >> result.major; + ss >> result.minor; + ss >> result.revision; + if (!ss.fail() && ss.eof()) + { + return result; + } + } + catch (...) + { + } + return std::nullopt; +} + +std::optional VersionHelper::fromString(std::string_view s) +{ + return ::fromString(s); +} + +std::optional VersionHelper::fromString(std::wstring_view s) +{ + return ::fromString(s); +} + std::wstring VersionHelper::toWstring() const { std::wstring result{ L"v" }; diff --git a/src/common/version/helper.h b/src/common/version/helper.h index 1f91958b6f..4abd464ac8 100644 --- a/src/common/version/helper.h +++ b/src/common/version/helper.h @@ -1,14 +1,17 @@ #pragma once #include +#include #include struct VersionHelper { - VersionHelper(std::string str); VersionHelper(const size_t major, const size_t minor, const size_t revision); auto operator<=>(const VersionHelper&) const = default; + + static std::optional fromString(std::string_view s); + static std::optional fromString(std::wstring_view s); size_t major; size_t minor; diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs index a89b46e6d7..63166703db 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs @@ -466,6 +466,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels // callback function to launch the URL to check for updates. private void CheckForUpdatesClick() { + RefreshUpdatingState(); IsNewVersionDownloading = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename); NotifyPropertyChanged(nameof(IsDownloadAllowed));