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
32 changes: 23 additions & 9 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,22 +280,36 @@ private void InitializeFromEnvironment()
};
}

/// <summary>
/// Applies UpdateOptions to _configInfo.
/// Uses ??= only for values that InitializeFromEnvironment() may have already
/// populated on the Upgrade path (Encoding, Format, DownloadTimeOut).
/// All other options are always applied from UpdateOptions — their defaults
/// are already functionally reasonable (e.g. MaxConcurrency=3, RetryCount=3).
/// </summary>
private void ApplyRuntimeOptions()
{
_configInfo.Encoding = GetOption(UpdateOptions.Encoding);
_configInfo.Format = GetOption(UpdateOptions.Format);
_configInfo.DownloadTimeOut = GetOption(UpdateOptions.DownloadTimeout) ?? 60;

// Download behaviour
// Preserve Upgrade path values set by InitializeFromEnvironment()
_configInfo.Encoding ??= GetOption(UpdateOptions.Encoding);
_configInfo.Format ??= GetOption(UpdateOptions.Format);
// Normalize legacy "ZIP" default (UpdateOptions) to Format.ZIP (".zip")
// so the pipeline constructs correct paths and CompressProvider matches its switch.
if (_configInfo.Format == "ZIP")
_configInfo.Format = Format.ZIP;
if (_configInfo.DownloadTimeOut <= 0)
_configInfo.DownloadTimeOut = GetOption(UpdateOptions.DownloadTimeout) ?? 60;

// bool? options: use ??= so user-configured false is preserved
_configInfo.PatchEnabled ??= GetOption(UpdateOptions.PatchEnabled);
_configInfo.BackupEnabled ??= GetOption(UpdateOptions.BackupEnabled);

// Always apply from UpdateOptions — no other code sets these before
// ApplyRuntimeOptions() runs. Defaults are functionally reasonable.
_configInfo.MaxConcurrency = GetOption(UpdateOptions.MaxConcurrency);
_configInfo.EnableResume = GetOption(UpdateOptions.EnableResume);
_configInfo.RetryCount = GetOption(UpdateOptions.RetryCount);
_configInfo.RetryInterval = GetOption(UpdateOptions.RetryInterval);
_configInfo.VerifyChecksum = GetOption(UpdateOptions.VerifyChecksum);

// Update behaviour
_configInfo.BackupEnabled = GetOption(UpdateOptions.BackupEnabled);
_configInfo.PatchEnabled = GetOption(UpdateOptions.PatchEnabled);
_configInfo.DiffMode = GetOption(UpdateOptions.DiffMode);
}

Expand Down
23 changes: 6 additions & 17 deletions src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,13 @@ public ClientUpdateStrategy UseUpdatePrecheck(Func<UpdateInfoEventArgs, bool> fu

private async Task ExecuteWorkflowAsync()
{
var defaultEncoding = Encoding.UTF8;
var defaultTimeout = 60;
if (true /* silent check would read from options */)
{
// Standard mode
await ExecuteStandardWorkflowAsync(defaultEncoding, defaultTimeout);
}
// Standard mode — silent mode is handled by GeneralUpdateBootstrap.LaunchSilentAsync().
// Runtime options (Encoding, Format, DownloadTimeOut, etc.) are already
// populated on _configInfo by Bootstrap.ApplyRuntimeOptions().
await ExecuteStandardWorkflowAsync();
}

private async Task ExecuteStandardWorkflowAsync(Encoding encoding, int timeout)
private async Task ExecuteStandardWorkflowAsync()
{
GeneralTracer.Info($"ClientUpdateStrategy: validating client={_configInfo!.ClientVersion}, upgrade={_configInfo.UpgradeClientVersion}");

Expand Down Expand Up @@ -145,7 +142,6 @@ private async Task ExecuteStandardWorkflowAsync(Encoding encoding, int timeout)
await SafeReportUpdateStartedAsync(hooksCtx).ConfigureAwait(false);

InitBlackList();
ApplyRuntimeOptions(encoding, timeout);

_configInfo.TempPath = StorageManager.GetTempDirectory("main_temp");
_configInfo.BackupDirectory = Path.Combine(_configInfo.InstallPath,
Expand All @@ -172,7 +168,7 @@ private async Task ExecuteStandardWorkflowAsync(Encoding encoding, int timeout)
Hash = a.SHA256,
Url = a.Url,
Version = a.Version,
Format = "ZIP"
Format = _configInfo.Format ?? "ZIP"
}).ToList();

_configInfo.ProcessInfo = JsonSerializer.Serialize(
Expand Down Expand Up @@ -236,13 +232,6 @@ private static IStrategy ResolveOsStrategy()
throw new PlatformNotSupportedException("The current operating system is not supported!");
}

private void ApplyRuntimeOptions(Encoding encoding, int timeout)
{
_configInfo!.Encoding = encoding;
_configInfo.Format = Format.ZIP;
_configInfo.DownloadTimeOut = timeout;
}

private void InitBlackList()
{
BlackListManager.Instance.AddBlackFiles(_configInfo!.BlackFiles);
Expand Down
4 changes: 2 additions & 2 deletions src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class OSSUpdateStrategy : IStrategy
{
private GlobalConfigInfo? _configInfo;
private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
private const int TimeOut = 60;
private const int DefaultTimeOut = 60;

/// <summary>Lifecycle hooks injected by the bootstrap.</summary>
public Hooks.IUpdateHooks Hooks { get; set; } = new Hooks.NoOpUpdateHooks();
Expand Down Expand Up @@ -157,7 +157,7 @@ private async Task DownloadAssetsAsync(List<DownloadAsset> assets)
}
else
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(TimeOut) };
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(_configInfo.DownloadTimeOut > 0 ? _configInfo.DownloadTimeOut : DefaultTimeOut) };
var orchestrator = new DefaultDownloadOrchestrator(httpClient);
await orchestrator.ExecuteAsync(plan, _appPath).ConfigureAwait(false);
}
Expand Down
7 changes: 0 additions & 7 deletions src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public async Task ExecuteAsync()
return;
}

