mirror of
https://github.com/netchx/netch.git
synced 2026-05-01 22:19:37 +08:00
Refactor Updater
Fix Download Update File block UI
This commit is contained in:
@@ -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, @"^\| (?<filename>.*) \| (?<sha256>.*) \|\r?$", RegexOptions.Multiline)
|
||||
.Skip(2);
|
||||
var matches = Regex.Matches(LatestRelease.body, @"^\| (?<filename>.*) \| (?<sha256>.*) \|\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()
|
||||
|
||||
@@ -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<int>();
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Download Update and apply update (all arguments are FullPath)
|
||||
/// </summary>
|
||||
/// <param name="downloadDirectory"></param>
|
||||
/// <param name="installDirectory"></param>
|
||||
/// <param name="onDownloadProgressChanged"></param>
|
||||
/// <param name="keyword"></param>
|
||||
/// <exception cref="MessageException"></exception>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download Update File
|
||||
/// </summary>
|
||||
/// <param name="onDownloadProgressChanged"></param>
|
||||
/// <param name="fileFullPath"></param>
|
||||
/// <param name="sha256"></param>
|
||||
/// <exception cref="MessageException"></exception>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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<int> progress)
|
||||
{
|
||||
await DownloadFileAsync(CreateRequest(address), fileFullPath, progress);
|
||||
}
|
||||
|
||||
public static async Task DownloadFileAsync(HttpWebRequest req, string fileFullPath, IProgress<int> 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<int> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user