Minecraft-Console-Client/MinecraftClient/UpgradeHelper.cs
2022-12-01 22:55:48 +08:00

321 lines
16 KiB
C#

using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Http.Handlers;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace MinecraftClient
{
internal static class UpgradeHelper
{
private const string GithubReleaseUrl = "https://github.com/MCCTeam/Minecraft-Console-Client/releases";
private static int running = 0; // Type: bool; 1 for running; 0 for stopped;
private static CancellationTokenSource cancellationTokenSource = new();
private static CancellationToken cancellationToken = CancellationToken.None;
private static long lastBytesTransferred = 0, minNotifyThreshold = 5 * 1024 * 1024;
private static DateTime downloadStartTime = DateTime.Now, lastNotifyTime = DateTime.Now;
private static TimeSpan minNotifyInterval = TimeSpan.FromMilliseconds(3000);
public static void CheckUpdate(bool forceUpdate = false)
{
bool needPromptUpdate = true;
if (!forceUpdate && CompareVersionInfo(Settings.Config.Head.CurrentVersion, Settings.Config.Head.LatestVersion))
{
needPromptUpdate = false;
ConsoleIO.WriteLineFormatted("§e" + string.Format(Translations.mcc_has_update, GithubReleaseUrl), true);
}
Task.Run(() =>
{
DoCheckUpdate(CancellationToken.None);
if (needPromptUpdate)
{
if (CompareVersionInfo(Settings.Config.Head.CurrentVersion, Settings.Config.Head.LatestVersion))
{
ConsoleIO.WriteLineFormatted("§e" + string.Format(Translations.mcc_has_update, GithubReleaseUrl), true);
}
else if (forceUpdate)
{
ConsoleIO.WriteLine(Translations.mcc_update_already_latest + ' ' + Translations.mcc_update_promote_force_cmd);
}
}
});
}
public static void DownloadLatestBuild(bool forceUpdate, bool isCommandLine = false)
{
if (Interlocked.Exchange(ref running, 1) == 1)
{
ConsoleIO.WriteLine(Translations.mcc_update_already_running);
}
else
{
if (!cancellationTokenSource.TryReset())
cancellationTokenSource = new();
cancellationToken = cancellationTokenSource.Token;
Task.Run(async () =>
{
string OSInfo = GetOSIdentifier();
if (Settings.Config.Logging.DebugMessages || string.IsNullOrEmpty(OSInfo))
ConsoleIO.WriteLine(string.Format("OS: {0}, Arch: {1}, Framework: {2}",
RuntimeInformation.OSDescription, RuntimeInformation.ProcessArchitecture, RuntimeInformation.FrameworkDescription));
if (string.IsNullOrEmpty(OSInfo))
{
ConsoleIO.WriteLine(Translations.mcc_update_platform_not_support);
}
else
{
string latestVersion = DoCheckUpdate(cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
}
else if (string.IsNullOrEmpty(latestVersion))
{
ConsoleIO.WriteLine(Translations.mcc_update_check_fail);
}
else if (!forceUpdate && !CompareVersionInfo(Settings.Config.Head.CurrentVersion, Settings.Config.Head.LatestVersion))
{
ConsoleIO.WriteLine(Translations.mcc_update_already_latest + ' ' +
(isCommandLine ? Translations.mcc_update_promote_force_cmd : Translations.mcc_update_promote_force));
}
else
{
ConsoleIO.WriteLine(string.Format(Translations.mcc_update_exist_update, latestVersion, OSInfo));
HttpClientHandler httpClientHandler = new() { AllowAutoRedirect = true };
AddProxySettings(httpClientHandler);
ProgressMessageHandler progressMessageHandler = new(httpClientHandler);
progressMessageHandler.HttpReceiveProgress += (_, info) =>
{
DateTime now = DateTime.Now;
if (now - lastNotifyTime > minNotifyInterval || info.BytesTransferred - lastBytesTransferred > minNotifyThreshold)
{
lastNotifyTime = now;
lastBytesTransferred = info.BytesTransferred;
if (info.TotalBytes.HasValue)
{
ConsoleIO.WriteLine(string.Format(Translations.mcc_update_progress,
(double)info.BytesTransferred / info.TotalBytes * 100.0,
(double)info.BytesTransferred / 1024 / 1024,
(double)info.TotalBytes / 1024 / 1024,
(double)info.BytesTransferred / 1024 / (now - downloadStartTime).TotalSeconds)
);
}
else
{
ConsoleIO.WriteLine(string.Format(Translations.mcc_update_progress_type2,
(double)info.BytesTransferred / 1024 / 1024,
(double)info.BytesTransferred / 1024 / (now - downloadStartTime).TotalSeconds)
);
}
}
};
HttpClient httpClient = new(progressMessageHandler);
try
{
string downloadUrl = $"{GithubReleaseUrl}/download/{latestVersion}/MinecraftClient-{OSInfo}.zip";
using HttpResponseMessage response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
downloadStartTime = DateTime.Now;
lastNotifyTime = DateTime.MinValue;
lastBytesTransferred = 0;
using Stream zipFileStream = await response.Content.ReadAsStreamAsync(cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
using ZipArchive zipArchive = new(zipFileStream, ZipArchiveMode.Read);
ConsoleIO.WriteLine(Translations.mcc_update_download_complete);
foreach (var archiveEntry in zipArchive.Entries)
{
if (archiveEntry.Name.StartsWith("MinecraftClient"))
{
string fileName = $"MinecraftClient-{latestVersion}{Path.GetExtension(archiveEntry.Name)}";
archiveEntry.ExtractToFile(fileName, true);
ConsoleIO.WriteLineFormatted("§e" + string.Format(Translations.mcc_update_save_as, fileName), true);
break;
}
}
zipArchive.Dispose();
}
zipFileStream.Dispose();
response.Dispose();
}
catch (TaskCanceledException) { }
catch (Exception e)
{
ConsoleIO.WriteLine($"{Translations.mcc_update_download_fail}\n{e.GetType().Name}: {e.Message}\n{e.StackTrace}");
}
httpClient.Dispose();
progressMessageHandler.Dispose();
httpClientHandler.Dispose();
}
}
Interlocked.Exchange(ref running, 0);
}, cancellationToken);
}
}
public static void CancelDownloadUpdate()
{
if (!cancellationTokenSource.IsCancellationRequested)
cancellationTokenSource.Cancel();
}
public static void HandleBlockingUpdate(bool forceUpgrade)
{
minNotifyInterval = TimeSpan.FromMilliseconds(500);
DownloadLatestBuild(forceUpgrade, true);
while (running == 1)
Thread.Sleep(500);
}
private static string DoCheckUpdate(CancellationToken cancellationToken)
{
string latestBuildInfo = string.Empty;
HttpClientHandler httpClientHandler = new() { AllowAutoRedirect = false };
AddProxySettings(httpClientHandler);
HttpClient httpClient = new(httpClientHandler);
Task<HttpResponseMessage>? httpWebRequest = null;
try
{
httpWebRequest = httpClient.GetAsync(GithubReleaseUrl + "/latest", HttpCompletionOption.ResponseHeadersRead, cancellationToken);
httpWebRequest.Wait();
if (!cancellationToken.IsCancellationRequested)
{
HttpResponseMessage res = httpWebRequest.Result;
if (res.Headers.Location != null)
{
Match match = Regex.Match(res.Headers.Location.ToString(), GithubReleaseUrl + @"/tag/(\d{4})(\d{2})(\d{2})-(\d+)");
if (match.Success && match.Groups.Count == 5)
{
string year = match.Groups[1].Value, month = match.Groups[2].Value, day = match.Groups[3].Value, run = match.Groups[4].Value;
string latestVersion = string.Format("GitHub build {0}, built on {1}-{2}-{3}", run, year, month, day);
latestBuildInfo = string.Format("{0}{1}{2}-{3}", year, month, day, run);
if (latestVersion != Settings.Config.Head.LatestVersion)
{
Settings.Config.Head.LatestVersion = latestVersion;
Program.WriteBackSettings(false);
}
}
}
res.Dispose();
}
httpWebRequest.Dispose();
}
catch (Exception) { }
finally { httpWebRequest?.Dispose(); }
httpClient.Dispose();
httpClientHandler.Dispose();
return latestBuildInfo;
}
private static string GetOSIdentifier()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
return "linux-arm64";
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
return "linux";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
return "osx";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
return "windows-x64";
else if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
return "windows-x86";
}
return string.Empty;
}
private static bool CompareVersionInfo(string? current, string? latest)
{
if (current == null || latest == null)
return false;
Regex reg = new(@"\w+\sbuild\s(\d+),\sbuilt\son\s(\d{4})[-\/\.\s]?(\d{2})[-\/\.\s]?(\d{2}).*");
Regex reg2 = new(@"\w+\sbuild\s(\d+),\sbuilt\son\s\w+\s(\d{2})[-\/\.\s]?(\d{2})[-\/\.\s]?(\d{4}).*");
DateTime? curTime = null, latestTime = null;
Match curMatch = reg.Match(current);
if (curMatch.Success && curMatch.Groups.Count == 5)
{
try { curTime = new(int.Parse(curMatch.Groups[2].Value), int.Parse(curMatch.Groups[3].Value), int.Parse(curMatch.Groups[4].Value)); }
catch { curTime = null; }
}
if (curTime == null)
{
curMatch = reg2.Match(current);
try { curTime = new(int.Parse(curMatch.Groups[4].Value), int.Parse(curMatch.Groups[3].Value), int.Parse(curMatch.Groups[2].Value)); }
catch { curTime = null; }
}
if (curTime == null)
return false;
Match latestMatch = reg.Match(latest);
if (latestMatch.Success && latestMatch.Groups.Count == 5)
{
try { latestTime = new(int.Parse(latestMatch.Groups[2].Value), int.Parse(latestMatch.Groups[3].Value), int.Parse(latestMatch.Groups[4].Value)); }
catch { latestTime = null; }
}
if (latestTime == null)
{
latestMatch = reg2.Match(latest);
try { latestTime = new(int.Parse(latestMatch.Groups[4].Value), int.Parse(latestMatch.Groups[3].Value), int.Parse(latestMatch.Groups[2].Value)); }
catch { latestTime = null; }
}
if (latestTime == null)
return false;
int curBuildId, latestBuildId;
try
{
curBuildId = int.Parse(curMatch.Groups[1].Value);
latestBuildId = int.Parse(latestMatch.Groups[1].Value);
}
catch { return false; }
if (latestTime > curTime)
return true;
else if (latestTime >= curTime && latestBuildId > curBuildId)
return true;
else
return false;
}
private static void AddProxySettings(HttpClientHandler httpClientHandler)
{
if (Settings.Config.Proxy.Enabled_Update)
{
string proxyAddress;
if (!string.IsNullOrWhiteSpace(Settings.Config.Proxy.Username) && !string.IsNullOrWhiteSpace(Settings.Config.Proxy.Password))
proxyAddress = string.Format("{0}://{3}:{4}@{1}:{2}",
Settings.Config.Proxy.Proxy_Type.ToString().ToLower(),
Settings.Config.Proxy.Server.Host,
Settings.Config.Proxy.Server.Port,
Settings.Config.Proxy.Username,
Settings.Config.Proxy.Password);
else
proxyAddress = string.Format("{0}://{1}:{2}",
Settings.Config.Proxy.Proxy_Type.ToString().ToLower(),
Settings.Config.Proxy.Server.Host, Settings.Config.Proxy.Server.Port);
httpClientHandler.Proxy = new WebProxy(proxyAddress, true);
}
}
}
}