Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,13 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra

// Inject binary differ into OS-level strategy for differential patching
// Must be called after Create() since _osStrategy is initialized there.
var differ = ResolveExtension<IBinaryDiffer>();
if (differ != null)
var dirtyStrategy = ResolveExtension<IDirtyStrategy>();
if (dirtyStrategy != null)
{
if (roleStrategy is ClientUpdateStrategy cs2)
cs2.SetDiffer(differ);
cs2.SetDirtyStrategy(dirtyStrategy);
else if (roleStrategy is UpgradeUpdateStrategy us2)
us2.SetDiffer(differ);
us2.SetDirtyStrategy(dirtyStrategy);
}

// Check custom skip condition before executing update
Expand Down
5 changes: 3 additions & 2 deletions src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ protected T GetOption<T>(UpdateOption<T>? option)
public TBootstrap SslPolicy<T>() where T : Security.ISslValidationPolicy, new()
{ _extensions[typeof(Security.ISslValidationPolicy)] = typeof(T); return (TBootstrap)this; }

public TBootstrap BinaryDiffer<T>() where T : Differential.IBinaryDiffer, new()
{ _extensions[typeof(Differential.IBinaryDiffer)] = typeof(T); return (TBootstrap)this; }

public TBootstrap PipelineFactory<T>() where T : Pipeline.IUpdatePipelineFactory, new()
{ _extensions[typeof(Pipeline.IUpdatePipelineFactory)] = typeof(T); return (TBootstrap)this; }
Expand Down Expand Up @@ -88,6 +86,9 @@ protected T GetOption<T>(UpdateOption<T>? option)
public TBootstrap DirtyStrategy<T>() where T : Differential.IDirtyStrategy, new()
{ _extensions[typeof(Differential.IDirtyStrategy)] = typeof(T); return (TBootstrap)this; }

public TBootstrap BinaryDiffer<T>() where T : Differential.IBinaryDiffer, new()
{ _extensions[typeof(Differential.IBinaryDiffer)] = typeof(T); return (TBootstrap)this; }