ApplyRuntimeOptions();
_osStrategy!.Create(_configInfo);

// Apply updates via OS-specific pipeline (Hash -> Compress -> Patch)
Expand Down Expand Up @@ -109,12 +108,6 @@ private static IStrategy ResolveOsStrategy()
throw new PlatformNotSupportedException("The current operating system is not supported!");
}

private void ApplyRuntimeOptions()
{
_configInfo!.Encoding = Encoding.UTF8;
_configInfo.Format = Format.ZIP;
}

// ════════════════════════════════════════════════════════════════
// Hooks & Reporter safe wrappers
// ════════════════════════════════════════════════════════════════
Expand Down
131 changes: 131 additions & 0 deletions tests/CoreTest/Bootstrap/BootstrapFullParameterMatrixTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.FileSystem;
Expand Down Expand Up @@ -78,6 +82,133 @@ [Fact] public void Hub_Configured() => Assert.NotNull(B().Option(UpdateOptions.H
new HubConfig { Url = "https://signalr.example.com/hub" }));
#endregion

#region Extension Injection — Hooks / Strategy / Policy / Differ / Pipeline / etc.

private sealed class StubHooks : GeneralUpdate.Core.Hooks.IUpdateHooks
{
public Task<bool> OnBeforeUpdateAsync(GeneralUpdate.Core.Hooks.UpdateContext ctx) => Task.FromResult(true);
public Task OnDownloadCompletedAsync(GeneralUpdate.Core.Hooks.DownloadContext ctx) => Task.CompletedTask;
public Task OnAfterUpdateAsync(GeneralUpdate.Core.Hooks.UpdateContext ctx) => Task.CompletedTask;
public Task OnUpdateErrorAsync(GeneralUpdate.Core.Hooks.UpdateContext ctx, Exception ex) => Task.CompletedTask;
public Task OnBeforeStartAppAsync(GeneralUpdate.Core.Hooks.UpdateContext ctx) => Task.CompletedTask;
}

private sealed class StubStrategy : GeneralUpdate.Core.Strategy.IStrategy
{
public void Create(GlobalConfigInfo parameter) { }
public void Execute() { }
public Task ExecuteAsync() => Task.CompletedTask;
public void StartApp() { }
}

private sealed class StubSslPolicy : GeneralUpdate.Core.Security.ISslValidationPolicy
{
public bool ValidateCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2? certificate,
System.Security.Cryptography.X509Certificates.X509Chain? chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors) => true;
}

private sealed class StubBinaryDiffer : GeneralUpdate.Core.Differential.IBinaryDiffer
{
public Task CleanAsync(string oldFilePath, string newFilePath, string patchFilePath,
CancellationToken cancellationToken = default) => Task.CompletedTask;
public Task DirtyAsync(string oldFilePath, string newFilePath, string patchFilePath,
CancellationToken cancellationToken = default) => Task.CompletedTask;
}

private sealed class StubPipelineFactory : GeneralUpdate.Core.Pipeline.IUpdatePipelineFactory
{
public Task ExecutePipelineAsync(GeneralUpdate.Core.Pipeline.PipelineContext context, CancellationToken token = default) => Task.CompletedTask;
}

private sealed class StubDownloadPolicy : GeneralUpdate.Core.Download.Abstractions.IDownloadPolicy
{
public Task<T> ExecuteAsync<T>(Func<CancellationToken, Task<T>> action, CancellationToken token = default) => action(token);
}

private sealed class StubDownloadExecutor : GeneralUpdate.Core.Download.Abstractions.IDownloadExecutor
{
public Task<GeneralUpdate.Core.Download.Models.DownloadResult> ExecuteAsync(string url, string destPath,
IProgress<GeneralUpdate.Core.Download.Models.DownloadProgress>? progress = null, CancellationToken token = default)
=> Task.FromResult(new GeneralUpdate.Core.Download.Models.DownloadResult(url, destPath, 0, TimeSpan.Zero, 0, true, null));
}

