From 4885e73a644e73543914c812fa68a94de9960464 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Mon, 25 May 2026 18:56:45 +0800 Subject: [PATCH 1/6] refactor: unify OSS update path through OSSUpdateStrategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move client-side OSS logic (version config download, upgrade check, process launch) from LaunchOssAsync() into OSSUpdateStrategy. The strategy now handles both client and upgrade OSS roles internally. - OSSUpdateStrategy.ExecuteAsync(): detects role via GlobalConfigInfoOSS env var, dispatches to ExecuteClientAsync() or ExecuteUpgradeAsync() accordingly - GeneralUpdateBootstrap: AppType.OSS now goes through LaunchWithStrategy(new OSSUpdateStrategy()) — consistent with Client and Upgrade paths - Deleted LaunchOssAsync(), DownloadOssFile(), IsOssUpgrade() from GeneralUpdateBootstrap Closes #409 --- .../Bootstrap/GeneralUpdateBootstrap.cs | 89 +------------ .../Strategy/OSSUpdateStrategy.cs | 117 +++++++++++++++++- 2 files changed, 113 insertions(+), 93 deletions(-) diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs index a9bdc513..12dbe549 100644 --- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs +++ b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs @@ -71,7 +71,7 @@ public override async Task LaunchAsync() { AppType.Client => await LaunchWithStrategy(new ClientUpdateStrategy()), AppType.Upgrade => await LaunchWithStrategy(new UpgradeUpdateStrategy()), - AppType.OSS => await LaunchOssAsync(), + AppType.OSS => await LaunchWithStrategy(new OSSUpdateStrategy()), _ => await LaunchWithStrategy(new ClientUpdateStrategy()) }; } @@ -153,73 +153,6 @@ private async Task LaunchWithStrategy(IStrategy roleStra return this; } - /// OSS workflow: download packages from cloud storage, apply updates. - private async Task LaunchOssAsync() - { - try - { - GeneralTracer.Debug("LaunchOssAsync start."); - - var json = Environments.GetEnvironmentVariable("GlobalConfigInfoOSS"); - if (!string.IsNullOrWhiteSpace(json)) - { - var strategy = new OSSUpdateStrategy(); - strategy.Create(_configInfo); - await strategy.ExecuteAsync(); - return this; - } - - // Client-side OSS - var basePath = AppDomain.CurrentDomain.BaseDirectory; - var versionFileName = $"{_configInfo.MainAppName ?? _configInfo.AppName}_versions.json"; - var versionsFilePath = Path.Combine(basePath, versionFileName); - - DownloadOssFile(_configInfo.UpdateUrl, versionsFilePath); - if (!File.Exists(versionsFilePath)) return this; - - var versions = StorageManager.GetJson>(versionsFilePath, - VersionOSSJsonContext.Default.ListVersionOSS); - if (versions == null || versions.Count == 0) return this; - - versions = versions.OrderByDescending(x => x.PubTime).ToList(); - var newVersion = versions.First(); - - if (!IsOssUpgrade(_configInfo.ClientVersion, newVersion.Version)) - { - GeneralTracer.Info("LaunchOssAsync: no upgrade needed."); - return this; - } - - // Use user-configured AppName, fall back to default updater name - var upgradeAppName = !string.IsNullOrWhiteSpace(_configInfo.AppName) && _configInfo.AppName != "Update.exe" - ? _configInfo.AppName - : "GeneralUpdate.Upgrade.exe"; - var appPath = Path.Combine(basePath, upgradeAppName); - if (!File.Exists(appPath)) - throw new Exception($"Upgrade application not found: {upgradeAppName}"); - - var ossConfig = new GlobalConfigInfoOSS - { - AppName = _configInfo.MainAppName ?? _configInfo.AppName, - CurrentVersion = _configInfo.ClientVersion, - VersionFileName = versionFileName, - Encoding = (_configInfo.Encoding?.CodePage ?? Encoding.UTF8.CodePage).ToString(), - Url = _configInfo.UpdateUrl - }; - - var serialized = JsonSerializer.Serialize(ossConfig, - GlobalConfigInfoOSSJsonContext.Default.GlobalConfigInfoOSS); - Environments.SetEnvironmentVariable("GlobalConfigInfoOSS", serialized); - Process.Start(appPath); - await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - GeneralTracer.Error("LaunchOssAsync failed.", ex); - EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, ex.Message)); - } - return this; - } // ════════════════════════════════════════════════════════════════ // Configuration @@ -373,26 +306,6 @@ private async Task CallSmallBowlHomeAsync(string processName) } } - private static void DownloadOssFile(string url, string path) - { - if (File.Exists(path)) - { - File.SetAttributes(path, FileAttributes.Normal); - File.Delete(path); - } - using var webClient = new System.Net.WebClient(); - webClient.DownloadFile(new Uri(url), path); - } - - private static bool IsOssUpgrade(string clientVersion, string serverVersion) - { - if (string.IsNullOrWhiteSpace(clientVersion) || string.IsNullOrWhiteSpace(serverVersion)) - return false; - return Version.TryParse(clientVersion, out var cv) - && Version.TryParse(serverVersion, out var sv) - && cv < sv; - } - // ════════════════════════════════════════════════════════════════ // Strategy & Events // ════════════════════════════════════════════════════════════════ diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs index 61739c04..a1298afd 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs @@ -4,7 +4,9 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Runtime.InteropServices; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using GeneralUpdate.Core.Compress; using GeneralUpdate.Core.Download; @@ -17,11 +19,13 @@ namespace GeneralUpdate.Core.Strategy; /// -/// OSS (Object Storage Service) update strategy. -/// Downloads version configuration, fetches update packages from OSS, -/// decompresses them, and launches the main application. -/// -/// +/// OSS (Object Storage Service) update strategy — handles both client and upgrade roles. +/// +/// Client side: downloads version configuration from server, checks for new +/// version, starts the upgrade process, and exits. +/// Upgrade side: reads version config, downloads update packages from OSS, +/// decompresses them, and launches the main application. +/// /// This replaces the legacy OSSStrategy and GeneralUpdateOSS classes. /// The OSS workflow is OS-agnostic — no platform-specific pipeline is required. /// @@ -50,6 +54,107 @@ public async Task ExecuteAsync() if (_configInfo == null) throw new InvalidOperationException("OSSUpdateStrategy not configured. Call Create() first."); + // Upgrade side: GlobalConfigInfoOSS env var was set by the client, + // so we download packages, decompress, and start the main app. + var ossJson = Environments.GetEnvironmentVariable("GlobalConfigInfoOSS"); + if (!string.IsNullOrWhiteSpace(ossJson)) + { + await ExecuteUpgradeAsync(); + return; + } + + // Client side: download version config, check for new version, + // start the upgrade process, and exit. + await ExecuteClientAsync(); + } + + #region Client-side OSS + + private async Task ExecuteClientAsync() + { + GeneralTracer.Debug("OSSUpdateStrategy: client-side OSS flow."); + + var basePath = AppDomain.CurrentDomain.BaseDirectory; + var versionFileName = $"{_configInfo!.MainAppName ?? _configInfo.AppName}_versions.json"; + var versionsFilePath = Path.Combine(basePath, versionFileName); + + DownloadOssVersionFile(_configInfo.UpdateUrl, versionsFilePath); + if (!File.Exists(versionsFilePath)) + { + GeneralTracer.Info("OSSUpdateStrategy: version config download failed, aborting."); + return; + } + + var versions = JsonSerializer.Deserialize( + File.ReadAllText(versionsFilePath), + JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); + if (versions == null || versions.Count == 0) + { + GeneralTracer.Info("OSSUpdateStrategy: no versions found, aborting."); + return; + } + + versions = versions.OrderByDescending(x => x.PubTime).ToList(); + var newVersion = versions.First(); + + if (!IsOssUpgrade(_configInfo.ClientVersion, newVersion.Version)) + { + GeneralTracer.Info("OSSUpdateStrategy: no upgrade needed."); + return; + } + + // Use user-configured AppName or default upgrade exe + var upgradeAppName = !string.IsNullOrWhiteSpace(_configInfo.AppName) && _configInfo.AppName != "Update.exe" + ? _configInfo.AppName + : "GeneralUpdate.Upgrade.exe"; + var appPath = Path.Combine(basePath, upgradeAppName); + if (!File.Exists(appPath)) + throw new FileNotFoundException($"Upgrade application not found: {upgradeAppName}"); + + var ossConfig = new GlobalConfigInfoOSS + { + AppName = _configInfo.MainAppName ?? _configInfo.AppName, + CurrentVersion = _configInfo.ClientVersion, + VersionFileName = versionFileName, + Encoding = (_configInfo.Encoding?.CodePage ?? Encoding.UTF8.CodePage).ToString(), + Url = _configInfo.UpdateUrl + }; + + Environments.SetEnvironmentVariable("GlobalConfigInfoOSS", + JsonSerializer.Serialize(ossConfig, + JsonContext.GlobalConfigInfoOSSJsonContext.Default.GlobalConfigInfoOSS)); + + Process.Start(appPath); + await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); + } + + private static void DownloadOssVersionFile(string url, string path) + { + if (File.Exists(path)) + { + File.SetAttributes(path, FileAttributes.Normal); + File.Delete(path); + } + using var httpClient = new HttpClient(); + var bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); + File.WriteAllBytes(path, bytes); + } + + private static bool IsOssUpgrade(string clientVersion, string serverVersion) + { + if (string.IsNullOrWhiteSpace(clientVersion) || string.IsNullOrWhiteSpace(serverVersion)) + return false; + return Version.TryParse(clientVersion, out var cv) + && Version.TryParse(serverVersion, out var sv) + && cv < sv; + } + + #endregion + + #region Upgrade-side OSS + + private async Task ExecuteUpgradeAsync() + { var ctx = BuildUpdateContext(); try { @@ -149,6 +254,8 @@ public void StartApp() GeneralTracer.Debug("OSSUpdateStrategy: application started."); } + #endregion + #region Helpers private async Task DownloadAssetsAsync(List assets) From 46c76072877fd0659ad83cb6f691c9bacfb34173 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Mon, 25 May 2026 19:03:20 +0800 Subject: [PATCH 2/6] refactor: simplify OSS to single-process flow, remove upgrade/client split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OSS mode no longer needs a separate upgrade process. The strategy downloads version config, downloads packages from OSS, decompresses, starts the main app, and exits — all in one process. - Removed client/upgrade env-var-based dispatch from OSSUpdateStrategy - Single ExecuteAsync() flow: download config -> download packages -> decompress -> start app -> exit - Removed GlobalConfigInfoOSS env var bridging (no longer needed) Related #409 --- .../Strategy/OSSUpdateStrategy.cs | 203 ++++++------------ 1 file changed, 64 insertions(+), 139 deletions(-) diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs index a1298afd..96e8e68b 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs @@ -4,31 +4,22 @@ using System.IO; using System.Linq; using System.Net.Http; -using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Threading.Tasks; using GeneralUpdate.Core.Compress; -using GeneralUpdate.Core.Download; +using GeneralUpdate.Core.Configuration; using GeneralUpdate.Core.Download.Abstractions; using GeneralUpdate.Core.Download.Models; using GeneralUpdate.Core.Download.Orchestrators; -using GeneralUpdate.Core.Download.Sources; -using GeneralUpdate.Core.Configuration; namespace GeneralUpdate.Core.Strategy; /// -/// OSS (Object Storage Service) update strategy — handles both client and upgrade roles. -/// -/// Client side: downloads version configuration from server, checks for new -/// version, starts the upgrade process, and exits. -/// Upgrade side: reads version config, downloads update packages from OSS, -/// decompresses them, and launches the main application. -/// -/// This replaces the legacy OSSStrategy and GeneralUpdateOSS classes. -/// The OSS workflow is OS-agnostic — no platform-specific pipeline is required. -/// +/// OSS (Object Storage Service) update strategy — single-process, no separate upgrade. +/// Downloads version configuration, fetches update packages from OSS, +/// decompresses them, starts the main application, and exits. +/// public class OSSUpdateStrategy : IStrategy { private GlobalConfigInfo? _configInfo; @@ -54,143 +45,59 @@ public async Task ExecuteAsync() if (_configInfo == null) throw new InvalidOperationException("OSSUpdateStrategy not configured. Call Create() first."); - // Upgrade side: GlobalConfigInfoOSS env var was set by the client, - // so we download packages, decompress, and start the main app. - var ossJson = Environments.GetEnvironmentVariable("GlobalConfigInfoOSS"); - if (!string.IsNullOrWhiteSpace(ossJson)) - { - await ExecuteUpgradeAsync(); - return; - } - - // Client side: download version config, check for new version, - // start the upgrade process, and exit. - await ExecuteClientAsync(); - } - - #region Client-side OSS - - private async Task ExecuteClientAsync() - { - GeneralTracer.Debug("OSSUpdateStrategy: client-side OSS flow."); - - var basePath = AppDomain.CurrentDomain.BaseDirectory; - var versionFileName = $"{_configInfo!.MainAppName ?? _configInfo.AppName}_versions.json"; - var versionsFilePath = Path.Combine(basePath, versionFileName); - - DownloadOssVersionFile(_configInfo.UpdateUrl, versionsFilePath); - if (!File.Exists(versionsFilePath)) - { - GeneralTracer.Info("OSSUpdateStrategy: version config download failed, aborting."); - return; - } - - var versions = JsonSerializer.Deserialize( - File.ReadAllText(versionsFilePath), - JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); - if (versions == null || versions.Count == 0) - { - GeneralTracer.Info("OSSUpdateStrategy: no versions found, aborting."); - return; - } - - versions = versions.OrderByDescending(x => x.PubTime).ToList(); - var newVersion = versions.First(); - - if (!IsOssUpgrade(_configInfo.ClientVersion, newVersion.Version)) - { - GeneralTracer.Info("OSSUpdateStrategy: no upgrade needed."); - return; - } - - // Use user-configured AppName or default upgrade exe - var upgradeAppName = !string.IsNullOrWhiteSpace(_configInfo.AppName) && _configInfo.AppName != "Update.exe" - ? _configInfo.AppName - : "GeneralUpdate.Upgrade.exe"; - var appPath = Path.Combine(basePath, upgradeAppName); - if (!File.Exists(appPath)) - throw new FileNotFoundException($"Upgrade application not found: {upgradeAppName}"); - - var ossConfig = new GlobalConfigInfoOSS - { - AppName = _configInfo.MainAppName ?? _configInfo.AppName, - CurrentVersion = _configInfo.ClientVersion, - VersionFileName = versionFileName, - Encoding = (_configInfo.Encoding?.CodePage ?? Encoding.UTF8.CodePage).ToString(), - Url = _configInfo.UpdateUrl - }; - - Environments.SetEnvironmentVariable("GlobalConfigInfoOSS", - JsonSerializer.Serialize(ossConfig, - JsonContext.GlobalConfigInfoOSSJsonContext.Default.GlobalConfigInfoOSS)); - - Process.Start(appPath); - await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); - } - - private static void DownloadOssVersionFile(string url, string path) - { - if (File.Exists(path)) - { - File.SetAttributes(path, FileAttributes.Normal); - File.Delete(path); - } - using var httpClient = new HttpClient(); - var bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); - File.WriteAllBytes(path, bytes); - } - - private static bool IsOssUpgrade(string clientVersion, string serverVersion) - { - if (string.IsNullOrWhiteSpace(clientVersion) || string.IsNullOrWhiteSpace(serverVersion)) - return false; - return Version.TryParse(clientVersion, out var cv) - && Version.TryParse(serverVersion, out var sv) - && cv < sv; - } - - #endregion - - #region Upgrade-side OSS - - private async Task ExecuteUpgradeAsync() - { var ctx = BuildUpdateContext(); try { + // 1. Download version configuration from server + GeneralTracer.Debug("OSSUpdateStrategy: 1. Downloading version configuration."); var versionFileName = $"{_configInfo.MainAppName ?? _configInfo.AppName}_versions.json"; + var versionsFilePath = Path.Combine(_appPath, versionFileName); + + DownloadVersionConfig(_configInfo.UpdateUrl, versionsFilePath); + if (!File.Exists(versionsFilePath)) + { + GeneralTracer.Info("OSSUpdateStrategy: version config download failed, aborting."); + return; + } - GeneralTracer.Debug("OSSUpdateStrategy: 1. Reading version configuration."); - var jsonPath = Path.Combine(_appPath, versionFileName); - if (!File.Exists(jsonPath) && DownloadSource == null) - throw new FileNotFoundException($"Version config not found: {jsonPath}"); + var versions = JsonSerializer.Deserialize( + File.ReadAllText(versionsFilePath), + JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); + if (versions == null || versions.Count == 0) + { + GeneralTracer.Info("OSSUpdateStrategy: no versions found, aborting."); + return; + } + + // 2. Check if upgrade is needed + versions = versions.OrderByDescending(x => x.PubTime).ToList(); + var latest = versions.First(); + if (!IsOssUpgrade(_configInfo.ClientVersion, latest.Version)) + { + GeneralTracer.Info("OSSUpdateStrategy: no upgrade needed."); + return; + } // Hooks: allow cancellation before download if (!await SafeOnBeforeUpdateAsync(ctx).ConfigureAwait(false)) { - GeneralTracer.Info("OSSUpdateStrategy: update cancelled by OnBeforeUpdateAsync hook."); + GeneralTracer.Info("OSSUpdateStrategy: update cancelled by hook."); return; } // Report: update started await SafeReportUpdateStartedAsync(ctx).ConfigureAwait(false); + // 3. Build download assets List assets; if (DownloadSource != null) { GeneralTracer.Debug("OSSUpdateStrategy: 2. Using injected IDownloadSource."); - var sourceAssets = await DownloadSource.ListAsync().ConfigureAwait(false); - assets = sourceAssets.ToList(); + assets = (await DownloadSource.ListAsync().ConfigureAwait(false)).ToList(); } else { - GeneralTracer.Debug("OSSUpdateStrategy: 2. Parsing version configuration from local JSON."); - var versions = System.Text.Json.JsonSerializer.Deserialize( - File.ReadAllText(jsonPath), - JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); - if (versions == null || versions.Count == 0) - throw new InvalidOperationException("No versions found in OSS configuration."); - + GeneralTracer.Debug("OSSUpdateStrategy: 2. Building assets from version config."); assets = versions.OrderBy(v => v.PubTime).Select(v => { if (string.IsNullOrWhiteSpace(v.Url)) @@ -199,7 +106,7 @@ private async Task ExecuteUpgradeAsync() var zipName = $"{v.PacketName ?? v.Version}zip"; if (!zipName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) zipName += ".zip"; - return new Download.Models.DownloadAsset( + return new DownloadAsset( Name: zipName, Url: v.Url, Size: 0, SHA256: v.Hash, Version: v.Version ?? "0.0.0"); }).ToList(); @@ -208,24 +115,23 @@ private async Task ExecuteUpgradeAsync() if (assets.Count == 0) throw new InvalidOperationException("No assets to download."); + // 4. Download packages GeneralTracer.Debug($"OSSUpdateStrategy: 3. Downloading {assets.Count} asset(s)."); - await DownloadAssetsAsync(assets); + await DownloadAssetsAsync(assets).ConfigureAwait(false); + // 5. Decompress GeneralTracer.Debug("OSSUpdateStrategy: 4. Decompressing packages."); DecompressAssets(assets); - // Hooks: download + decompress completed await SafeOnDownloadCompletedAsync(ctx).ConfigureAwait(false); await SafeOnAfterUpdateAsync(ctx).ConfigureAwait(false); - - // Report: update applied await SafeReportUpdateAppliedAsync(ctx).ConfigureAwait(false); - - // Hooks: before starting main app await SafeOnBeforeStartAppAsync(ctx).ConfigureAwait(false); + // 6. Start main app and exit GeneralTracer.Debug("OSSUpdateStrategy: 5. Launching main application."); StartApp(); + await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); } catch (Exception ex) { @@ -254,10 +160,29 @@ public void StartApp() GeneralTracer.Debug("OSSUpdateStrategy: application started."); } - #endregion - #region Helpers + private static void DownloadVersionConfig(string url, string path) + { + if (File.Exists(path)) + { + File.SetAttributes(path, FileAttributes.Normal); + File.Delete(path); + } + using var httpClient = new HttpClient(); + var bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult(); + File.WriteAllBytes(path, bytes); + } + + private static bool IsOssUpgrade(string clientVersion, string serverVersion) + { + if (string.IsNullOrWhiteSpace(clientVersion) || string.IsNullOrWhiteSpace(serverVersion)) + return false; + return Version.TryParse(clientVersion, out var cv) + && Version.TryParse(serverVersion, out var sv) + && cv < sv; + } + private async Task DownloadAssetsAsync(List assets) { var plan = new DownloadPlan(assets, false); @@ -268,7 +193,7 @@ private async Task DownloadAssetsAsync(List assets) } else { - using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(_configInfo.DownloadTimeOut > 0 ? _configInfo.DownloadTimeOut : DefaultTimeOut) }; + 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); } From 4890f8db8f801bc6fd33df11ff17d2a5a700c284 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Mon, 25 May 2026 19:06:10 +0800 Subject: [PATCH 3/6] refactor: OSS client/upgrade split with upgrade process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OSS mode uses a separate upgrade process to download packages and decompress, but does NOT check UpgradeClientVersion (the upgrade process itself never needs upgrading — differs from standard flow). Client side (main app): Download version config → check update → start upgrade process → exit Upgrade side (GeneralUpdate.Upgrade.exe): Read version config → download OSS packages → decompress → start main app Related #409 --- .../Strategy/OSSUpdateStrategy.cs | 206 +++++++++++------- 1 file changed, 126 insertions(+), 80 deletions(-) diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs index 96e8e68b..3b5f71b1 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs @@ -16,9 +16,13 @@ namespace GeneralUpdate.Core.Strategy; /// -/// OSS (Object Storage Service) update strategy — single-process, no separate upgrade. -/// Downloads version configuration, fetches update packages from OSS, -/// decompresses them, starts the main application, and exits. +/// OSS (Object Storage Service) update strategy — client/upgrade split. +/// +/// Client side: downloads version config, checks for updates, +/// starts the upgrade process with config, and exits. +/// Upgrade side: reads version config, downloads packages from OSS, +/// decompresses them, starts the main app, and exits. +/// /// public class OSSUpdateStrategy : IStrategy { @@ -26,13 +30,9 @@ public class OSSUpdateStrategy : IStrategy private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory; private const int DefaultTimeOut = 60; - /// Lifecycle hooks injected by the bootstrap. public Hooks.IUpdateHooks Hooks { get; set; } = new Hooks.NoOpUpdateHooks(); - /// Update status reporter injected by the bootstrap. public Download.Reporting.IUpdateReporter Reporter { get; set; } = new Download.Reporting.NoOpUpdateReporter(); - /// Download source for OSS version listing. Override via .DownloadSource<OssDownloadSource>(). public IDownloadSource? DownloadSource { get; set; } - /// Download orchestrator. Override via .DownloadOrchestrator<T>(). public IDownloadOrchestrator? DownloadOrchestrator { get; set; } public void Create(GlobalConfigInfo parameter) @@ -45,82 +45,143 @@ public async Task ExecuteAsync() if (_configInfo == null) throw new InvalidOperationException("OSSUpdateStrategy not configured. Call Create() first."); - var ctx = BuildUpdateContext(); - try + // Upgrade side: GlobalConfigInfoOSS env var was set by the client process. + // We download packages, decompress, start the main app, and exit. + var ossJson = Environments.GetEnvironmentVariable("GlobalConfigInfoOSS"); + if (!string.IsNullOrWhiteSpace(ossJson)) { - // 1. Download version configuration from server - GeneralTracer.Debug("OSSUpdateStrategy: 1. Downloading version configuration."); - var versionFileName = $"{_configInfo.MainAppName ?? _configInfo.AppName}_versions.json"; - var versionsFilePath = Path.Combine(_appPath, versionFileName); + await ExecuteUpgradeAsync(); + return; + } - DownloadVersionConfig(_configInfo.UpdateUrl, versionsFilePath); - if (!File.Exists(versionsFilePath)) - { - GeneralTracer.Info("OSSUpdateStrategy: version config download failed, aborting."); - return; - } + // Client side: download version config, check for update, + // start the upgrade process, and exit. + await ExecuteClientAsync(); + } - var versions = JsonSerializer.Deserialize( - File.ReadAllText(versionsFilePath), - JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); - if (versions == null || versions.Count == 0) - { - GeneralTracer.Info("OSSUpdateStrategy: no versions found, aborting."); - return; - } + // ════════════════════════════════════════════════════════════════ + // Client side: check version, start upgrade process + // ════════════════════════════════════════════════════════════════ - // 2. Check if upgrade is needed - versions = versions.OrderByDescending(x => x.PubTime).ToList(); - var latest = versions.First(); - if (!IsOssUpgrade(_configInfo.ClientVersion, latest.Version)) - { - GeneralTracer.Info("OSSUpdateStrategy: no upgrade needed."); - return; - } + private async Task ExecuteClientAsync() + { + GeneralTracer.Debug("OSSUpdateStrategy (client): checking for updates."); + + var versionFileName = $"{_configInfo!.MainAppName ?? _configInfo.AppName}_versions.json"; + var versionsFilePath = Path.Combine(_appPath, versionFileName); + + DownloadVersionConfig(_configInfo.UpdateUrl, versionsFilePath); + if (!File.Exists(versionsFilePath)) + { + GeneralTracer.Info("OSSUpdateStrategy: version config download failed, aborting."); + return; + } + + var versions = JsonSerializer.Deserialize( + File.ReadAllText(versionsFilePath), + JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); + if (versions == null || versions.Count == 0) + { + GeneralTracer.Info("OSSUpdateStrategy: no versions found, aborting."); + return; + } + + versions = versions.OrderByDescending(x => x.PubTime).ToList(); + var latest = versions.First(); + + if (!IsOssUpgrade(_configInfo.ClientVersion, latest.Version)) + { + GeneralTracer.Info("OSSUpdateStrategy: no upgrade needed."); + return; + } + + // Use user-configured AppName or default upgrade exe + var upgradeAppName = !string.IsNullOrWhiteSpace(_configInfo.AppName) && _configInfo.AppName != "Update.exe" + ? _configInfo.AppName + : "GeneralUpdate.Upgrade.exe"; + var appPath = Path.Combine(_appPath, upgradeAppName); + if (!File.Exists(appPath)) + throw new FileNotFoundException($"Upgrade application not found: {upgradeAppName}"); + + // Pass config to the upgrade process via AES-encrypted file + var ossConfig = new GlobalConfigInfoOSS + { + AppName = _configInfo.MainAppName ?? _configInfo.AppName, + CurrentVersion = _configInfo.ClientVersion, + VersionFileName = versionFileName, + Encoding = (_configInfo.Encoding?.CodePage ?? Encoding.UTF8.CodePage).ToString(), + Url = _configInfo.UpdateUrl + }; + + Environments.SetEnvironmentVariable("GlobalConfigInfoOSS", + JsonSerializer.Serialize(ossConfig, + JsonContext.GlobalConfigInfoOSSJsonContext.Default.GlobalConfigInfoOSS)); + + Process.Start(appPath); + await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); + } + + // ════════════════════════════════════════════════════════════════ + // Upgrade side: download packages, decompress, start main app + // ════════════════════════════════════════════════════════════════ + + private async Task ExecuteUpgradeAsync() + { + var ctx = BuildUpdateContext(); + try + { + var versionFileName = $"{_configInfo!.MainAppName ?? _configInfo.AppName}_versions.json"; + var jsonPath = Path.Combine(_appPath, versionFileName); + + 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("OSSUpdateStrategy: update cancelled by hook."); + GeneralTracer.Info("OSSUpdateStrategy (upgrade): cancelled by hook."); return; } - // Report: update started await SafeReportUpdateStartedAsync(ctx).ConfigureAwait(false); - // 3. Build download assets + // Build download assets from version config or injected source List assets; if (DownloadSource != null) { - GeneralTracer.Debug("OSSUpdateStrategy: 2. Using injected IDownloadSource."); assets = (await DownloadSource.ListAsync().ConfigureAwait(false)).ToList(); } else { - GeneralTracer.Debug("OSSUpdateStrategy: 2. Building assets from version config."); - assets = versions.OrderBy(v => v.PubTime).Select(v => - { - if (string.IsNullOrWhiteSpace(v.Url)) - throw new InvalidOperationException( - $"OSS version '{v.PacketName ?? v.Version}' has no download URL."); - var zipName = $"{v.PacketName ?? v.Version}zip"; - if (!zipName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) - zipName += ".zip"; - return new DownloadAsset( - Name: zipName, Url: v.Url, Size: 0, - SHA256: v.Hash, Version: v.Version ?? "0.0.0"); - }).ToList(); + var versions = JsonSerializer.Deserialize( + File.ReadAllText(jsonPath), + JsonContext.VersionOSSJsonContext.Default.ListVersionOSS); + if (versions == null || versions.Count == 0) + throw new InvalidOperationException("No versions found in OSS configuration."); + + assets = versions.OrderBy(v => v.PubTime) + .Where(v => new Version(v.Version ?? "0.0.0") > new Version(_configInfo.ClientVersion)) + .Select(v => + { + if (string.IsNullOrWhiteSpace(v.Url)) + throw new InvalidOperationException( + $"OSS version '{v.PacketName ?? v.Version}' has no download URL."); + var zipName = $"{v.PacketName ?? v.Version}zip"; + if (!zipName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) + zipName += ".zip"; + return new DownloadAsset( + Name: zipName, Url: v.Url, Size: 0, + SHA256: v.Hash, Version: v.Version ?? "0.0.0"); + }).ToList(); } if (assets.Count == 0) throw new InvalidOperationException("No assets to download."); - // 4. Download packages - GeneralTracer.Debug($"OSSUpdateStrategy: 3. Downloading {assets.Count} asset(s)."); + GeneralTracer.Debug($"OSSUpdateStrategy (upgrade): downloading {assets.Count} asset(s)."); await DownloadAssetsAsync(assets).ConfigureAwait(false); - // 5. Decompress - GeneralTracer.Debug("OSSUpdateStrategy: 4. Decompressing packages."); + GeneralTracer.Debug("OSSUpdateStrategy (upgrade): decompressing."); DecompressAssets(assets); await SafeOnDownloadCompletedAsync(ctx).ConfigureAwait(false); @@ -128,24 +189,19 @@ public async Task ExecuteAsync() await SafeReportUpdateAppliedAsync(ctx).ConfigureAwait(false); await SafeOnBeforeStartAppAsync(ctx).ConfigureAwait(false); - // 6. Start main app and exit - GeneralTracer.Debug("OSSUpdateStrategy: 5. Launching main application."); + GeneralTracer.Debug("OSSUpdateStrategy (upgrade): launching main app."); StartApp(); - await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); } catch (Exception ex) { await SafeOnUpdateErrorAsync(ctx, ex).ConfigureAwait(false); await SafeReportUpdateFailedAsync(ctx, ex).ConfigureAwait(false); - GeneralTracer.Error("OSSUpdateStrategy.ExecuteAsync failed.", ex); + GeneralTracer.Error("OSSUpdateStrategy.ExecuteUpgradeAsync failed.", ex); throw; } } - public void Execute() - { - ExecuteAsync().GetAwaiter().GetResult(); - } + public void Execute() => ExecuteAsync().GetAwaiter().GetResult(); public void StartApp() { @@ -157,7 +213,7 @@ public void StartApp() throw new FileNotFoundException($"Application not found: {appPath}"); Process.Start(appPath); - GeneralTracer.Debug("OSSUpdateStrategy: application started."); + GeneralTracer.Debug("OSSUpdateStrategy: main application started."); } #region Helpers @@ -186,14 +242,16 @@ private static bool IsOssUpgrade(string clientVersion, string serverVersion) private async Task DownloadAssetsAsync(List assets) { var plan = new DownloadPlan(assets, false); - if (DownloadOrchestrator != null) { await DownloadOrchestrator.ExecuteAsync(plan, _appPath).ConfigureAwait(false); } else { - using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(_configInfo?.DownloadTimeOut > 0 ? _configInfo!.DownloadTimeOut : DefaultTimeOut) }; + 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); } @@ -213,10 +271,6 @@ private void DecompressAssets(List assets) } } - // ════════════════════════════════════════════════════════════════ - // Hooks & Reporter safe wrappers - // ════════════════════════════════════════════════════════════════ - private Hooks.UpdateContext BuildUpdateContext() { return new Hooks.UpdateContext( @@ -233,38 +287,32 @@ private async Task SafeOnBeforeUpdateAsync(Hooks.UpdateContext ctx) try { return await Hooks.OnBeforeUpdateAsync(ctx).ConfigureAwait(false); } catch (Exception ex) { GeneralTracer.Warn($"OnBeforeUpdateAsync hook failed: {ex.Message}"); return true; } } - private async Task SafeOnBeforeStartAppAsync(Hooks.UpdateContext ctx) { try { await Hooks.OnBeforeStartAppAsync(ctx).ConfigureAwait(false); } catch (Exception ex) { GeneralTracer.Warn($"OnBeforeStartAppAsync hook failed: {ex.Message}"); } } - private async Task SafeOnUpdateErrorAsync(Hooks.UpdateContext ctx, Exception error) { try { await Hooks.OnUpdateErrorAsync(ctx, error).ConfigureAwait(false); } catch (Exception ex) { GeneralTracer.Warn($"OnUpdateErrorAsync hook failed: {ex.Message}"); } } - private async Task SafeOnAfterUpdateAsync(Hooks.UpdateContext ctx) { try { await Hooks.OnAfterUpdateAsync(ctx).ConfigureAwait(false); } catch (Exception ex) { GeneralTracer.Warn($"OnAfterUpdateAsync hook failed: {ex.Message}"); } } - private async Task SafeOnDownloadCompletedAsync(Hooks.UpdateContext ctx) { try { var downloadCtx = new Hooks.DownloadContext( _configInfo?.MainAppName ?? _configInfo?.AppName ?? "unknown", - _configInfo?.LastVersion ?? "", - 0, TimeSpan.Zero, _appPath, true); + _configInfo?.LastVersion ?? "", 0, TimeSpan.Zero, _appPath, true); await Hooks.OnDownloadCompletedAsync(downloadCtx).ConfigureAwait(false); } catch (Exception ex) { GeneralTracer.Warn($"OnDownloadCompletedAsync hook failed: {ex.Message}"); } } - private async Task SafeReportUpdateStartedAsync(Hooks.UpdateContext ctx) { try @@ -276,7 +324,6 @@ await Reporter.ReportAsync(new Download.Reporting.UpdateReport( } catch (Exception ex) { GeneralTracer.Warn($"Report UpdateStarted failed: {ex.Message}"); } } - private async Task SafeReportUpdateAppliedAsync(Hooks.UpdateContext ctx) { try @@ -288,7 +335,6 @@ await Reporter.ReportAsync(new Download.Reporting.UpdateReport( } catch (Exception ex) { GeneralTracer.Warn($"Report UpdateApplied failed: {ex.Message}"); } } - private async Task SafeReportUpdateFailedAsync(Hooks.UpdateContext ctx, Exception error) { try From fb6a36ac2adad26b5ef7ac1eb6a95a0af4094a8a Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Mon, 25 May 2026 19:12:08 +0800 Subject: [PATCH 4/6] refactor: add AppType.OSSClient/OSSUpgrade, remove env-var dispatch Replace AppType.OSS with explicit OSSClient/OSSUpgrade to match the Client/Upgrade pattern. OSSUpdateStrategy now accepts AppType via constructor instead of detecting role via GlobalConfigInfoOSS env var. - AppType enum: OSS = 3 -> OSSClient = 3, OSSUpgrade = 4 - Bootstrap dispatch: OSSClient/OSSUpgrade through LaunchWithStrategy - OSSUpdateStrategy: role dispatched by constructor AppType parameter - Updated tests for new enum values Closes #409 --- .../Bootstrap/GeneralUpdateBootstrap.cs | 6 +++-- .../Configuration/AppType.cs | 7 ++++-- .../Strategy/OSSUpdateStrategy.cs | 24 ++++++++++--------- .../BootstrapFullParameterMatrixTests.cs | 3 ++- .../Configuration/ConfigurationModelsTests.cs | 4 +++- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs index 12dbe549..02a1936a 100644 --- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs +++ b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs @@ -27,7 +27,8 @@ namespace GeneralUpdate.Core; /// /// — validate versions, download, start upgrade process /// — receive ProcessInfo, apply updates, start main app -/// — OSS-based cloud storage update +/// — OSS client: download version config, start upgrade process +/// — OSS upgrade: download packages from cloud, start main app /// /// /// @@ -71,7 +72,8 @@ public override async Task LaunchAsync() { AppType.Client => await LaunchWithStrategy(new ClientUpdateStrategy()), AppType.Upgrade => await LaunchWithStrategy(new UpgradeUpdateStrategy()), - AppType.OSS => await LaunchWithStrategy(new OSSUpdateStrategy()), + AppType.OSSClient => await LaunchWithStrategy(new OSSUpdateStrategy(AppType.OSSClient)), + AppType.OSSUpgrade => await LaunchWithStrategy(new OSSUpdateStrategy(AppType.OSSUpgrade)), _ => await LaunchWithStrategy(new ClientUpdateStrategy()) }; } diff --git a/src/c#/GeneralUpdate.Core/Configuration/AppType.cs b/src/c#/GeneralUpdate.Core/Configuration/AppType.cs index 216c5e7c..3d15602b 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/AppType.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/AppType.cs @@ -11,6 +11,9 @@ public enum AppType /// Upgrade application — applies downloaded update packages, starts main app. Upgrade = 2, - /// OSS (Object Storage Service) update mode — downloads from cloud storage. - OSS = 3 + /// OSS client mode — checks version config, starts upgrade process. + OSSClient = 3, + + /// OSS upgrade mode — downloads packages from OSS, deploys to client. + OSSUpgrade = 4 } diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs index 3b5f71b1..6b082e7d 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs @@ -16,20 +16,26 @@ namespace GeneralUpdate.Core.Strategy; /// -/// OSS (Object Storage Service) update strategy — client/upgrade split. +/// OSS (Object Storage Service) update strategy — client/upgrade split via AppType. /// -/// Client side: downloads version config, checks for updates, -/// starts the upgrade process with config, and exits. -/// Upgrade side: reads version config, downloads packages from OSS, +/// — downloads version config, checks for updates, +/// starts the upgrade process, and exits. +/// — reads version config, downloads packages from OSS, /// decompresses them, starts the main app, and exits. /// /// public class OSSUpdateStrategy : IStrategy { + private readonly AppType _role; private GlobalConfigInfo? _configInfo; private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory; private const int DefaultTimeOut = 60; + public OSSUpdateStrategy(AppType role = AppType.OSSClient) + { + _role = role; + } + public Hooks.IUpdateHooks Hooks { get; set; } = new Hooks.NoOpUpdateHooks(); public Download.Reporting.IUpdateReporter Reporter { get; set; } = new Download.Reporting.NoOpUpdateReporter(); public IDownloadSource? DownloadSource { get; set; } @@ -45,17 +51,13 @@ public async Task ExecuteAsync() if (_configInfo == null) throw new InvalidOperationException("OSSUpdateStrategy not configured. Call Create() first."); - // Upgrade side: GlobalConfigInfoOSS env var was set by the client process. - // We download packages, decompress, start the main app, and exit. - var ossJson = Environments.GetEnvironmentVariable("GlobalConfigInfoOSS"); - if (!string.IsNullOrWhiteSpace(ossJson)) + // Dispatch by role — no env-var detection needed. + if (_role == AppType.OSSUpgrade) { await ExecuteUpgradeAsync(); return; } - // Client side: download version config, check for update, - // start the upgrade process, and exit. await ExecuteClientAsync(); } @@ -278,7 +280,7 @@ private Hooks.UpdateContext BuildUpdateContext() _configInfo?.InstallPath ?? _appPath, _configInfo?.ClientVersion ?? "0.0.0", _configInfo?.LastVersion, - AppType.OSS + AppType.OSSUpgrade ); } diff --git a/tests/CoreTest/Bootstrap/BootstrapFullParameterMatrixTests.cs b/tests/CoreTest/Bootstrap/BootstrapFullParameterMatrixTests.cs index 8054aebe..fdc7c245 100644 --- a/tests/CoreTest/Bootstrap/BootstrapFullParameterMatrixTests.cs +++ b/tests/CoreTest/Bootstrap/BootstrapFullParameterMatrixTests.cs @@ -47,7 +47,8 @@ public void Dispose() #region Core [Fact] public void AppType_Client() => Assert.NotNull(B().Option(UpdateOptions.AppType, AppType.Client)); [Fact] public void AppType_Upgrade() => Assert.NotNull(B().Option(UpdateOptions.AppType, AppType.Upgrade)); - [Fact] public void AppType_OSS() => Assert.NotNull(B().Option(UpdateOptions.AppType, AppType.OSS)); + [Fact] public void AppType_OSSClient() => Assert.NotNull(B().Option(UpdateOptions.AppType, AppType.OSSClient)); + [Fact] public void AppType_OSSUpgrade() => Assert.NotNull(B().Option(UpdateOptions.AppType, AppType.OSSUpgrade)); [Fact] public void DiffMode_Serial() => Assert.NotNull(B().Option(UpdateOptions.DiffMode, DiffMode.Serial)); [Fact] public void DiffMode_Parallel() => Assert.NotNull(B().Option(UpdateOptions.DiffMode, DiffMode.Parallel)); [Fact] public void Encoding_Utf8() => Assert.NotNull(B().Option(UpdateOptions.Encoding, Encoding.UTF8)); diff --git a/tests/CoreTest/Configuration/ConfigurationModelsTests.cs b/tests/CoreTest/Configuration/ConfigurationModelsTests.cs index 1aef0fd1..97bb3356 100644 --- a/tests/CoreTest/Configuration/ConfigurationModelsTests.cs +++ b/tests/CoreTest/Configuration/ConfigurationModelsTests.cs @@ -258,7 +258,9 @@ public void DownloadResult_FailureWithRetries() [Fact] public void AppType_UpgradeIs2() => Assert.Equal(2, (int)AppType.Upgrade); [Fact] - public void AppType_OSSIs3() => Assert.Equal(3, (int)AppType.OSS); + public void AppType_OSSClientIs3() => Assert.Equal(3, (int)AppType.OSSClient); + [Fact] + public void AppType_OSSUpgradeIs4() => Assert.Equal(4, (int)AppType.OSSUpgrade); [Fact] public void DiffMode_SerialAndParallel_AreDefined() From 21d1f960088481adfee909794733da9aa28748d9 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Mon, 25 May 2026 19:22:41 +0800 Subject: [PATCH 5/6] fix: remove dead GlobalConfigInfoOSS write from OSS client path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upgrade side (ExecuteUpgradeAsync) reads version config directly from the local JSON file — it never reads GlobalConfigInfoOSS env var. Writing it in ExecuteClientAsync was dead code. Removed ossConfig + Environments.SetEnvironmentVariable block. Related #409 --- .../Strategy/OSSUpdateStrategy.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs index 6b082e7d..f05e0ded 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs @@ -105,20 +105,6 @@ private async Task ExecuteClientAsync() if (!File.Exists(appPath)) throw new FileNotFoundException($"Upgrade application not found: {upgradeAppName}"); - // Pass config to the upgrade process via AES-encrypted file - var ossConfig = new GlobalConfigInfoOSS - { - AppName = _configInfo.MainAppName ?? _configInfo.AppName, - CurrentVersion = _configInfo.ClientVersion, - VersionFileName = versionFileName, - Encoding = (_configInfo.Encoding?.CodePage ?? Encoding.UTF8.CodePage).ToString(), - Url = _configInfo.UpdateUrl - }; - - Environments.SetEnvironmentVariable("GlobalConfigInfoOSS", - JsonSerializer.Serialize(ossConfig, - JsonContext.GlobalConfigInfoOSSJsonContext.Default.GlobalConfigInfoOSS)); - Process.Start(appPath); await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); } From 59b1e7d8ac7913c9f2ef1402ec1fd34e974b664d Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Mon, 25 May 2026 19:32:29 +0800 Subject: [PATCH 6/6] fix: guard against empty UpdateUrl in OSS client download DownloadVersionConfig calls HttpClient which throws InvalidOperationException when UpdateUrl is empty/null. Skip the download when UpdateUrl is not configured, then check for local version config file existence as before. Fixes OssIntegrationTests.OSSUpdateStrategy_RequiresConfig test. Related #409 --- src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs index f05e0ded..e7233392 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs @@ -72,7 +72,11 @@ private async Task ExecuteClientAsync() var versionFileName = $"{_configInfo!.MainAppName ?? _configInfo.AppName}_versions.json"; var versionsFilePath = Path.Combine(_appPath, versionFileName); - DownloadVersionConfig(_configInfo.UpdateUrl, versionsFilePath); + if (!string.IsNullOrEmpty(_configInfo.UpdateUrl)) + { + DownloadVersionConfig(_configInfo.UpdateUrl, versionsFilePath); + } + if (!File.Exists(versionsFilePath)) { GeneralTracer.Info("OSSUpdateStrategy: version config download failed, aborting.");