public TBootstrap ConfigureBlackList(BlackListConfig config)
{
_instances[typeof(BlackListConfig)] = config ?? BlackListConfig.Empty;
Expand Down
47 changes: 12 additions & 35 deletions src/c#/GeneralUpdate.Core/Differential/IBinaryDiffer.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,17 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace GeneralUpdate.Core.Differential
{
/// <summary>
/// Defines a pluggable binary differential algorithm (diff generation and patch application).
/// Implementations may use different strategies: BSDIFF, HDiffPatch-style, VCDIFF, etc.
/// </summary>
/// <remarks>
/// This interface lives in Core so that Pipeline middleware can depend on it
/// without creating a circular dependency on the GeneralUpdate.Differential assembly.
///
/// Concrete implementations (StreamingHdiffDiffer, BSDIFF, etc.) live in
/// GeneralUpdate.Differential and are injected via Bootstrap.BinaryDiffer&lt;T&gt;().
/// </remarks>
public interface IBinaryDiffer
{
/// <summary>
/// Generates a binary patch from <paramref name="oldFilePath"/> to <paramref name="newFilePath"/>,
/// writing the result to <paramref name="patchFilePath"/>.
/// </summary>
Task CleanAsync(
string oldFilePath,
string newFilePath,
string patchFilePath,
CancellationToken cancellationToken = default);
namespace GeneralUpdate.Core.Differential;

/// <summary>
/// Applies a binary patch to <paramref name="oldFilePath"/>, producing
/// <paramref name="newFilePath"/> using the patch at <paramref name="patchFilePath"/>.
/// </summary>
Task DirtyAsync(
string oldFilePath,
string newFilePath,
string patchFilePath,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Pluggable file-level binary patch-application algorithm.
/// Implement this to customize how individual files are patched (BSDIFF, HDiffPatch, etc.).
///
/// For full directory-level control, inject <see cref="IDirtyStrategy"/> instead.
/// </summary>
public interface IBinaryDiffer
{
/// <summary>Applies a binary patch: oldFile + patchFile → newFile.</summary>
Task DirtyAsync(string oldFilePath, string newFilePath, string patchFilePath,
CancellationToken cancellationToken = default);
}
16 changes: 8 additions & 8 deletions src/c#/GeneralUpdate.Core/Pipeline/PatchMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace GeneralUpdate.Core.Pipeline;
/// Differential patch middleware. Applies binary patches (BSDIFF, HDiffPatch, etc.)
/// to bring files from an old version to a new version.
///
/// The <see cref="IBinaryDiffer"/> implementation is resolved from
/// <see cref="PipelineContext"/> (key "BinaryDiffer"), set by
/// <see cref="GeneralUpdate.Core.Strategy.AbstractStrategy"/> when the differ is injected via
/// The <see cref="IDirtyStrategy"/> implementation is resolved from
/// <see cref="PipelineContext"/> (key "DirtyStrategy"), set by
/// <see cref="Strategy.AbstractStrategy"/> when the differ is injected via
/// <c>Bootstrap.BinaryDiffer&lt;T&gt;()</c>. Without injection, patches are skipped.
/// </summary>
public class PatchMiddleware : IMiddleware
Expand All @@ -23,19 +23,19 @@ public async Task InvokeAsync(PipelineContext context)
var targetPath = context.Get<string>("PatchPath");

// Resolve differ from pipeline context (injected via AbstractStrategy)
var differ = context.Get<IBinaryDiffer>("BinaryDiffer");
var dirtyStrategy = context.Get<IDirtyStrategy>("DirtyStrategy");

if (differ == null)
if (dirtyStrategy == null)
{
GeneralTracer.Info("PatchMiddleware.InvokeAsync: no IBinaryDiffer injected — patch skipped. " +
"Use Bootstrap.BinaryDiffer<T>() to enable differential patching.");
GeneralTracer.Info("PatchMiddleware.InvokeAsync: no IDirtyStrategy injected — patch skipped. " +
"Use Bootstrap.DirtyStrategy<T>() to enable differential patching.");
return;
}

GeneralTracer.Info($"PatchMiddleware.InvokeAsync: applying differential patch. SourcePath={sourcePath}, PatchPath={targetPath}");
try
{
await differ.DirtyAsync(sourcePath, targetPath, targetPath);
await dirtyStrategy.ExecuteAsync(sourcePath, targetPath);
GeneralTracer.Info("PatchMiddleware.InvokeAsync: differential patch applied successfully.");
}
catch (Exception ex)
Expand Down
8 changes: 6 additions & 2 deletions src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ public abstract class AbstractStrategy : IStrategy
protected IUpdateReporter? Reporter { get; set; }

/// <summary>Optional binary differ for differential patch updates.</summary>
public IBinaryDiffer? Differ { get; set; }
public IDirtyStrategy? DirtyStrategy { get; set; }

/// <summary>Optional file-level binary differ for patch application.</summary>
public IBinaryDiffer? BinaryDiffer { get; set; }

public virtual void Execute() => throw new NotImplementedException();

Expand Down Expand Up @@ -95,7 +98,8 @@ protected virtual PipelineContext CreatePipelineContext(VersionInfo version, str
context.Add("PatchPath", patchPath);
context.Add("PatchEnabled", _configinfo.PatchEnabled);
// Binary differ for differential patching
context.Add("BinaryDiffer", Differ);
context.Add("DirtyStrategy", DirtyStrategy);
context.Add("BinaryDiffer", BinaryDiffer);

return context;
}
Expand Down
113 changes: 77 additions & 36 deletions src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using GeneralUpdate.Core.Differential;
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -46,8 +47,8 @@ public void Create(GlobalConfigInfo parameter)
{
_configInfo = parameter ?? throw new ArgumentNullException(nameof(parameter));
_osStrategy = ResolveOsStrategy();
if (_pendingDiffer != null && _osStrategy is AbstractStrategy abs)
abs.Differ = _pendingDiffer;
if (_pendingDirtyStrategy != null && _osStrategy is AbstractStrategy abs)
abs.DirtyStrategy = _pendingDirtyStrategy;
}

public async Task ExecuteAsync()
Expand Down Expand Up @@ -75,16 +76,23 @@ public void Execute()
ExecuteAsync().GetAwaiter().GetResult();
}

private Differential.IBinaryDiffer? _pendingDiffer;
private IDirtyStrategy? _pendingDirtyStrategy;

/// <summary>Sets the binary differ on the underlying OS-level strategy for differential patch updates.
/// Safe to call before or after Create(). If called before, the differ is cached and applied when Create() resolves _osStrategy.</summary>
public void SetDiffer(Differential.IBinaryDiffer? differ)
/// <summary>Sets the directory-level dirty strategy on the underlying OS-level strategy for differential patch updates.
/// Safe to call before or after Create(). If called before, the strategy is cached and applied when Create() resolves _osStrategy.</summary>
public void SetDirtyStrategy(IDirtyStrategy? dirtyStrategy)
{
if (_osStrategy is AbstractStrategy abs)
abs.Differ = differ;
abs.DirtyStrategy = dirtyStrategy;
else
_pendingDiffer = differ;
_pendingDirtyStrategy = dirtyStrategy;
}

/// <summary>Sets the file-level binary differ on the underlying OS-level strategy.</summary>
public void SetBinaryDiffer(IBinaryDiffer? binaryDiffer)
{
if (_osStrategy is AbstractStrategy abs)
abs.BinaryDiffer = binaryDiffer;
}

public void StartApp()
Expand Down Expand Up @@ -174,31 +182,6 @@ private async Task ExecuteStandardWorkflowAsync()
return;
}

// Build process info for the upgrade process
// Convert DownloadAsset list to VersionInfo for ProcessInfo compatibility
var downloadVersions = downloadPlan.Assets.Select(a => new VersionInfo
{
Name = a.Name,
Hash = a.SHA256,
Url = a.Url,
Version = a.Version,
Format = _configInfo.Format ?? "ZIP"
}).ToList();

var processInfo = ConfigurationMapper.MapToProcessInfo(
_configInfo, downloadVersions,
_configInfo.BlackFormats ?? BlackListDefaults.DefaultBlackFormats,
_configInfo.BlackFiles ?? BlackListDefaults.DefaultBlackFiles,
_configInfo.SkipDirectorys ?? BlackListDefaults.DefaultSkipDirectories);

// Keep JSON string for backward compatibility (GlobalConfigInfo.ProcessInfo)
_configInfo.ProcessInfo = JsonSerializer.Serialize(processInfo,
ProcessInfoJsonContext.Default.ProcessInfo);

// Wire ProcessInfo via AES-encrypted file IPC.
new EncryptedFileProcessInfoProvider().Send(processInfo);
GeneralTracer.Info("ClientUpdateStrategy: ProcessInfo sent via encrypted file IPC.");

// Backup — conditionally skipped when BackupEnabled is false
if (_configInfo.BackupEnabled != false)
{
Expand Down Expand Up @@ -232,12 +215,70 @@ private async Task ExecuteStandardWorkflowAsync()
await SafeReportDownloadCompletedAsync(hooksCtx).ConfigureAwait(false);
await SafeOnDownloadCompletedAsync(hooksCtx).ConfigureAwait(false);

// Apply updates and start app
await _osStrategy.ExecuteAsync();
// Phase: apply Upgrade packages — update Upgrade.exe itself before launching it.
// Safe because MainApp and Upgrade.exe are different files (no lock conflict).
var allVersions = downloadPlan.Assets.Select(a => new VersionInfo
{
Name = a.Name,
Hash = a.SHA256,
Url = a.Url,
Version = a.Version,
Format = _configInfo.Format ?? "ZIP",
AppType = a.IsForcibly ? null : null // preserve original AppType
}).ToList();

// Rebuild the full VersionInfo list with AppType preserved from download source
var downloadVersions = downloadPlan.Assets.Select(a => new VersionInfo
{
Name = a.Name,
Hash = a.SHA256,
Url = a.Url,
Version = a.Version,
Format = _configInfo.Format ?? "ZIP",
AppType = _configInfo.IsUpgradeUpdate == true && a.Version != _configInfo.ClientVersion
? (int)AppType.Upgrade : (int)AppType.Client
}).ToList();

// Split: Upgrade versions vs MainApp versions
var upgradeVersions = downloadVersions.Where(v => v.AppType == (int)AppType.Upgrade).ToList();
var clientVersions = downloadVersions.Where(v => v.AppType != (int)AppType.Upgrade).ToList();

GeneralTracer.Info($"ClientUpdateStrategy: Upgrade packages={upgradeVersions.Count}, MainApp packages={clientVersions.Count}");

// Apply Upgrade packages now (update Upgrade.exe before launching it)
if (upgradeVersions.Count > 0)
{
GeneralTracer.Info("ClientUpdateStrategy: applying Upgrade packages.");
_configInfo.UpdateVersions = upgradeVersions;
_osStrategy!.Create(_configInfo);
await _osStrategy.ExecuteAsync();
}

// Send IPC with remaining MainApp versions for the upgrade process
var processInfo = ConfigurationMapper.MapToProcessInfo(
_configInfo, clientVersions,
_configInfo.BlackFormats ?? BlackListDefaults.DefaultBlackFormats,
_configInfo.BlackFiles ?? BlackListDefaults.DefaultBlackFiles,
_configInfo.SkipDirectorys ?? BlackListDefaults.DefaultSkipDirectories);

_configInfo.ProcessInfo = JsonSerializer.Serialize(processInfo,
ProcessInfoJsonContext.Default.ProcessInfo);
new EncryptedFileProcessInfoProvider().Send(processInfo);
GeneralTracer.Info("ClientUpdateStrategy: ProcessInfo sent with MainApp versions only.");

await SafeOnAfterUpdateAsync(hooksCtx).ConfigureAwait(false);
await SafeReportUpdateAppliedAsync(hooksCtx).ConfigureAwait(false);
await SafeOnBeforeStartAppAsync(hooksCtx).ConfigureAwait(false);
_osStrategy.StartApp();

// Launch the upgrade process to apply MainApp updates
var updaterPath = Path.Combine(_configInfo.InstallPath, _configInfo.AppName);
if (!File.Exists(updaterPath))
throw new FileNotFoundException($"Upgrade application not found: {updaterPath}");

GeneralTracer.Info($"ClientUpdateStrategy: launching upgrade process {updaterPath}");
Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = updaterPath });
GeneralTracer.Info("ClientUpdateStrategy: upgrade process launched, exiting.");
await GracefulExit.CurrentProcessAsync().ConfigureAwait(false);
Comment on lines +279 to +281
}

