diff --git a/MAA.sln.DotSettings b/MAA.sln.DotSettings index d895a48022..6fa9bc7786 100644 --- a/MAA.sln.DotSettings +++ b/MAA.sln.DotSettings @@ -30,6 +30,7 @@ SSS WSA XYAZ + True True True True @@ -95,6 +96,7 @@ True True True + True True True True @@ -123,6 +125,7 @@ True True True + True True True True diff --git a/src/MaaWpfGui/Constants/MaaUrls.cs b/src/MaaWpfGui/Constants/MaaUrls.cs index 75e78ae4b7..e223512d10 100644 --- a/src/MaaWpfGui/Constants/MaaUrls.cs +++ b/src/MaaWpfGui/Constants/MaaUrls.cs @@ -88,6 +88,7 @@ namespace MaaWpfGui.Constants public const string MirrorChyanWebsite = "https://mirrorc.top"; public const string MirrorChyanLine2 = "http://47.96.114.138:8800"; + public const string MirrorChyanAppUpdate = $"{MirrorChyanWebsite}/api/resources/MAA/latest"; public const string MirrorChyanResourceUpdate = $"{MirrorChyanWebsite}/api/resources/MaaResource/latest"; } } diff --git a/src/MaaWpfGui/Extensions/StringExtensions.cs b/src/MaaWpfGui/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..af54eba4e1 --- /dev/null +++ b/src/MaaWpfGui/Extensions/StringExtensions.cs @@ -0,0 +1,37 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License v3.0 only as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// +namespace MaaWpfGui.Extensions +{ + public static class StringExtensions + { + /// + /// Mask the string with a visible start and end. + /// + /// The string to be masked. + /// The number of characters to remain visible at the start of the string. + /// The number of characters to remain visible at the end of the string. + /// The character used for masking. + /// A masked string with the specified number of visible characters at the start and end. + public static string Mask(this string input, int visibleStartLength = 3, int visibleEndLength = 3, char maskChar = '*') + { + if (input.Length <= visibleStartLength + visibleEndLength) + { + return new(maskChar, input.Length); + } + + var maskLength = input.Length - visibleStartLength - visibleEndLength; + var maskedPart = new string(maskChar, maskLength); + return input[..visibleStartLength] + maskedPart + input[^visibleEndLength..]; + } + } +} diff --git a/src/MaaWpfGui/Models/ResourceUpdater.cs b/src/MaaWpfGui/Models/ResourceUpdater.cs index fffa8c2aab..52d1081fee 100644 --- a/src/MaaWpfGui/Models/ResourceUpdater.cs +++ b/src/MaaWpfGui/Models/ResourceUpdater.cs @@ -22,6 +22,7 @@ using System.Net.Http; using System.Threading.Tasks; using System.Web; using MaaWpfGui.Constants; +using MaaWpfGui.Extensions; using MaaWpfGui.Helper; using MaaWpfGui.Main; using MaaWpfGui.ViewModels; @@ -86,7 +87,7 @@ namespace MaaWpfGui.Models // 只有 Release 版本才会检查更新 // ReSharper disable once UnusedMember.Global - public static async void UpdateAndToastAsync() + public static async Task UpdateAndToastAsync() { var ret = await UpdateAsync(); @@ -496,7 +497,7 @@ namespace MaaWpfGui.Models var url = $"{MaaUrls.MirrorChyanResourceUpdate}?current_version={currentVersion}&cdk={cdk}&user_agent=MaaWpfGui"; var response = await Instances.HttpService.GetAsync(new(url), logUri: false); - _logger.Information($"current_version: {currentVersion}, cdk: {((cdk.Length > 6) ? (cdk[..3] + new string('*', cdk.Length - 6) + cdk[^3..]) : cdk)}"); + _logger.Information($"current_version: {currentVersion}, cdk: {cdk.Mask()}"); if (response is null) { @@ -546,9 +547,9 @@ namespace MaaWpfGui.Models return (CheckUpdateRetT.AlreadyLatest, null); } + // 到这里已经确定有新版本了 _logger.Information($"New version found: {version}"); - // 到这里已经确定有新版本了 if (string.IsNullOrEmpty(cdk)) { ToastNotification.ShowDirect(LocalizationHelper.GetString("MirrorChyanResourceUpdateTip")); @@ -706,10 +707,10 @@ namespace MaaWpfGui.Models file.CopyTo(tempPath, true); // 覆盖现有文件 } - foreach (DirectoryInfo subdir in dirs) + foreach (DirectoryInfo subDir in dirs) { - string tempPath = Path.Combine(destDirName, subdir.Name); - DirectoryMerge(subdir.FullName, tempPath); + string tempPath = Path.Combine(destDirName, subDir.Name); + DirectoryMerge(subDir.FullName, tempPath); } } } diff --git a/src/MaaWpfGui/Services/Web/HttpService.cs b/src/MaaWpfGui/Services/Web/HttpService.cs index a28cea0dc3..8fe04b1d80 100755 --- a/src/MaaWpfGui/Services/Web/HttpService.cs +++ b/src/MaaWpfGui/Services/Web/HttpService.cs @@ -231,7 +231,7 @@ namespace MaaWpfGui.Services.Web try { var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - using (var tempFileStream = new FileStream(fullFilePathWithTemp, FileMode.Create, FileAccess.Write)) + await using (var tempFileStream = new FileStream(fullFilePathWithTemp, FileMode.Create, FileAccess.Write)) { // 记录初始化 long value = 0; diff --git a/src/MaaWpfGui/ViewModels/UI/RootViewModel.cs b/src/MaaWpfGui/ViewModels/UI/RootViewModel.cs index 6773d4e0a8..9960eb7e06 100644 --- a/src/MaaWpfGui/ViewModels/UI/RootViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/RootViewModel.cs @@ -19,7 +19,7 @@ using HandyControl.Tools; using MaaWpfGui.Constants; using MaaWpfGui.Helper; using MaaWpfGui.Main; -using Microsoft.VisualBasic.Logging; +using MaaWpfGui.ViewModels.UserControl.Settings; using Microsoft.WindowsAPICodePack.Taskbar; using Stylet; @@ -42,7 +42,7 @@ namespace MaaWpfGui.ViewModels.UI InitViewModels(); InitProxy(); - if (SettingsViewModel.VersionUpdateSettings.UpdateNightly && !SettingsViewModel.VersionUpdateSettings.HasAcknowledgedNightlyWarning) + if (SettingsViewModel.VersionUpdateSettings.VersionType == VersionUpdateSettingsUserControlModel.UpdateVersionType.Nightly && !SettingsViewModel.VersionUpdateSettings.HasAcknowledgedNightlyWarning) { MessageBoxHelper.Show(LocalizationHelper.GetString("NightlyWarning")); } @@ -66,7 +66,7 @@ namespace MaaWpfGui.ViewModels.UI } }); - Instances.VersionUpdateViewModel.ShowUpdateOrDownload(); + _ = Instances.VersionUpdateViewModel.ShowUpdateOrDownload(); } private static async void InitProxy() diff --git a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs index 7de0be6eaa..d7a6f89c28 100644 --- a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs @@ -15,7 +15,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; @@ -23,14 +22,14 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; -using HandyControl.Controls; -using HandyControl.Data; using MaaWpfGui.Constants; +using MaaWpfGui.Extensions; using MaaWpfGui.Helper; using MaaWpfGui.Main; using MaaWpfGui.Models; using MaaWpfGui.Services; using MaaWpfGui.States; +using MaaWpfGui.ViewModels.UserControl.Settings; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Semver; @@ -176,6 +175,10 @@ public class VersionUpdateViewModel : Screen private JObject? _latestJson; private JObject? _assetsObject; + private string? _mirrorcDownloadUrl; + private string? _mirrorcVersionName; + private string? _mirrorcReleaseNote; + /// /// 检查是否有已下载的更新包 /// @@ -224,9 +227,29 @@ public class VersionUpdateViewModel : Screen } string removeListFile = Path.Combine(extractDir, "removelist.txt"); + string[] removeList = []; if (File.Exists(removeListFile)) { - string[] removeList = File.ReadAllLines(removeListFile); + removeList = File.ReadAllLines(removeListFile); + } + + string mirrorChyanChangeFile = Path.Combine(extractDir, "changes.json"); + if (File.Exists(mirrorChyanChangeFile)) + { + try + { + string json = File.ReadAllText(mirrorChyanChangeFile); + var jObject = JObject.Parse(json); + removeList = jObject["deleted"]?.ToObject() ?? []; + } + catch (Exception e) + { + _logger.Error($"parse mirrorChyan changes.json error: {e.Message}"); + } + } + + if (removeList.Length > 0) + { foreach (string file in removeList) { string path = Path.Combine(curDir, file); @@ -392,6 +415,19 @@ public class VersionUpdateViewModel : Screen OnlyGameResourceUpdated, } + public enum AppUpdateSource + { + /// + /// Maa API + /// + MaaApi, + + /// + /// MirrorChyan + /// + MirrorChyan, + } + // ReSharper disable once IdentifierTypo // ReSharper disable once UnusedMember.Global public enum Downloader @@ -420,7 +456,8 @@ public class VersionUpdateViewModel : Screen /// /// 如果是在更新后第一次启动,显示ReleaseNote弹窗,否则检查更新并下载更新包。 /// - public async void ShowUpdateOrDownload() + /// Task + public async Task ShowUpdateOrDownload() { if (IsFirstBootAfterUpdate) { @@ -448,7 +485,6 @@ public class VersionUpdateViewModel : Screen } else { - // var ret = await CheckAndDownloadUpdate(); // await ResourceUpdater.CheckAndDownloadUpdate(); // 跑个空任务避免 async warning await Task.Run(() => { }); @@ -470,200 +506,243 @@ public class VersionUpdateViewModel : Screen /// 操作成功返回 ,反之则返回 private async Task CheckAndDownloadVersionUpdate() { - var checkResult = await CheckUpdateInner(); - - return checkResult; - - async Task CheckUpdateInner() + // 检查更新 + var (checkRet, source) = await CheckUpdate(); + if (checkRet != CheckUpdateRetT.OK) { - // 检查更新 - var checkRet = await CheckUpdate(); - if (checkRet != CheckUpdateRetT.OK) + return checkRet; + } + + return source switch + { + AppUpdateSource.MaaApi => await HandleUpdateFromMaaApi(), + AppUpdateSource.MirrorChyan => await HandleUpdateFromMirrorChyan(), + _ => CheckUpdateRetT.UnknownError, + }; + } + + private async Task HandleUpdateFromMaaApi() + { + // 保存新版本的信息 + var name = _latestJson?["name"]?.ToString(); + UpdateTag = string.IsNullOrEmpty(name) ? (_latestJson?["tag_name"]?.ToString() ?? string.Empty) : name; + var body = _latestJson?["body"]?.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(body)) + { + var curHash = ComparableHash(_curVersion); + var latestHash = ComparableHash(_latestVersion); + + if (curHash != null && latestHash != null) { - return checkRet; - } - - // 保存新版本的信息 - var name = _latestJson?["name"]?.ToString(); - UpdateTag = string.IsNullOrEmpty(name) ? (_latestJson?["tag_name"]?.ToString() ?? string.Empty) : name; - var body = _latestJson?["body"]?.ToString() ?? string.Empty; - if (string.IsNullOrEmpty(body)) - { - var curHash = ComparableHash(_curVersion); - var latestHash = ComparableHash(_latestVersion); - - if (curHash != null && latestHash != null) - { - body = $"**Full Changelog**: [{curHash} -> {latestHash}](https://github.com/MaaAssistantArknights/MaaAssistantArknights/compare/{curHash}...{latestHash})"; - } - } - - UpdateInfo = body; - UpdateUrl = _latestJson?["html_url"]?.ToString() ?? string.Empty; - - bool otaFound = _assetsObject != null; - bool goDownload = otaFound && SettingsViewModel.VersionUpdateSettings.AutoDownloadUpdatePackage; - (string text, Action action) = ( - LocalizationHelper.GetString("NewVersionFoundButtonGoWebpage"), - () => - { - if (!string.IsNullOrWhiteSpace(UpdateUrl)) - { - Process.Start(new ProcessStartInfo(UpdateUrl) { UseShellExecute = true }); - } - } - ); - - { - using var toast = new ToastNotification((otaFound ? LocalizationHelper.GetString("NewVersionFoundTitle") : LocalizationHelper.GetString("NewVersionFoundButNoPackageTitle")) + " : " + UpdateTag); - if (goDownload) - { - OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadPreparing")); - toast.AppendContentText(LocalizationHelper.GetString("NewVersionFoundDescDownloading")); - } - - if (!otaFound) - { - toast.AppendContentText(LocalizationHelper.GetString("NewVersionFoundButNoPackageDesc")); - } - - int count = 0; - foreach (var line in UpdateInfo.Split('\n')) - { - if (line.StartsWith("#") || string.IsNullOrWhiteSpace(line)) - { - continue; - } - - toast.AppendContentText(line); - if (++count >= 10) - { - break; - } - } - - toast.AddButton(text, ToastNotification.GetActionTagForOpenWeb(UpdateUrl)); - toast.ShowUpdateVersion(); - } - - UpdatePackageName = _assetsObject?["name"]?.ToString() ?? string.Empty; - - if (!goDownload || string.IsNullOrWhiteSpace(UpdatePackageName)) - { - OutputDownloadProgress(string.Empty); - return CheckUpdateRetT.NoNeedToUpdate; - } - - if (_assetsObject == null) - { - return CheckUpdateRetT.FailedToGetInfo; - } - - string? rawUrl = _assetsObject["browser_download_url"]?.ToString(); - var mirrors = _assetsObject["mirrors"]?.ToObject>(); - - var urls = new List(); - if (mirrors != null) - { - urls.AddRange(mirrors); - } - - // 负载均衡 - // var rand = new Random(); - // urls = urls.OrderBy(_ => rand.Next()).ToList(); - if (rawUrl != null) - { - urls.Add(rawUrl); - } - - _logger.Information("Start test legacy download urls"); - - // run latency test parallel - var tasks = urls.ConvertAll(url => Instances.HttpService.HeadAsync(new Uri(url))); - var latencies = await Task.WhenAll(tasks); - - var proxy = ConfigurationHelper.GetValue(ConfigurationKeys.UpdateProxy, string.Empty); - var hasProxy = !string.IsNullOrEmpty(proxy); - - // select the fastest mirror - _logger.Information("Selecting the fastest mirror:"); - var selected = 0; - for (int i = 0; i < latencies.Length; i++) - { - // ReSharper disable once StringLiteralTypo - var isInChina = urls[i].Contains("s3.maa-org.net") || urls[i].Contains("maa-ota.annangela.cn"); - - if (latencies[i] < 0) - { - _logger.Warning("\turl: {CDNUrl} not available", urls[i]); - continue; - } - - _logger.Information("\turl: {CDNUrl}, legacy: {1:0.00}ms", urls[i], latencies[i]); - - if (hasProxy && isInChina) - { - // 如果设置了代理,国内镜像的延迟加上一个固定值 - latencies[i] += 6480; - } - - if (latencies[selected] < 0 || (latencies[i] >= 0 && latencies[i] < latencies[selected])) - { - selected = i; - } - } - - if (latencies[selected] < 0) - { - _logger.Error("All mirrors are not available"); - return CheckUpdateRetT.NetworkError; - } - - _logger.Information("Selected mirror: {CDNUrl}", urls[selected]); - - var downloaded = await DownloadGithubAssets(urls[selected], _assetsObject); - if (downloaded) - { - OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadCompletedTitle")); - } - else - { - OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); - { - var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); - toast.AppendContentText(LocalizationHelper.GetString("NewVersionDownloadFailedDesc")) - .AddButton(text, ToastNotification.GetActionTagForOpenWeb(UpdateUrl)) - .Show(); - } - - return CheckUpdateRetT.NoNeedToUpdate; - } - - return CheckUpdateRetT.OK; - - string? ComparableHash(string version) - { - if (IsStdVersion(version)) - { - return version; - } - else if (SemVersion.TryParse(version, SemVersionStyles.AllowLowerV, out var semVersion) && - IsNightlyVersion(semVersion)) - { - // v4.6.6-1.g{Hash} - // v4.6.7-beta.2.8.g{Hash} - var commitHash = semVersion.PrereleaseIdentifiers.Last().ToString(); - if (commitHash.StartsWith("g")) - { - commitHash = commitHash.Remove(0, 1); - } - - return commitHash; - } - - return null; + body = $"**Full Changelog**: [{curHash} -> {latestHash}](https://github.com/MaaAssistantArknights/MaaAssistantArknights/compare/{curHash}...{latestHash})"; } } + + UpdateInfo = body; + UpdateUrl = _latestJson?["html_url"]?.ToString() ?? string.Empty; + + bool otaFound = _assetsObject != null; + bool goDownload = otaFound && SettingsViewModel.VersionUpdateSettings.AutoDownloadUpdatePackage; + + ShowUpdateInfo(otaFound, LocalizationHelper.GetString("NewVersionFoundButtonGoWebpage")); + + UpdatePackageName = _assetsObject?["name"]?.ToString() ?? string.Empty; + + if (!goDownload || string.IsNullOrWhiteSpace(UpdatePackageName)) + { + OutputDownloadProgress(string.Empty); + return CheckUpdateRetT.NoNeedToUpdate; + } + + if (_assetsObject == null) + { + return CheckUpdateRetT.FailedToGetInfo; + } + + string? rawUrl = _assetsObject["browser_download_url"]?.ToString(); + var mirrors = _assetsObject["mirrors"]?.ToObject>(); + + var urls = new List(); + if (mirrors != null) + { + urls.AddRange(mirrors); + } + + // 负载均衡 + // var rand = new Random(); + // urls = urls.OrderBy(_ => rand.Next()).ToList(); + if (rawUrl != null) + { + urls.Add(rawUrl); + } + + _logger.Information("Start test legacy download urls"); + + // run latency test parallel + var tasks = urls.ConvertAll(url => Instances.HttpService.HeadAsync(new Uri(url))); + var latencies = await Task.WhenAll(tasks); + + var proxy = ConfigurationHelper.GetValue(ConfigurationKeys.UpdateProxy, string.Empty); + var hasProxy = !string.IsNullOrEmpty(proxy); + + // select the fastest mirror + _logger.Information("Selecting the fastest mirror:"); + var selected = 0; + for (int i = 0; i < latencies.Length; i++) + { + // ReSharper disable once StringLiteralTypo + var isInChina = urls[i].Contains("s3.maa-org.net") || urls[i].Contains("maa-ota.annangela.cn"); + + if (latencies[i] < 0) + { + _logger.Warning("\turl: {CDNUrl} not available", urls[i]); + continue; + } + + _logger.Information("\turl: {CDNUrl}, legacy: {1:0.00}ms", urls[i], latencies[i]); + + if (hasProxy && isInChina) + { + // 如果设置了代理,国内镜像的延迟加上一个固定值 + latencies[i] += 6480; + } + + if (latencies[selected] < 0 || (latencies[i] >= 0 && latencies[i] < latencies[selected])) + { + selected = i; + } + } + + if (latencies[selected] < 0) + { + _logger.Error("All mirrors are not available"); + return CheckUpdateRetT.NetworkError; + } + + _logger.Information("Selected mirror: {CDNUrl}", urls[selected]); + + var downloaded = await DownloadGithubAssets(urls[selected], _assetsObject); + if (downloaded) + { + OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadCompletedTitle")); + } + else + { + OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); + { + var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); + toast.AppendContentText(LocalizationHelper.GetString("NewVersionDownloadFailedDesc")) + .AddButton(LocalizationHelper.GetString("NewVersionFoundButtonGoWebpage"), ToastNotification.GetActionTagForOpenWeb(UpdateUrl)) + .Show(); + } + + return CheckUpdateRetT.NoNeedToUpdate; + } + + return CheckUpdateRetT.OK; + + string? ComparableHash(string version) + { + if (IsStdVersion(version)) + { + return version; + } + + if (!SemVersion.TryParse(version, SemVersionStyles.AllowLowerV, out var semVersion) || + !IsNightlyVersion(semVersion)) + { + return null; + } + + // v4.6.6-1.g{Hash} + // v4.6.7-beta.2.8.g{Hash} + var commitHash = semVersion.PrereleaseIdentifiers[^1].ToString(); + if (commitHash.StartsWith('g')) + { + commitHash = commitHash.Remove(0, 1); + } + + return commitHash; + } + } + + private void ShowUpdateInfo(bool otaFound, string? text) + { + bool goDownload = otaFound && SettingsViewModel.VersionUpdateSettings.AutoDownloadUpdatePackage; + + using var toast = new ToastNotification((otaFound ? LocalizationHelper.GetString("NewVersionFoundTitle") : LocalizationHelper.GetString("NewVersionFoundButNoPackageTitle")) + " : " + UpdateTag); + if (goDownload) + { + OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadPreparing")); + toast.AppendContentText(LocalizationHelper.GetString("NewVersionFoundDescDownloading")); + } + + if (!otaFound) + { + toast.AppendContentText(LocalizationHelper.GetString("NewVersionFoundButNoPackageDesc")); + } + + int count = 0; + foreach (var line in UpdateInfo.Split('\n')) + { + if (line.StartsWith('#') || string.IsNullOrWhiteSpace(line)) + { + continue; + } + + toast.AppendContentText(line); + if (++count >= 10) + { + break; + } + } + + if (!string.IsNullOrEmpty(text)) + { + toast.AddButton(text, ToastNotification.GetActionTagForOpenWeb(UpdateUrl)); + } + + toast.ShowUpdateVersion(); + } + + private async Task HandleUpdateFromMirrorChyan() + { + if (string.IsNullOrEmpty(_mirrorcDownloadUrl)) + { + return CheckUpdateRetT.FailedToGetInfo; + } + + UpdateTag = _mirrorcVersionName ?? string.Empty; + UpdateInfo = _mirrorcReleaseNote ?? string.Empty; + + bool goDownload = SettingsViewModel.VersionUpdateSettings.AutoDownloadUpdatePackage; + if (!goDownload) + { + OutputDownloadProgress(string.Empty); + return CheckUpdateRetT.NoNeedToUpdate; + } + + UpdatePackageName = "MirrorChyanApp" + _mirrorcVersionName + ".zip"; + var downloaded = await DownloadFromMirrorChyan(_mirrorcDownloadUrl, + UpdatePackageName); + + if (downloaded) + { + OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadCompletedTitle")); + } + else + { + OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); + { + var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); + toast.AppendContentText(LocalizationHelper.GetString("NewVersionDownloadFailedDesc")) + .Show(); + } + + return CheckUpdateRetT.NoNeedToUpdate; + } + + return CheckUpdateRetT.OK; } public async Task AskToRestart(bool isResource = false) @@ -693,22 +772,43 @@ public class VersionUpdateViewModel : Screen /// 检查更新。 /// /// 检查到更新返回 ,反之则返回 - private async Task CheckUpdate() + private async Task<(CheckUpdateRetT Ret, AppUpdateSource? Source)> CheckUpdate() { // 调试版不检查更新 if (IsDebugVersion()) { - return CheckUpdateRetT.NoNeedToUpdateDebugVersion; + return (CheckUpdateRetT.NoNeedToUpdateDebugVersion, null); + } + + // mirrorChyan 暂时没有支持 nightly,之后加一加 + if ((SettingsViewModel.VersionUpdateSettings.VersionType is + VersionUpdateSettingsUserControlModel.UpdateVersionType.Beta or + VersionUpdateSettingsUserControlModel.UpdateVersionType.Stable) + && !string.IsNullOrEmpty(SettingsViewModel.VersionUpdateSettings.MirrorChyanCdk)) + { + try + { + var ret = await CheckUpdateByMirrorChyan(); + if (ret is CheckUpdateRetT.OK or CheckUpdateRetT.AlreadyLatest) + { + return (ret, AppUpdateSource.MirrorChyan); + } + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to check update by MirrorChyan, rollback to maaApi"); + } } try { - return await CheckUpdateByMaaApi(); + var ret = await CheckUpdateByMaaApi(); + return (ret, AppUpdateSource.MaaApi); } catch (Exception ex) { _logger.Error(ex, "Failed to check update by Maa API."); - return CheckUpdateRetT.FailedToGetInfo; + return (CheckUpdateRetT.FailedToGetInfo, AppUpdateSource.MaaApi); } } @@ -727,23 +827,15 @@ public class VersionUpdateViewModel : Screen return CheckUpdateRetT.FailedToGetInfo; } - string? latestVersion; - string? detailUrl; - if (SettingsViewModel.VersionUpdateSettings.UpdateNightly) + string versionType = SettingsViewModel.VersionUpdateSettings.VersionType switch { - latestVersion = json["alpha"]?["version"]?.ToString(); - detailUrl = json["alpha"]?["detail"]?.ToString(); - } - else if (SettingsViewModel.VersionUpdateSettings.UpdateBeta) - { - latestVersion = json["beta"]?["version"]?.ToString(); - detailUrl = json["beta"]?["detail"]?.ToString(); - } - else - { - latestVersion = json["stable"]?["version"]?.ToString(); - detailUrl = json["stable"]?["detail"]?.ToString(); - } + VersionUpdateSettingsUserControlModel.UpdateVersionType.Beta => "beta", + VersionUpdateSettingsUserControlModel.UpdateVersionType.Nightly => "alpha", + _ => "stable", + }; + + var latestVersion = json[versionType]?["version"]?.ToString(); + var detailUrl = json[versionType]?["detail"]?.ToString(); latestVersion ??= string.Empty; detailUrl ??= string.Empty; @@ -834,6 +926,94 @@ public class VersionUpdateViewModel : Screen return CheckUpdateRetT.OK; } + private async Task CheckUpdateByMirrorChyan() + { + var cdk = SettingsViewModel.VersionUpdateSettings.MirrorChyanCdk.Trim(); + + string channel = SettingsViewModel.VersionUpdateSettings.VersionType switch + { + VersionUpdateSettingsUserControlModel.UpdateVersionType.Beta => "beta", + VersionUpdateSettingsUserControlModel.UpdateVersionType.Nightly => "alpha", + _ => "stable", + }; + + var arch = IsArm ? "arm64" : "x64"; + var url = $"{MaaUrls.MirrorChyanAppUpdate}?current_version={_curVersion}&cdk={cdk}&user_agent=MaaWpfGui&os=win&arch={arch}&channel={channel}"; + + var response = await Instances.HttpService.GetAsync(new(url), logUri: false); + _logger.Information($"current_version: {_curVersion}, cdk: {cdk.Mask()}, arch: {arch}, channel: {channel}"); + + if (response is null) + { + _logger.Error("response is null, try mirrorc line2"); + url = url.Replace(MaaUrls.MirrorChyanWebsite, MaaUrls.MirrorChyanLine2); + response = await Instances.HttpService.GetAsync(new(url), logUri: false); + if (response is null) + { + _logger.Error("mirrorc line2 failed too"); + return CheckUpdateRetT.NetworkError; + } + } + + var jsonStr = await response.Content.ReadAsStringAsync(); + _logger.Information(jsonStr); + JObject? data = null; + try + { + data = (JObject?)JsonConvert.DeserializeObject(jsonStr); + } + catch (Exception ex) + { + _logger.Error("Failed to deserialize json: " + ex.Message); + } + + if (data is null) + { + return CheckUpdateRetT.UnknownError; + } + + if (data["code"]?.ToString() != "0") + { + var msg = data["msg"]?.ToString(); + if (!string.IsNullOrEmpty(msg)) + { + ToastNotification.ShowDirect(msg); + } + + return CheckUpdateRetT.UnknownError; + } + + var version = data["data"]?["version_name"]?.ToString(); + if (string.IsNullOrEmpty(version)) + { + return CheckUpdateRetT.UnknownError; + } + + if (!NeedToUpdate(version)) + { + return CheckUpdateRetT.AlreadyLatest; + } + + // 到这里已经确定有新版本了 + _logger.Information($"New version found: {version}"); + + _mirrorcVersionName = version; + _mirrorcReleaseNote = data["data"]?["release_note"]?.ToString(); + + if (string.IsNullOrEmpty(cdk)) + { + ToastNotification.ShowDirect(LocalizationHelper.GetString("MirrorChyanResourceUpdateTip")); + + // 现在仅有cdk才会调用到这里,不应该没cdk的,直接先报个错 + // 以后如果所有人都使用mirrorc检查更新,这里再改一下 + return CheckUpdateRetT.UnknownError; + } + + _mirrorcDownloadUrl = data["data"]?["url"]?.ToString(); + + return CheckUpdateRetT.OK; + } + private bool NeedToUpdate(string latestVersion) { if (IsDebugVersion()) @@ -847,10 +1027,8 @@ public class VersionUpdateViewModel : Screen { return curVersionObj.CompareSortOrderTo(latestVersionObj) < 0; } - else - { - return string.CompareOrdinal(_curVersion, latestVersion) < 0; - } + + return string.CompareOrdinal(_curVersion, latestVersion) < 0; } /// @@ -859,14 +1037,14 @@ public class VersionUpdateViewModel : Screen /// 下载链接 /// Github Assets 对象 /// 操作成功返回 true,反之则返回 false - private async Task DownloadGithubAssets(string url, JObject assetsObject) + private static async Task DownloadGithubAssets(string url, JObject assetsObject) { _logItemViewModels = Instances.TaskQueueViewModel.LogItemViewModels; try { return await Instances.HttpService.DownloadFileAsync( - new Uri(url), - assetsObject["name"].ToString(), + new(url), + assetsObject["name"]!.ToString(), assetsObject["content_type"]?.ToString()) .ConfigureAwait(false); } @@ -876,23 +1054,32 @@ public class VersionUpdateViewModel : Screen } } - private static ObservableCollection _logItemViewModels; + private static async Task DownloadFromMirrorChyan(string url, string filename) + { + _logItemViewModels = Instances.TaskQueueViewModel.LogItemViewModels; + try + { + return await Instances.HttpService.DownloadFileAsync( + new(url), filename) + .ConfigureAwait(false); + } + catch (Exception) + { + return false; + } + } + + private static ObservableCollection? _logItemViewModels; public static void OutputDownloadProgress(long value = 0, long maximum = 1, int len = 0, double ts = 1) { string progress = $"[{value / 1048576.0:F}MiB/{maximum / 1048576.0:F}MiB ({value * 100.0 / maximum:F}%)"; double speedInKiBPerSecond = len / ts / 1024.0; - string speedDisplay; - if (speedInKiBPerSecond >= 1024) - { - speedDisplay = $"{speedInKiBPerSecond / 1024.0:F} MiB/s"; - } - else - { - speedDisplay = $"{speedInKiBPerSecond:F} KiB/s"; - } + var speedDisplay = speedInKiBPerSecond >= 1024 + ? $"{speedInKiBPerSecond / 1024.0:F} MiB/s" + : $"{speedInKiBPerSecond:F} KiB/s"; OutputDownloadProgress(progress + $" {speedDisplay}"); } @@ -953,7 +1140,7 @@ public class VersionUpdateViewModel : Screen return false; } - if (version.StartsWith("c") || version.StartsWith("20") || version.Contains("Local")) + if (version.StartsWith('c') || version.StartsWith("20") || version.Contains("Local")) { return false; } @@ -978,30 +1165,6 @@ public class VersionUpdateViewModel : Screen // v4.6.7-beta.2.1.g1234567 // v4.6.8-5.g1234567 var lastId = version.PrereleaseIdentifiers.LastOrDefault().ToString(); - return lastId.StartsWith("g") && lastId.Length >= 7; + return lastId.StartsWith('g') && lastId.Length >= 7; } - - /* - /// - /// 复制文件夹内容并覆盖已存在的相同名字的文件 - /// - /// 源文件夹 - /// 目标文件夹 - public static void CopyFilesRecursively(string sourcePath, string targetPath) - { - Directory.CreateDirectory(targetPath); - - // Now Create all of the directories - foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(dirPath.Replace(sourcePath, targetPath)); - } - - // Copy all the files & Replaces any files with the same name - foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) - { - File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true); - } - } - */ } diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs index 24544c3067..6fd11a95e4 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs @@ -210,22 +210,6 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase } } - /// - /// Gets a value indicating whether to update nightly. - /// - public bool UpdateNightly - { - get => _versionType == UpdateVersionType.Nightly; - } - - /// - /// Gets a value indicating whether to update beta version. - /// - public bool UpdateBeta - { - get => _versionType == UpdateVersionType.Beta; - } - private bool _updateCheck = Convert.ToBoolean(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.UpdateCheck, bool.TrueString)); public List> ResourceUpdateSourceList { get; } = [ @@ -244,7 +228,7 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase set { SetAndNotify(ref _resourceUpdateSource, value); - ConfigurationHelper.SetGlobalValue(ConfigurationKeys.ResourceUpdateSource, value.ToString()); + ConfigurationHelper.SetGlobalValue(ConfigurationKeys.ResourceUpdateSource, value); } }