commit 546b509cd55fea6dff5a59d37677195bd6f64a79 parent 54d6e5b73dac1566e55b62d2c9f3332b6df68026 Author: JohnMcPMS <johnmcp@microsoft.com> Date: Fri, 13 Oct 2023 09:07:39 -0700 Use package version as potential last update timestamp (#3759) This change moves the background update determination to be under the control of the source reference, and then uses the package version (which is based on the creation time for us) as a potential later "last update time". This enables an update to the package from outside of our control to still prevent any update check from occurring. Diffstat:
18 files changed, 358 insertions(+), 99 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -281,6 +281,7 @@ minexample minidump minschema missingdependency +mkgmtime MMmmbbbb mof monicka diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -204,6 +204,7 @@ <ClCompile Include="CompositeSource.cpp" /> <ClCompile Include="Correlation.cpp" /> <ClCompile Include="CustomHeader.cpp" /> + <ClCompile Include="DateTime.cpp" /> <ClCompile Include="Dependencies.cpp" /> <ClCompile Include="Downloader.cpp" /> <ClCompile Include="DownloadFlow.cpp" /> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -323,6 +323,9 @@ <ClCompile Include="CheckpointDatabase.cpp"> <Filter>Source Files\Repository</Filter> </ClCompile> + <ClCompile Include="DateTime.cpp"> + <Filter>Source Files\Common</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLITests/DateTime.cpp b/src/AppInstallerCLITests/DateTime.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include <AppInstallerDateTime.h> + +using namespace AppInstaller::Utility; +using namespace TestCommon; +using namespace std::chrono; + +namespace Catch +{ + template<> + struct StringMaker<std::chrono::system_clock::time_point> + { + static std::string convert(const std::chrono::system_clock::time_point& value) + { + std::ostringstream stream; + OutputTimePoint(stream, value); + return std::move(stream).str(); + } + }; +} + +void VerifyGetTimePointFromVersion(std::string_view version, int year, int month, int day, int hour, int minute) +{ + system_clock::time_point result = GetTimePointFromVersion(UInt64Version{ std::string{ version } }); + + tm time{}; + auto tt = system_clock::to_time_t(result); + _gmtime64_s(&time, &tt); + + REQUIRE(year == time.tm_year + 1900); + REQUIRE(month == time.tm_mon + 1); + REQUIRE(day == time.tm_mday); + REQUIRE(hour == time.tm_hour); + REQUIRE(minute == time.tm_min); +} + +std::string StringFromTimePoint(system_clock::time_point input) +{ + tm time{}; + auto tt = system_clock::to_time_t(input); + _gmtime64_s(&time, &tt); + + std::ostringstream stream; + stream << time.tm_year + 1900 << '.' << ((time.tm_mon + 1) * 100) + time.tm_mday << '.' << ((time.tm_hour + 1) * 100) + time.tm_min; + return std::move(stream).str(); +} + +TEST_CASE("GetTimePointFromVersion", "[datetime]") +{ + // Years out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "1969.1231.2459.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "3001.101.100.0" }) == system_clock::time_point::min()); + + // Months out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.1.100.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.1301.100.0" }) == system_clock::time_point::min()); + + // Days out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.100.100.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.132.100.0" }) == system_clock::time_point::min()); + + // Hours out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.0.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.2500.0" }) == system_clock::time_point::min()); + + // Minutes out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.160.0" }) == system_clock::time_point::min()); + + // In range baseline + VerifyGetTimePointFromVersion("2023.101.100.0", 2023, 1, 1, 0, 0); + + // Time for presents! + VerifyGetTimePointFromVersion("2023.1225.814.0", 2023, 12, 25, 7, 14); + + // Epoch time + REQUIRE(GetTimePointFromVersion(UInt64Version{ "1970.101.100.0" }) == system_clock::time_point{}); + + // Round trip now + system_clock::time_point now = system_clock::now(); + REQUIRE(GetTimePointFromVersion(UInt64Version{ StringFromTimePoint(now) }) == time_point_cast<minutes>(now)); +} diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp @@ -644,17 +644,21 @@ TEST_CASE("InstallFlow_Portable_SymlinkCreationFail", "[InstallFlow][workflow]") OverridePortableInstaller(installContext); TestHook::SetCreateSymlinkResult_Override createSymlinkResultOverride(false); const auto& targetDirectory = tempDirectory.GetPath(); + const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; installContext.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); installContext.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); installContext.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); InstallCommand install({}); install.Execute(installContext); - INFO(installOutput.str()); - const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; - REQUIRE(std::filesystem::exists(portableTargetPath)); - REQUIRE(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); + { + INFO(installOutput.str()); + + // Use CHECK to allow the uninstall to still occur + CHECK(std::filesystem::exists(portableTargetPath)); + CHECK(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); + } // Perform uninstall std::ostringstream uninstallOutput; diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp @@ -575,6 +575,7 @@ TEST_CASE("RepoSources_UpdateOnOpen", "[sources]") bool updateCalledOnFactory = false; TestSourceFactory factory{ SourcesTestSource::Create }; factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; }; + factory.ShouldUpdateBeforeOpenResult = true; TestHook_SetSourceFactoryOverride(type, factory); SetSetting(Stream::UserSources, s_SingleSource); diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp @@ -301,14 +301,20 @@ namespace TestCommon std::shared_ptr<ISourceReference> TestSourceFactory::Create(const SourceDetails& details) { + std::shared_ptr<TestSourceReference> result; + if (OnOpenWithCustomHeader) { - return std::make_shared<TestSourceReference>(details, OnOpenWithCustomHeader); + result = std::make_shared<TestSourceReference>(details, OnOpenWithCustomHeader); } else { - return std::make_shared<TestSourceReference>(details, OnOpen); + result = std::make_shared<TestSourceReference>(details, OnOpen); } + + result->ShouldUpdateBeforeOpenResult = ShouldUpdateBeforeOpenResult; + + return result; } bool TestSourceFactory::Add(SourceDetails& details, IProgressCallback&) diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h @@ -115,6 +115,9 @@ namespace TestCommon bool SetCustomHeader(std::optional<std::string> header) override { m_header = header; return true; } + bool ShouldUpdateBeforeOpenResult = false; + bool ShouldUpdateBeforeOpen(const std::optional<AppInstaller::Repository::TimeSpan>&) override { return ShouldUpdateBeforeOpenResult; } + std::shared_ptr<AppInstaller::Repository::ISource> Open(AppInstaller::IProgressCallback&) override { if (m_onOpenWithCustomHeader) @@ -156,6 +159,7 @@ namespace TestCommon // Make copies of self when requested. operator std::function<std::unique_ptr<AppInstaller::Repository::ISourceFactory>()>(); + bool ShouldUpdateBeforeOpenResult = false; OpenFunctor OnOpen; OpenFunctorWithCustomHeader OnOpenWithCustomHeader; AddFunctor OnAdd; diff --git a/src/AppInstallerCommonCore/Public/winget/MsixManifest.h b/src/AppInstallerCommonCore/Public/winget/MsixManifest.h @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once - #include "AppInstallerStrings.h" #include "AppInstallerVersions.h" diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -444,6 +444,7 @@ <ClInclude Include="SourceFactory.h" /> <ClInclude Include="SourceList.h" /> <ClInclude Include="SourcePolicy.h" /> + <ClInclude Include="SourceUpdateChecks.h" /> <ClInclude Include="SQLiteStatementBuilder.h" /> <ClInclude Include="SQLiteTempTable.h" /> <ClInclude Include="SQLiteWrapper.h" /> @@ -541,6 +542,7 @@ <ClCompile Include="Rest\Schema\SearchResponseParser.cpp" /> <ClCompile Include="SourceList.cpp" /> <ClCompile Include="SourcePolicy.cpp" /> + <ClCompile Include="SourceUpdateChecks.cpp" /> <ClCompile Include="SQLiteStatementBuilder.cpp" /> <ClCompile Include="SQLiteTempTable.cpp" /> <ClCompile Include="SQLiteWrapper.cpp" /> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -414,6 +414,9 @@ <ClInclude Include="Public\winget\Checkpoint.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="SourceUpdateChecks.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -653,9 +656,12 @@ <ClCompile Include="Microsoft\Schema\Checkpoint_1_0\CheckpointDatabaseInterface_1_0.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClInclude Include="Microsoft\Schema\Checkpoint_1_0\CheckpointTable.cpp"> + <ClCompile Include="Microsoft\Schema\Checkpoint_1_0\CheckpointTable.cpp"> <Filter>Microsoft\Schema\Checkpoint_1_0</Filter> - </ClInclude> + </ClCompile> + <ClCompile Include="SourceUpdateChecks.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerRepositoryCore/ISource.h b/src/AppInstallerRepositoryCore/ISource.h @@ -86,6 +86,9 @@ namespace AppInstaller::Repository // Set caller. virtual void SetCaller(std::string) {} + // Determine if the source needs to be updated before being opened. + virtual bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>&) { return false; } + // Opens the source. This function should throw upon open failure rather than returning an empty pointer. virtual std::shared_ptr<ISource> Open(IProgressCallback& progress) = 0; }; diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -4,7 +4,9 @@ #include "Microsoft/PreIndexedPackageSourceFactory.h" #include "Microsoft/SQLiteIndex.h" #include "Microsoft/SQLiteIndexSource.h" +#include "SourceUpdateChecks.h" +#include <AppInstallerDateTime.h> #include <AppInstallerDeployment.h> #include <AppInstallerDownloader.h> #include <AppInstallerMsixInfo.h> @@ -345,6 +347,82 @@ namespace AppInstaller::Repository::Microsoft return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); } + std::optional<Msix::PackageVersion> PackagedContextGetCurrentVersion(const SourceDetails& details) + { + auto extension = GetExtensionFromDetails(details); + + if (extension) + { + auto version = extension->GetPackageVersion(); + return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision }; + } + else + { + return std::nullopt; + } + } + + // Constructs the location that we will write files to. + std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) + { + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); + result /= PreIndexedPackageSourceFactory::Type(); + result /= GetPackageFamilyNameFromDetails(details); + return result; + } + + std::optional<Msix::PackageVersion> DesktopContextGetCurrentVersion(const SourceDetails& details) + { + std::filesystem::path packageState = GetStatePathFromDetails(details); + std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; + + if (std::filesystem::exists(packagePath)) + { + // If we already have a trusted index package, use it to determine if we need to update or not. + Msix::WriteLockedMsixFile indexPackage{ packagePath }; + if (indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) + { + Msix::MsixInfo msixInfo{ packagePath }; + auto manifest = msixInfo.GetAppPackageManifests(); + + if (manifest.size() == 1) + { + return manifest[0].GetIdentity().GetVersion(); + } + } + } + + return std::nullopt; + } + + bool CheckForUpdateBeforeOpen(const SourceDetails& details, std::optional<Msix::PackageVersion> currentVersion, const std::optional<TimeSpan>& requestedUpdateInterval) + { + // If we can't find a good package, then we have to update to operate + if (!currentVersion) + { + AICLI_LOG(Repo, Verbose, << "Source `" << details.Name << "` has no data"); + return true; + } + + using namespace std::chrono_literals; + using clock = std::chrono::system_clock; + + // Attempt to convert the package version to a time_point + clock::time_point versionTime = Utility::GetTimePointFromVersion(currentVersion.value()); + + // Since we expect that the version time indicates creation time, don't let it be far in the future. + auto now = clock::now(); + if (versionTime > now && versionTime - now > 24h) + { + versionTime = clock::time_point::min(); + } + + // Use the later of the version and last update times + clock::time_point timeToCheck = (versionTime > details.LastUpdateTime ? versionTime : details.LastUpdateTime); + + return IsAfterUpdateCheckTime(details.Name, timeToCheck, requestedUpdateInterval); + } + struct PackagedContextSourceReference : public ISourceReference { PackagedContextSourceReference(const SourceDetails& details) : m_details(details) @@ -359,6 +437,11 @@ namespace AppInstaller::Repository::Microsoft SourceDetails& GetDetails() override { return m_details; }; + bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>& requestedUpdateInterval) override + { + return CheckForUpdateBeforeOpen(m_details, PackagedContextGetCurrentVersion(m_details), requestedUpdateInterval); + } + std::shared_ptr<ISource> Open(IProgressCallback& progress) override { Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); @@ -405,17 +488,7 @@ namespace AppInstaller::Repository::Microsoft std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) override { - auto extension = GetExtensionFromDetails(details); - - if (extension) - { - auto version = extension->GetPackageVersion(); - return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision }; - } - else - { - return std::nullopt; - } + return PackagedContextGetCurrentVersion(details); } bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) override @@ -492,15 +565,6 @@ namespace AppInstaller::Repository::Microsoft } }; - // Constructs the location that we will write files to. - std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) - { - std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); - result /= PreIndexedPackageSourceFactory::Type(); - result /= GetPackageFamilyNameFromDetails(details); - return result; - } - struct DesktopContextSourceReference : public ISourceReference { DesktopContextSourceReference(const SourceDetails& details) : m_details(details) @@ -515,6 +579,11 @@ namespace AppInstaller::Repository::Microsoft SourceDetails& GetDetails() override { return m_details; }; + bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>& requestedUpdateInterval) override + { + return CheckForUpdateBeforeOpen(m_details, DesktopContextGetCurrentVersion(m_details), requestedUpdateInterval); + } + std::shared_ptr<ISource> Open(IProgressCallback& progress) override { Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); @@ -574,26 +643,7 @@ namespace AppInstaller::Repository::Microsoft std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) override { - std::filesystem::path packageState = GetStatePathFromDetails(details); - std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; - - if (std::filesystem::exists(packagePath)) - { - // If we already have a trusted index package, use it to determine if we need to update or not. - Msix::WriteLockedMsixFile indexPackage{ packagePath }; - if (indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) - { - Msix::MsixInfo msixInfo{ packagePath }; - auto manifest = msixInfo.GetAppPackageManifests(); - - if (manifest.size() == 1) - { - return manifest[0].GetIdentity().GetVersion(); - } - } - } - - return std::nullopt; + return DesktopContextGetCurrentVersion(details); } bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) override diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -11,6 +11,7 @@ #include "Microsoft/PreIndexedPackageSourceFactory.h" #include "Rest/RestSourceFactory.h" #include "PackageTrackingCatalogSourceFactory.h" +#include "SourceUpdateChecks.h" #ifndef AICLI_DISABLE_TEST_HOOKS #include "Microsoft/ConfigurableTestSourceFactory.h" @@ -50,11 +51,6 @@ namespace AppInstaller::Repository } } - bool IsUpdateSuppressed(const SourceDetails& details) - { - return std::chrono::system_clock::now() < details.DoNotUpdateBefore; - } - struct AddOrUpdateResult { bool UpdateChecked = false; @@ -155,48 +151,6 @@ namespace AppInstaller::Repository return (origin == SourceOrigin::Default || origin == SourceOrigin::GroupPolicy || origin == SourceOrigin::User); } - // Determines whether (and logs why) a source should be updated before it is opened. - bool ShouldUpdateBeforeOpen(const SourceDetails& details, std::optional<TimeSpan> backgroundUpdateInterval) - { - if (!ContainsAvailablePackagesInternal(details.Origin)) - { - return false; - } - - // Do not update if we are still before the update block time. - if (IsUpdateSuppressed(details)) - { - AICLI_LOG(Repo, Info, << "Background update is suppressed until: " << details.DoNotUpdateBefore); - return false; - } - - constexpr static TimeSpan s_ZeroMins = 0min; - TimeSpan autoUpdateTime; - if (backgroundUpdateInterval.has_value()) - { - autoUpdateTime = backgroundUpdateInterval.value(); - } - else - { - autoUpdateTime = User().Get<Setting::AutoUpdateTimeInMinutes>(); - } - - // A value of zero means no auto update, to get update the source run `winget update` - if (autoUpdateTime != s_ZeroMins) - { - auto timeSinceLastUpdate = std::chrono::system_clock::now() - details.LastUpdateTime; - if (timeSinceLastUpdate > autoUpdateTime) - { - AICLI_LOG(Repo, Info, << "Source past auto update time [" << - std::chrono::duration_cast<std::chrono::minutes>(autoUpdateTime).count() << " mins]; it has been at least " << - std::chrono::duration_cast<std::chrono::minutes>(timeSinceLastUpdate).count() << " mins"); - return true; - } - } - - return false; - } - SourceDetails GetPredefinedSourceDetails(PredefinedSource source) { SourceDetails details; @@ -704,9 +658,10 @@ namespace AppInstaller::Repository // Check for updates before opening. for (auto& sourceReference : m_sourceReferences) { - auto& details = sourceReference->GetDetails(); - if (ShouldUpdateBeforeOpen(details, m_backgroundUpdateInterval)) + if (ShouldUpdateBeforeOpen(sourceReference.get(), m_backgroundUpdateInterval)) { + auto& details = sourceReference->GetDetails(); + try { // TODO: Consider adding a context callback to indicate we are doing the same action diff --git a/src/AppInstallerRepositoryCore/SourceUpdateChecks.cpp b/src/AppInstallerRepositoryCore/SourceUpdateChecks.cpp @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SourceUpdateChecks.h" +#include "ISource.h" +#include <winget/UserSettings.h> + +using namespace std::chrono_literals; + +namespace AppInstaller::Repository +{ + bool ShouldUpdateBeforeOpen(ISourceReference* sourceReference, const std::optional<TimeSpan>& requestedUpdateInterval) + { + const SourceDetails& details = sourceReference->GetDetails(); + + // Always respect this value to prevent server overloading + if (IsBeforeDoNotUpdateBeforeTime(details)) + { + return false; + } + + // Allow the source reference to decide beyond this + return sourceReference->ShouldUpdateBeforeOpen(requestedUpdateInterval); + } + + bool IsBeforeDoNotUpdateBeforeTime(const SourceDetails& details) + { + if (std::chrono::system_clock::now() < details.DoNotUpdateBefore) + { + AICLI_LOG(Repo, Info, << "Background update for `" << details.Name << "` is suppressed until: " << details.DoNotUpdateBefore); + return true; + } + else + { + return false; + } + } + + bool IsAfterUpdateCheckTime(const SourceDetails& details, std::optional<TimeSpan> requestedUpdateInterval) + { + return IsAfterUpdateCheckTime(details.Name, details.LastUpdateTime, requestedUpdateInterval); + } + + bool IsAfterUpdateCheckTime(std::string_view name, std::chrono::system_clock::time_point lastUpdateTime, std::optional<TimeSpan> requestedUpdateInterval) + { + constexpr static TimeSpan s_ZeroMins = 0min; + + TimeSpan autoUpdateTime; + if (requestedUpdateInterval) + { + autoUpdateTime = requestedUpdateInterval.value(); + } + else + { + autoUpdateTime = Settings::User().Get<Settings::Setting::AutoUpdateTimeInMinutes>(); + } + + // A value of zero means no auto update, to get update the source run `winget update` + if (autoUpdateTime != s_ZeroMins) + { + auto timeSinceLastUpdate = std::chrono::system_clock::now() - lastUpdateTime; + if (timeSinceLastUpdate > autoUpdateTime) + { + AICLI_LOG(Repo, Info, << "Source `" << name << "` after auto update time [" << + (requestedUpdateInterval ? "(override) " : "") << + std::chrono::duration_cast<std::chrono::minutes>(autoUpdateTime).count() << " mins]; it has been at least " << + std::chrono::duration_cast<std::chrono::minutes>(timeSinceLastUpdate).count() << " mins"); + return true; + } + } + + return false; + } +} diff --git a/src/AppInstallerRepositoryCore/SourceUpdateChecks.h b/src/AppInstallerRepositoryCore/SourceUpdateChecks.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Public/winget/RepositorySource.h" +#include <chrono> + +namespace AppInstaller::Repository +{ + // Determines if the given source should update before opening. + bool ShouldUpdateBeforeOpen(ISourceReference* sourceReference, const std::optional<TimeSpan>& requestedUpdateInterval); + + // Determines if the current time is before a previously stored "do note update before" time. + bool IsBeforeDoNotUpdateBeforeTime(const SourceDetails& details); + + // Determines if the given details and desired update interval indicate an update check should occur. + bool IsAfterUpdateCheckTime(const SourceDetails& details, std::optional<TimeSpan> requestedUpdateInterval); + + // Determines if the given details and desired update interval indicate an update check should occur. + bool IsAfterUpdateCheckTime(std::string_view name, std::chrono::system_clock::time_point lastUpdateTime, std::optional<TimeSpan> requestedUpdateInterval); +} diff --git a/src/AppInstallerSharedLib/DateTime.cpp b/src/AppInstallerSharedLib/DateTime.cpp @@ -80,4 +80,45 @@ namespace AppInstaller::Utility { return std::chrono::system_clock::from_time_t(static_cast<time_t>(epoch)); } + + std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version) + { + // Our custom format for converting UTC into a version is: + // Major :: `Year` [1, 9999] + // Minor :: `Month * 100 + Day` where Month [1, 12] and Day [1, 31] + // Build :: `Hour * 100 + Minute` where Hour [1, 24] and Minute [0, 59] + // Revision :: Milliseconds, but since no seconds are available we will disregard this + + tm versionTime{}; + + // Limit to the range supported by _mkgmtime64, which is 1970 to 3000 (hello to Y3K maintainers from 2023!) + UINT64 majorVersion = version.Major(); + if (majorVersion < 1970 || majorVersion > 3000) + { + return std::chrono::system_clock::time_point::min(); + } + versionTime.tm_year = static_cast<int>(majorVersion) - 1900; + + UINT64 minorVersion = version.Minor(); + UINT64 monthValue = minorVersion / 100; + UINT64 dayValue = minorVersion % 100; + if (monthValue < 1 || monthValue > 12 || dayValue < 1 || dayValue > 31) + { + return std::chrono::system_clock::time_point::min(); + } + versionTime.tm_mon = static_cast<int>(monthValue) - 1; + versionTime.tm_mday = static_cast<int>(dayValue); + + UINT64 buildVersion = version.Build(); + UINT64 hourValue = buildVersion / 100; + UINT64 minuteValue = buildVersion % 100; + if (hourValue < 1 || hourValue > 24 || minuteValue > 59) + { + return std::chrono::system_clock::time_point::min(); + } + versionTime.tm_hour = static_cast<int>(hourValue) - 1; + versionTime.tm_min = static_cast<int>(minuteValue); + + return std::chrono::system_clock::from_time_t(_mkgmtime64(&versionTime)); + } } diff --git a/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h b/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once - +#include <AppInstallerVersions.h> #include <chrono> #include <ostream> @@ -26,4 +26,9 @@ namespace AppInstaller::Utility // Converts the given unix epoch time to a system_clock::time_point. std::chrono::system_clock::time_point ConvertUnixEpochToSystemClock(int64_t epoch); + + // Converts the given package version into a time_point using our custom format. + // Ensure that the package is expected to use this format, or you may get strange times. + // If the version is not convertable, the minimum time is returned. + std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version); }