Support downloading updates via command.

This commit is contained in:
BruceChen 2022-12-01 22:55:48 +08:00
parent 3713fa2dbe
commit 28827b720a
10 changed files with 2463 additions and 1974 deletions

View file

@ -0,0 +1,50 @@
using System.Collections.Generic;
namespace MinecraftClient.Commands
{
class Upgrade : Command
{
public override string CmdName { get { return "upgrade"; } }
public override string CmdUsage { get { return "upgrade [-f|check|cancel|download]"; } }
public override string CmdDesc { get { return string.Empty; } }
public override string Run(McClient handler, string command, Dictionary<string, object>? localVars)
{
if (HasArg(command))
{
string[] args = GetArgs(command);
return args[0] switch
{
"-f" => DownloadUpdate(force: true),
"-force" => DownloadUpdate(force: true),
"cancel" => CancelDownloadUpdate(),
"check" => CheckUpdate(),
"download" => DownloadUpdate(force: args.Length > 1 && (args[1] == "-f" || args[1] == "-force")),
_ => GetCmdDescTranslated(),
};
}
else
{
return DownloadUpdate(force: false);
}
}
private static string DownloadUpdate(bool force)
{
UpgradeHelper.DownloadLatestBuild(force);
return Translations.mcc_update_start;
}
private static string CancelDownloadUpdate()
{
UpgradeHelper.CancelDownloadUpdate();
return Translations.mcc_update_cancel;
}
private static string CheckUpdate()
{
UpgradeHelper.CheckUpdate(forceUpdate: true);
return Translations.mcc_update_start;
}
}
}

View file

@ -32,6 +32,7 @@
<PackageReference Include="DSharpPlus" Version="4.2.0" />
<PackageReference Include="DynamicExpresso.Core" Version="2.13.0" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="12.2.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="6.0.0" />

View file

@ -3,10 +3,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Inventory.ItemPalettes;
@ -200,6 +198,18 @@ namespace MinecraftClient
return;
}
if (args.Contains("--upgrade"))
{
UpgradeHelper.HandleBlockingUpdate(forceUpgrade: false);
return;
}
if (args.Contains("--force-upgrade"))
{
UpgradeHelper.HandleBlockingUpdate(forceUpgrade: true);
return;
}
if (args.Contains("--generate"))
{
string dataGenerator = "";
@ -284,47 +294,7 @@ namespace MinecraftClient
}
// Check for updates
{
bool needPromptUpdate = true;
if (Settings.CheckUpdate(Config.Head.CurrentVersion, Config.Head.LatestVersion))
{
needPromptUpdate = false;
ConsoleIO.WriteLineFormatted("§e" + string.Format(Translations.mcc_has_update, Settings.GithubReleaseUrl));
}
Task.Run(() =>
{
HttpClientHandler httpClientHandler = new() { AllowAutoRedirect = false };
HttpClient httpClient = new(httpClientHandler);
Task<HttpResponseMessage>? httpWebRequest = null;
try
{
httpWebRequest = httpClient.GetAsync(Settings.GithubLatestReleaseUrl, HttpCompletionOption.ResponseHeadersRead);
httpWebRequest.Wait();
HttpResponseMessage res = httpWebRequest.Result;
if (res.Headers.Location != null)
{
Match match = Regex.Match(res.Headers.Location.ToString(), Settings.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);
if (needPromptUpdate)
if (Settings.CheckUpdate(Config.Head.CurrentVersion, Config.Head.LatestVersion))
ConsoleIO.WriteLineFormatted("§e" + string.Format(Translations.mcc_has_update, Settings.GithubReleaseUrl));
if (latestVersion != Config.Head.LatestVersion)
{
Config.Head.LatestVersion = latestVersion;
WriteBackSettings(false);
}
}
}
}
catch (Exception) { }
finally { httpWebRequest?.Dispose(); }
httpClient.Dispose();
httpClientHandler.Dispose();
});
}
UpgradeHelper.CheckUpdate();
// Load command-line arguments
if (args.Length >= 1)

View file

@ -17,6 +17,9 @@ namespace MinecraftClient.Proxy
[TomlDoNotInlineObject]
public class Configs
{
[TomlInlineComment("$Proxy.Enabled_Update$")]
public bool Enabled_Update = false;
[TomlInlineComment("$Proxy.Enabled_Login$")]
public bool Enabled_Login = false;
@ -79,7 +82,7 @@ namespace MinecraftClient.Proxy
case Configs.ProxyType.SOCKS5: innerProxytype = ProxyType.Socks5; break;
}
if (Config.Username != "" && Config.Password != "")
if (!string.IsNullOrWhiteSpace(Config.Username)&& !string.IsNullOrWhiteSpace(Config.Password))
proxy = factory.CreateProxyClient(innerProxytype, Config.Server.Host, Config.Server.Port, Config.Username, Config.Password);
else
proxy = factory.CreateProxyClient(innerProxytype, Config.Server.Host, Config.Server.Port);

