diff --git a/README.md b/README.md index 1c5fd317ec7c4da9b6503c9c067673c98925bc52..3477a6e61d60353f9b5a500b17a170bc087866d3 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 0dd7f5ec0f2acbf523a44d5e90f1645c7339b01a..0785198be120357df2d22ba974ba00008d2f1ae3 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 c1b58af2d61cb7f2b397668ebc6323d3ee409126..29de55e45aa4c84b814a9477f2c61eca600f3c90 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/c#/GeneralUpdate.ClientCore/Driver/.gitkeep b/src/c#/GeneralUpdate.ClientCore/Driver/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/c#/GeneralUpdate.ClientCore/GeneralClientOSS.cs b/src/c#/GeneralUpdate.ClientCore/GeneralClientOSS.cs index 0a4128c75e1cd451263ba5faac34d042428c9143..fb5af438f60ea570ec7baea88446aefdb7fc0b91 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 15b2e58532cdbeff68f2dcb7e5a13af19d406c09..9a5012e291376521dd17c060ec98d339dc933127 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 bb5a531de37bf516fc35df0e28f157cf9edabad3..5a06c2b3a66dc7c96b4c94def7b87fb0d3206fd2 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 b31adf4eaeb09c8341572512a6fa75267c2d7dc6..f093e351b1c9daf24713493fd7611b62dca0dab4 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 40f787fca3024c41f01b707a6d9ae22c40d988e1..8b5e76a15ee23e3d59e967fdec98924f94c2b03c 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 c714325ef59d370127a7de987cac0e08a98e8c6b..2e5919865d40cec0dd60c1ad40c065e7c30837fc 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 5dab4f9b8aadf89cc1eca976728f8ee3e2ad8190..ae70b982e43e8146fb73ccba56369d9857a7e5a3 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 ddbc8e2b3e80cee892f4526f1c2e350aac7c26db..8d5046639a71463872976ab7548ef9ee150d3614 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 9757268ded07bfaba96f278901e25cfc4a6e0514..3db8ae943ccfa1e43d1c2491d884bc36328c39f9 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 3c3fa856ea19fdf4b51d72f886f76a2ffda6ac26..6fab56078c773e99418c6884b63fb8c890415c89 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 5b7ac961dc8c74880f08b581d65cebbf15774271..5e1f8507d5288dd7c0433768093ca469f69aa836 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 0000000000000000000000000000000000000000..a3c71f340a172c49ab31ca74d5eb76fffcad918b --- /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 9cfee0753ca5c77a9de3fe6a99226f85fcb19c59..00402ca0c51e296f6fc061c1ba18c87b93e658ee 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 9c015e4aab3868d1a6d9f9481a488703a287996d..6a550f33ee88cd8b3c020df506da1c4c465b8f0a 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 0606ad05e3dbbbc2c114ce47ccc98671f3a19e3d..a04224ce98eb7eb5cffd1d9c17e5ef6c2429241f 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 32d4c09d4ccd4bb00220fca6d229859d6f4e21c1..b0e981b0ed6d0a4ada22bf9ec837ecc6e87a9e70 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 102d432469e2282f3e0d6b76d84899c78608bd85..3183a4ebc345e5125d6053717f2a9264b8d40b5e 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 1b8d13ad8bda0a527e74931ff6ec74167e28c309..1b9b902796474ffddbdaca5364c3a3fe8a66e668 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 1e762ad73b401b330612231dc0f797d01a5c3131..3bd060bd1d58a73e871f2ebfa59d8fe262918a77 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 64bebd9f838c81e16c533a5a4d59ea6f6ea60e13..cc9c554c429756450a2bbc1c813b4952670b2dfd 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 } }