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 +}