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
1 change: 1 addition & 0 deletions ipc/BOWL_TEST_ENV_VAR.enc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
j{NˆÂ;qmŒ8Ä
17 changes: 15 additions & 2 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using GeneralUpdate.Core.Hooks;
using GeneralUpdate.Core.Ipc;
using GeneralUpdate.Core.Download.Reporting;
using GeneralUpdate.Core.Differential;

namespace GeneralUpdate.Core;

Expand Down Expand Up @@ -130,6 +131,17 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra

roleStrategy.Create(_configInfo);

// 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)
{
if (roleStrategy is ClientUpdateStrategy cs2)
cs2.SetDiffer(differ);
else if (roleStrategy is UpgradeUpdateStrategy us2)
us2.SetDiffer(differ);
}

// Check custom skip condition before executing update
if (_customSkipOption?.Invoke() == true)
{
Expand Down Expand Up @@ -161,8 +173,9 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
// ════════════════════════════════════════════════════════════════

public GeneralUpdateBootstrap SetConfig(Configinfo configInfo)
{
_configInfo = ConfigurationMapper.MapToGlobalConfigInfo(configInfo);
{
configInfo.Validate();
_configInfo = ConfigurationMapper.MapToGlobalConfigInfo(configInfo);

var appType = GetOption(UpdateOptions.AppType);
if (appType != AppType.Upgrade)
Expand Down
4 changes: 0 additions & 4 deletions src/c#/GeneralUpdate.Core/Configuration/GlobalConfigInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,6 @@ public class GlobalConfigInfo : BaseConfigInfo
/// </summary>
public string ProcessInfo { get; set; }

/// <summary>
/// Directory path containing driver files for update.
/// Used when DriveEnabled is true to locate driver files for installation.
/// </summary>
/// <summary>
/// Indicates whether differential patch update is enabled.
/// Computed from UpdateOption.Patch or defaults to true.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,16 @@ public async Task<DownloadReport> ExecuteAsync(

var tasks = plan.Assets.Select(async asset =>
{
await sem.WaitAsync(token).ConfigureAwait(false);
var acquired = await sem.WaitAsync(TimeSpan.FromMinutes(5), token).ConfigureAwait(false);
if (!acquired)
{
GeneralTracer.Warn("DefaultDownloadOrchestrator: semaphore wait timed out for " + asset.Name + ", skipping.");
lock (results)
{
results.Add(new DownloadResult(asset, null, 0, TimeSpan.Zero, 0, false, "Semaphore wait timed out"));
}
return;
}
try
{
var fileName = GetFileName(asset);
Expand Down
2 changes: 1 addition & 1 deletion src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@

public static string GetTempDirectory(string name)
{
var path = $"generalupdate_{DateTime.Now:yyyy-MM-dd}_{name}";
var path = $"generalupdate_{DateTime.Now:yyyy-MM-dd-HHmmss-fff}_{System.Diagnostics.Process.GetCurrentProcess().Id}_{name}";
var tempDir = Path.Combine(Path.GetTempPath(), path);
if (!Directory.Exists(tempDir))
{
Expand Down Expand Up @@ -255,7 +255,7 @@
/// <summary>
/// Recursively read all files in the folder path.
/// </summary>
private IEnumerable<FileNode> ReadFileNode(string path, string rootPath = null)

Check warning on line 258 in src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 258 in src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs

View workflow job for this annotation

GitHub Actions / aot-verify

Cannot convert null literal to non-nullable reference type.

Check warning on line 258 in src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.
{
var resultFiles = new List<FileNode>();
rootPath ??= path;
Expand Down
23 changes: 8 additions & 15 deletions src/c#/GeneralUpdate.Core/Pipeline/PatchMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,22 @@ 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 injected via
/// 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
/// <c>Bootstrap.BinaryDiffer&lt;T&gt;()</c>. Without injection, patches are skipped.
/// </summary>
public class PatchMiddleware : IMiddleware
{
private readonly IBinaryDiffer? _differ;

/// <summary>Parameterless constructor (required by PipelineBuilder). Uses no differ.</summary>
public PatchMiddleware() { }

/// <summary>Creates a PatchMiddleware with an optional differ.</summary>
/// <param name="differ">Binary differ implementation. If null, patches are skipped.</param>
public PatchMiddleware(IBinaryDiffer? differ)
{
_differ = differ;
}

public async Task InvokeAsync(PipelineContext context)
{
var sourcePath = context.Get<string>("SourcePath");
var targetPath = context.Get<string>("PatchPath");

if (_differ == null)
// Resolve differ from pipeline context (injected via AbstractStrategy)
var differ = context.Get<IBinaryDiffer>("BinaryDiffer");

if (differ == null)
{
GeneralTracer.Info("PatchMiddleware.InvokeAsync: no IBinaryDiffer injected — patch skipped. " +
"Use Bootstrap.BinaryDiffer<T>() to enable differential patching.");
Expand All @@ -42,7 +35,7 @@ public async Task InvokeAsync(PipelineContext context)
GeneralTracer.Info($"PatchMiddleware.InvokeAsync: applying differential patch. SourcePath={sourcePath}, PatchPath={targetPath}");
try
{
await _differ.DirtyAsync(sourcePath, targetPath, targetPath);
await differ.DirtyAsync(sourcePath, targetPath, targetPath);
GeneralTracer.Info("PatchMiddleware.InvokeAsync: differential patch applied successfully.");
}
catch (Exception ex)
Expand Down
31 changes: 30 additions & 1 deletion src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using GeneralUpdate.Core.Differential;
using GeneralUpdate.Core.FileSystem;
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Pipeline;
Expand All @@ -22,6 +23,9 @@ public abstract class AbstractStrategy : IStrategy

/// <summary>Optional reporter for update status reporting.</summary>
protected IUpdateReporter? Reporter { get; set; }

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

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

Expand All @@ -46,6 +50,7 @@ public virtual async Task ExecuteAsync()
{
status = ReportType.Failure;
HandleExecuteException(e);
TryRollback();
}
finally
{
Expand Down Expand Up @@ -89,6 +94,8 @@ protected virtual PipelineContext CreatePipelineContext(VersionInfo version, str
context.Add("SourcePath", _configinfo.InstallPath);
context.Add("PatchPath", patchPath);
context.Add("PatchEnabled", _configinfo.PatchEnabled);
// Binary differ for differential patching
context.Add("BinaryDiffer", Differ);

return context;
}
Expand Down Expand Up @@ -135,10 +142,32 @@ protected static string CheckPath(string path, string name)
// The Hooks and Reporter properties are declared here so subclasses inherit them
// without redeclaring.

/// <summary>
/// Attempts to restore from backup when a pipeline execution fails.
/// Only restores if a backup directory exists for the current version.
/// </summary>
private void TryRollback()
{
try
{
var backupDir = _configinfo.BackupDirectory;
if (!string.IsNullOrWhiteSpace(backupDir) && Directory.Exists(backupDir))
{
GeneralTracer.Warn($"AbstractStrategy.TryRollback: restoring from backup {backupDir} -> {_configinfo.InstallPath}");
StorageManager.Restore(backupDir, _configinfo.InstallPath);
GeneralTracer.Info("AbstractStrategy.TryRollback: restore completed.");
}
}
catch (Exception ex)
{
GeneralTracer.Error("AbstractStrategy.TryRollback: rollback failed.", ex);
}
}

private static void Clear(string path)
{
if (Directory.Exists(path))
StorageManager.DeleteDirectory(path);
}
}
}
}
14 changes: 14 additions & 0 deletions src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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;
}

public async Task ExecuteAsync()
Expand Down Expand Up @@ -73,6 +75,18 @@ public void Execute()
ExecuteAsync().GetAwaiter().GetResult();
}

private Differential.IBinaryDiffer? _pendingDiffer;

/// <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)
{
if (_osStrategy is AbstractStrategy abs)
abs.Differ = differ;
else
_pendingDiffer = differ;
}

public void StartApp()
{
_osStrategy?.StartApp();
Expand Down
31 changes: 23 additions & 8 deletions src/c#/GeneralUpdate.Core/Strategy/MacStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,40 @@ public override async Task ExecuteAsync()

public override void StartApp()
{
var mainApp = Path.Combine(
_configinfo.InstallPath ?? string.Empty,
_configinfo.MainAppName ?? string.Empty);
try
{
var mainApp = Path.Combine(
_configinfo.InstallPath ?? string.Empty,
_configinfo.MainAppName ?? string.Empty);

if (!string.IsNullOrEmpty(_configinfo.MainAppName) && File.Exists(mainApp))
if (!string.IsNullOrEmpty(_configinfo.MainAppName) && File.Exists(mainApp))
{
GeneralTracer.Info($"MacStrategy: starting {mainApp}");
System.Diagnostics.Process.Start(mainApp);
}
}
catch (Exception e)
{
GeneralTracer.Error("The StartApp method in MacStrategy threw an exception.", e);
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(e, e.Message));
}
finally
{
GeneralTracer.Info($"MacStrategy: starting {mainApp}");
System.Diagnostics.Process.Start(mainApp);
GeneralTracer.Info("MacStrategy.StartApp: releasing tracer and terminating updater process.");
GeneralTracer.Dispose();
GracefulExit.CurrentProcessAsync().GetAwaiter().GetResult();
}
}

public override void Create(GlobalConfigInfo configInfo) => _configinfo = configInfo;

protected override PipelineBuilder BuildPipeline(PipelineContext context)
{
GeneralTracer.Info($"MacStrategy.BuildPipeline: assembling middleware pipeline. PatchEnabled={_configinfo.PatchEnabled}");
var builder = new PipelineBuilder(context)
.UseMiddleware<HashMiddleware>()
.UseMiddlewareIf<PatchMiddleware>(_configinfo.PatchEnabled)
.UseMiddleware<CompressMiddleware>()
.UseMiddleware<PatchMiddleware>();
.UseMiddleware<HashMiddleware>();
return builder;
}
}
6 changes: 5 additions & 1 deletion src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ private async Task ExecuteUpgradeAsync()
await SafeOnUpdateErrorAsync(ctx, ex).ConfigureAwait(false);
await SafeReportUpdateFailedAsync(ctx, ex).ConfigureAwait(false);
GeneralTracer.Error("OSSUpdateStrategy.ExecuteUpgradeAsync failed.", ex);
throw;
GeneralUpdate.Core.Event.EventManager.Instance.Dispatch(this, new GeneralUpdate.Core.Event.ExceptionEventArgs(ex, ex.Message));
}
finally
{
await GracefulExit.CurrentProcessAsync().ConfigureAwait(false);
}
}

Expand Down
14 changes: 14 additions & 0 deletions src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,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;
}

public async Task ExecuteAsync()
Expand Down Expand Up @@ -90,6 +92,18 @@ public void Execute()
ExecuteAsync().GetAwaiter().GetResult();
}

private Differential.IBinaryDiffer? _pendingDiffer;

/// <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)
{
if (_osStrategy is AbstractStrategy abs)
abs.Differ = differ;
else
_pendingDiffer = differ;
}

public void StartApp()
{
_osStrategy?.StartApp();
Expand Down
Loading
Loading