#endregion
Expand Down
28 changes: 18 additions & 10 deletions src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using GeneralUpdate.Core.Differential;
using System;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -34,8 +35,8 @@ public void Create(GlobalConfigInfo parameter)
{
_configInfo = parameter ?? throw new ArgumentNullException(nameof(parameter));
_osStrategy = ResolveOsStrategy();
if (_pendingDiffer != null && _osStrategy is AbstractStrategy abs)
abs.Differ = _pendingDiffer;
if (_pendingDirtyStrategy != null && _osStrategy is AbstractStrategy abs)
abs.DirtyStrategy = _pendingDirtyStrategy;
}

public async Task ExecuteAsync()
Expand All @@ -56,10 +57,10 @@ public async Task ExecuteAsync()

_osStrategy!.Create(_configInfo);

// Apply updates via OS-specific pipeline (Hash -> Compress -> Patch)
// Apply MainApp updates — Client already applied Upgrade packages, IPC only has MainApp versions
if (_configInfo.UpdateVersions?.Count > 0)
{
GeneralTracer.Info($"UpgradeUpdateStrategy: applying {_configInfo.UpdateVersions.Count} update(s).");
GeneralTracer.Info("UpgradeUpdateStrategy: applying " + _configInfo.UpdateVersions.Count + " MainApp update(s).");
await _osStrategy.ExecuteAsync();
}
else
Expand Down Expand Up @@ -92,16 +93,23 @@ public void Execute()
ExecuteAsync().GetAwaiter().GetResult();
}

