From 958c28c6955c5845ba241575fa6372de2743ff61 Mon Sep 17 00:00:00 2001 From: ChsBuffer <33744752+chsbuffer@users.noreply.github.com> Date: Sun, 11 Jul 2021 03:59:40 +0800 Subject: [PATCH] Refactor Updater Fix Download Update File block UI --- Netch/Controllers/UpdateChecker.cs | 17 ++-- Netch/Forms/MainForm.cs | 57 ++++++++++-- Netch/Services/Updater.cs | 135 ++++++----------------------- Netch/Utils/Utils.cs | 12 ++- Netch/Utils/WebUtil.cs | 36 ++++++++ 5 files changed, 130 insertions(+), 127 deletions(-) diff --git a/Netch/Controllers/UpdateChecker.cs b/Netch/Controllers/UpdateChecker.cs index a10fb1a4..d870b5a8 100644 --- a/Netch/Controllers/UpdateChecker.cs +++ b/Netch/Controllers/UpdateChecker.cs @@ -1,6 +1,4 @@ -using Netch.Models.GitHubRelease; -using Netch.Utils; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -8,6 +6,8 @@ using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Netch.Models.GitHubRelease; +using Netch.Utils; using Serilog; namespace Netch.Controllers @@ -71,13 +71,9 @@ namespace Netch.Controllers } } - public static void GetLatestUpdateFileNameAndHash(out string fileName, out string sha256, string? keyword = null) + public static (string fileName, string sha256) GetLatestUpdateFileNameAndHash(string? keyword = null) { - fileName = string.Empty; - sha256 = string.Empty; - - var matches = Regex.Matches(LatestRelease.body, @"^\| (?.*) \| (?.*) \|\r?$", RegexOptions.Multiline) - .Skip(2); + var matches = Regex.Matches(LatestRelease.body, @"^\| (?.*) \| (?.*) \|\r?$", RegexOptions.Multiline).Skip(2); /* Skip(2) @@ -87,8 +83,7 @@ namespace Netch.Controllers Match match = keyword == null ? matches.First() : matches.First(m => m.Groups["filename"].Value.Contains(keyword)); - fileName = match.Groups["filename"].Value; - sha256 = match.Groups["sha256"].Value; + return (match.Groups["filename"].Value, match.Groups["sha256"].Value); } public static string GetLatestReleaseContent() diff --git a/Netch/Forms/MainForm.cs b/Netch/Forms/MainForm.cs index cb6ddef2..9fbb8552 100644 --- a/Netch/Forms/MainForm.cs +++ b/Netch/Forms/MainForm.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; @@ -428,15 +429,59 @@ namespace Netch.Forms try { - await Updater.DownloadAndUpdate(Path.Combine(Global.NetchDir, "data"), - Global.NetchDir, - (_, args) => BeginInvoke(new Action(() => NewVersionLabel.Text = $"{args.ProgressPercentage}%"))); + var progress = new Progress(); + progress.ProgressChanged += (_, percentage) => { NewVersionLabel.Text = $"{percentage}%"; }; + + string downloadDirectory = Path.Combine(Global.NetchDir, "data"); + + var (updateFileName, sha256) = UpdateChecker.GetLatestUpdateFileNameAndHash(); + var updateFileUrl = UpdateChecker.LatestRelease.assets[0].browser_download_url!; + + var updateFileFullName = Path.Combine(downloadDirectory, updateFileName); + var updater = new Updater(updateFileFullName, Global.NetchDir); + + var downloaded = false; + if (File.Exists(updateFileFullName)) + if (Utils.Utils.SHA256CheckSum(updateFileFullName) == sha256) + downloaded = true; + else + File.Delete(updateFileFullName); + + if (!downloaded) + { + try + { + await WebUtil.DownloadFileAsync(updateFileUrl, updateFileFullName, progress); + } + catch (Exception e1) + { + Log.Warning(e1, "Download Update File Failed"); + throw new MessageException($"Download Update File Failed: {e1.Message}"); + } + + if (Utils.Utils.SHA256CheckSum(updateFileFullName) != sha256) + throw new MessageException(i18N.Translate("The downloaded file has the wrong hash")); + } + + ModeHelper.SuspendWatcher = true; + await Stop(); + await Configuration.SaveAsync(); + + // Update + await Task.Run(updater.ApplyUpdate); + + // release mutex, exit + Netch.SingleInstance.Dispose(); + Process.Start(Global.NetchExecutable); + Environment.Exit(0); + } + catch (MessageException exception) + { + NotifyTip(exception.Message, info: false); } catch (Exception exception) { - if (exception is not MessageException) - Log.Error(exception, "更新未处理异常"); - + Log.Error(exception, "更新未处理异常"); NotifyTip(exception.Message, info: false); } finally diff --git a/Netch/Services/Updater.cs b/Netch/Services/Updater.cs index 224a93b3..8af00f9b 100644 --- a/Netch/Services/Updater.cs +++ b/Netch/Services/Updater.cs @@ -1,13 +1,9 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; using System.Text; -using System.Threading.Tasks; -using Netch.Controllers; using Netch.Models; using Netch.Properties; using Netch.Utils; @@ -17,76 +13,24 @@ namespace Netch.Services { public class Updater { - #region Download Update and apply update - - /// - /// Download Update and apply update (all arguments are FullPath) - /// - /// - /// - /// - /// - /// - public static async Task DownloadAndUpdate(string downloadDirectory, - string installDirectory, - DownloadProgressChangedEventHandler onDownloadProgressChanged, - string? keyword = null) - { - UpdateChecker.GetLatestUpdateFileNameAndHash(out var updateFileName, out var sha256, keyword); - - // update file Full Path - var updateFile = Path.Combine(downloadDirectory, updateFileName); - var updater = new Updater(updateFile, installDirectory); - - if (File.Exists(updateFile)) - { - if (Utils.Utils.SHA256CheckSum(updateFile) == sha256) - { - await updater.ApplyUpdate(); - return; - } - - File.Delete(updateFile); - } - - DownloadUpdateFile(onDownloadProgressChanged, updateFile, sha256); - await updater.ApplyUpdate(); - } - - /// - /// Download Update File - /// - /// - /// - /// - /// - private static void DownloadUpdateFile(DownloadProgressChangedEventHandler onDownloadProgressChanged, string fileFullPath, string sha256) - { - using WebClient client = new(); - try - { - client.DownloadProgressChanged += onDownloadProgressChanged; - client.DownloadFile(new Uri(UpdateChecker.LatestRelease.assets[0].browser_download_url), fileFullPath); - } - finally - { - client.DownloadProgressChanged -= onDownloadProgressChanged; - } - - if (Utils.Utils.SHA256CheckSum(fileFullPath) != sha256) - throw new MessageException(i18N.Translate("The downloaded file has the wrong hash")); - } + #region Static #endregion - private readonly string _updateFile; - private readonly string _installDirectory; - private readonly string _tempDirectory; + #region Class - private Updater(string updateFile, string installDirectory) + private string UpdateFile { get; } + + private string InstallDirectory { get; } + + private readonly string _tempDirectory; + private static readonly string[] KeepDirectories = { "data", "mode\\Custom", "logging" }; + private static readonly string[] KeepFiles = { ModeHelper.DisableModeDirectoryFileName }; + + internal Updater(string updateFile, string installDirectory) { - _updateFile = updateFile; - _installDirectory = installDirectory; + UpdateFile = updateFile; + InstallDirectory = installDirectory; _tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(_tempDirectory); @@ -94,60 +38,40 @@ namespace Netch.Services #region Apply Update - private async Task ApplyUpdate() + internal void ApplyUpdate() { - var mainForm = Global.MainForm; - - #region PreUpdate - - ModeHelper.SuspendWatcher = true; - // Stop and Save - await mainForm.Stop(); - await Configuration.SaveAsync(); - - #endregion - - // extract Update file to {tempDirectory}\extract var extractPath = Path.Combine(_tempDirectory, "extract"); + int exitCode; - if ((exitCode = Extract(extractPath, true)) != 0) + if ((exitCode = Extract(extractPath)) != 0) throw new MessageException(i18N.Translate($"7za unexpectedly exited. ({exitCode})")); + // update archive file must have a top-level directory "Netch" var updateDirectory = Path.Combine(extractPath, "Netch"); if (!Directory.Exists(updateDirectory)) throw new MessageException(i18N.Translate("Update file top-level directory not exist")); + // {_tempDirectory}/extract/Netch/Netch.exe var updateMainProgramFilePath = Path.Combine(updateDirectory, "Netch.exe"); if (!File.Exists(updateMainProgramFilePath)) throw new MessageException(i18N.Translate($"Update file main program not exist")); - - // rename install directory files with .old suffix unless in keep folders MarkFilesOld(); // move {tempDirectory}\extract\Netch to install folder - MoveAllFilesOver(updateDirectory, _installDirectory); - - // release mutex, exit - mainForm.Invoke(new Action(Netch.SingleInstance.Dispose)); - Process.Start(Global.NetchExecutable); - Environment.Exit(0); + MoveFilesOver(updateDirectory, InstallDirectory); } private void MarkFilesOld() { - var keepDirs = new[] { "data", "mode\\Custom", "logging" }; + var keepDirFullPath = KeepDirectories.Select(d => Path.Combine(InstallDirectory, d)).ToImmutableList(); - var keepDirFullPath = keepDirs.Select(d => Path.Combine(_installDirectory, d)).ToImmutableList(); - - foreach (var file in Directory.GetFiles(_installDirectory, "*", SearchOption.AllDirectories)) + foreach (var file in Directory.GetFiles(InstallDirectory, "*", SearchOption.AllDirectories)) { - // skip keep files if (keepDirFullPath.Any(p => file.StartsWith(p))) continue; - // skip disable state files - if (Path.GetFileName(file) is ModeHelper.DisableModeDirectoryFileName) + if (KeepFiles.Contains(Path.GetFileName(file))) continue; try @@ -162,7 +86,7 @@ namespace Netch.Services } } - private int Extract(string destDirName, bool overwrite) + private int Extract(string destDirName) { // release 7za.exe to {tempDirectory}\7za.exe var temp7za = Path.Combine(_tempDirectory, "7za.exe"); @@ -170,12 +94,7 @@ namespace Netch.Services if (!File.Exists(temp7za)) File.WriteAllBytes(temp7za, Resources._7za); - // run 7za - var argument = new StringBuilder(); - argument.Append($" x \"{_updateFile}\" -o\"{destDirName}\""); - if (overwrite) - argument.Append(" -y"); - + var argument = new StringBuilder($" x \"{UpdateFile}\" -o\"{destDirName}\" -y"); var process = Process.Start(new ProcessStartInfo(temp7za, argument.ToString()) { UseShellExecute = false @@ -185,7 +104,7 @@ namespace Netch.Services return process.ExitCode; } - private static void MoveAllFilesOver(string source, string target) + private static void MoveFilesOver(string source, string target) { foreach (string directory in Directory.GetDirectories(source)) { @@ -194,7 +113,7 @@ namespace Netch.Services if (!Directory.Exists(Path.Combine(target, dirName))) Directory.CreateDirectory(Path.Combine(target, dirName)); - MoveAllFilesOver(directory, Path.Combine(target, dirName)); + MoveFilesOver(directory, Path.Combine(target, dirName)); } foreach (string file in Directory.GetFiles(source)) @@ -223,5 +142,7 @@ namespace Netch.Services } #endregion + + #endregion } } \ No newline at end of file diff --git a/Netch/Utils/Utils.cs b/Netch/Utils/Utils.cs index 2f438cc7..f42ce61b 100644 --- a/Netch/Utils/Utils.cs +++ b/Netch/Utils/Utils.cs @@ -103,16 +103,22 @@ namespace Netch.Utils { try { - var sha256 = SHA256.Create(); using var fileStream = File.OpenRead(filePath); - return string.Concat(sha256.ComputeHash(fileStream).Select(b => b.ToString("x2"))); + return SHA256ComputeCore(fileStream); } - catch + catch (Exception e) { + Log.Warning(e, $"Compute file \"{filePath}\" sha256 failed"); return ""; } } + private static string SHA256ComputeCore(Stream stream) + { + using var sha256 = SHA256.Create(); + return string.Concat(sha256.ComputeHash(stream).Select(b => b.ToString("x2"))); + } + public static string GetFileVersion(string file) { if (File.Exists(file)) diff --git a/Netch/Utils/WebUtil.cs b/Netch/Utils/WebUtil.cs index 3c6fdb9c..52bb30ac 100644 --- a/Netch/Utils/WebUtil.cs +++ b/Netch/Utils/WebUtil.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Net; using System.Text; @@ -90,5 +91,40 @@ namespace Netch.Utils await input.CopyToAsync(fileStream); fileStream.Flush(); } + + public static async Task DownloadFileAsync(string address, string fileFullPath, IProgress progress) + { + await DownloadFileAsync(CreateRequest(address), fileFullPath, progress); + } + + public static async Task DownloadFileAsync(HttpWebRequest req, string fileFullPath, IProgress progress) + { + using var webResponse = (HttpWebResponse)await req.GetResponseAsync(); + await using var input = webResponse.GetResponseStream(); + await using var fileStream = File.OpenWrite(fileFullPath); + + var task = input.CopyToAsync(fileStream); + ReportStream(webResponse.ContentLength, task, fileStream, progress, 200).Forget(); + + await task; + fileStream.Flush(); + progress.Report(100); + } + + private static async Task ReportStream(long total, IAsyncResult downloadTask, Stream stream, IProgress progress, int interval) + { + var n = 0; + while (!downloadTask.IsCompleted) + { + var n1 = (int)((double)stream.Length / total * 100); + if (n != n1) + { + n = n1; + progress.Report(n); + } + + await Task.Delay(interval); + } + } } } \ No newline at end of file