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
59 changes: 56 additions & 3 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using GeneralUpdate.Core.JsonContext;
using GeneralUpdate.Core.Strategy;
using GeneralUpdate.Core.Network;
using GeneralUpdate.Core.Security;
using GeneralUpdate.Core.Hooks;
using GeneralUpdate.Core.Ipc;
using GeneralUpdate.Core.Download.Reporting;
Expand Down Expand Up @@ -98,12 +99,37 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
var reporter = ResolveExtension<Download.Reporting.IUpdateReporter>() ??
new Download.Reporting.NoOpUpdateReporter();

// ── Network-level extensions (global, applied before any HTTP call) ──
var sslPolicy = ResolveExtension<Security.ISslValidationPolicy>();
if (sslPolicy != null) Network.VersionService.SetSslValidationPolicy(sslPolicy);
var authProvider = ResolveExtension<Security.IHttpAuthProvider>();
if (authProvider != null) Network.VersionService.SetDefaultAuthProvider(authProvider);

// ── Phase 1: inject all dependencies before Create ──
roleStrategy.Hooks = hooks;
roleStrategy.Reporter = reporter;

var binaryDiffer = ResolveExtension<IBinaryDiffer>();
var dirtyStrategy = ResolveExtension<IDirtyStrategy>();
var downloadOrchestrator = ResolveExtension<Download.Abstractions.IDownloadOrchestrator>();
var downloadPolicy = ResolveExtension<Download.Abstractions.IDownloadPolicy>();
var downloadExecutor = ResolveExtension<Download.Abstractions.IDownloadExecutor>();

// Build download pipeline factory from registered extension type.
// Tries a string constructor first (for passing the expected hash, à la DefaultDownloadPipeline),
// falls back to parameterless constructor otherwise.
Func<string?, Download.Abstractions.IDownloadPipeline>? downloadPipelineFactory = null;
var downloadPipeline = ResolveExtension<Download.Abstractions.IDownloadPipeline>();
if (downloadPipeline != null)
{
var pipelineType = downloadPipeline.GetType();
var stringCtor = pipelineType.GetConstructor([typeof(string)]);
Comment on lines +121 to +126
if (stringCtor != null)
downloadPipelineFactory = hash => (Download.Abstractions.IDownloadPipeline)stringCtor.Invoke([hash]);
else
downloadPipelineFactory = _ => (Download.Abstractions.IDownloadPipeline)Activator.CreateInstance(pipelineType);
}

var diffPipeline = BuildDiffPipeline();

switch (roleStrategy)
Expand All @@ -119,15 +145,36 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
if (binaryDiffer != null) cs.SetBinaryDiffer(binaryDiffer);
if (dirtyStrategy != null) cs.SetDirtyStrategy(dirtyStrategy);
cs.SetDiffPipeline(diffPipeline);
if (downloadOrchestrator != null) cs.SetOrchestrator(downloadOrchestrator);
if (downloadPolicy != null) cs.SetDownloadPolicy(downloadPolicy);
if (downloadExecutor != null) cs.SetDownloadExecutor(downloadExecutor);
if (downloadPipelineFactory != null) cs.SetDownloadPipelineFactory(downloadPipelineFactory);
break;

case UpgradeUpdateStrategy us:
if (binaryDiffer != null) us.SetBinaryDiffer(binaryDiffer);
if (dirtyStrategy != null) us.SetDirtyStrategy(dirtyStrategy);
if (binaryDiffer != null)
us.SetBinaryDiffer(binaryDiffer);
if (dirtyStrategy != null)
us.SetDirtyStrategy(dirtyStrategy);
us.SetDiffPipeline(diffPipeline);
break;
}

// Inject custom OS-level strategy if registered via Strategy<T>()
var customOsStrategy = ResolveExtension<IStrategy>();
if (customOsStrategy != null)
{
switch (roleStrategy)
{
case ClientUpdateStrategy cs:
cs.SetOsStrategy(customOsStrategy);
break;
case UpgradeUpdateStrategy us:
us.SetOsStrategy(customOsStrategy);
break;
}
}

roleStrategy.Create(_configInfo);

// Check custom skip condition before executing update
Expand Down Expand Up @@ -316,9 +363,15 @@ private async Task LaunchSilentAsync()
var reporter = ResolveExtension<Download.Reporting.IUpdateReporter>() ??
new Download.Reporting.NoOpUpdateReporter();

