diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs index 70386c1dfa..cb296cf961 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs @@ -448,19 +448,27 @@ nativePackage.AvailableVersions is { } versions private IReadOnlyList GetCachedLocalWinGetPackages(int? cacheSeconds = null) { - if (_localPackagesProvider is not null) - { - return _localPackagesProvider(); - } + long sourceIndexGeneration = WinGet.SourceIndexGeneration; return cacheSeconds is null - ? TaskRecycler>.RunOrAttach(GetLocalWinGetPackages) + ? TaskRecycler>.RunOrAttach( + EnumerateLocalWinGetPackages, + sourceIndexGeneration + ) : TaskRecycler>.RunOrAttach( - GetLocalWinGetPackages, + EnumerateLocalWinGetPackages, + sourceIndexGeneration, cacheSeconds.Value ); } + private IReadOnlyList EnumerateLocalWinGetPackages(long sourceIndexGeneration) + { + return _localPackagesProvider is not null + ? _localPackagesProvider() + : GetLocalWinGetPackages(); + } + private IReadOnlyList GetAvailableUpdatesFromSystemCli(Exception ex) { var unwrappedException = UnwrapException(ex); diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs index a668c2e698..add00707fa 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs @@ -92,6 +92,13 @@ public void Dispose() } } + private static long _sourceIndexGeneration; + + internal static long SourceIndexGeneration => Volatile.Read(ref _sourceIndexGeneration); + + internal static void MarkSourceIndexRefreshed() => + Interlocked.Increment(ref _sourceIndexGeneration); + public WinGet() { Capabilities = new ManagerCapabilities @@ -811,12 +818,19 @@ public override void RefreshPackageIndexes() p.StartInfo.Environment["TMP"] = WinGetTemp; } - p.Start(); - logger.AddToStdOut(p.StandardOutput.ReadToEnd()); - logger.AddToStdErr(p.StandardError.ReadToEnd()); - logger.Close(p.ExitCode); - p.WaitForExit(); - p.Close(); + try + { + p.Start(); + logger.AddToStdOut(p.StandardOutput.ReadToEnd()); + logger.AddToStdErr(p.StandardError.ReadToEnd()); + logger.Close(p.ExitCode); + p.WaitForExit(); + p.Close(); + } + finally + { + MarkSourceIndexRefreshed(); + } } private string GetCliToolProxyArgument() diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs index e78cad554b..2d8ba8cf11 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/PackageManager.cs @@ -463,7 +463,10 @@ private IReadOnlyList _getAvailableUpdates(bool SecondAttempt) } try { - RefreshPackageIndexesSafely(); + if (!SecondAttempt) + { + RefreshPackageIndexesSafely(); + } var packages = RunListingTaskWithTimeout( GetAvailableUpdates_UnSafe, diff --git a/src/UniGetUI.PackageEngine.Tests/PackageManagerTests.cs b/src/UniGetUI.PackageEngine.Tests/PackageManagerTests.cs index 0aab8e4b23..7a8ccaf2fc 100644 --- a/src/UniGetUI.PackageEngine.Tests/PackageManagerTests.cs +++ b/src/UniGetUI.PackageEngine.Tests/PackageManagerTests.cs @@ -266,7 +266,7 @@ public void FindPackagesRetriesOnceAfterFailure() } [Fact] - public void GetAvailableUpdatesRetriesOnceAndRefreshesIndexesPerAttempt() + public void GetAvailableUpdatesRetriesOnceWithoutRefreshingIndexesAgain() { var manager = CreateReadyManager(); var attempts = 0; @@ -283,7 +283,7 @@ public void GetAvailableUpdatesRetriesOnceAndRefreshesIndexesPerAttempt() var package = Assert.Single(packages); Assert.Equal("Contoso.Update", package.Id); Assert.Equal(1, manager.AttemptFastRepairCalls); - Assert.Equal(2, manager.RefreshPackageIndexesCalls); + Assert.Equal(1, manager.RefreshPackageIndexesCalls); } [Fact] diff --git a/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs b/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs index ecee1fec0b..cd578a32ae 100644 --- a/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs +++ b/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs @@ -735,6 +735,65 @@ public void NativeWinGetHelperUsesSystemCliFallbackForUpdatesWhenCompositeCatalo Assert.Equal("2.0.0", package.NewVersionString); } + [Fact] + public void NativeWinGetHelperTakesANewCatalogSnapshotAfterTheSourceIndexIsRefreshed() + { + WinGet.MarkSourceIndexRefreshed(); + int snapshots = 0; + var helper = new NativeWinGetHelper( + new TestableWinGet(), + systemCliHelperFactory: null, + skipInitialization: true, + localPackagesProvider: () => + { + snapshots++; + return []; + } + ); + + helper.GetInstalledPackages_UnSafe(); + Assert.Equal(1, snapshots); + + WinGet.MarkSourceIndexRefreshed(); + helper.GetAvailableUpdates_UnSafe(); + + Assert.Equal(2, snapshots); + } + + [Fact] + public void NativeWinGetHelperReusesTheCatalogSnapshotWhileTheSourceIndexIsUnchanged() + { + WinGet.MarkSourceIndexRefreshed(); + int snapshots = 0; + var helper = new NativeWinGetHelper( + new TestableWinGet(), + systemCliHelperFactory: null, + skipInitialization: true, + localPackagesProvider: () => + { + snapshots++; + return []; + } + ); + + helper.GetInstalledPackages_UnSafe(); + helper.GetAvailableUpdates_UnSafe(); + helper.GetAvailableUpdates_UnSafe(); + + Assert.Equal(1, snapshots); + } + + [Fact] + public void RefreshPackageIndexesAdvancesTheSourceIndexGenerationWhenTheCliCallFails() + { + var manager = new TestableWinGet(); + long generationBefore = WinGet.SourceIndexGeneration; + + Assert.ThrowsAny(manager.RefreshPackageIndexes); + + Assert.NotEqual(generationBefore, WinGet.SourceIndexGeneration); + } + [Fact] public void NativeWinGetHelperSelectReachableCatalogsSkipsUnavailableSources() {