diff --git a/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs b/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs
index 6195ff8e..5b791b27 100644
--- a/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs
+++ b/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs
@@ -205,4 +205,12 @@ public class VersionEntry : VersionIdentity
///
[JsonPropertyName("fallbackFullHash")]
public string? FallbackFullHash { get; set; }
+
+ ///
+ /// The version string of the fallback full replacement package.
+ /// Used by to skip chain packages
+ /// already covered by a previous fallback.
+ ///
+ [JsonPropertyName("fallbackFullVersion")]
+ public string? FallbackFullVersion { get; set; }
}
diff --git a/src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs b/src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs
index 21fe3f68..eca0432e 100644
--- a/src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs
+++ b/src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs
@@ -205,7 +205,8 @@ public static DownloadPlan Build(
{
FallbackFullName = match.Name,
FallbackFullUrl = match.Url,
- FallbackFullHash = match.SHA256
+ FallbackFullHash = match.SHA256,
+ FallbackFullVersion = match.Version
};
}
return chain;
diff --git a/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs b/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs
index b84c784c..8c406cfb 100644
--- a/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs
+++ b/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs
@@ -15,6 +15,7 @@ public record DownloadAsset(
string? FallbackFullName = null,
string? FallbackFullUrl = null,
string? FallbackFullHash = null,
+ string? FallbackFullVersion = null,
bool IsForcibly = false,
bool IsFreeze = false,
int RecordId = 0,
diff --git a/src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs
index 49f6e56a..e2029d36 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs
@@ -7,6 +7,7 @@
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Pipeline;
using GeneralUpdate.Core.Configuration;
+using GeneralUpdate.Core.Utilities;
using GeneralUpdate.Core.Hooks;
using IUpdateReporter = GeneralUpdate.Core.Download.Reporting.IUpdateReporter;
@@ -146,8 +147,25 @@ public virtual async Task ExecuteAsync()
AllPackagesSucceeded = true;
var status = ReportType.None;
patchRoot = StorageManager.GetTempDirectory(Patchs);
+
+ // Track the highest version already applied via full-package fallback,
+ // so subsequent chain packages covered by that version are skipped.
+ SemVersion? fallbackEffectiveVersion = null;
+
foreach (var version in _configinfo.UpdateVersions)
{
+ // When a previous chain package fell back to a full package,
+ // the application is already at or beyond that full version.
+ // Skip any remaining chain packages whose version is ≤ the
+ // fallback version — applying them would be redundant.
+ if (fallbackEffectiveVersion != null
+ && version.PackageType == (int)PackageType.Chain
+ && Semver.TryParse(version.Version, out var versionSv)
+ && versionSv <= fallbackEffectiveVersion)
+ {
+ GeneralTracer.Info($"AbstractStrategy.ExecuteAsync: skipping {version.Version} ({version.Name}) — already covered by fallback full package v{fallbackEffectiveVersion}.");
+ continue;
+ }
try
{
// Use a version-specific subdirectory under patchRoot so that
@@ -193,6 +211,14 @@ public virtual async Task ExecuteAsync()
var fallbackBuilder = BuildPipeline(fallbackContext);
await fallbackBuilder.Build();
status = ReportType.Success;
+ // Record the fallback full package version so subsequent
+ // chain packages ≤ this version are skipped.
+ if (!string.IsNullOrEmpty(version.FallbackFullVersion)
+ && Semver.TryParse(version.FallbackFullVersion, out var ffv)
+ && (fallbackEffectiveVersion == null || ffv > fallbackEffectiveVersion))
+ {
+ fallbackEffectiveVersion = ffv;
+ }
}
catch (Exception e)
{
diff --git a/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs
index 660b2514..44fb76c0 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs
@@ -631,7 +631,8 @@ async Task DownloadAndApplyAsync(Download.Models.DownloadPlan plan, UpdateScenar
PackageType = a.PackageType,
FallbackFullName = a.FallbackFullName,
FallbackFullUrl = a.FallbackFullUrl,
- FallbackFullHash = a.FallbackFullHash
+ FallbackFullHash = a.FallbackFullHash,
+ FallbackFullVersion = a.FallbackFullVersion
}).ToList();
var uVersions = dVersions.Where(v => v.AppType == (int)AppType.Upgrade).ToList();
diff --git a/tests/ClientTest/Program.cs b/tests/ClientTest/Program.cs
index 7a3dba76..26ba9884 100644
--- a/tests/ClientTest/Program.cs
+++ b/tests/ClientTest/Program.cs
@@ -7,6 +7,8 @@
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Hooks;
using GeneralUpdate.Core.Security;
+using GeneralUpdate.Core.Pipeline;
+using GeneralUpdate.Core.Configuration;
try
{
@@ -87,6 +89,7 @@ static async Task RunUpdateTestAsync()
}
})
.SetOption(Option.AppType, AppType.Client)
+ .UseDiffPipeline(builder => builder.WithStopOnFirstError(true))
.Hooks()
.AddListenerMultiDownloadStatistics(OnDownloadStatistics)
.AddListenerMultiDownloadCompleted(OnDownloadCompleted)
@@ -140,7 +143,7 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e)
{
foreach (var vi in e.Info.Body)
{
- var mode = vi.IsCrossVersion == true ? "CVP" : "Chain";
+ var mode = vi.PackageType == (int)PackageType.Full ? "Full" : "Chain";
var appType = vi.AppType switch
{
1 => "Client",
@@ -149,8 +152,7 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e)
};
Console.WriteLine($" - [{mode}] {vi.Version} ({vi.Name}) [{vi.Size} bytes] " +
$"AppType={appType} " +
- $"{(vi.IsForcibly == true ? "(forced)" : "")}" +
- $"{(!string.IsNullOrEmpty(vi.FromVersion) ? $" from={vi.FromVersion}" : "")}");
+ $"{(vi.IsForcibly == true ? "(forced)" : "")}");
}
}
else
diff --git a/tests/UpgradeTest/Program.cs b/tests/UpgradeTest/Program.cs
index 091150dc..96992e0b 100644
--- a/tests/UpgradeTest/Program.cs
+++ b/tests/UpgradeTest/Program.cs
@@ -3,6 +3,7 @@
using GeneralUpdate.Core.Download;
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Hooks;
+using GeneralUpdate.Core.Pipeline;
try
{
@@ -41,6 +42,7 @@ static async Task RunStandardUpgradeAsync()
await new GeneralUpdateBootstrap()
.SetOption(Option.AppType, AppType.Upgrade)
+ .UseDiffPipeline(builder => builder.WithStopOnFirstError(true))
.Hooks()
.AddListenerMultiDownloadStatistics(OnDownloadStatistics)
.AddListenerMultiDownloadCompleted(OnDownloadCompleted)