private sealed class StubDownloadSource : GeneralUpdate.Core.Download.Abstractions.IDownloadSource
{
public Task<IReadOnlyList<GeneralUpdate.Core.Download.Models.DownloadAsset>> ListAsync(CancellationToken token = default)
=> Task.FromResult<IReadOnlyList<GeneralUpdate.Core.Download.Models.DownloadAsset>>(Array.Empty<GeneralUpdate.Core.Download.Models.DownloadAsset>());
}

private sealed class StubDownloadPipeline : GeneralUpdate.Core.Download.Abstractions.IDownloadPipeline
{
public Task<string> ProcessAsync(string downloadedPath, CancellationToken token = default) => Task.FromResult("");
}

private sealed class StubUpdateReporter : GeneralUpdate.Core.Download.Reporting.IUpdateReporter
{
public Task ReportAsync(GeneralUpdate.Core.Download.Reporting.UpdateReport report, CancellationToken token = default) => Task.CompletedTask;
}

private sealed class StubUpdateAuth : GeneralUpdate.Core.Security.IHttpAuthProvider
{
public Task ApplyAuthAsync(HttpRequestMessage request, CancellationToken token = default) => Task.CompletedTask;
}

private sealed class StubDownloadOrchestrator : GeneralUpdate.Core.Download.Abstractions.IDownloadOrchestrator
{
public Task<GeneralUpdate.Core.Download.Abstractions.DownloadReport> ExecuteAsync(
GeneralUpdate.Core.Download.Models.DownloadPlan plan, string destDir, int maxConcurrency = 3,
IProgress<GeneralUpdate.Core.Download.Models.DownloadProgress>? progress = null, CancellationToken token = default)
=> Task.FromResult(new GeneralUpdate.Core.Download.Abstractions.DownloadReport(Array.Empty<GeneralUpdate.Core.Download.Models.DownloadResult>(), 0, TimeSpan.Zero, 0, 0));
}

private sealed class StubCleanStrategy : GeneralUpdate.Core.Differential.ICleanStrategy
{
public Task ExecuteAsync(string sourcePath, string targetPath, string patchPath) => Task.CompletedTask;
}

private sealed class StubDirtyStrategy : GeneralUpdate.Core.Differential.IDirtyStrategy
{
public Task ExecuteAsync(string appPath, string patchPath) => Task.CompletedTask;
}

[Fact] public void Inject_Hooks() => Assert.NotNull(B().Hooks<StubHooks>());
[Fact] public void Inject_Strategy() => Assert.NotNull(B().Strategy<StubStrategy>());
[Fact] public void Inject_SslPolicy() => Assert.NotNull(B().SslPolicy<StubSslPolicy>());
[Fact] public void Inject_BinaryDiffer() => Assert.NotNull(B().BinaryDiffer<StubBinaryDiffer>());
[Fact] public void Inject_PipelineFactory() => Assert.NotNull(B().PipelineFactory<StubPipelineFactory>());
[Fact] public void Inject_DownloadPolicy() => Assert.NotNull(B().DownloadPolicy<StubDownloadPolicy>());
[Fact] public void Inject_DownloadExecutor() => Assert.NotNull(B().DownloadExecutor<StubDownloadExecutor>());
[Fact] public void Inject_DownloadSource() => Assert.NotNull(B().DownloadSource<StubDownloadSource>());
[Fact] public void Inject_DownloadPipeline() => Assert.NotNull(B().DownloadPipeline<StubDownloadPipeline>());
[Fact] public void Inject_UpdateReporter() => Assert.NotNull(B().UpdateReporter<StubUpdateReporter>());
[Fact] public void Inject_UpdateAuth() => Assert.NotNull(B().UpdateAuth<StubUpdateAuth>());
[Fact] public void Inject_DownloadOrchestrator() => Assert.NotNull(B().DownloadOrchestrator<StubDownloadOrchestrator>());
[Fact] public void Inject_CleanStrategy() => Assert.NotNull(B().CleanStrategy<StubCleanStrategy>());
[Fact] public void Inject_DirtyStrategy() => Assert.NotNull(B().DirtyStrategy<StubDirtyStrategy>());

[Fact]
public void Chain_AllExtensionsInjected()
{
var b = B()
.Hooks<StubHooks>()
.UpdateReporter<StubUpdateReporter>()
.DownloadPolicy<StubDownloadPolicy>()
.DownloadExecutor<StubDownloadExecutor>()
.DownloadSource<StubDownloadSource>()
.DownloadPipeline<StubDownloadPipeline>()
.DownloadOrchestrator<StubDownloadOrchestrator>()
.BinaryDiffer<StubBinaryDiffer>()
.CleanStrategy<StubCleanStrategy>()
.DirtyStrategy<StubDirtyStrategy>()
.SslPolicy<StubSslPolicy>()
.UpdateAuth<StubUpdateAuth>()
.PipelineFactory<StubPipelineFactory>()
.Strategy<StubStrategy>();
Assert.NotNull(b);
}
#endregion

#region Full Combination Chains
[Fact] public void Chain_AllFrameworkOptions()
{
Expand Down
Loading