diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralClientBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralClientBootstrap.cs
deleted file mode 100644
index efaa354b..00000000
--- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralClientBootstrap.cs
+++ /dev/null
@@ -1,464 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-using GeneralUpdate.Core.Strategy;
-using GeneralUpdate.Core.FileSystem;
-using GeneralUpdate.Core.Download;
-using GeneralUpdate.Core;
-using GeneralUpdate.Core.Event;
-using GeneralUpdate.Core.Configuration;
-using GeneralUpdate.Core.JsonContext;
-using GeneralUpdate.Core.Network;
-
-namespace GeneralUpdate.Core;
-
-///
-/// This component is used only for client application bootstrapping classes.
-///
-[Obsolete("Use GeneralUpdateBootstrap with Option(UpdateOptions.AppType, AppType.ClientApp) instead. This class will be removed in v11. See migration guide at https://github.com/GeneralLibrary/GeneralUpdate/issues/344")]
- public class GeneralClientBootstrap : AbstractBootstrap
-{
- ///
- /// All update actions of the core object for automatic upgrades will be related to the packet object.
- ///
- private GlobalConfigInfo? _configInfo;
- private IStrategy? _strategy;
- private Func? _updatePrecheck;
- private readonly List> _customOptions = new();
-
- #region Public Methods
-
- ///
- /// Main function for booting the update startup.
- ///
- ///
- public override async Task LaunchAsync()
- {
- try
- {
- GeneralTracer.Debug("GeneralClientBootstrap Launch.");
- CallSmallBowlHome(_configInfo.Bowl);
- ExecuteCustomOptions();
- await ExecuteWorkflowAsync();
- }
- catch (Exception exception)
- {
- GeneralTracer.Error("The LaunchAsync method in the GeneralClientBootstrap class throws an exception." , exception);
- EventManager.Instance.Dispatch(this, new ExceptionEventArgs(exception, exception.Message));
- }
- return this;
- }
-
- ///
- /// Configure server address (Recommended Windows,Linux,Mac).
- ///
- public GeneralClientBootstrap SetConfig(Configinfo configInfo)
- {
- Debug.Assert(configInfo != null, "configInfo should not be null");
- configInfo?.Validate();
-
- // Use ConfigurationMapper instead of manual field mapping
- // This ensures all fields are consistently mapped and reduces maintenance burden
- _configInfo = ConfigurationMapper.MapToGlobalConfigInfo(configInfo);
-
- return this;
- }
-
- ///
- /// Registers a callback that is invoked when update information is available.
- /// The callback receives the full and returns
- /// true to skip the update or false to proceed with the automatic upgrade.
- /// Built-in forced-update protection is still applied; if any version is marked as
- /// forcibly required the callback return value is ignored and the update proceeds.
- ///
- ///
- /// A function that receives containing complete update
- /// details and returns true to skip, or false to proceed with the update.
- ///
- /// The current bootstrap instance for fluent method chaining.
- public GeneralClientBootstrap AddListenerUpdatePrecheck(Func func)
- {
- if (func == null) throw new ArgumentNullException(nameof(func));
- _updatePrecheck = func;
- return this;
- }
-
- ///
- /// Add an asynchronous custom operation.
- /// In theory, any custom operation can be done. It is recommended to register the environment check method to ensure
- /// that there are normal dependencies and environments after the update is completed.
- ///
- public GeneralClientBootstrap AddCustomOption(List> funcList)
- {
- Debug.Assert(funcList != null && funcList.Any());
- _customOptions.AddRange(funcList);
- return this;
- }
-
- public GeneralClientBootstrap AddListenerMultiAllDownloadCompleted(
- Action callbackAction)
- => AddListener(callbackAction);
-
- public GeneralClientBootstrap AddListenerMultiDownloadCompleted(
- Action callbackAction)
- => AddListener(callbackAction);
-
- public GeneralClientBootstrap AddListenerMultiDownloadError(
- Action callbackAction)
- => AddListener(callbackAction);
-
- public GeneralClientBootstrap AddListenerMultiDownloadStatistics(
- Action callbackAction)
- => AddListener(callbackAction);
-
- public GeneralClientBootstrap AddListenerException(Action callbackAction)
- => AddListener(callbackAction);
-
- public GeneralClientBootstrap AddListenerUpdateInfo(Action callbackAction)
- => AddListener(callbackAction);
-
- #endregion Public Methods
-
- #region Private Methods
-
- private async Task ExecuteWorkflowAsync()
- {
- try
- {
- Debug.Assert(_configInfo != null);
- if (GetOption(UpdateOption.EnableSilentUpdate))
- {
- GeneralTracer.Info("GeneralClientBootstrap.ExecuteWorkflowAsync: silent update mode enabled, delegating to SilentUpdateMode.");
- await new SilentUpdateMode(
- _configInfo,
- GetOption(UpdateOption.Encoding) ?? Encoding.Default,
- GetOption(UpdateOption.Format) ?? Format.ZIP,
- GetOption(UpdateOption.DownloadTimeOut) ?? 60,
- GetOption(UpdateOption.Patch) ?? true,
- GetOption(UpdateOption.BackUp) ?? true).StartAsync();
- return;
- }
- //Request the upgrade information needed by the client and upgrade end, and determine if an upgrade is necessary.
- GeneralTracer.Info($"GeneralClientBootstrap.ExecuteWorkflowAsync: validating client version={_configInfo.ClientVersion} and upgrade version={_configInfo.UpgradeClientVersion}");
- var mainResp = await VersionService.Validate(_configInfo.UpdateUrl
- , _configInfo.ClientVersion
- , AppType.ClientApp
- , _configInfo.AppSecretKey
- , GetPlatform()
- , _configInfo.ProductId
- , _configInfo.Scheme
- , _configInfo.Token);
-
- var upgradeResp = await VersionService.Validate(_configInfo.UpdateUrl
- , _configInfo.UpgradeClientVersion
- , AppType.UpgradeApp
- , _configInfo.AppSecretKey
- , GetPlatform()
- , _configInfo.ProductId
- , _configInfo.Scheme
- , _configInfo.Token);
-
- _configInfo.IsUpgradeUpdate = CheckUpgrade(upgradeResp);
- _configInfo.IsMainUpdate = CheckUpgrade(mainResp);
- GeneralTracer.Info($"GeneralClientBootstrap.ExecuteWorkflowAsync: version validation completed. IsMainUpdate={_configInfo.IsMainUpdate}, IsUpgradeUpdate={_configInfo.IsUpgradeUpdate}");
-
- var updateInfoArgs = new UpdateInfoEventArgs(mainResp);
- EventManager.Instance.Dispatch(this, updateInfoArgs);
-
- //If the main program needs to be forced to update, the skip will not take effect.
- var isForcibly = CheckForcibly(mainResp.Body) || CheckForcibly(upgradeResp.Body);
- if (CanSkip(isForcibly, updateInfoArgs))
- {
- GeneralTracer.Info("GeneralClientBootstrap.ExecuteWorkflowAsync: update skipped by precheck callback.");
- return;
- }
-
- //black list initialization.
- BlackListManager.Instance?.AddBlackFiles(_configInfo.BlackFiles);
- BlackListManager.Instance?.AddBlackFormats(_configInfo.BlackFormats);
- BlackListManager.Instance?.AddSkipDirectorys(_configInfo.SkipDirectorys);
-
- _configInfo.Encoding = GetOption(UpdateOption.Encoding) ?? Encoding.Default;
- _configInfo.Format = GetOption(UpdateOption.Format) ?? Format.ZIP;
- _configInfo.DownloadTimeOut = GetOption(UpdateOption.DownloadTimeOut) ?? 60;
- _configInfo.DriveEnabled = GetOption(UpdateOption.Drive) ?? false;
- _configInfo.PatchEnabled = GetOption(UpdateOption.Patch) ?? true;
- _configInfo.TempPath = StorageManager.GetTempDirectory("main_temp");
- _configInfo.BackupDirectory = Path.Combine(_configInfo.InstallPath,
- $"{StorageManager.DirectoryName}{_configInfo.ClientVersion}");
-
- _configInfo.UpdateVersions = _configInfo.IsUpgradeUpdate ? upgradeResp.Body.OrderBy(x => x.ReleaseDate).ToList() : [];
-
- if (_configInfo.IsMainUpdate)
- {
- _configInfo.LastVersion = mainResp.Body.OrderBy(x => x.ReleaseDate).Last().Version;
- GeneralTracer.Info($"GeneralClientBootstrap.ExecuteWorkflowAsync: main update required, LastVersion={_configInfo.LastVersion}");
-
- var failed = CheckFail(_configInfo.LastVersion);
- if (failed)
- {
- GeneralTracer.Warn($"GeneralClientBootstrap.ExecuteWorkflowAsync: version {_configInfo.LastVersion} matches or precedes a known failed upgrade, aborting.");
- return;
- }
-
- // Use ConfigurationMapper to create ProcessInfo instead of manual parameter passing
- // This centralizes the complex parameter mapping logic and reduces errors
- var processInfo = ConfigurationMapper.MapToProcessInfo(
- _configInfo,
- mainResp.Body,
- BlackListManager.Instance.BlackFormats.ToList(),
- BlackListManager.Instance.BlackFiles.ToList(),
- BlackListManager.Instance.SkipDirectorys.ToList());
-
- _configInfo.ProcessInfo =
- JsonSerializer.Serialize(processInfo, ProcessInfoJsonContext.Default.ProcessInfo);
- }
-
- if (GetOption(UpdateOption.BackUp) ?? true)
- {
- GeneralTracer.Info($"GeneralClientBootstrap.ExecuteWorkflowAsync: backing up from {_configInfo.InstallPath} to {_configInfo.BackupDirectory}");
- StorageManager.Backup(_configInfo.InstallPath
- , _configInfo.BackupDirectory
- , BlackListManager.Instance.SkipDirectorys);
- GeneralTracer.Info("GeneralClientBootstrap.ExecuteWorkflowAsync: backup completed.");
- }
-
- StrategyFactory();
- GeneralTracer.Info($"GeneralClientBootstrap.ExecuteWorkflowAsync: executing update scenario. IsUpgradeUpdate={_configInfo.IsUpgradeUpdate}, IsMainUpdate={_configInfo.IsMainUpdate}");
- switch (_configInfo.IsUpgradeUpdate)
- {
- case true when _configInfo.IsMainUpdate:
- //Both upgrade and main program update.
- GeneralTracer.Info("GeneralClientBootstrap.ExecuteWorkflowAsync: both upgrade and main update required - downloading and executing.");
- await Download();
- await _strategy?.ExecuteAsync()!;
- _strategy?.StartApp();
- break;
- case true when !_configInfo.IsMainUpdate:
- //Upgrade program update.
- GeneralTracer.Info("GeneralClientBootstrap.ExecuteWorkflowAsync: upgrade-only update - downloading and executing.");
- await Download();
- await _strategy?.ExecuteAsync()!;
- break;
- case false when _configInfo.IsMainUpdate:
- //Main program update.
- GeneralTracer.Info("GeneralClientBootstrap.ExecuteWorkflowAsync: main update only - starting application (updater will handle it).");
- _strategy?.StartApp();
- break;
- }
- }
- catch (Exception exception)
- {
- GeneralTracer.Error("The ExecuteWorkflowAsync method in the GeneralClientBootstrap class throws an exception." , exception);
- EventManager.Instance.Dispatch(this, new ExceptionEventArgs(exception, exception.Message));
- }
- }
-
- private async Task Download()
- {
- var manager = new DownloadManager(_configInfo.TempPath, _configInfo.Format, _configInfo.DownloadTimeOut);
- manager.MultiAllDownloadCompleted += OnMultiAllDownloadCompleted;
- manager.MultiDownloadCompleted += OnMultiDownloadCompleted;
- manager.MultiDownloadError += OnMultiDownloadError;
- manager.MultiDownloadStatistics += OnMultiDownloadStatistics;
- foreach (var versionInfo in _configInfo.UpdateVersions)
- {
- manager.Add(new DownloadTask(manager, versionInfo));
- }
-
- await manager.LaunchTasksAsync();
- }
-
- private int GetPlatform()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return PlatformType.Windows;
- }
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return PlatformType.Linux;
- }
-
- return -1;
- }
-
- ///
- /// Check if there has been a recent update failure.(only windows)
- ///
- ///
- ///
- private bool CheckFail(string version)
- {
- /*
- Read the version number of the last failed upgrade from the system environment variables, then compare it with the version number of the current request.
- If it is less than or equal to the failed version number, do not perform the update.
- */
- var fail = Environments.GetEnvironmentVariable("UpgradeFail");
- if (string.IsNullOrEmpty(fail) || string.IsNullOrEmpty(version))
- return false;
-
- var failVersion = new Version(fail);
- var lastVersion = new Version(version);
- return failVersion >= lastVersion;
- }
-
- ///
- /// Determine whether the current version verification result indicates that an update is needed.
- ///
- ///
- ///
- private bool CheckUpgrade(VersionRespDTO? response)
- {
- if (response is null)
- return false;
-
- if (response.Body is null)
- return false;
-
- if (response.Code == 200)
- return response.Body.Count > 0;
-
- return false;
- }
-
- ///
- /// During the iteration process, if any version requires a mandatory update, all the update content from this request should be updated.
- ///
- ///
- ///
- private bool CheckForcibly(List? versions)
- {
- if (versions == null)
- return false;
-
- foreach (var item in versions)
- {
- if (item.IsForcibly == true)
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// User decides if update is required.
- ///
- /// Whether the update is forcibly required; if true, skip is never allowed.
- /// Update information passed to the precheck callback.
- /// true to skip (abort) the update; false to continue execution.
- private bool CanSkip(bool isForcibly, UpdateInfoEventArgs updateInfo)
- {
- if (isForcibly) return false;
- return _updatePrecheck?.Invoke(updateInfo) == true;
- }
-
- private void CallSmallBowlHome(string processName)
- {
- if (string.IsNullOrWhiteSpace(processName))
- return;
-
- try
- {
- var processes = Process.GetProcessesByName(processName);
- if (processes.Length == 0)
- {
- GeneralTracer.Info($"No process named {processName} found.");
- return;
- }
-
- foreach (var process in processes)
- {
- GeneralTracer.Info($"Killing process {process.ProcessName} (ID: {process.Id})");
- process.Kill();
- }
- }
- catch (Exception ex)
- {
- GeneralTracer.Error("The CallSmallBowlHome method in the GeneralClientBootstrap class throws an exception.", ex);
- EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, ex.Message));
- }
- }
-
- ///
- /// Performs all injected custom operations.
- ///
- ///
- private void ExecuteCustomOptions()
- {
- if (!_customOptions.Any()) return;
-
- foreach (var option in _customOptions)
- {
- if (!option.Invoke())
- {
- var exception = new Exception($"{nameof(option)}Execution failure!");
- var args = new ExceptionEventArgs(exception, exception.Message);
- GeneralTracer.Error("The ExecuteCustomOptions method in the GeneralClientBootstrap class throws an exception.", exception);
- EventManager.Instance.Dispatch(this, args);
- }
- }
- }
-
- protected override void ExecuteStrategy() => throw new NotImplementedException();
-
- protected override Task ExecuteStrategyAsync() => throw new NotImplementedException();
-
- protected override GeneralClientBootstrap StrategyFactory()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- _strategy = new WindowsStrategy();
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- _strategy = new LinuxStrategy();
- else
- throw new PlatformNotSupportedException("The current operating system is not supported!");
-
- _strategy?.Create(_configInfo!);
- return this;
- }
-
- private GeneralClientBootstrap AddListener(Action callbackAction) where TArgs : EventArgs
- {
- Debug.Assert(callbackAction != null);
- EventManager.Instance.AddListener(callbackAction);
- return this;
- }
-
- private void OnMultiDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e)
- {
- var message = ObjectTranslator.GetPacketHash(e.Version);
- GeneralTracer.Info($"Multi download statistics, {message}[BytesReceived]:{e.BytesReceived} [ProgressPercentage]:{e.ProgressPercentage} [Remaining]:{e.Remaining} [TotalBytesToReceive]:{e.TotalBytesToReceive} [Speed]:{e.Speed}");
- EventManager.Instance.Dispatch(sender, e);
- }
-
- private void OnMultiDownloadCompleted(object sender, MultiDownloadCompletedEventArgs e)
- {
- var message = ObjectTranslator.GetPacketHash(e.Version);
- GeneralTracer.Info($"Multi download completed, {message}[IsComplated]:{e.IsComplated}.");
- EventManager.Instance.Dispatch(sender, e);
- }
-
- private void OnMultiDownloadError(object sender, MultiDownloadErrorEventArgs e)
- {
- var message = ObjectTranslator.GetPacketHash(e.Version);
- GeneralTracer.Error($"Multi download error {message}.", e.Exception);
- EventManager.Instance.Dispatch(sender, e);
- }
-
- private void OnMultiAllDownloadCompleted(object sender, MultiAllDownloadCompletedEventArgs e)
- {
- GeneralTracer.Info($"Multi all download completed {e.IsAllDownloadCompleted}.");
- EventManager.Instance.Dispatch(sender, e);
- }
-
- #endregion Private Methods
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
index ef52d3ce..80505796 100644
--- a/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
+++ b/src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
@@ -27,9 +27,7 @@ namespace GeneralUpdate.Core;
///
///
///
-/// Migration from GeneralClientBootstrap:
-/// Replace new GeneralClientBootstrap().SetConfig(cfg).LaunchAsync() with
-/// new GeneralUpdateBootstrap().Option(UpdateOptions.AppType, AppType.ClientApp).SetConfig(cfg).LaunchAsync() .
+/// For Client mode, use Option(UpdateOptions.AppType, AppType.ClientApp) .
///
public class GeneralUpdateBootstrap : AbstractBootstrap
{
@@ -72,7 +70,7 @@ private async Task LaunchWithStrategy(IStrategy roleStra
clientStrat.UseUpdatePrecheck(_updatePrecheck);
foreach (var opt in _customOptions)
clientStrat.UseCustomOption(opt);
- CallSmallBowlHome(_configInfo.Bowl);
+ await CallSmallBowlHomeAsync(_configInfo.Bowl).ConfigureAwait(false);
}
roleStrategy.Create(_configInfo);
@@ -141,7 +139,7 @@ private async Task LaunchOssAsync()
GlobalConfigInfoOSSJsonContext.Default.GlobalConfigInfoOSS);
Environments.SetEnvironmentVariable("GlobalConfigInfoOSS", serialized);
Process.Start(appPath);
- Process.GetCurrentProcess().Kill();
+ await GracefulExit.CurrentProcessAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -241,7 +239,7 @@ private void InitBlackList()
BlackListManager.Instance.AddSkipDirectorys(_configInfo.SkipDirectorys);
}
- private void CallSmallBowlHome(string processName)
+ private async Task CallSmallBowlHomeAsync(string processName)
{
if (string.IsNullOrWhiteSpace(processName)) return;
try
@@ -249,13 +247,13 @@ private void CallSmallBowlHome(string processName)
var processes = Process.GetProcessesByName(processName);
foreach (var process in processes)
{
- GeneralTracer.Info($"Killing process {process.ProcessName} (ID: {process.Id})");
- process.Kill();
+ GeneralTracer.Info($"Shutting down process {process.ProcessName} (ID: {process.Id})");
+ await GracefulExit.ShutdownAsync(process).ConfigureAwait(false);
}
}
catch (Exception ex)
{
- GeneralTracer.Error("CallSmallBowlHome failed.", ex);
+ GeneralTracer.Error("CallSmallBowlHomeAsync failed.", ex);
}
}
diff --git a/src/c#/GeneralUpdate.Core/Configuration/ConfigurationMapper.cs b/src/c#/GeneralUpdate.Core/Configuration/ConfigurationMapper.cs
index a36a1c0e..4a3fdfc8 100644
--- a/src/c#/GeneralUpdate.Core/Configuration/ConfigurationMapper.cs
+++ b/src/c#/GeneralUpdate.Core/Configuration/ConfigurationMapper.cs
@@ -75,7 +75,7 @@ public static ProcessInfo MapToProcessInfo(
throw new ArgumentNullException(nameof(source), "GlobalConfigInfo source cannot be null");
// Create ProcessInfo with all required parameters in a single location
- // This replaces the error-prone manual parameter passing in GeneralClientBootstrap
+ // Centralized parameter mapping for ProcessInfo creation
return new ProcessInfo(
appName: source.MainAppName, // Maps MainAppName to ProcessInfo.AppName
installPath: source.InstallPath,
diff --git a/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
index 893703f5..deb66ecc 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -49,7 +49,7 @@ public async Task ExecuteAsync()
try
{
GeneralTracer.Debug("ClientUpdateStrategy.ExecuteAsync start.");
- CallSmallBowlHome(_configInfo.Bowl);
+ await CallSmallBowlHomeAsync(_configInfo.Bowl);
ExecuteCustomOptions();
await ExecuteWorkflowAsync();
}
@@ -88,7 +88,7 @@ public ClientUpdateStrategy UseCustomOption(Func func)
private async Task ExecuteWorkflowAsync()
{
- // Silent mode — delegate to SilentUpdateMode
+ // Silent mode ¡ª delegate to SilentUpdateMode
// (encoding/format/timeout are read from _configInfo)
var defaultEncoding = Encoding.UTF8;
var defaultTimeout = 60;
@@ -167,18 +167,18 @@ private async Task ExecuteStandardWorkflowAsync(Encoding encoding, int timeout)
switch (_configInfo.IsUpgradeUpdate)
{
case true when _configInfo.IsMainUpdate:
- GeneralTracer.Info("ClientUpdateStrategy: both upgrade+main — downloading and executing.");
+ GeneralTracer.Info("ClientUpdateStrategy: both upgrade+main ¡ª downloading and executing.");
await DownloadAsync();
await _osStrategy.ExecuteAsync();
_osStrategy.StartApp();
break;
case true when !_configInfo.IsMainUpdate:
- GeneralTracer.Info("ClientUpdateStrategy: upgrade-only — downloading and executing.");
+ GeneralTracer.Info("ClientUpdateStrategy: upgrade-only ¡ª downloading and executing.");
await DownloadAsync();
await _osStrategy.ExecuteAsync();
break;
case false when _configInfo.IsMainUpdate:
- GeneralTracer.Info("ClientUpdateStrategy: main-only — starting updater.");
+ GeneralTracer.Info("ClientUpdateStrategy: main-only ¡ª starting updater.");
_osStrategy.StartApp();
break;
}
@@ -194,6 +194,8 @@ private static IStrategy ResolveOsStrategy()
return new WindowsStrategy();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return new LinuxStrategy();
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ return new MacStrategy();
throw new PlatformNotSupportedException("The current operating system is not supported!");
}
@@ -254,7 +256,7 @@ private static int GetPlatform()
return -1;
}
- private void CallSmallBowlHome(string processName)
+ private async Task CallSmallBowlHomeAsync(string processName)
{
if (string.IsNullOrWhiteSpace(processName)) return;
try
@@ -263,13 +265,13 @@ private void CallSmallBowlHome(string processName)
if (processes.Length == 0) return;
foreach (var process in processes)
{
- GeneralTracer.Info($"Killing process {process.ProcessName} (ID: {process.Id})");
- process.Kill();
+ GeneralTracer.Info($"Shutting down process {process.ProcessName} (ID: {process.Id})");
+ await GracefulExit.ShutdownAsync(process).ConfigureAwait(false);
}
}
catch (Exception ex)
{
- GeneralTracer.Error("CallSmallBowlHome failed.", ex);
+ GeneralTracer.Error("CallSmallBowlHomeAsync failed.", ex);
}
}
diff --git a/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs
index 7361d0a0..8a70e89e 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs
@@ -76,7 +76,7 @@ public override void StartApp()
{
GeneralTracer.Info("GeneralUpdate.Core.LinuxStrategy.StartApp: releasing tracer and terminating updater process.");
GeneralTracer.Dispose();
- Process.GetCurrentProcess().Kill();
+ GracefulExit.CurrentProcessAsync().GetAwaiter().GetResult();
}
}
@@ -136,4 +136,4 @@ private void ExecuteScript()
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, "Script execution failed"));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs
index 4b7522f2..3b9ae7ca 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/UpgradeUpdateStrategy.cs
@@ -1,16 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
+using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using GeneralUpdate.Core.Configuration;
-using GeneralUpdate.Core.Download;
using GeneralUpdate.Core.Event;
-using GeneralUpdate.Core.FileSystem;
-using GeneralUpdate.Core.Network;
namespace GeneralUpdate.Core.Strategy;
@@ -21,15 +14,16 @@ namespace GeneralUpdate.Core.Strategy;
///
/// This is the AppType.UpgradeApp role strategy. It composes an OS-specific
/// strategy for platform operations (Windows/Linux/Mac).
+///
+/// Design: Upgrade does NOT validate versions or download packages.
+/// The client has already validated versions, downloaded all packages, and
+/// passed the results via ProcessInfo. Upgrade only applies updates and
+/// starts the main application — zero network.
///
public class UpgradeUpdateStrategy : IStrategy
{
private GlobalConfigInfo? _configInfo;
private IStrategy? _osStrategy;
- private readonly Download.Abstractions.IDownloadOrchestrator? _orchestrator;
- private readonly DiffMode _diffMode = DiffMode.Serial;
-
- public UpgradeUpdateStrategy(Download.Abstractions.IDownloadOrchestrator? orchestrator = null) { _orchestrator = orchestrator; }
public void Create(GlobalConfigInfo parameter)
{
@@ -44,23 +38,22 @@ public async Task ExecuteAsync()
try
{
GeneralTracer.Debug("UpgradeUpdateStrategy.ExecuteAsync start.");
- StrategyFactory();
- var mode = UpdateMode.Default; // should come from config
- switch (mode)
+ ApplyRuntimeOptions();
+ _osStrategy!.Create(_configInfo);
+
+ // Apply updates via OS-specific pipeline (Hash → Compress → Patch)
+ if (_configInfo.UpdateVersions?.Count > 0)
+ {
+ GeneralTracer.Info($"UpgradeUpdateStrategy: applying {_configInfo.UpdateVersions.Count} update(s).");
+ await _osStrategy.ExecuteAsync();
+ }
+ else
{
- case UpdateMode.Default:
- ApplyRuntimeOptions();
- _osStrategy!.Create(_configInfo);
- await DownloadAsync();
- await _osStrategy.ExecuteAsync();
- break;
- case UpdateMode.Scripts:
- await ExecuteScriptsWorkflowAsync();
- break;
- default:
- throw new ArgumentOutOfRangeException();
+ GeneralTracer.Info("UpgradeUpdateStrategy: no updates to apply, starting application directly.");
}
+
+ _osStrategy.StartApp();
}
catch (Exception ex)
{
@@ -79,57 +72,6 @@ public void StartApp()
_osStrategy?.StartApp();
}
- #region Scripts Mode Workflow
-
- private async Task ExecuteScriptsWorkflowAsync()
- {
- GeneralTracer.Info($"UpgradeUpdateStrategy: validating version. Url={_configInfo!.UpdateUrl}, Version={_configInfo.ClientVersion}");
-
- var mainResp = await VersionService.Validate(
- _configInfo.UpdateUrl, _configInfo.ClientVersion,
- AppType.ClientApp, _configInfo.AppSecretKey,
- GetPlatform(), _configInfo.ProductId,
- _configInfo.Scheme, _configInfo.Token);
-
- _configInfo.IsMainUpdate = CheckUpgrade(mainResp);
- EventManager.Instance.Dispatch(this, new UpdateInfoEventArgs(mainResp));
-
- if (CheckForcibly(mainResp.Body) == false && ShouldSkip())
- {
- GeneralTracer.Info("UpgradeUpdateStrategy: update skipped.");
- return;
- }
-
- InitBlackList();
- ApplyRuntimeOptions();
-
- _configInfo.TempPath = StorageManager.GetTempDirectory("main_temp");
- _configInfo.BackupDirectory = Path.Combine(
- _configInfo.InstallPath,
- $"{StorageManager.DirectoryName}{_configInfo.ClientVersion}");
-
- _configInfo.UpdateVersions = mainResp.Body!
- .OrderBy(x => x.ReleaseDate).ToList();
-
- Backup();
-
- _osStrategy!.Create(_configInfo);
-
- if (_configInfo.IsMainUpdate)
- {
- GeneralTracer.Info("UpgradeUpdateStrategy: main update required.");
- await DownloadAsync();
- await _osStrategy.ExecuteAsync();
- }
- else
- {
- GeneralTracer.Info("UpgradeUpdateStrategy: no update needed, starting application.");
- _osStrategy.StartApp();
- }
- }
-
- #endregion
-
#region Helpers
private static IStrategy ResolveOsStrategy()
@@ -138,57 +80,15 @@ private static IStrategy ResolveOsStrategy()
return new WindowsStrategy();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return new LinuxStrategy();
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ return new MacStrategy();
throw new PlatformNotSupportedException("The current operating system is not supported!");
}
- private void StrategyFactory()
- {
- _osStrategy ??= ResolveOsStrategy();
- }
-
private void ApplyRuntimeOptions()
{
_configInfo!.Encoding = Encoding.UTF8;
_configInfo.Format = Format.ZIP;
- _configInfo.DownloadTimeOut = 60;
- }
-
- private void InitBlackList()
- {
- BlackListManager.Instance.AddBlackFiles(_configInfo!.BlackFiles);
- BlackListManager.Instance.AddBlackFormats(_configInfo.BlackFormats);
- BlackListManager.Instance.AddSkipDirectorys(_configInfo.SkipDirectorys);
- }
-
- private void Backup()
- {
- GeneralTracer.Info($"UpgradeUpdateStrategy: backing up {_configInfo!.InstallPath} -> {_configInfo.BackupDirectory}");
- StorageManager.Backup(_configInfo.InstallPath, _configInfo.BackupDirectory,
- BlackListManager.Instance.SkipDirectorys);
- }
-
- private async Task DownloadAsync()
- {
- var manager = new DownloadManager(
- _configInfo!.TempPath, _configInfo.Format, _configInfo.DownloadTimeOut);
- foreach (var version in _configInfo.UpdateVersions)
- manager.Add(new DownloadTask(manager, version));
- await manager.LaunchTasksAsync();
- }
-
- private static bool ShouldSkip() => false;
-
- private static bool CheckUpgrade(VersionRespDTO? response)
- => response?.Code == 200 && response.Body?.Count > 0;
-
- private static bool CheckForcibly(IEnumerable? versions)
- => versions?.Any(v => v.IsForcibly == true) == true;
-
- private static int GetPlatform()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return PlatformType.Windows;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return PlatformType.Linux;
- return -1;
}
#endregion
diff --git a/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs
index cfefcfbe..0bf48ace 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs
@@ -78,8 +78,8 @@ public override void StartApp()
{
GeneralTracer.Info("GeneralUpdate.Core.WindowsStrategy.StartApp: releasing tracer and terminating updater process.");
GeneralTracer.Dispose();
- Process.GetCurrentProcess().Kill();
+ GracefulExit.CurrentProcessAsync().GetAwaiter().GetResult();
}
}
}
-}
\ No newline at end of file
+}