var sslPolicy = ResolveExtension<Security.ISslValidationPolicy>();
if (sslPolicy != null) Network.VersionService.SetSslValidationPolicy(sslPolicy);
var authProvider = ResolveExtension<Security.IHttpAuthProvider>();
if (authProvider != null) Network.VersionService.SetDefaultAuthProvider(authProvider);

var orchestrator = new Silent.SilentPollOrchestrator(_configInfo, silentOptions)
.WithHooks(hooks)
.WithReporter(reporter);
.WithReporter(reporter)
.WithOsStrategy(ResolveExtension<IStrategy>());

await orchestrator.StartAsync().ConfigureAwait(false);
GeneralTracer.Info("GeneralUpdateBootstrap: silent update mode started, returning to caller.");
Expand Down
33 changes: 21 additions & 12 deletions src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,22 @@ protected T GetOption<T>(UpdateOption<T>? option)
return (TBootstrap)this;
}

public TBootstrap PipelineFactory<T>() where T : Pipeline.IUpdatePipelineFactory, new()
{
_extensions[typeof(Pipeline.IUpdatePipelineFactory)] = typeof(T);
return (TBootstrap)this;
}

/// <summary>
/// Registers a custom retry/timeout policy for download operations.
/// Only effective when using the default download orchestrator.
/// If <see cref="DownloadOrchestrator{T}"/> is also set, this is ignored.
/// </summary>
public TBootstrap DownloadPolicy<T>() where T : Download.Abstractions.IDownloadPolicy, new()
{
_extensions[typeof(Download.Abstractions.IDownloadPolicy)] = typeof(T);
return (TBootstrap)this;
}

/// <summary>
/// Registers a custom single-file download executor (e.g. for non-HTTP protocols).
/// Only effective when using the default download orchestrator.
/// If <see cref="DownloadOrchestrator{T}"/> is also set, this is ignored.
/// </summary>
public TBootstrap DownloadExecutor<T>() where T : Download.Abstractions.IDownloadExecutor, new()
{
_extensions[typeof(Download.Abstractions.IDownloadExecutor)] = typeof(T);
Expand All @@ -90,6 +94,11 @@ protected T GetOption<T>(UpdateOption<T>? option)
return (TBootstrap)this;
}

/// <summary>
/// Registers a custom post-download processing pipeline (hash verification, decryption, virus scan).
/// Only effective when using the default download orchestrator.
/// If <see cref="DownloadOrchestrator{T}"/> is also set, this is ignored.
/// </summary>
public TBootstrap DownloadPipeline<T>() where T : Download.Abstractions.IDownloadPipeline, new()
{
_extensions[typeof(Download.Abstractions.IDownloadPipeline)] = typeof(T);
Expand All @@ -108,18 +117,18 @@ protected T GetOption<T>(UpdateOption<T>? option)
return (TBootstrap)this;
}

/// <summary>
/// Registers a custom download orchestrator that handles batch downloads end-to-end.
/// This is the top-level download abstraction. When set, <see cref="DownloadPolicy{T}"/>,
/// <see cref="DownloadExecutor{T}"/>, and <see cref="DownloadPipeline{T}"/> are ignored
/// — the custom orchestrator owns the entire download pipeline.
/// </summary>
public TBootstrap DownloadOrchestrator<T>() where T : Download.Abstractions.IDownloadOrchestrator, new()
{
_extensions[typeof(Download.Abstractions.IDownloadOrchestrator)] = typeof(T);
return (TBootstrap)this;
}

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

public TBootstrap DirtyStrategy<T>() where T : Differential.IDirtyStrategy, new()
{
_extensions[typeof(Differential.IDirtyStrategy)] = typeof(T);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,21 @@ public class DefaultDownloadOrchestrator : IDownloadOrchestrator
private readonly HttpClient _httpClient;
private readonly IDownloadPolicy _policy;
private readonly DownloadOrchestratorOptions _options;

public DefaultDownloadOrchestrator(HttpClient httpClient, DownloadOrchestratorOptions? options = null, IDownloadPolicy? policy = null)
private readonly IDownloadExecutor? _customExecutor;
private readonly Func<string?, IDownloadPipeline>? _pipelineFactory;

public DefaultDownloadOrchestrator(
HttpClient httpClient,
DownloadOrchestratorOptions? options = null,
IDownloadPolicy? policy = null,
IDownloadExecutor? executor = null,
Func<string?, IDownloadPipeline>? pipelineFactory = null)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_options = options ?? new DownloadOrchestratorOptions();
_policy = policy ?? new DefaultRetryPolicy(_options.RetryCount, _options.RetryInterval);
_customExecutor = executor;
_pipelineFactory = pipelineFactory;
}