View file

@ -790,8 +790,7 @@ namespace MinecraftClient {
///NOTE: This is an experimental feature, the bot can be slow at times, you need to walk with a normal speed and to sometimes stop for it to be able to keep up with you
///It&apos;s similar to making animals follow you when you&apos;re holding food in your hand.
///This is due to a slow pathfinding algorithm, we&apos;re working on getting a better one
///You can tweak the update limit and find what works best for you. (NOTE: Do not but a very low one, because you might achieve the opposite,
/// [rest of string was truncated]&quot;;.
///You can tweak the update limit and find what works best for you. (NOTE: Do not but a very low one, because you might achieve the opposite, /// [rest of string was truncated]&quot;;.
/// </summary>
internal static string ChatBot_FollowPlayer {
get {
@ -1680,6 +1679,15 @@ namespace MinecraftClient {
}
}
/// <summary>
/// Looks up a localized string similar to Whether to download MCC updates via proxy..
/// </summary>
internal static string Proxy_Enabled_Update {
get {
return ResourceManager.GetString("Proxy.Enabled_Update", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Only required for password-protected proxies..
/// </summary>

View file

@ -3,7 +3,7 @@
<!--
Microsoft ResX Schema
Version 1.3
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
@ -14,16 +14,17 @@
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
@ -45,7 +46,7 @@
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
@ -59,18 +60,37 @@
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
@ -89,13 +109,13 @@
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppVars.Variables" xml:space="preserve">
<value>can be used in some other fields as %yourvar%
@ -723,6 +743,9 @@ If the connection to the Minecraft game server is blocked by the firewall, set E
<data name="Proxy.Enabled_Login" xml:space="preserve">
<value>Whether to connect to the login server through a proxy.</value>
</data>
<data name="Proxy.Enabled_Update" xml:space="preserve">
<value>Whether to download MCC updates via proxy.</value>
</data>
<data name="Proxy.Password" xml:space="preserve">
<value>Only required for password-protected proxies.</value>
</data>

View file

@ -5220,7 +5220,8 @@ namespace MinecraftClient {
}
/// <summary>
/// Looks up a localized string similar to New version of MCC available: {0}.
/// Looks up a localized string similar to A new version of MCC is available and you can download it via /upgrade
///Or download it manually: {0}.
/// </summary>
internal static string mcc_has_update {
get {
@ -5529,6 +5530,132 @@ namespace MinecraftClient {
}
}
/// <summary>
/// Looks up a localized string similar to The current version is already up to date or is a development build..
/// </summary>
internal static string mcc_update_already_latest {
get {
return ResourceManager.GetString("mcc.update.already_latest", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Checking for updates has already started..
/// </summary>
internal static string mcc_update_already_running {
get {
return ResourceManager.GetString("mcc.update.already_running", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The download has been cancelled..
/// </summary>
internal static string mcc_update_cancel {
get {
return ResourceManager.GetString("mcc.update.cancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to check for updates from Github, please check your internet connection or enable proxy..
/// </summary>
internal static string mcc_update_check_fail {
get {
return ResourceManager.GetString("mcc.update.check_fail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-updating: Download is complete, decompressing..
/// </summary>
internal static string mcc_update_download_complete {
get {
return ResourceManager.GetString("mcc.update.download_complete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-updating: An error occurred while downloading or saving the file:.
/// </summary>
internal static string mcc_update_download_fail {
get {
return ResourceManager.GetString("mcc.update.download_fail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-updating: New version detected: &quot;{0}&quot;, download will start soon (platform: {1})..
/// </summary>
internal static string mcc_update_exist_update {
get {
return ResourceManager.GetString("mcc.update.exist_update", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The latest release does not contain a build that matches your operating system and CPU architecture..
/// </summary>
internal static string mcc_update_platform_not_support {
get {
return ResourceManager.GetString("mcc.update.platform_not_support", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-updating: {0:00.00}%, Downloaded {1:00.0}MB of {2:00.0}MB, Avg {3:0.0}KB/s.
/// </summary>
internal static string mcc_update_progress {
get {
return ResourceManager.GetString("mcc.update.progress", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-updating: Downloaded {0:00.0}MB, Avg {1:0.0}KB/s.
/// </summary>
internal static string mcc_update_progress_type2 {
get {
return ResourceManager.GetString("mcc.update.progress_type2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add &quot;-f&quot; to force the download..
/// </summary>
internal static string mcc_update_promote_force {
get {
return ResourceManager.GetString("mcc.update.promote_force", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use &quot;--force-upgrade&quot; to force the download..
/// </summary>
internal static string mcc_update_promote_force_cmd {
get {
return ResourceManager.GetString("mcc.update.promote_force_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-updating: File saved as: {0}.
/// </summary>
internal static string mcc_update_save_as {
get {
return ResourceManager.GetString("mcc.update.save_as", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Start checking for updates..
/// </summary>
internal static string mcc_update_start {
get {
return ResourceManager.GetString("mcc.update.start", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please use the newly generated MinecraftClient.ini.
/// </summary>

View file

@ -1857,7 +1857,8 @@ You can use "/chunk status {0:0.0} {1:0.0} {2:0.0}" to check the chunk loading s
<value>Handshake successful. (Server ID: {0})</value>
</data>
<data name="mcc.has_update" xml:space="preserve">
<value>New version of MCC available: {0}</value>
<value>A new version of MCC is available and you can download it via /upgrade
Or download it manually: {0}</value>
</data>
<data name="mcc.help_us_translate" xml:space="preserve">
<value>Help us translate MCC: {0}</value>
@ -1962,6 +1963,48 @@ MCC is running with default settings.</value>
<value>Unknown or not supported MC version {0}.
Switching to autodetection mode.</value>
</data>
<data name="mcc.update.already_latest" xml:space="preserve">
<value>The current version is already up to date or is a development build.</value>
</data>
<data name="mcc.update.already_running" xml:space="preserve">
<value>Checking for updates has already started.</value>
</data>
<data name="mcc.update.cancel" xml:space="preserve">
<value>The download has been cancelled.</value>
</data>
<data name="mcc.update.check_fail" xml:space="preserve">
<value>Unable to check for updates from Github, please check your internet connection or enable proxy.</value>
</data>
<data name="mcc.update.download_complete" xml:space="preserve">
<value>Self-updating: Download is complete, decompressing.</value>
</data>
<data name="mcc.update.download_fail" xml:space="preserve">
<value>Self-updating: An error occurred while downloading or saving the file:</value>
</data>
<data name="mcc.update.exist_update" xml:space="preserve">
<value>Self-updating: New version detected: "{0}", download will start soon (platform: {1}).</value>
</data>
<data name="mcc.update.platform_not_support" xml:space="preserve">
<value>The latest release does not contain a build that matches your operating system and CPU architecture.</value>
</data>
<data name="mcc.update.progress" xml:space="preserve">
<value>Self-updating: {0:00.00}%, Downloaded {1:00.0}MB of {2:00.0}MB, Avg {3:0.0}KB/s</value>
</data>
<data name="mcc.update.progress_type2" xml:space="preserve">
<value>Self-updating: Downloaded {0:00.0}MB, Avg {1:0.0}KB/s</value>
</data>
<data name="mcc.update.promote_force" xml:space="preserve">
<value>Add "-f" to force the download.</value>
</data>
<data name="mcc.update.promote_force_cmd" xml:space="preserve">
<value>Use "--force-upgrade" to force the download.</value>
</data>
<data name="mcc.update.save_as" xml:space="preserve">
<value>Self-updating: File saved as: {0}</value>
</data>
<data name="mcc.update.start" xml:space="preserve">
<value>Start checking for updates.</value>
</data>
<data name="mcc.use_new_config" xml:space="preserve">
<value>Please use the newly generated MinecraftClient.ini</value>
</data>

View file

@ -37,8 +37,6 @@ namespace MinecraftClient
public static string TranslationsFile_Website_Download = "http://resources.download.minecraft.net";
public const string TranslationProjectUrl = "https://crwd.in/minecraft-console-client";
public const string GithubReleaseUrl = "https://github.com/MCCTeam/Minecraft-Console-Client/releases";
public const string GithubLatestReleaseUrl = GithubReleaseUrl + "/latest";
public static GlobalConfig Config = new();
@ -1688,61 +1686,6 @@ namespace MinecraftClient
}
}
public static bool CheckUpdate(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;
}
public static int DoubleToTick(double time)
{
time = Math.Min(int.MaxValue / 10, time);

View file

@ -0,0 +1,321 @@
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);
}
}
}
}