private Differential.IBinaryDiffer? _pendingDiffer;
private IDirtyStrategy? _pendingDirtyStrategy;

/// <summary>Sets the binary differ on the underlying OS-level strategy for differential patch updates.
/// Safe to call before or after Create(). If called before, the differ is cached and applied when Create() resolves _osStrategy.</summary>
public void SetDiffer(Differential.IBinaryDiffer? differ)
/// <summary>Sets the directory-level dirty strategy on the underlying OS-level strategy for differential patch updates.
/// Safe to call before or after Create(). If called before, the strategy is cached and applied when Create() resolves _osStrategy.</summary>
public void SetDirtyStrategy(IDirtyStrategy? dirtyStrategy)
{
if (_osStrategy is AbstractStrategy abs)
abs.Differ = differ;
abs.DirtyStrategy = dirtyStrategy;
else
_pendingDiffer = differ;
_pendingDirtyStrategy = dirtyStrategy;
}

/// <summary>Sets the file-level binary differ on the underlying OS-level strategy.</summary>
public void SetBinaryDiffer(IBinaryDiffer? binaryDiffer)
{
if (_osStrategy is AbstractStrategy abs)
abs.BinaryDiffer = binaryDiffer;
}

public void StartApp()
Expand Down
28 changes: 12 additions & 16 deletions src/c#/GeneralUpdate.Differential/Abstractions/IBinaryDiffer.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
// IBinaryDiffer has been moved to GeneralUpdate.Core.Differential.
// This file provides a backward-compatible type alias.
// New code should reference GeneralUpdate.Core.Differential.IBinaryDiffer directly.

using System.Threading;
using System.Threading.Tasks;
using CoreBinaryDiffer = GeneralUpdate.Core.Differential.IBinaryDiffer;

namespace GeneralUpdate.Differential.Abstractions
namespace GeneralUpdate.Differential.Abstractions;

/// <summary>
/// Binary differential algorithm with both patch generation and application.
/// Extends <see cref="CoreBinaryDiffer"/> (DirtyAsync) with CleanAsync.
/// </summary>
public interface IBinaryDiffer : CoreBinaryDiffer
{
/// <summary>
/// Binary differential algorithm abstraction.
/// </summary>
/// <remarks>
/// <b>Migration note:</b> This interface is an alias for
/// <see cref="CoreBinaryDiffer"/>. Use
/// <c>using GeneralUpdate.Core.Differential;</c> directly in new code.
/// </remarks>
public interface IBinaryDiffer : CoreBinaryDiffer
{
}
/// <summary>Generates a patch: oldFile vs newFile → patchFile.</summary>
Task CleanAsync(string oldFilePath, string newFilePath, string patchFilePath,
CancellationToken cancellationToken = default);
}
Loading
Loading