From b0b27dca267ad2f3c307a212f50997732c4f6ed1 Mon Sep 17 00:00:00 2001 From: Juster Zhu Date: Sun, 3 Dec 2023 21:35:21 +0800 Subject: [PATCH] Complete the Windows service --- README.md | 34 +++++ README_en.md | 36 +++++ src/c#/GeneralUpdate.Client/MainPage.xaml.cs | 2 + .../Domain/PO}/.gitkeep | 0 .../Domain/PO/Assembler/.gitkeep | 0 .../GeneralUpdate.ClientCore/Driver/.gitkeep | 0 .../GeneralClientOSS.cs | 2 +- .../GeneralUpdate.ClientCore.csproj | 18 ++- .../Bootstrap/AbstractBootstrap.cs | 3 +- .../Bootstrap/UpdateOption.cs | 9 +- .../Domain/Entity/Packet.cs | 9 +- .../Domain/PO/WillMessagePO.cs | 52 +++++-- .../Pipelines/Context/BaseContext.cs | 8 +- .../Pipelines/Middleware/ConfigMiddleware.cs | 18 +-- .../Pipelines/Middleware/HashMiddleware.cs | 20 +-- .../Middleware/MiddlewareExtensions.cs | 8 +- .../Pipelines/Middleware/PatchMiddleware.cs | 19 +-- .../Middleware/WillMessageMiddleware.cs | 16 ++ .../Pipelines/Middleware/ZipMiddleware.cs | 21 +-- .../Strategys/OSSStrategy.cs | 2 +- .../PlatformWindows/WindowsStrategy.cs | 24 +-- src/c#/GeneralUpdate.Core/Utils/FileUtil.cs | 13 +- .../WillMessage/WillMessageManager.cs | 138 ++++++++++++++---- .../DifferentialCore.cs | 4 +- .../GeneralUpdate.SystemService.csproj | 1 - .../Services/WillMessageService.cs | 135 +++++++++++++---- 26 files changed, 434 insertions(+), 158 deletions(-) rename src/c#/{GeneralUpdate.SystemService/ContentProvider => GeneralUpdate.ClientCore/Domain/PO}/.gitkeep (100%) create mode 100644 src/c#/GeneralUpdate.ClientCore/Domain/PO/Assembler/.gitkeep create mode 100644 src/c#/GeneralUpdate.ClientCore/Driver/.gitkeep create mode 100644 src/c#/GeneralUpdate.Core/Pipelines/Middleware/WillMessageMiddleware.cs diff --git a/README.md b/README.md index 1c5fd31..3477a6e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,40 @@ | OSS | 支持 | 极简化更新,是一套独立的更新机制。只需要在文件服务器中放置version.json的版本配置文件。组件会根据配置文件中的版本信息进行更新下载。(支持Windows,MAUI Android) | | 回滚 | 待测试 | 逐版本更新时会备份每个版本,如果更新失败则逐版本回滚。 | | 驱动更新 | 待测试 | 逐版本更新时会备份每个版本的驱动文件(.inf),如果更新失败则逐版本回滚。 | +| 系统服务 | 待测试 | 开机时和升级时会检查升级是否成功,如果失败则根据遗言还原之前的备份。遗言是更新之前就已经自动创建在C:\generalupdate_willmessages目录下的will_message.json文件。will_message.json的内容是持久化回滚备份的文件目录相关信息。(需要部署GeneralUpdate.SystemService系统服务) | +| 自定义方法列表 | 待测试 | 注入一个自定义方法集合,该集合会在更新启动前执行。执行自定义方法列表如果出现任何异常,将通过异常订阅通知。(推荐在更新之前检查当前软件环境) | + + + +**GeneralUpdate.SystemService发布/部署** + +GeneralUpdate.SystemService是一个windows系统服务,并不是部署在服务端的web api。它的主要作用是监听更新过程,以及更新崩溃之后还原。 + +**发布:** + +推荐发布Single file,如果想发布AOT版本需要移除源码中映射代码。 + +```shell +dotnet publish -r win-x64 -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true --self-contained true +``` + +**创建/部署windows服务:** + +```shell +sc create MyWorkerService binPath="C:\your_path\GeneralUpdate.SystemService.exe" +``` + +**启动已部署的windows服务:** + +```shell +sc start GeneralUpdate.SystemService +``` + +**删除已部署的windows服务:** + +```shell +sc delete GeneralUpdate.SystemService +``` diff --git a/README_en.md b/README_en.md index 0dd7f5e..0785198 100644 --- a/README_en.md +++ b/README_en.md @@ -28,6 +28,42 @@ | OSS(MAUI) | yes | Minimal updates require only the version configuration file of version.json to be placed on the file server. Components are updated and downloaded based on the version information in the configuration file.(Supported windows,MAUI Android) | | Restore | test | Each version is backed up during a version-by-version update and rolled back version-by-version if the update fails. | | Driver upgrade | test | The driver file (.INF) of each version is backed up during the version-by-version update and is rolled back version-by-version if the update fails. | +| System service | test | The upgrade is checked for success at boot and upgrade, and if it fails, the previous backup is restored according to the last word. The last word is that the will_message.json file in the C:\generalupdate_willmessages directory was automatically created before the update. will_message.json is about the file directory of the persistent rollback backup.(need to deploy GeneralUpdate. SystemService system service) | +| A list of custom methods | test | Inject a custom collection of methods that are executed before the update starts. Execute a custom method list, and if there are any exceptions, you will be notified by exception subscription.(It is recommended to check the current software environment before updating) | + + + +**GeneralUpdate.SystemService Publish/Deploy** + +GeneralUpdate.SystemService is a Windows system service, not a web API deployed on the server. Its main purpose is to listen for the update process and restore after an update crash. + +**Publish:** + +It is recommended to release a single file, if you want to release the AOT version, you need to remove the mapping code from the source code. + +```shell +dotnet publish -r win-x64 -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true --self-contained true +``` + +**Create/deploy Windows services:** + +```shell +sc create MyWorkerService binPath="C:\your_path\GeneralUpdate.SystemService.exe" +``` + +**Start the deployed Windows service:** + +```shell +sc start GeneralUpdate.SystemService +``` + +**Delete the deployed Windows service:** + +```shell +sc delete GeneralUpdate.SystemService +``` + + ### 2.Help documentation ### diff --git a/src/c#/GeneralUpdate.Client/MainPage.xaml.cs b/src/c#/GeneralUpdate.Client/MainPage.xaml.cs index c1b58af..29de55e 100644 --- a/src/c#/GeneralUpdate.Client/MainPage.xaml.cs +++ b/src/c#/GeneralUpdate.Client/MainPage.xaml.cs @@ -71,6 +71,8 @@ namespace GeneralUpdate.Client .Option(UpdateOption.DownloadTimeOut, 60) .Option(UpdateOption.Encoding, Encoding.Default) .Option(UpdateOption.Format, Format.ZIP) + .Option(UpdateOption.Drive, true) + .Option(UpdateOption.WillMessage, true) .Strategy() //注入一个func让用户决定是否跳过本次更新,如果是强制更新则不生效 .SetCustomSkipOption(ShowCustomOption) diff --git a/src/c#/GeneralUpdate.SystemService/ContentProvider/.gitkeep b/src/c#/GeneralUpdate.ClientCore/Domain/PO/.gitkeep similarity index 100% rename from src/c#/GeneralUpdate.SystemService/ContentProvider/.gitkeep rename to src/c#/GeneralUpdate.ClientCore/Domain/PO/.gitkeep diff --git a/src/c#/GeneralUpdate.ClientCore/Domain/PO/Assembler/.gitkeep b/src/c#/GeneralUpdate.ClientCore/Domain/PO/Assembler/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/c#/GeneralUpdate.ClientCore/Driver/.gitkeep b/src/c#/GeneralUpdate.ClientCore/Driver/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/c#/GeneralUpdate.ClientCore/GeneralClientOSS.cs b/src/c#/GeneralUpdate.ClientCore/GeneralClientOSS.cs index 0a4128c..fb5af43 100644 --- a/src/c#/GeneralUpdate.ClientCore/GeneralClientOSS.cs +++ b/src/c#/GeneralUpdate.ClientCore/GeneralClientOSS.cs @@ -32,7 +32,7 @@ namespace GeneralUpdate.ClientCore var versionsFilePath = Path.Combine(basePath, configParams.VersionFileName); DownloadFile(configParams.Url + "/" + configParams.VersionFileName, versionsFilePath); if (!File.Exists(versionsFilePath)) return; - var versions = FileUtil.ReadJsonFile>(versionsFilePath); + var versions = FileUtil.GetJson>(versionsFilePath); if (versions == null || versions.Count == 0) return; versions = versions.OrderByDescending(x => x.PubTime).ToList(); var newVersion = versions.First(); diff --git a/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj b/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj index 15b2e58..9a5012e 100644 --- a/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj +++ b/src/c#/GeneralUpdate.ClientCore/GeneralUpdate.ClientCore.csproj @@ -42,9 +42,12 @@ + + + @@ -91,7 +94,9 @@ + + @@ -100,6 +105,13 @@ + + + + + + + @@ -123,11 +135,13 @@ + + @@ -140,6 +154,7 @@ + @@ -200,9 +215,9 @@ - + @@ -273,6 +288,7 @@ + diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/AbstractBootstrap.cs b/src/c#/GeneralUpdate.Core/Bootstrap/AbstractBootstrap.cs index bb5a531..5a06c2b 100644 --- a/src/c#/GeneralUpdate.Core/Bootstrap/AbstractBootstrap.cs +++ b/src/c#/GeneralUpdate.Core/Bootstrap/AbstractBootstrap.cs @@ -63,7 +63,8 @@ namespace GeneralUpdate.Core.Bootstrap Packet.DownloadTimeOut = GetOption(UpdateOption.DownloadTimeOut); Packet.AppName = $"{Packet.AppName ?? GetOption(UpdateOption.MainApp)}{EXECUTABLE_FILE}"; Packet.TempPath = $"{FileUtil.GetTempDirectory(Packet.LastVersion)}{Path.DirectorySeparatorChar}"; - Packet.IsRestore = GetOption(UpdateOption.Restore); + Packet.DriveEnabled = GetOption(UpdateOption.Drive); + Packet.WillMessageEnabled = GetOption(UpdateOption.WillMessage); var manager = new DownloadManager(Packet.TempPath, Packet.Format, Packet.DownloadTimeOut); manager.MultiAllDownloadCompleted += OnMultiAllDownloadCompleted; manager.MultiDownloadCompleted += OnMultiDownloadCompleted; diff --git a/src/c#/GeneralUpdate.Core/Bootstrap/UpdateOption.cs b/src/c#/GeneralUpdate.Core/Bootstrap/UpdateOption.cs index b31adf4..f093e35 100644 --- a/src/c#/GeneralUpdate.Core/Bootstrap/UpdateOption.cs +++ b/src/c#/GeneralUpdate.Core/Bootstrap/UpdateOption.cs @@ -40,9 +40,14 @@ namespace GeneralUpdate.Core.Bootstrap public static readonly UpdateOption DownloadTimeOut = ValueOf("DOWNLOADTIMEOUT"); /// - /// Should the rollback current backup feature be enabled. + /// Whether to enable the driver upgrade function. /// - public static readonly UpdateOption Restore = ValueOf("RESTORE"); + public static readonly UpdateOption Drive = ValueOf("DRIVE"); + + /// + /// Whether open note function, if you want to start needs to be synchronized to deploy 'GeneralUpdate. SystemService' service. + /// + public static readonly UpdateOption WillMessage = ValueOf("WILLMESSAGE"); #endregion parameter configuration diff --git a/src/c#/GeneralUpdate.Core/Domain/Entity/Packet.cs b/src/c#/GeneralUpdate.Core/Domain/Entity/Packet.cs index 40f787f..8b5e76a 100644 --- a/src/c#/GeneralUpdate.Core/Domain/Entity/Packet.cs +++ b/src/c#/GeneralUpdate.Core/Domain/Entity/Packet.cs @@ -133,8 +133,13 @@ namespace GeneralUpdate.Core.Domain.Entity public List BlackFormats { get; set; } /// - /// Whether to enable automatic backup during the upgrade. + /// Whether to enable the driver upgrade function. /// - public bool IsRestore { get; set; } + public bool DriveEnabled { get; set; } + + /// + /// Whether open note function, if you want to start needs to be synchronized to deploy 'GeneralUpdate. SystemService' service. + /// + public bool WillMessageEnabled { get; set; } } } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Core/Domain/PO/WillMessagePO.cs b/src/c#/GeneralUpdate.Core/Domain/PO/WillMessagePO.cs index c714325..2e59198 100644 --- a/src/c#/GeneralUpdate.Core/Domain/PO/WillMessagePO.cs +++ b/src/c#/GeneralUpdate.Core/Domain/PO/WillMessagePO.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace GeneralUpdate.Core.Domain.PO { - internal enum WillMessageStatus + public enum WillMessageStatus { /// /// Processing has not yet begun. @@ -19,11 +19,9 @@ namespace GeneralUpdate.Core.Domain.PO Failed } - internal class BackupPO + public class BackupPO { - public string Name { get; set; } - - public string InstallPath { get; set; } + public string AppPath { get; set; } public string BackupPath { get; set; } @@ -32,14 +30,48 @@ namespace GeneralUpdate.Core.Domain.PO public int AppType { get; set; } } - internal class WillMessagePO + public class WillMessagePO { - public Stack> Message { get; set; } + public Stack Message { get; private set; } + public WillMessageStatus Status { get; private set; } + public DateTime CreateTime { get; private set; } + public DateTime ChangeTime { get; private set; } + + private WillMessagePO() { } + + public class Builder + { + private readonly WillMessagePO _messagePO = new WillMessagePO(); - public WillMessageStatus Status { get; set; } + public Builder SetMessage(Stack message) + { + _messagePO.Message = message ?? throw new ArgumentNullException($"{nameof(message)} cannot be null"); + return this; + } - public DateTime CreateTime { get; set; } + public Builder SetStatus(WillMessageStatus status) + { + _messagePO.Status = status; + return this; + } - public DateTime ChangeTime { get; set; } + public Builder SetCreateTime(DateTime createTime) + { + _messagePO.CreateTime = createTime; + return this; + } + + public Builder SetChangeTime(DateTime changeTime) + { + _messagePO.ChangeTime = changeTime; + return this; + } + + public WillMessagePO Build() + { + return _messagePO; + } + } } + } diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Context/BaseContext.cs b/src/c#/GeneralUpdate.Core/Pipelines/Context/BaseContext.cs index 5dab4f9..ae70b98 100644 --- a/src/c#/GeneralUpdate.Core/Pipelines/Context/BaseContext.cs +++ b/src/c#/GeneralUpdate.Core/Pipelines/Context/BaseContext.cs @@ -16,6 +16,7 @@ namespace GeneralUpdate.Core.Pipelines.Context public string TargetPath { get; private set; } public string SourcePath { get; private set; } public string Format { get; private set; } + public int AppType { get; private set; } public Encoding Encoding { get; private set; } public List BlackFiles { get; private set; } public List BlackFileFormats { get; private set; } @@ -74,11 +75,16 @@ namespace GeneralUpdate.Core.Pipelines.Context return this; } + public Builder SetAppType(int type) + { + _context.AppType = type; + return this; + } + public BaseContext Build() { return _context; } } } - } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ConfigMiddleware.cs b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ConfigMiddleware.cs index ddbc8e2..8d50466 100644 --- a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ConfigMiddleware.cs +++ b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ConfigMiddleware.cs @@ -1,6 +1,5 @@ using GeneralUpdate.Core.Domain.Enum; using GeneralUpdate.Core.Events; -using GeneralUpdate.Core.Events.CommonArgs; using GeneralUpdate.Core.Events.MultiEventArgs; using GeneralUpdate.Core.Pipelines.Context; using GeneralUpdate.Differential.Config; @@ -14,19 +13,10 @@ namespace GeneralUpdate.Core.Pipelines.Middleware { public async Task InvokeAsync(BaseContext context, MiddlewareStack stack) { - try - { - EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Hash, "Update configuration file ...")); - await ConfigFactory.Instance.Deploy(); - var node = stack.Pop(); - if (node != null) await node.Next.Invoke(context, stack); - } - catch (Exception ex) - { - var exception = new Exception($"{ex.Message} !", ex.InnerException); - EventManager.Instance.Dispatch>(this, new ExceptionEventArgs(exception)); - throw exception; - } + EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Hash, "Update configuration file ...")); + await ConfigFactory.Instance.Deploy(); + var node = stack.Pop(); + if (node != null) await node.Next.Invoke(context, stack); } } } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/HashMiddleware.cs b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/HashMiddleware.cs index 9757268..3db8ae9 100644 --- a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/HashMiddleware.cs +++ b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/HashMiddleware.cs @@ -13,20 +13,12 @@ namespace GeneralUpdate.Core.Pipelines.Middleware { public async Task InvokeAsync(BaseContext context, MiddlewareStack stack) { - Exception exception = null; - try - { - EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Hash, "Verify file MD5 code ...")); - var version = context.Version; - bool isVerify = VerifyFileHash(context.ZipfilePath, version.Hash); - if (!isVerify) exception = new Exception($"The update package hash code is inconsistent ! version-{version.Version} hash-{version.Hash} ."); - var node = stack.Pop(); - if (node != null) await node.Next.Invoke(context, stack); - } - catch (Exception ex) - { - EventManager.Instance.Dispatch>(this, new ExceptionEventArgs(exception ?? ex)); - } + EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Hash, "Verify file MD5 code ...")); + var version = context.Version; + bool isVerify = VerifyFileHash(context.ZipfilePath, version.Hash); + if (!isVerify) throw new Exception($"The update package hash code is inconsistent ! version-{version.Version} hash-{version.Hash} ."); + var node = stack.Pop(); + if (node != null) await node.Next.Invoke(context, stack); } private bool VerifyFileHash(string fileName, string hash) diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/MiddlewareExtensions.cs b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/MiddlewareExtensions.cs index 3c3fa85..6fab560 100644 --- a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/MiddlewareExtensions.cs +++ b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/MiddlewareExtensions.cs @@ -15,12 +15,16 @@ namespace GeneralUpdate.Core.Pipelines.Middleware private const DynamicallyAccessedMemberTypes MiddlewareAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods; - public static IPipelineBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)] TMiddleware>(this IPipelineBuilder pipeline) => pipeline.UseMiddleware(typeof(TMiddleware)); + public static IPipelineBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)] TMiddleware>(this IPipelineBuilder pipeline) => pipeline.UseMiddleware(typeof(TMiddleware),true); + + public static IPipelineBuilder UseMiddlewareIf<[DynamicallyAccessedMembers(MiddlewareAccessibility)] TMiddleware>(this IPipelineBuilder pipeline,bool condition) => pipeline.UseMiddleware(typeof(TMiddleware), condition); public static IPipelineBuilder UseMiddleware( this IPipelineBuilder pipeline, - [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware) + [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, bool condition) { + if(!condition) return pipeline; + if (!typeof(IMiddleware).IsAssignableFrom(middleware)) throw new ArgumentException($"The middleware type must implement \"{typeof(IMiddleware)}\"."); diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/PatchMiddleware.cs b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/PatchMiddleware.cs index 5b7ac96..5e1f850 100644 --- a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/PatchMiddleware.cs +++ b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/PatchMiddleware.cs @@ -1,6 +1,5 @@ using GeneralUpdate.Core.Domain.Enum; using GeneralUpdate.Core.Events; -using GeneralUpdate.Core.Events.CommonArgs; using GeneralUpdate.Core.Events.MultiEventArgs; using GeneralUpdate.Core.Pipelines.Context; using GeneralUpdate.Differential; @@ -13,19 +12,11 @@ namespace GeneralUpdate.Core.Pipelines.Middleware { public async Task InvokeAsync(BaseContext context, MiddlewareStack stack) { - try - { - EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Patch, "Update patch file ...")); - DifferentialCore.Instance.SetBlocklist(context.BlackFiles, context.BlackFileFormats); - await DifferentialCore.Instance.Dirty(context.SourcePath, context.TargetPath); - var node = stack.Pop(); - if (node != null) await node.Next.Invoke(context, stack); - } - catch (Exception ex) - { - var exception = new Exception($"{ex.Message} !", ex.InnerException); - EventManager.Instance.Dispatch>(this, new ExceptionEventArgs(exception)); - } + EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Patch, "Update patch file ...")); + DifferentialCore.Instance.SetBlocklist(context.BlackFiles, context.BlackFileFormats); + await DifferentialCore.Instance.Dirty(context.SourcePath, context.TargetPath); + var node = stack.Pop(); + if (node != null) await node.Next.Invoke(context, stack); } } } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/WillMessageMiddleware.cs b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/WillMessageMiddleware.cs new file mode 100644 index 0000000..a3c71f3 --- /dev/null +++ b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/WillMessageMiddleware.cs @@ -0,0 +1,16 @@ +using GeneralUpdate.Core.Pipelines.Context; +using GeneralUpdate.Core.WillMessage; +using System.Threading.Tasks; + +namespace GeneralUpdate.Core.Pipelines.Middleware +{ + internal class WillMessageMiddleware : IMiddleware + { + public async Task InvokeAsync(BaseContext context, MiddlewareStack stack) + { + WillMessageManager.Instance.Backup(context.SourcePath, context.TargetPath, context.Version.ToString(), context.AppType); + var node = stack.Pop(); + if (node != null) await node.Next.Invoke(context, stack); + } + } +} diff --git a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ZipMiddleware.cs b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ZipMiddleware.cs index 9cfee07..00402ca 100644 --- a/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ZipMiddleware.cs +++ b/src/c#/GeneralUpdate.Core/Pipelines/Middleware/ZipMiddleware.cs @@ -14,21 +14,12 @@ namespace GeneralUpdate.Core.Pipelines.Middleware { public async Task InvokeAsync(BaseContext context, MiddlewareStack stack) { - Exception exception = null; - try - { - EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Updatefile, "In the unzipped file ...")); - var version = context.Version; - bool isUnzip = UnZip(context); - if (!isUnzip) throw exception = new Exception($"Unzip file failed , Version-{version.Version} MD5-{version.Hash} !"); - //await ConfigFactory.Instance.Scan(context.SourcePath, context.TargetPath); - var node = stack.Pop(); - if (node != null) await node.Next.Invoke(context, stack); - } - catch (Exception ex) - { - EventManager.Instance.Dispatch>(this, new ExceptionEventArgs(exception ?? ex)); - } + EventManager.Instance.Dispatch>(this, new MultiDownloadProgressChangedEventArgs(context.Version, ProgressType.Updatefile, "In the unzipped file ...")); + var version = context.Version; + bool isUnzip = UnZip(context); + if (!isUnzip) throw new Exception($"Unzip file failed , Version-{version.Version} Hash-{version.Hash} !"); + var node = stack.Pop(); + if (node != null) await node.Next.Invoke(context, stack); } /// diff --git a/src/c#/GeneralUpdate.Core/Strategys/OSSStrategy.cs b/src/c#/GeneralUpdate.Core/Strategys/OSSStrategy.cs index 9c015e4..6a550f3 100644 --- a/src/c#/GeneralUpdate.Core/Strategys/OSSStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategys/OSSStrategy.cs @@ -49,7 +49,7 @@ namespace GeneralUpdate.Core.Strategys if (!File.Exists(jsonPath)) throw new FileNotFoundException(jsonPath); //2.Parse the JSON version configuration file content. - var versions = FileUtil.ReadJsonFile>(jsonPath); + var versions = FileUtil.GetJson>(jsonPath); if (versions == null) throw new NullReferenceException(nameof(versions)); //3.Download version by version according to the version of the configuration file. diff --git a/src/c#/GeneralUpdate.Core/Strategys/PlatformWindows/WindowsStrategy.cs b/src/c#/GeneralUpdate.Core/Strategys/PlatformWindows/WindowsStrategy.cs index 0606ad0..a04224c 100644 --- a/src/c#/GeneralUpdate.Core/Strategys/PlatformWindows/WindowsStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategys/PlatformWindows/WindowsStrategy.cs @@ -6,7 +6,7 @@ using GeneralUpdate.Core.Pipelines; using GeneralUpdate.Core.Pipelines.Context; using GeneralUpdate.Core.Pipelines.Middleware; using GeneralUpdate.Core.Utils; -using GeneralUpdate.Differential; +using GeneralUpdate.Core.WillMessage; using System; using System.Diagnostics; using System.IO; @@ -47,7 +47,7 @@ namespace GeneralUpdate.Core.Strategys.PlatformWindows { var patchPath = FileUtil.GetTempDirectory(PATCHS); var zipFilePath = $"{Packet.TempPath}{version.Name}{Packet.Format}"; - + var context = new BaseContext.Builder() .SetVersion(version) .SetZipfilePath(zipFilePath) @@ -57,11 +57,14 @@ namespace GeneralUpdate.Core.Strategys.PlatformWindows .SetEncoding(Packet.Encoding) .SetBlackFiles(Packet.BlackFiles) .SetBlackFileFormats(Packet.BlackFormats) + .SetAppType(Packet.AppType) .Build(); var pipelineBuilder = new PipelineBuilder(context). UseMiddleware(). UseMiddleware(). + UseMiddlewareIf(Packet.DriveEnabled). + UseMiddlewareIf(Packet.WillMessageEnabled). UseMiddleware(); await pipelineBuilder.Launch(); } @@ -87,11 +90,11 @@ namespace GeneralUpdate.Core.Strategys.PlatformWindows { case AppType.ClientApp: Environment.SetEnvironmentVariable("ProcessBase64", Packet.ProcessBase64, EnvironmentVariableTarget.Machine); - WaitForProcessToStart(path, TimeSpan.FromSeconds(60), Restore); + WaitForProcessToStart(path, 20, ()=> WillMessageManager.Instance.Check()); break; case AppType.UpgradeApp: - WaitForProcessToStart(path, TimeSpan.FromSeconds(60), Restore); + WaitForProcessToStart(path, 20, () => WillMessageManager.Instance.Check()); break; } return true; @@ -109,7 +112,7 @@ namespace GeneralUpdate.Core.Strategys.PlatformWindows public override string GetPlatform() => PlatformType.Windows; - #endregion Public Methods +#endregion Public Methods #region Private Methods @@ -139,25 +142,24 @@ namespace GeneralUpdate.Core.Strategys.PlatformWindows /// Process objects to monitor /// The maximum interval for waiting for the process to start (The default value is 60 seconds). /// - private void WaitForProcessToStart(string applicationPath, TimeSpan timeout, Action callbackAction = null) + private void WaitForProcessToStart(string applicationPath, int timeout, Action callbackAction = null) { using (var process = Process.Start(applicationPath)) { var startTime = DateTime.UtcNow; - while (DateTime.UtcNow - startTime < timeout) + var timeSpan = TimeSpan.FromSeconds(timeout); + while (DateTime.UtcNow - startTime < timeSpan) { - Thread.Sleep(3 * 1000); + Thread.Sleep(2 * 1000); if (!process.HasExited) { - callbackAction?.Invoke(applicationPath); + callbackAction?.Invoke(); return; } } } } - private void Restore(string targetDir) => DifferentialCore.Instance.Restore(targetDir); - #endregion Private Methods } } \ No newline at end of file diff --git a/src/c#/GeneralUpdate.Core/Utils/FileUtil.cs b/src/c#/GeneralUpdate.Core/Utils/FileUtil.cs index 32d4c09..b0e981b 100644 --- a/src/c#/GeneralUpdate.Core/Utils/FileUtil.cs +++ b/src/c#/GeneralUpdate.Core/Utils/FileUtil.cs @@ -104,7 +104,7 @@ namespace GeneralUpdate.Core.Utils } } - public static void CreateJsonFile(string targetPath,string fileName,T obj) + public static void CreateJson(string targetPath,string fileName,T obj) { if (!Directory.Exists(targetPath)) Directory.CreateDirectory(targetPath); var fileFullPath = Path.Combine(targetPath,fileName); @@ -113,7 +113,7 @@ namespace GeneralUpdate.Core.Utils File.WriteAllText(fileFullPath, jsonString); } - public static T ReadJsonFile(string path) + public static T GetJson(string path) { if (File.Exists(path)) { @@ -126,9 +126,14 @@ namespace GeneralUpdate.Core.Utils return default(T); } - public static void DeleteFile(string path) + /// + /// Delete the backup file directory and recursively delete all backup content. + /// + public static void DeleteDir(string path) { - if (File.Exists(path)) File.Delete(path); + if (string.IsNullOrWhiteSpace(path)) return; + if (Directory.Exists(path)) + Directory.Delete(path, true); } } diff --git a/src/c#/GeneralUpdate.Core/WillMessage/WillMessageManager.cs b/src/c#/GeneralUpdate.Core/WillMessage/WillMessageManager.cs index 102d432..3183a4e 100644 --- a/src/c#/GeneralUpdate.Core/WillMessage/WillMessageManager.cs +++ b/src/c#/GeneralUpdate.Core/WillMessage/WillMessageManager.cs @@ -1,11 +1,9 @@ using GeneralUpdate.Core.Domain.PO; using GeneralUpdate.Core.Utils; -using GeneralUpdate.Differential.ContentProvider; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; namespace GeneralUpdate.Core.WillMessage { @@ -15,10 +13,17 @@ namespace GeneralUpdate.Core.WillMessage internal const string DEFULT_WILL_MESSAGE_DIR = @"C:\generalupdate_willmessages"; internal const string DEFULT_WILL_MESSAGE_FILE = "will_message.json"; + internal const string BACKUP_ROOT_PATH = @"C:\generalupdate_backup"; + private string _packetPath; + private string _appPath; + private string _backupPath; + private Stack _backupStack = new Stack(); private string _willMessageFile; private WillMessagePO _willMessage; + private bool _isFirstTime = true; + private static WillMessageManager _instance; private readonly static object _instanceLock = new object(); @@ -56,66 +61,135 @@ namespace GeneralUpdate.Core.WillMessage internal WillMessagePO GetWillMessage(string path = null) { - _willMessageFile = string.IsNullOrWhiteSpace(path) ? GetFilePath() : path; - return _willMessage = FileUtil.ReadJsonFile(_willMessageFile); + _willMessageFile = string.IsNullOrWhiteSpace(path) ? GetWillMessagePath() : path; + return _willMessage = FileUtil.GetJson(_willMessageFile); } internal void Clear() { - FileUtil.DeleteFile(_willMessageFile); + _packetPath = null; + _appPath = null; + _backupPath = null; _willMessage = null; - DeleteRootDir(); + _willMessageFile = null; + _backupStack.Clear(); + FileUtil.DeleteDir(DEFULT_WILL_MESSAGE_DIR); + FileUtil.DeleteDir(BACKUP_ROOT_PATH); } - internal async Task> Backup(string appPath, string packetPath, string version,int appType) + internal void Backup(string appPath, string packetPath, string version,int appType) { if (!Directory.Exists(BACKUP_ROOT_PATH)) Directory.CreateDirectory(BACKUP_ROOT_PATH); - var versionDir = Path.Combine(BACKUP_ROOT_PATH, version); + var versionDir = Path.Combine(BACKUP_ROOT_PATH, version, appType == 1 ? "ClientApp" : "UpgradeApp"); if (!Directory.Exists(versionDir)) Directory.CreateDirectory(versionDir); - //Take the left tree as the center to match the files that are not in the right tree . - var fileProvider = new FileProvider(); - var nodes = await fileProvider.Compare(appPath, packetPath); - var backups = new List(); - foreach (var node in nodes.Item3) + _appPath = appPath; + _packetPath = packetPath; + _backupPath = versionDir; + ProcessDirectory(_packetPath, _appPath, _backupPath); + _backupStack.Push(new BackupPO { Version = version, AppType = appType, AppPath = _appPath, BackupPath = _backupPath }); + } + + internal void Restore() + { + if (_willMessage == null || _willMessage.Message == null) return; + while (_willMessage.Message.Any()) { - backups.Add(new BackupPO { Name = node.Name , Version = version , AppType = appType, InstallPath = node.Path , BackupPath = versionDir }); + var message = _willMessage.Message.Pop(); + _appPath = message.AppPath; + _backupPath = message.BackupPath; + ProcessDirectory(_backupPath, _backupPath, _appPath); } - return backups; } - private void BuilderWillMessage() + internal void Builder() { - _willMessage = new WillMessagePO(); - _willMessage.ChangeTime = DateTime.Now; - _willMessage.CreateTime = DateTime.Now; - _willMessage.Status = WillMessageStatus.NotStarted; + if (!_backupStack.Any()) return; + + _willMessage = new WillMessagePO.Builder() + .SetMessage(_backupStack) + .SetStatus(WillMessageStatus.NotStarted) + .SetCreateTime(DateTime.Now) + .SetChangeTime(DateTime.Now) + .Build(); + FileUtil.CreateJson(Path.Combine(DEFULT_WILL_MESSAGE_DIR, DateTime.Now.ToString("yyyyMMdd")), DEFULT_WILL_MESSAGE_FILE, _willMessage); + } + + internal void Check() + { + var message = GetWillMessage(); + if (message == null) return; + if (_isFirstTime && message?.Status == WillMessageStatus.NotStarted) + { + Restore(); + _isFirstTime = false; + return; + } + + switch (message?.Status) + { + case WillMessageStatus.NotStarted: + return; + case WillMessageStatus.Failed: + Restore(); + break; + case WillMessageStatus.Completed: + Clear(); + break; + } } #endregion #region Private Methods - private string GetFilePath() => Path.Combine(DEFULT_WILL_MESSAGE_DIR, $"{DateTime.Now.ToString("yyyyMMdd")}_{DEFULT_WILL_MESSAGE_FILE}"); + private string GetWillMessagePath() => Path.Combine(DEFULT_WILL_MESSAGE_DIR, DateTime.Now.ToString("yyyyMMdd"), DEFULT_WILL_MESSAGE_FILE); - private void Create(WillMessagePO willMessage) + private void ProcessDirectory(string targetDirectory, string basePath, string destPath) { - if (willMessage == null) return; - _willMessage = willMessage; - FileUtil.CreateJsonFile(DEFULT_WILL_MESSAGE_DIR, $"{DateTime.Now.ToString("yyyyMMdd")}_{DEFULT_WILL_MESSAGE_FILE}", willMessage); + var fileNames = Directory.GetFiles(targetDirectory); + foreach (string fileName in fileNames) + { + ProcessFile(fileName, basePath, destPath); + } + + var subdirectoryEntries = Directory.GetDirectories(targetDirectory); + foreach (string subdirectory in subdirectoryEntries) + { + ProcessDirectory(subdirectory, basePath, destPath); + } + } + + private void ProcessFile(string path, string basePath, string destPath) + { + var relativePath = GetRelativePath(basePath, path); + var sourceFilePath = Path.Combine(basePath, relativePath); + var destFilePath = Path.Combine(destPath, relativePath); + + if (File.Exists(sourceFilePath)) + { + var destDirPath = Path.GetDirectoryName(destFilePath); + if (!Directory.Exists(destDirPath)) + { + Directory.CreateDirectory(destDirPath); + } + + File.Copy(sourceFilePath, destFilePath, true); + } } - /// - /// Delete the backup file directory and recursively delete all backup content. - /// - private void DeleteRootDir() + private string GetRelativePath(string fromPath, string toPath) { - if (string.IsNullOrWhiteSpace(BACKUP_ROOT_PATH)) return; - if (Directory.Exists(BACKUP_ROOT_PATH)) - Directory.Delete(BACKUP_ROOT_PATH, true); + var fromUri = new Uri(fromPath); + var toUri = new Uri(toPath); + + var relativeUri = fromUri.MakeRelativeUri(toUri); + var relativePath = Uri.UnescapeDataString(relativeUri.ToString()); + + return relativePath.Replace('/', Path.DirectorySeparatorChar); } #endregion diff --git a/src/c#/GeneralUpdate.Differential/DifferentialCore.cs b/src/c#/GeneralUpdate.Differential/DifferentialCore.cs index 1b8d13a..1b9b902 100644 --- a/src/c#/GeneralUpdate.Differential/DifferentialCore.cs +++ b/src/c#/GeneralUpdate.Differential/DifferentialCore.cs @@ -122,7 +122,7 @@ namespace GeneralUpdate.Differential //If a file is found that needs to be deleted, a list of files is written to the update package. var exceptFiles = await fileProvider.Except(appPath, targetPath); if(exceptFiles != null && exceptFiles.Count() > 0) - FileUtil.CreateJsonFile(patchPath, DELETE_FILES_NAME, exceptFiles); + FileUtil.CreateJson(patchPath, DELETE_FILES_NAME, exceptFiles); var factory = new GeneralZipFactory(); _compressProgressCallback = compressProgressCallback; @@ -156,7 +156,7 @@ namespace GeneralUpdate.Differential var deleteListJson = patchFiles.FirstOrDefault(i=>i.Name.Equals(DELETE_FILES_NAME)); if (deleteListJson != null) { - var deleteFiles = FileUtil.ReadJsonFile>(deleteListJson.FullName); + var deleteFiles = FileUtil.GetJson>(deleteListJson.FullName); var hashAlgorithm = new Sha256HashAlgorithm(); foreach (var file in deleteFiles) { diff --git a/src/c#/GeneralUpdate.SystemService/GeneralUpdate.SystemService.csproj b/src/c#/GeneralUpdate.SystemService/GeneralUpdate.SystemService.csproj index 1e762ad..3bd060b 100644 --- a/src/c#/GeneralUpdate.SystemService/GeneralUpdate.SystemService.csproj +++ b/src/c#/GeneralUpdate.SystemService/GeneralUpdate.SystemService.csproj @@ -21,6 +21,5 @@ - diff --git a/src/c#/GeneralUpdate.SystemService/Services/WillMessageService.cs b/src/c#/GeneralUpdate.SystemService/Services/WillMessageService.cs index 64bebd9..cc9c554 100644 --- a/src/c#/GeneralUpdate.SystemService/Services/WillMessageService.cs +++ b/src/c#/GeneralUpdate.SystemService/Services/WillMessageService.cs @@ -1,5 +1,8 @@ -using GeneralUpdate.Core.Domain.PO; -using GeneralUpdate.Core.WillMessage; +using GeneralUpdate.Core.WillMessage; +using System.Diagnostics; +#if WINDOWS +using System.Runtime.InteropServices; +#endif namespace GeneralUpdate.SystemService.Services { @@ -7,64 +10,136 @@ namespace GeneralUpdate.SystemService.Services { #region Private Members +#if WINDOWS + const uint SMTO_ABORTIFHUNG = 0x0002; + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); +#endif + private readonly string? _path; private FileSystemWatcher _fileWatcher; + private ILogger _logger; - #endregion +#endregion #region Constructors - public WillMessageService(IConfiguration configuration) => _path = configuration.GetValue("WatcherPath"); - - #endregion + public WillMessageService(IConfiguration configuration, ILogger logger) + { + _path = configuration.GetValue("WatcherPath"); + _logger = logger; + } - #region Public Properties #endregion #region Public Methods + public override Task StartAsync(CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("Will message check executed."); + WillMessageManager.Instance.Check(); + } + catch (Exception ex) + { + _logger.LogError($"StartAsync error: {ex}!"); + } + return base.StartAsync(cancellationToken); + } + protected override Task ExecuteAsync(CancellationToken stoppingToken) { - stoppingToken.Register(() => OnStopping()); - _fileWatcher = new FileSystemWatcher(_path); - // Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories. - _fileWatcher.NotifyFilter = NotifyFilters.LastWrite; - // Only watch text files. - _fileWatcher.Filter = "*.*"; - _fileWatcher.Changed += OnChanged; - _fileWatcher.EnableRaisingEvents = true; + try + { + _logger.LogInformation("File watcher executed."); + stoppingToken.Register(() => OnStopping()); + _fileWatcher = new FileSystemWatcher(_path); + // Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories. + _fileWatcher.NotifyFilter = NotifyFilters.LastWrite; + // Only watch text files. + _fileWatcher.Filter = "*.*"; + _fileWatcher.Changed += OnChanged; + _fileWatcher.EnableRaisingEvents = true; + } + catch (Exception ex) + { + _logger.LogError($"ExecuteAsync error: {ex}!"); + } return Task.CompletedTask; } + public override Task StopAsync(CancellationToken cancellationToken) + { + try + { + _logger.LogInformation("Will message clear executed."); + WillMessageManager.Instance.Clear(); + } + catch (Exception ex) + { + _logger.LogError($"StopAsync error: {ex}!"); + } + return base.StopAsync(cancellationToken); + } + #endregion #region Private Methods - private void OnChanged(object sender, FileSystemEventArgs e) + private void OnChanged(object sender, FileSystemEventArgs e) { - var message = WillMessageManager.Instance.GetWillMessage(); - if (message == null) return; - switch (message.Status) + try + { + _logger.LogInformation("Will message check executed."); + WillMessageManager.Instance.Check(); + } + catch (Exception ex) { - case WillMessageStatus.NotStarted: - return; - case WillMessageStatus.Failed: - //WillMessageManager.Instance.Restore(); - break; - case WillMessageStatus.Completed: - WillMessageManager.Instance.Clear(); - break; + _logger.LogError($"OnChanged error:{ex}"); } } private void OnStopping() { if (_fileWatcher == null) return; - _fileWatcher.EnableRaisingEvents = false; - _fileWatcher.Dispose(); + try + { + _logger.LogInformation("OnStopping executed."); + _fileWatcher.EnableRaisingEvents = false; + _fileWatcher.Dispose(); + } + catch (Exception ex) + { + _logger.LogError($"OnStopping error:{ex}"); + } } + private void Diagnosis(string processName) + { + try + { + Process[] processes = Process.GetProcesses(); + foreach (Process p in processes) + { + if (string.Equals(processName, p.ProcessName, StringComparison.OrdinalIgnoreCase) && !p.MainWindowHandle.Equals(IntPtr.Zero)) + { +#if WINDOWS + UIntPtr result; + IntPtr sendResult = SendMessageTimeout(p.MainWindowHandle, 0x0, UIntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, 3000, out result); + bool notResponding = sendResult == IntPtr.Zero; + _logger.LogInformation($"Process: {p.ProcessName}, Responding: {!notResponding}"); +#endif + } + } + } + catch (Exception ex) + { + _logger.LogError($"Diagnosis error:{ex}"); + } + } - #endregion +#endregion } } -- Gitee