Refactor Updater

Fix Download Update File block UI
This commit is contained in:
ChsBuffer
2021-07-11 03:59:40 +08:00
parent 320b1f66c9
commit 958c28c695
5 changed files with 130 additions and 127 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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))

View 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);
}
}
}
}