diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
index a071a6db..0c486a6f 100644
--- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
+++ b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
@@ -53,18 +53,33 @@ namespace GeneralUpdate.Core;
/// components, network policies, and OS strategy.
///
///
+/// Standard flow (Client):
///
/// var result = await new GeneralUpdateBootstrap()
-/// .SetConfig(new UpdateRequest {
-/// UpdateUrl = "https://api.example.com",
-/// ClientVersion = "1.0.0",
-/// InstallPath = @"C:\MyApp",
-/// AppSecretKey = "my-key"
-/// })
+/// .SetSource("https://api.example.com", "my-key")
/// .SetOption(Option.AppType, AppType.Client)
/// .Hooks<MyCustomHooks>()
/// .LaunchAsync();
///
+/// OSS flow (Object Storage Service):
+///
+/// // OssClient — only secrets in code; identity from generalupdate.manifest.json
+/// var result = await new GeneralUpdateBootstrap()
+/// .SetSource("https://oss.example.com/versions.json", "my-key")
+/// .SetOption(Option.AppType, AppType.OssClient)
+/// .Hooks<MyCustomHooks>()
+/// .LaunchAsync();
+///
+/// // OssUpgrade — running from a subdirectory; pass installPath to locate
+/// // generalupdate.manifest.json in the parent directory
+/// var installPath = Path.GetFullPath(Path.Combine(baseDir, ".."));
+/// var result = await new GeneralUpdateBootstrap()
+/// .SetSource("https://oss.example.com/versions.json", "my-key",
+/// installPath: installPath)
+/// .SetOption(Option.AppType, AppType.OssUpgrade)
+/// .Hooks<MyCustomHooks>()
+/// .LaunchAsync();
+///
///
public class GeneralUpdateBootstrap : AbstractBootstrap
{
@@ -232,15 +247,29 @@ public GeneralUpdateBootstrap SetConfig(string filePath)
///
/// Zero-config entry-point: supply only secrets. Provides sensible identity defaults
/// that pass ; the real identity metadata is
- /// discovered later by from generalupdate.manifest.json.
+ /// discovered later by or
+ /// from generalupdate.manifest.json. Works with all
+ /// modes (Client, Upgrade, OssClient, OssUpgrade).
///
+ /// The remote endpoint for update validation or version config retrieval.
+ /// The application secret key used for authentication.
+ /// Optional URL for reporting update status back to the server.
+ /// Optional URL scheme override (e.g. "https").
+ /// Optional authentication token for API requests.
+ ///
+ /// Optional override for the installation root directory. When null,
+ /// defaults to . Set this when
+ /// the current process runs from a subdirectory (e.g. the OSS upgrade process in
+ /// update/) and generalupdate.manifest.json lives in the parent.
+ ///
/// This bootstrap instance for chaining.
public GeneralUpdateBootstrap SetSource(
string updateUrl,
string appSecretKey,
string? reportUrl = null,
string? scheme = null,
- string? token = null)
+ string? token = null,
+ string? installPath = null)
{
var config = new UpdateRequest
{
@@ -250,6 +279,8 @@ public GeneralUpdateBootstrap SetSource(
Scheme = scheme,
ReportUrl = reportUrl
};
+ if (installPath != null)
+ config.InstallPath = installPath;
return SetConfig(config);
}
diff --git a/src/c#/GeneralUpdate.Core/Configuration/UpdateRequest.cs b/src/c#/GeneralUpdate.Core/Configuration/UpdateRequest.cs
index d3a675ac..b3f9ea30 100644
--- a/src/c#/GeneralUpdate.Core/Configuration/UpdateRequest.cs
+++ b/src/c#/GeneralUpdate.Core/Configuration/UpdateRequest.cs
@@ -62,17 +62,8 @@ public void Validate()
if (!string.IsNullOrWhiteSpace(UpdateLogUrl) && !Uri.IsWellFormedUriString(UpdateLogUrl, UriKind.Absolute))
throw new ArgumentException("Invalid UpdateLogUrl");
- if (string.IsNullOrWhiteSpace(UpdateAppName))
- throw new ArgumentException("UpdateAppName cannot be empty");
-
- if (string.IsNullOrWhiteSpace(MainAppName))
- throw new ArgumentException("MainAppName cannot be empty");
-
if (string.IsNullOrWhiteSpace(AppSecretKey))
throw new ArgumentException("AppSecretKey cannot be empty");
-
- if (string.IsNullOrWhiteSpace(ClientVersion))
- throw new ArgumentException("ClientVersion cannot be empty");
}
}
}
diff --git a/src/c#/GeneralUpdate.Core/Strategy/OssStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OssStrategy.cs
index 0d6d31db..65270a2b 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/OssStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/OssStrategy.cs
@@ -150,7 +150,12 @@ public async Task ExecuteAsync()
if (_configInfo == null)
throw new InvalidOperationException("OssStrategy not configured. Call Create() first.");
- // Dispatch by role �?no env-var detection needed.
+ // Fill missing identity fields from generalupdate.manifest.json,
+ // making the manifest the single source of configuration across
+ // all update flows (standard ClientStrategy and OssStrategy).
+ Configuration.AppMetadataDiscoverer.Discover(_configInfo);
+
+ // Dispatch by role — no env-var detection needed.
if (_role == AppType.OssUpgrade)
{
await ExecuteUpgradeAsync();
@@ -189,35 +194,63 @@ private async Task ExecuteClientAsync()
var versionFileName = $"{_configInfo.MainAppName ?? _configInfo.UpdateAppName}_versions.json";
var versionsFilePath = Path.Combine(installPath, versionFileName);
+ GeneralTracer.Info($"[OssClient] InstallPath={installPath}");
+ GeneralTracer.Info($"[OssClient] VersionFileName={versionFileName}");
+ GeneralTracer.Info($"[OssClient] ClientVersion={_configInfo.ClientVersion}");
+ GeneralTracer.Info($"[OssClient] MainAppName={_configInfo.MainAppName}");
+ GeneralTracer.Info($"[OssClient] UpdateAppName={_configInfo.UpdateAppName}");
+
if (!string.IsNullOrEmpty(_configInfo.UpdateUrl))
{
+ GeneralTracer.Info($"[OssClient] Downloading from {_configInfo.UpdateUrl} ...");
await DownloadVersionConfig(_configInfo.UpdateUrl, versionsFilePath).ConfigureAwait(false);
+ GeneralTracer.Info($"[OssClient] Downloaded -> {versionsFilePath}");
}
if (!File.Exists(versionsFilePath))
{
- GeneralTracer.Info("OssStrategy: version config download failed, aborting.");
+ GeneralTracer.Info("[OssClient] FAIL: version config file not found after download!");
return;
}
- var versions = JsonSerializer.Deserialize(
- File.ReadAllText(versionsFilePath),
- JsonContext.OssVersionRecordJsonContext.Default.ListOssVersionRecord);
+ var jsonText = File.ReadAllText(versionsFilePath);
+ GeneralTracer.Info($"[OssClient] JSON downloaded ({jsonText.Length} chars)");
+
+ List versions;
+ try
+ {
+ versions = JsonSerializer.Deserialize(
+ jsonText,
+ JsonContext.OssVersionRecordJsonContext.Default.ListOssVersionRecord);
+ }
+ catch (Exception ex)
+ {
+ GeneralTracer.Error($"[OssClient] JSON deserialize error: {ex.GetType().Name}: {ex.Message}", ex);
+ throw;
+ }
+
if (versions == null || versions.Count == 0)
{
- GeneralTracer.Info("OssStrategy: no versions found, aborting.");
+ GeneralTracer.Info($"[OssClient] FAIL: no versions found (count={versions?.Count.ToString() ?? "null"})");
return;
}
+ GeneralTracer.Info($"[OssClient] Deserialized {versions.Count} version(s)");
+ foreach (var v in versions)
+ GeneralTracer.Info($" - {v.PacketName} v{v.Version} PubTime={v.PubTime:O}");
+
versions = versions.OrderByDescending(x => x.PubTime).ToList();
var latest = versions.First();
+ GeneralTracer.Info($"[OssClient] Latest: {latest.Version} (PubTime={latest.PubTime:O})");
if (!IsOssUpgrade(_configInfo.ClientVersion, latest.Version))
{
- GeneralTracer.Info("OssStrategy: no upgrade needed.");
+ GeneralTracer.Info($"[OssClient] No upgrade needed: {_configInfo.ClientVersion} >= {latest.Version}");
return;
}
+ GeneralTracer.Info($"[OssClient] Upgrade needed: {_configInfo.ClientVersion} -> {latest.Version}");
+
// Resolve upgrade exe: prefer UpdatePath, fall back to InstallPath
var upgradeDir = !string.IsNullOrWhiteSpace(_configInfo.UpdatePath)
? (Path.IsPathRooted(_configInfo.UpdatePath)
@@ -228,10 +261,28 @@ private async Task ExecuteClientAsync()
? _configInfo.UpdateAppName
: "GeneralUpdate.Upgrade.exe";
var appPath = Path.Combine(upgradeDir, upgradeAppName);
+ GeneralTracer.Info($"[OssClient] Resolved upgrade path: {appPath}");
+
+ // List exe files in the directory to help diagnose missing file issues
+ try
+ {
+ var dirFiles = Directory.GetFiles(upgradeDir, "*.exe").Select(f => Path.GetFileName(f));
+ GeneralTracer.Info($"[OssClient] *.exe files in {upgradeDir}: [{string.Join(", ", dirFiles)}]");
+ }
+ catch (Exception ex)
+ {
+ GeneralTracer.Warn($"[OssClient] Could not list directory {upgradeDir}: {ex.Message}");
+ }
+
if (!File.Exists(appPath))
+ {
+ GeneralTracer.Error($"[OssClient] FAIL: Upgrade app NOT FOUND at {appPath}");
throw new FileNotFoundException($"Upgrade application not found: {appPath}");
+ }
+ GeneralTracer.Info($"[OssClient] Launching upgrade: {appPath}");
Process.Start(appPath);
+ GeneralTracer.Info("[OssClient] Upgrade launched, exiting.");
await GracefulExit.CurrentProcessAsync().ConfigureAwait(false);
}
@@ -273,15 +324,6 @@ private async Task ExecuteUpgradeAsync()
if (!File.Exists(jsonPath) && DownloadSource == null)
throw new FileNotFoundException($"Version config not found: {jsonPath}");
- // Hooks: allow cancellation before download
- if (!await SafeOnBeforeUpdateAsync(ctx).ConfigureAwait(false))
- {
- GeneralTracer.Info("OssStrategy (upgrade): cancelled by hook.");
- return;
- }
-
- await SafeReportUpdateStartedAsync(ctx).ConfigureAwait(false);
-
// Build download assets from version config or injected source
List assets;
if (DownloadSource != null)
@@ -314,6 +356,25 @@ private async Task ExecuteUpgradeAsync()
if (assets.Count == 0)
throw new InvalidOperationException("No assets to download.");
+ // Compute LastVersion deterministically via Version comparison
+ // so hooks see the correct TargetVersion regardless of source ordering.
+ _configInfo.LastVersion = assets
+ .Select(a => new Version(a.Version))
+ .Max()!
+ .ToString();
+ ctx = BuildUpdateContext();
+
+ // Hooks: allow cancellation before download. Called after assets are
+ // built and LastVersion is set so OnBeforeUpdateAsync sees the real
+ // TargetVersion for decision-making.
+ if (!await SafeOnBeforeUpdateAsync(ctx).ConfigureAwait(false))
+ {
+ GeneralTracer.Info("OssStrategy (upgrade): cancelled by hook.");
+ return;
+ }
+
+ await SafeReportUpdateStartedAsync(ctx).ConfigureAwait(false);
+
GeneralTracer.Debug($"OssStrategy (upgrade): downloading {assets.Count} asset(s).");
await DownloadAssetsAsync(assets, installPath).ConfigureAwait(false);
@@ -321,6 +382,12 @@ private async Task ExecuteUpgradeAsync()
var encoding = Encoding.GetEncoding(_configInfo?.Encoding?.CodePage ?? Encoding.UTF8.CodePage);
DecompressAssets(assets, installPath, encoding);
+ // Update generalupdate.manifest.json ClientVersion so the client
+ // reads the correct version on next startup, preventing infinite loops.
+ Configuration.ManifestInfo.TryUpdateVersion(
+ installPath,
+ clientVersion: _configInfo.LastVersion);
+
await SafeOnDownloadCompletedAsync(ctx).ConfigureAwait(false);
await SafeOnAfterUpdateAsync(ctx).ConfigureAwait(false);
await SafeReportUpdateAppliedAsync(ctx).ConfigureAwait(false);
diff --git a/tests/ClientTest/Program.cs b/tests/ClientTest/Program.cs
index a68ec96f..e0e9668c 100644
--- a/tests/ClientTest/Program.cs
+++ b/tests/ClientTest/Program.cs
@@ -6,6 +6,68 @@
using GeneralUpdate.Core.Hooks;
try
+{
+ await RunOssClientAsync();
+ /*var isOssMode = args.Length > 0 && args[0] == "--oss";
+
+ if (isOssMode)
+ {
+ await RunOssClientAsync();
+ }
+ else
+ {
+ await RunStandardClientAsync();
+ }*/
+}
+catch (Exception ex)
+{
+ Console.WriteLine($"FATAL: {ex}");
+ Console.WriteLine("Press Enter to exit...");
+ Console.ReadLine();
+ Environment.Exit(1);
+}
+
+Console.WriteLine("Press Enter to exit...");
+Console.ReadLine();
+
+// ═══════════════════════════════════════════════════════════════════
+// OSS Client mode — version JSON download → version compare → launch upgrade
+// ═══════════════════════════════════════════════════════════════════
+static async Task RunOssClientAsync()
+{
+ Console.WriteLine("=== GeneralUpdate OSS Client Test ===");
+ Console.WriteLine($"Started at {DateTime.Now}");
+ Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}");
+
+ // Only secrets are supplied in code. Identity fields (MainAppName,
+ // ClientVersion, UpdateAppName, UpdatePath) are read from
+ // generalupdate.manifest.json by OssStrategy via
+ // AppMetadataDiscoverer.Discover() — same as the standard flow.
+ var updateUrl = "http://localhost:5000/packages/versions.json";
+ var appSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6";
+
+ Console.WriteLine($"UpdateUrl: {updateUrl}");
+ Console.WriteLine();
+
+ await new GeneralUpdateBootstrap()
+ .SetSource(updateUrl, appSecretKey)
+ .SetOption(Option.AppType, AppType.OssClient)
+ .Hooks()
+ .AddListenerMultiDownloadStatistics(OnDownloadStatistics)
+ .AddListenerMultiDownloadCompleted(OnDownloadCompleted)
+ .AddListenerMultiAllDownloadCompleted(OnAllDownloadCompleted)
+ .AddListenerMultiDownloadError(OnDownloadError)
+ .AddListenerException(OnException)
+ .AddListenerUpdateInfo(OnUpdateInfo)
+ .LaunchAsync();
+
+ Console.WriteLine("OSS Client test completed.");
+}
+
+// ═══════════════════════════════════════════════════════════════════
+// Standard Client mode — silent poll with IPC handoff to Upgrade
+// ═══════════════════════════════════════════════════════════════════
+static async Task RunStandardClientAsync()
{
Console.WriteLine("=== GeneralUpdate Client Test (Silent Mode) ===");
Console.WriteLine($"Started at {DateTime.Now}");
@@ -47,14 +109,12 @@
Console.WriteLine();
// Keep the process alive so the background poll loop can work.
- // When the user presses Ctrl+C or Enter, the process exits and
- // ProcessExit fires, which triggers the upgrade launch.
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
Console.WriteLine();
Console.WriteLine("[Shutdown] Ctrl+C pressed. Exiting...");
- e.Cancel = true; // Prevent immediate kill — let ProcessExit fire
+ e.Cancel = true;
cts.Cancel();
};
@@ -67,10 +127,6 @@
// Expected on Ctrl+C — graceful shutdown
}
- // Explicitly launch the upgrade process before exiting.
- // ProcessExit may not fire reliably in all scenarios (e.g. console Ctrl+C),
- // so we call TryLaunchUpgrade() directly as the primary launch path.
- // If ProcessExit also fires later, the _updaterStarted guard prevents a double-launch.
Console.WriteLine("[Shutdown] Launching upgrade process...");
if (orchestrator != null && orchestrator.HasPreparedUpdate)
{
@@ -86,11 +142,10 @@
Console.WriteLine("[Shutdown] Client test exiting gracefully.");
}
-catch (Exception ex)
-{
- Console.WriteLine($"FATAL: {ex}");
- Environment.Exit(1);
-}
+
+// ═══════════════════════════════════════════════════════════════════
+// Event handlers (shared across both modes)
+// ═══════════════════════════════════════════════════════════════════
static void OnDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e)
{
@@ -136,6 +191,10 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e)
}
}
+// ═══════════════════════════════════════════════════════════════════
+// Hooks (shared across both modes)
+// ═══════════════════════════════════════════════════════════════════
+
sealed class ClientTestHooks : IUpdateHooks
{
public async Task OnBeforeUpdateAsync(HookContext ctx)
diff --git a/tests/CoreTest/Bootstrap/ParameterMatrixAndEventTests.cs b/tests/CoreTest/Bootstrap/ParameterMatrixAndEventTests.cs
index cad3684a..2626e4c5 100644
--- a/tests/CoreTest/Bootstrap/ParameterMatrixAndEventTests.cs
+++ b/tests/CoreTest/Bootstrap/ParameterMatrixAndEventTests.cs
@@ -190,8 +190,9 @@ public void Configinfo_Validate_MissingUpdateUrl_Throws()
}
[Fact]
- public void Configinfo_Validate_MissingMainAppName_Throws()
+ public void Configinfo_Validate_MissingMainAppName_DoesNotThrow()
{
+ // MainAppName is optional — filled from manifest by strategies.
var config = new UpdateRequest
{
UpdateUrl = "https://api.example.com",
@@ -200,12 +201,14 @@ public void Configinfo_Validate_MissingMainAppName_Throws()
MainAppName = null!
};
- Assert.Throws(() => config.Validate());
+ var ex = Record.Exception(() => config.Validate());
+ Assert.Null(ex);
}
[Fact]
- public void Configinfo_Validate_MissingClientVersion_Throws()
+ public void Configinfo_Validate_MissingClientVersion_DoesNotThrow()
{
+ // ClientVersion is optional — filled from manifest by strategies.
var config = new UpdateRequest
{
UpdateUrl = "https://api.example.com",
@@ -214,7 +217,8 @@ public void Configinfo_Validate_MissingClientVersion_Throws()
ClientVersion = null!
};
- Assert.Throws(() => config.Validate());
+ var ex = Record.Exception(() => config.Validate());
+ Assert.Null(ex);
}
[Theory]
diff --git a/tests/CoreTest/Configuration/UpdateRequestTests.cs b/tests/CoreTest/Configuration/UpdateRequestTests.cs
index bc0c14c2..c11a784c 100644
--- a/tests/CoreTest/Configuration/UpdateRequestTests.cs
+++ b/tests/CoreTest/Configuration/UpdateRequestTests.cs
@@ -95,8 +95,9 @@ public void Validate_UpdateLogUrlInvalid_ThrowsArgumentException()
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
- public void Validate_UpgradeAppNameNullOrWhitespace_ThrowsArgumentException(string appName)
+ public void Validate_UpgradeAppNameNullOrWhitespace_NoException(string appName)
{
+ // UpdateAppName is optional — filled from manifest by strategies.
var config = new UpdateRequest
{
UpdateUrl = "https://api.example.com",
@@ -106,15 +107,17 @@ public void Validate_UpgradeAppNameNullOrWhitespace_ThrowsArgumentException(stri
ClientVersion = "1.0.0",
InstallPath = "C:\\app"
};
- Assert.Throws(() => config.Validate());
+ var ex = Record.Exception(() => config.Validate());
+ Assert.Null(ex);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
- public void Validate_MainAppNameNullOrWhitespace_ThrowsArgumentException(string mainUpgradeAppName)
+ public void Validate_MainAppNameNullOrWhitespace_NoException(string mainUpgradeAppName)
{
+ // MainAppName is optional — filled from manifest by strategies.
var config = new UpdateRequest
{
UpdateUrl = "https://api.example.com",
@@ -124,7 +127,8 @@ public void Validate_MainAppNameNullOrWhitespace_ThrowsArgumentException(string
ClientVersion = "1.0.0",
InstallPath = "C:\\app"
};
- Assert.Throws(() => config.Validate());
+ var ex = Record.Exception(() => config.Validate());
+ Assert.Null(ex);
}
[Theory]
@@ -149,8 +153,9 @@ public void Validate_AppSecretKeyNullOrWhitespace_ThrowsArgumentException(string
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
- public void Validate_ClientVersionNullOrWhitespace_ThrowsArgumentException(string clientVersion)
+ public void Validate_ClientVersionNullOrWhitespace_NoException(string clientVersion)
{
+ // ClientVersion is optional — filled from manifest by strategies.
var config = new UpdateRequest
{
UpdateUrl = "https://api.example.com",
@@ -160,7 +165,8 @@ public void Validate_ClientVersionNullOrWhitespace_ThrowsArgumentException(strin
ClientVersion = clientVersion,
InstallPath = "C:\\app"
};
- Assert.Throws(() => config.Validate());
+ var ex = Record.Exception(() => config.Validate());
+ Assert.Null(ex);
}
[Theory]
diff --git a/tests/UpgradeTest/Program.cs b/tests/UpgradeTest/Program.cs
index 2f510234..ef728de2 100644
--- a/tests/UpgradeTest/Program.cs
+++ b/tests/UpgradeTest/Program.cs
@@ -5,6 +5,70 @@
using GeneralUpdate.Core.Hooks;
try
+{
+ await RunOssUpgradeAsync();
+ /*var isOssMode = args.Length > 0 && args[0] == "--oss";
+
+ if (isOssMode)
+ {
+ await RunOssUpgradeAsync();
+ }
+ else
+ {
+ await RunStandardUpgradeAsync();
+ }*/
+}
+catch (Exception ex)
+{
+ Console.WriteLine($"FATAL: {ex}");
+ Environment.Exit(1);
+}
+
+// ═══════════════════════════════════════════════════════════════════
+// OSS Upgrade mode — read versions.json → download packages → decompress → launch main app
+// ═══════════════════════════════════════════════════════════════════
+static async Task RunOssUpgradeAsync()
+{
+ Console.WriteLine("=== GeneralUpdate OSS Upgrade Test ===");
+ Console.WriteLine($"Started at {DateTime.Now}");
+ Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}");
+
+ // OssUpgrade runs from a subdirectory (e.g. update/), but the manifest
+ // and versions.json are in the parent directory (same as OssClient).
+ // InstallPath resolves to the parent so OssStrategy reads the correct
+ // manifest and versions.json.
+ var baseDir = AppDomain.CurrentDomain.BaseDirectory;
+ var installPath = Path.GetFullPath(Path.Combine(baseDir, ".."));
+
+ Console.WriteLine($"BaseDir={baseDir}");
+ Console.WriteLine($"InstallPath={installPath} (parent, where manifest + versions.json live)");
+ Console.WriteLine();
+
+ // Only secrets and InstallPath are supplied in code. Identity fields
+ // (MainAppName, ClientVersion, UpdateAppName) are read from
+ // generalupdate.manifest.json by OssStrategy via
+ // AppMetadataDiscoverer.Discover() — same as the standard OSS client flow.
+ await new GeneralUpdateBootstrap()
+ .SetSource(
+ "http://localhost:5000/packages/versions.json",
+ "dfeb5833-975e-4afb-88f1-6278ee9aeff6",
+ installPath: installPath)
+ .SetOption(Option.AppType, AppType.OssUpgrade)
+ .Hooks()
+ .AddListenerMultiDownloadStatistics(OnDownloadStatistics)
+ .AddListenerMultiDownloadCompleted(OnDownloadCompleted)
+ .AddListenerMultiAllDownloadCompleted(OnAllDownloadCompleted)
+ .AddListenerMultiDownloadError(OnDownloadError)
+ .AddListenerException(OnException)
+ .LaunchAsync();
+
+ Console.WriteLine("OSS Upgrade test completed.");
+}
+
+// ═══════════════════════════════════════════════════════════════════
+// Standard Upgrade mode — read IPC config → apply patches → launch main app
+// ═══════════════════════════════════════════════════════════════════
+static async Task RunStandardUpgradeAsync()
{
Console.WriteLine("=== GeneralUpdate Upgrade Test ===");
Console.WriteLine($"Started at {DateTime.Now}");
@@ -26,11 +90,10 @@
Console.WriteLine("Upgrade test completed.");
}
-catch (Exception ex)
-{
- Console.WriteLine($"FATAL: {ex}");
- Environment.Exit(1);
-}
+
+// ═══════════════════════════════════════════════════════════════════
+// Event handlers (shared across both modes)
+// ═══════════════════════════════════════════════════════════════════
static void OnDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e)
{
@@ -62,6 +125,10 @@ static void OnException(object sender, ExceptionEventArgs e)
Console.WriteLine($"[Error] {e.Exception}");
}
+// ═══════════════════════════════════════════════════════════════════
+// Hooks (shared across both modes)
+// ═══════════════════════════════════════════════════════════════════
+
sealed class UpgradeTestHooks : IUpdateHooks
{
public async Task OnBeforeUpdateAsync(HookContext ctx)
@@ -79,6 +146,8 @@ public async Task OnDownloadCompletedAsync(DownloadContext ctx)
public async Task OnAfterUpdateAsync(HookContext ctx)
{
Console.WriteLine($"[Hook] OnAfterUpdate: {ctx.CurrentVersion} -> {ctx.TargetVersion}");
+ // Manifest ClientVersion is updated by OssStrategy itself via
+ // ManifestInfo.TryUpdateVersion() after decompression.
await Task.CompletedTask;
}