diff --git a/Netch/Controllers/UpdateChecker.cs b/Netch/Controllers/UpdateChecker.cs index 930e213f..edae28d5 100644 --- a/Netch/Controllers/UpdateChecker.cs +++ b/Netch/Controllers/UpdateChecker.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; -using System.IO; +using System.Linq; using System.Net; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using Netch.Models.GitHubRelease; using Netch.Utils; -using static Netch.Updater.Updater; namespace Netch.Controllers { @@ -24,9 +23,11 @@ namespace Netch.Controllers public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}"; - public static string? LatestVersionNumber; - public static string? LatestVersionUrl; - public static Release? LatestRelease; + public static Release LatestRelease = null!; + + public static string LatestVersionNumber => LatestRelease.tag_name; + + public static string LatestVersionUrl => LatestRelease.html_url; public static event EventHandler? NewVersionFound; @@ -34,7 +35,7 @@ namespace Netch.Controllers public static event EventHandler? NewVersionNotFound; - public static async void Check(bool isPreRelease) + public static async Task Check(bool isPreRelease) { try { @@ -43,10 +44,8 @@ namespace Netch.Controllers var json = await WebUtil.DownloadStringAsync(WebUtil.CreateRequest(url)); - var releases = JsonSerializer.Deserialize>(json); + var releases = JsonSerializer.Deserialize>(json)!; LatestRelease = VersionUtil.GetLatestRelease(releases, isPreRelease); - LatestVersionNumber = LatestRelease.tag_name; - LatestVersionUrl = LatestRelease.html_url; Logging.Info($"Github 最新发布版本: {LatestRelease.tag_name}"); if (VersionUtil.CompareVersion(LatestRelease.tag_name, Version) > 0) { @@ -70,47 +69,31 @@ namespace Netch.Controllers } } - public static async Task DownloadUpdate(DownloadProgressChangedEventHandler onDownloadProgressChanged) + public static bool GetFileNameAndHashFromMarkdownForm(in string text, out string fileName, out string sha256, string? keyword = null) { - using WebClient client = new(); - - var latestVersionDownloadUrl = LatestRelease!.assets[0].browser_download_url; - var tagPage = await client.DownloadStringTaskAsync(LatestVersionUrl!); - var match = Regex.Match(tagPage, @"(?.*)", RegexOptions.Singleline); - - // TODO Replace with regex get basename and sha256 - var fileName = Path.GetFileName(new Uri(latestVersionDownloadUrl).LocalPath); - fileName = fileName.Insert(fileName.LastIndexOf('.'), LatestVersionNumber!); - var fileFullPath = Path.Combine(Global.NetchDir, "data", fileName); - - var sha256 = match.Groups["sha256"].Value; - - if (File.Exists(fileFullPath)) - { - if (Utils.Utils.SHA256CheckSum(fileFullPath) == sha256) - { - UpdateNetch(fileFullPath); - return; - } - - File.Delete(fileFullPath); - } - + IEnumerable matches; try { - client.DownloadProgressChanged += onDownloadProgressChanged; - await client.DownloadFileTaskAsync(new Uri(latestVersionDownloadUrl), fileFullPath); - client.DownloadProgressChanged -= onDownloadProgressChanged; + matches = Regex.Matches(text, @"^\| (?.*) \| (?.*) \|\r?$", RegexOptions.Multiline).Cast().Skip(2); } catch (Exception e) { - throw new Exception(i18N.Translate("Download Update Failed", ": ") + e.Message); + Logging.Error(e.ToString()); + throw new Exception(i18N.Translate("Find update filename and hash failed")); } - if (Utils.Utils.SHA256CheckSum(fileFullPath) != sha256) - throw new Exception(i18N.Translate("The downloaded file has the wrong hash")); + Match match = keyword == null ? matches.First() : matches.First(m => m.Groups["filename"].Value.Contains(keyword)); - UpdateNetch(fileFullPath); + if (match != null) + { + fileName = match.Groups["filename"].Value; + sha256 = match.Groups["sha256"].Value; + return true; + } + + fileName = string.Empty; + sha256 = string.Empty; + return false; } } } \ No newline at end of file diff --git a/Netch/Forms/MainForm.cs b/Netch/Forms/MainForm.cs index c353671d..046e12c6 100644 --- a/Netch/Forms/MainForm.cs +++ b/Netch/Forms/MainForm.cs @@ -1338,7 +1338,7 @@ namespace Netch.Forms Hide(); } - public async void Exit(bool forceExit = false) + public async void Exit(bool forceExit = false, bool saveConfiguration = true) { if (!IsWaiting() && !Global.Settings.StopWhenExited && !forceExit) { @@ -1351,7 +1351,11 @@ namespace Netch.Forms State = State.Terminating; NotifyIcon.Visible = false; Hide(); - Configuration.Save(); + + if (saveConfiguration) + { + Configuration.Save(); + } foreach (var file in new[] {"data\\last.json", "data\\privoxy.conf"}) if (File.Exists(file)) @@ -1411,12 +1415,12 @@ namespace Netch.Forms NewVersionLabel.Visible = true; }; - UpdateChecker.Check(Global.Settings.CheckBetaUpdate); + UpdateChecker.Check(Global.Settings.CheckBetaUpdate).Wait(); } private async void NewVersionLabel_Click(object sender, EventArgs e) { - if (!UpdateChecker.LatestRelease!.assets.Any()) + if (ModifierKeys == Keys.Control || !UpdateChecker.LatestRelease!.assets.Any()) { Utils.Utils.Open(UpdateChecker.LatestVersionUrl!); return; @@ -1436,7 +1440,7 @@ namespace Netch.Forms BeginInvoke(new Action(() => { NewVersionLabel.Text = $"{args.ProgressPercentage}%"; })); } - await UpdateChecker.DownloadUpdate(OnDownloadProgressChanged); + await Updater.Updater.DownloadAndUpdate(Path.Combine(Global.NetchDir, "data"), Global.NetchDir, OnDownloadProgressChanged); } catch (Exception exception) { diff --git a/Netch/Netch.cs b/Netch/Netch.cs index f9a53a42..d4ab6020 100644 --- a/Netch/Netch.cs +++ b/Netch/Netch.cs @@ -28,7 +28,7 @@ namespace Netch Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) + ";" + Path.Combine(Global.NetchDir, "bin"), EnvironmentVariableTarget.Process); - Updater.Updater.CleanOld(); + Updater.Updater.CleanOld(Global.NetchDir); // 预创建目录 var directories = new[] {"mode\\Custom", "data", "i18n", "logging"}; diff --git a/Netch/Updater/Updater.cs b/Netch/Updater/Updater.cs index 188eae1f..d06c500a 100644 --- a/Netch/Updater/Updater.cs +++ b/Netch/Updater/Updater.cs @@ -1,29 +1,56 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Net; using System.Text; -using Netch.Forms; +using System.Threading.Tasks; +using Netch.Controllers; using Netch.Properties; +using Netch.Utils; namespace Netch.Updater { - public static class Updater + public class Updater { - public static void UpdateNetch(string updateFilePath) - { - var tempFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - var extractPath = Path.Combine(tempFolder, "extract"); - int exitCode; - if ((exitCode = Extract(updateFilePath, extractPath, true, tempFolder)) != 0) - { - MessageBoxX.Show($"7za exit with code {exitCode}"); - return; - } + private static IEnumerable _keepDirectory = new List(new[] {"data", "mode\\Custom"}); + private readonly string _targetPath; + private readonly string _tempFolder; + private readonly string _updateFilePath; - foreach (var file in Directory.GetFiles(Global.NetchDir, "*", SearchOption.AllDirectories)) + private Updater(string updateFilePath, string targetPath) + { + _targetPath = targetPath; + _tempFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_tempFolder); + + _updateFilePath = Path.GetFullPath(updateFilePath); + _keepDirectory = _keepDirectory.Select(s => Path.Combine(targetPath, s)); + } + + private void ApplyUpdate() + { + var extractPath = Path.Combine(_tempFolder, "extract"); + int exitCode; + if ((exitCode = Extract(extractPath, true)) != 0) + throw new Exception(i18N.Translate($"7za exit with code {exitCode}")); + + MarkFilesOld(); + + MoveAllFilesOver(Path.Combine(extractPath, "Netch"), _targetPath); + + Configuration.Save(); + Global.Mutex.ReleaseMutex(); + Process.Start(Global.NetchExecutable); + Global.MainForm.Exit(true, false); + } + + private void MarkFilesOld() + { + foreach (var file in Directory.GetFiles(_targetPath, "*", SearchOption.AllDirectories)) { - if (new[] {"data", "mode\\Custom"}.ToList().Any(p => file.StartsWith(Path.Combine(Global.NetchDir, p)))) + if (_keepDirectory.Any(p => file.StartsWith(p))) continue; try @@ -35,31 +62,19 @@ namespace Netch.Updater throw new Exception("Updater wasn't able to rename file: " + file); } } - - MoveDirectory(Path.Combine(extractPath, "Netch"), Global.NetchDir, true); - - Global.Mutex.ReleaseMutex(); - Process.Start(Global.NetchExecutable); - Global.MainForm.Exit(true); } - private static int Extract(string archiveFileName, string destDirName, bool overwrite, string? tempFolder = null) + private int Extract(string destDirName, bool overwrite) { - tempFolder ??= Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - var temp7za = Path.Combine(tempFolder, "7za.exe"); - archiveFileName = Path.GetFullPath(archiveFileName); - destDirName = Path.GetFullPath(destDirName); - - if (!Directory.Exists(tempFolder)) - Directory.CreateDirectory(tempFolder); + var temp7za = Path.Combine(_tempFolder, "7za.exe"); if (!File.Exists(temp7za)) File.WriteAllBytes(temp7za, Resources._7za); var argument = new StringBuilder(); - argument.Append($" x \"{archiveFileName}\" -o\"{destDirName}\" "); + argument.Append($" x \"{_updateFilePath}\" -o\"{destDirName}\""); if (overwrite) - argument.Append(" -y "); + argument.Append(" -y"); var process = Process.Start(new ProcessStartInfo { @@ -67,41 +82,35 @@ namespace Netch.Updater WindowStyle = ProcessWindowStyle.Hidden, FileName = temp7za, Arguments = argument.ToString() - }); + })!; - process?.WaitForExit(); - return process?.ExitCode ?? 2; + process.WaitForExit(); + return process.ExitCode; } - private static void MoveDirectory(string sourceDirName, string destDirName, bool overwrite) + private static void MoveAllFilesOver(string source, string target) { - sourceDirName = Path.GetFullPath(sourceDirName); - destDirName = Path.GetFullPath(destDirName); - if (!overwrite) + foreach (string directory in Directory.GetDirectories(source)) { - Directory.Move(sourceDirName, destDirName); - } - else - { - foreach (var dir in Directory.GetDirectories(sourceDirName, "*", SearchOption.AllDirectories)) - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); + string dirName = Path.GetFileName(directory); - foreach (var f in Directory.GetFiles(sourceDirName, "*", SearchOption.AllDirectories)) - try - { - File.Move(f, f.Replace(sourceDirName, destDirName)); - } - catch (Exception) - { - throw new Exception("Updater wasn't able to move file: " + f); - } + if (!Directory.Exists(Path.Combine(target, dirName))) + Directory.CreateDirectory(Path.Combine(target, dirName)); + + MoveAllFilesOver(directory, Path.Combine(target, dirName)); + } + + foreach (string file in Directory.GetFiles(source)) + { + var destFile = Path.Combine(target, Path.GetFileName(file)); + File.Delete(destFile); + File.Move(file, destFile); } } - public static void CleanOld() + public static void CleanOld(string targetPath) { - foreach (var f in Directory.GetFiles(Global.NetchDir, "*.old", SearchOption.AllDirectories)) + foreach (var f in Directory.GetFiles(targetPath, "*.old", SearchOption.AllDirectories)) try { File.Delete(f); @@ -111,5 +120,41 @@ namespace Netch.Updater // ignored } } + + public static async Task DownloadAndUpdate(string downloadPath, + string targetPath, + DownloadProgressChangedEventHandler onDownloadProgressChanged) + { + var keyword = (string?) null; + + if (!UpdateChecker.GetFileNameAndHashFromMarkdownForm(UpdateChecker.LatestRelease.body, out var fileName, out var sha256, keyword)) + throw new Exception(i18N.Translate("parse release note failed")); + + var fileFullPath = Path.Combine(downloadPath, fileName); + var updater = new Updater(fileFullPath, targetPath); + + if (!(File.Exists(fileFullPath) && Utils.Utils.SHA256CheckSum(fileFullPath) == sha256)) + { + using WebClient client = new(); + try + { + client.DownloadProgressChanged += onDownloadProgressChanged; + await client.DownloadFileTaskAsync(new Uri(UpdateChecker.LatestRelease.assets[0].browser_download_url), fileFullPath); + } + catch (Exception e) + { + throw new Exception(i18N.Translate("Download Update Failed", ": ") + e.Message); + } + finally + { + client.DownloadProgressChanged -= onDownloadProgressChanged; + } + + if (Utils.Utils.SHA256CheckSum(fileFullPath) != sha256) + throw new Exception(i18N.Translate("The downloaded file has the wrong hash")); + } + + updater.ApplyUpdate(); + } } } \ No newline at end of file