/// <summary>Execute downloads for all assets in the plan.</summary>
Expand Down Expand Up @@ -82,8 +91,8 @@ public async Task<DownloadReport> ExecuteAsync(
var fileName = GetFileName(asset);
var destPath = Path.Combine(destDir, fileName);

var executor = new HttpDownloadExecutor(_httpClient, _options.DownloadTimeout, _options.EnableResume);
var pipeline = new DefaultDownloadPipeline(asset.SHA256);
var executor = _customExecutor ?? new HttpDownloadExecutor(_httpClient, _options.DownloadTimeout, _options.EnableResume);
var pipeline = _pipelineFactory?.Invoke(asset.SHA256) ?? new DefaultDownloadPipeline(asset.SHA256);
Comment on lines +94 to +95

var result = await _policy.ExecuteAsync(async ct =>
{
Expand Down
8 changes: 6 additions & 2 deletions src/c#/GeneralUpdate.Core/Network/VersionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class VersionService
{
private static readonly HttpClient _sharedClient;
private static ISslValidationPolicy _globalSslPolicy = new StrictSslValidationPolicy();
private static IHttpAuthProvider? _globalAuthProvider;

private readonly IHttpAuthProvider _auth;
private readonly TimeSpan _timeout;
Expand All @@ -34,6 +35,9 @@ static VersionService()
public static void SetSslValidationPolicy(ISslValidationPolicy policy)
=> _globalSslPolicy = policy ?? throw new ArgumentNullException(nameof(policy));

public static void SetDefaultAuthProvider(IHttpAuthProvider? provider)
=> _globalAuthProvider = provider;

private static bool SharedCertValidation(HttpRequestMessage m, X509Certificate2? c,
X509Chain? ch, SslPolicyErrors e)
=> _globalSslPolicy.ValidateCertificate(c, ch, e);
Expand All @@ -48,7 +52,7 @@ public VersionService(IHttpAuthProvider? auth = null, TimeSpan? timeout = null,
public static Task Report(string url, int recordId, int status, int? type,
string scheme = null, string token = null, CancellationToken ct = default)
{
var a = HttpAuthProviderFactory.Create(scheme, token, null);
var a = _globalAuthProvider ?? HttpAuthProviderFactory.Create(scheme, token, null);
return new VersionService(a).ReportAsync(url, recordId, status, type, ct);
}

Expand All @@ -57,7 +61,7 @@ public static Task<VersionRespDTO> Validate(string url, string version,
AppType appType, string appKey, PlatformType platform, string productId,
string scheme = null, string token = null, CancellationToken ct = default)
{
var a = HttpAuthProviderFactory.Create(scheme, token, appKey);
var a = _globalAuthProvider ?? HttpAuthProviderFactory.Create(scheme, token, appKey);
return new VersionService(a).ValidateAsync(url, version, (int)appType, appKey, (int)platform, productId, ct);
}

Expand Down
5 changes: 4 additions & 1 deletion src/c#/GeneralUpdate.Core/Silent/SilentPollOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class SilentPollOrchestrator : IDisposable
private int _updaterStarted;
private IUpdateHooks? _hooks;
private IUpdateReporter? _reporter;
private IStrategy? _customOsStrategy;
private Configuration.ProcessInfo? _preparedProcessInfo;
private List<VersionInfo> _clientVersions = new();

Expand All @@ -50,6 +51,7 @@ public SilentPollOrchestrator(GlobalConfigInfo configInfo, SilentOptions options

public SilentPollOrchestrator WithHooks(IUpdateHooks? hooks) { _hooks = hooks; return this; }
public SilentPollOrchestrator WithReporter(IUpdateReporter? reporter) { _reporter = reporter; return this; }
public SilentPollOrchestrator WithOsStrategy(IStrategy? strategy) { _customOsStrategy = strategy; return this; }

public Task StartAsync()
{
Expand Down Expand Up @@ -335,8 +337,9 @@ private void InitBlackList()
StorageManager.BlackListMatcher = new DefaultBlackListMatcher(effectiveConfig);
}

private static IStrategy CreateStrategy()
private IStrategy CreateStrategy()
{
if (_customOsStrategy != null) return _customOsStrategy;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return new WindowsStrategy();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return new LinuxStrategy();
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return new MacStrategy();
Expand Down
Loading
Loading