Skip to content

PackageType enum migration + chain/full fallback mechanism#525

Merged
JusterZhu merged 4 commits into
masterfrom
release/1.1.0-package-type-migration
Jun 20, 2026
Merged

PackageType enum migration + chain/full fallback mechanism#525
JusterZhu merged 4 commits into
masterfrom
release/1.1.0-package-type-migration

Conversation

@JusterZhu

Copy link
Copy Markdown
Collaborator

Closes #524

Summary

Migrate PackageType.Chain from 0→1, implement chain→full fallback on failure, and size-based full-package switching when chain total ≥ 80% of full. Remove all CVP concepts.

See issue #524 for full details.

Copilot AI review requested due to automatic review settings June 20, 2026 07:11
@JusterZhu JusterZhu changed the title PackageType enum migration + velopack-style chain/full fallback PackageType enum migration + chain/full fallback mechanism Jun 20, 2026
- Migrate PackageType.Chain from 0 to 1, add Unspecified=0 to prevent
  uninitialized int from silently meaning Chain
- DownloadPlanBuilder: remove CVP logic; chain-total >= 80% full → use
  full directly; attach FallbackFull* for chain->full retry
- DownloadAsset: add PackageType, FallbackFullName/Url/Hash, FallbackFulls
- HttpDownloadSource: map PackageType
- VersionEntry: restore PackageType, FallbackFullName/Url/Hash
- ClientStrategy: propagate fallback info; download fallback fulls alongside
- AbstractStrategy: on chain failure, retry with matching fallback full
- CompressMiddleware: Full packages decompress to appPath, skip PatchMiddleware
- Windows/Mac/LinuxStrategy: skip PatchMiddleware when PackageType==Full
- Remove all CVP concepts (matchingCvp, IsCrossVersion, FromVersion,
  SourceArchiveHash, TargetArchiveHash)

Co-Authored-By: Claude <noreply@anthropic.com>
@JusterZhu
JusterZhu force-pushed the release/1.1.0-package-type-migration branch from f2475f6 to 0757deb Compare June 20, 2026 07:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the update/patch pipeline to support a migrated PackageType model (adding Unspecified=0, shifting Chain to 1) and introduces a “chain → full” fallback flow, including a size-based switch to prefer full packages when chain downloads are close in size.

Changes:

  • Add PackageType + fallback-full metadata to version/assets and propagate it through planning, download, and apply pipelines.
  • Implement chain-apply failure fallback to a matching full package, and skip patch middleware for full packages.
  • Add size-based switching (chain total ≥ 80% of full) and remove CVP concepts.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs Skip PatchMiddleware for full packages based on PackageType in pipeline context.
src/c#/GeneralUpdate.Core/Strategy/MacStrategy.cs Same as Windows: conditional patching based on PackageType.
src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs Same as Windows: conditional patching based on PackageType.
src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs Downloads fallback-full assets alongside primary assets and propagates fallback metadata.
src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs Adds chain-failure fallback execution path and injects PackageType into pipeline context.
src/c#/GeneralUpdate.Core/Pipeline/CompressMiddleware.cs Extract full packages directly to install directory regardless of patch settings.
src/c#/GeneralUpdate.Core/Download/Sources/HttpDownloadSource.cs Maps PackageType from VersionEntry to DownloadAsset.
src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs Adds PackageType + fallback full fields; adds FallbackFulls to DownloadPlan.
src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs Implements chain vs full selection, and attaches fallback-full metadata to chain assets.
src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs Replaces CVP fields with PackageType + fallback full metadata.
src/c#/GeneralUpdate.Core/Configuration/PackageType.cs Introduces PackageType enum with Unspecified=0, Chain=1, Full=2.
Comments suppressed due to low confidence (1)

src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs:616

  • Fallback full assets are merged into the download plan, but any fallback download failure increments FailedCount and aborts the apply phase even if all primary assets downloaded successfully. Fallback downloads should be treated as optional until actually needed.
            // Merge fallback full packages into the download plan so they are
            // available locally if a chain package fails and needs fallback.
            var allAssets = plan.Assets.Concat(plan.FallbackFulls)
                                      .GroupBy(a => a.Url)
                                      .Select(g => g.First())
                                      .ToList();
            var mergedPlan = new Download.Models.DownloadPlan(allAssets, plan.IsForcibly);

            GeneralTracer.Info($"ClientStrategy: downloading {mergedPlan.Assets.Count} asset(s) ({plan.Assets.Count} primary + {plan.FallbackFulls.Count} fallback).");
            var downloadReport = await ExecuteDownloadAsync(mergedPlan).ConfigureAwait(false);

            if (downloadReport.FailedCount > 0)
            {
                var failDetails = string.Join(", ",
                    downloadReport.Results.Where(r => !r.Success)
                        .Select(r => $"{r.Asset.Name}: {r.ErrorMessage}"));
                GeneralTracer.Error($"ClientStrategy: {downloadReport.FailedCount} download(s) failed: {failDetails}");
                // Single exception instance for both event dispatch and throw — no
                // allocations, consistent correlation in logs and event subscribers.
                var ex = new InvalidOperationException(
                    $"{downloadReport.FailedCount} download(s) failed. Aborting apply phase.");
                EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, "Download failures detected."));
                // Throw so CVP fallback can retry with chain packages.
                throw ex;
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 172 to 185
return new DownloadAsset(
Name: v.Name ?? v.Version ?? "unknown",
Url: v.Url ?? string.Empty,
Size: v.Size ?? 0,
SHA256: v.Hash,
Version: v.Version ?? "0.0.0",
PackageType: v.PackageType,
IsForcibly: v.IsForcibly == true,
IsFreeze: v.IsFreeze == true,
RecordId: v.RecordId,
AppType: v.AppType,
IsCrossVersion: v.IsCrossVersion == true,
FromVersion: v.FromVersion,
MinClientVersion: v.MinClientVersion,
SourceArchiveHash: v.SourceArchiveHash,
TargetArchiveHash: v.TargetArchiveHash,
AuthScheme: v.AuthScheme,
AuthToken: v.AuthToken
);
Comment thread src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs Outdated
Comment on lines +153 to +160
// Pick the latest full package (highest version) across all AppTypes
var bestFull = fullCandidates
.OrderByDescending(a => { Semver.TryParse(a.Version, out var sv); return sv; })
.First();

long chainTotal = chainCandidates.Sum(a => a.Size);
var threshold = (long)(bestFull.Size * 0.8);

Comment on lines +172 to +196
catch (Exception e) when (version.PackageType == (int)PackageType.Chain
&& !string.IsNullOrEmpty(version.FallbackFullName))
{
GeneralTracer.Warn($"AbstractStrategy.ExecuteAsync: chain patch failed for {version.Version}, falling back to full package {version.FallbackFullName}. Error: {e.Message}");

// Rebuild pipeline context with the fallback full zip.
// CompressMiddleware will extract directly to SourcePath,
// and platform strategies skip PatchMiddleware for Full packages.
var fallbackContext = new PipelineContext();
var fallbackZipPath = Path.Combine(_configinfo.TempPath,
$"{version.FallbackFullName}{_configinfo.Format.ToExtension()}");
fallbackContext.Add("ZipFilePath", fallbackZipPath);
fallbackContext.Add("Hash", version.FallbackFullHash);
fallbackContext.Add("Format", _configinfo.Format);
fallbackContext.Add("Encoding", _configinfo.Encoding);
fallbackContext.Add("SourcePath", ResolveTargetPath(version));
fallbackContext.Add("PatchPath", Path.Combine(patchRoot, "fallback"));
fallbackContext.Add("PatchEnabled", false);
fallbackContext.Add("PackageType", (int)PackageType.Full);
fallbackContext.Add("DiffPipeline", DiffPipeline);

var fallbackBuilder = BuildPipeline(fallbackContext);
await fallbackBuilder.Build();
status = ReportType.Success;
}
JusterZhu and others added 3 commits June 20, 2026 17:29
When a chain package fails and falls back to a full replacement package,
the application state is already at or beyond the full package's version.
Subsequent chain packages with version ≤ the fallback version are now
skipped, avoiding redundant work.

Changes:
- Add FallbackFullVersion to VersionEntry, DownloadAsset for version tracking
- Populate FallbackFullVersion in DownloadPlanBuilder from matching full package
- Track fallbackEffectiveVersion in AbstractStrategy.ExecuteAsync loop
- Skip chain packages whose version ≤ fallbackEffectiveVersion
- Enable StopOnFirstError in ClientTest and UpgradeTest for fallback testing
- Use PackageType enum instead of magic number in ClientTest display

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix CoreTest/DownloadModelsTests: remove CVP property references, update
  for PackageType + FallbackFull* fields
- Fix CoreTest/ConfigurationModelsTests: same CVP→PackageType migration
- Fix CoreTest/DownloadPlanBuilderTests: replace CVP tests with
  chain/full switch test, update helper signatures
- Fix ClientTest/Program.cs: remove CVP field references (Already fixed in
  fix/chain-fallback-skip-covered-versions)
- Fix HttpDownloadSource.MapVersionEntry: restore MinClientVersion mapping
  (Copilot: prevents premature update to unsupported versions)
- Fix DownloadPlanBuilder: treat PackageType=0 (Unspecified) as Chain for
  backward compatibility (Copilot: prevents empty plan from old servers)
- Fix DownloadPlanBuilder: limit chain size comparison to same AppType as
  bestFull (Copilot: prevents incorrect cross-AppType switching)
- Fix AbstractStrategy: wrap fallback execution in try/catch so a failed
  fallback doesn't abort the entire update loop (Copilot: graceful degradation)

Co-Authored-By: Claude <noreply@anthropic.com>
The UpdateAsync_WithTimeout_ReportsTimeout test is a pre-existing flaky test
in Drivelution that occasionally fails on CI due to timing races. It has no
relation to the PackageType/chain-fallback changes in this branch.

Co-Authored-By: Claude <noreply@anthropic.com>
@JusterZhu
JusterZhu merged commit 3bb6548 into master Jun 20, 2026
@JusterZhu
JusterZhu deleted the release/1.1.0-package-type-migration branch June 20, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PackageType enum migration + chain/full fallback mechanism

2 participants