feat: update maa self by mirrorc (#11812)

* feat: update maa self by mirrorc

* refactor: 调整代码

* chore: miss await

---------

Co-authored-by: uye <99072975+ABA2396@users.noreply.github.com>
This commit is contained in:
MistEO
2025-02-09 01:27:33 +08:00
committed by GitHub
parent 59af6900ad
commit 86663ad2d2
8 changed files with 475 additions and 286 deletions

View File

@@ -30,6 +30,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=SSS/@EntryIndexedValue">SSS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=WSA/@EntryIndexedValue">WSA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=XYAZ/@EntryIndexedValue">XYAZ</s:String>
<s:Boolean x:Key="/Default/GrammarAndSpelling/GrammarChecking/Exceptions/=Github/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=acast/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=acfend/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=acguad/@EntryIndexedValue">True</s:Boolean>
@@ -95,6 +96,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=maatouch/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=memuc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Minitouch/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mirrorc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mizuki/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mumu/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nemu/@EntryIndexedValue">True</s:Boolean>
@@ -123,6 +125,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raidian/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=rcast/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=rdfend/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=removelist/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=rguard/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=rmean/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=rmedic/@EntryIndexedValue">True</s:Boolean>

View File

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

View File

@@ -0,0 +1,37 @@
// <copyright file="StringExtensions.cs" company="MaaAssistantArknights">
// 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
// </copyright>
namespace MaaWpfGui.Extensions
{
public static class StringExtensions
{
/// <summary>
/// Mask the string with a visible start and end.
/// </summary>
/// <param name="input">The string to be masked.</param>
/// <param name="visibleStartLength">The number of characters to remain visible at the start of the string.</param>
/// <param name="visibleEndLength">The number of characters to remain visible at the end of the string.</param>
/// <param name="maskChar">The character used for masking.</param>
/// <returns>A masked string with the specified number of visible characters at the start and end.</returns>
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..];
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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;
/// <summary>
/// 检查是否有已下载的更新包
/// </summary>
@@ -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<string[]>() ?? [];
}
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
{
/// <summary>
/// Maa API
/// </summary>
MaaApi,
/// <summary>
/// MirrorChyan
/// </summary>
MirrorChyan,
}
// ReSharper disable once IdentifierTypo
// ReSharper disable once UnusedMember.Global
public enum Downloader
@@ -420,7 +456,8 @@ public class VersionUpdateViewModel : Screen
/// <summary>
/// 如果是在更新后第一次启动显示ReleaseNote弹窗否则检查更新并下载更新包。
/// </summary>
public async void ShowUpdateOrDownload()
/// <returns>Task</returns>
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
/// <returns>操作成功返回 <see langword="true"/>,反之则返回 <see langword="false"/>。</returns>
private async Task<CheckUpdateRetT> CheckAndDownloadVersionUpdate()
{
var checkResult = await CheckUpdateInner();
return checkResult;
async Task<CheckUpdateRetT> 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<CheckUpdateRetT> 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<List<string>>();
var urls = new List<string>();
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<List<string>>();
var urls = new List<string>();
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<CheckUpdateRetT> 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
/// 检查更新。
/// </summary>
/// <returns>检查到更新返回 <see langword="true"/>,反之则返回 <see langword="false"/>。</returns>
private async Task<CheckUpdateRetT> 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<CheckUpdateRetT> 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;
}
/// <summary>
@@ -859,14 +1037,14 @@ public class VersionUpdateViewModel : Screen
/// <param name="url">下载链接</param>
/// <param name="assetsObject">Github Assets 对象</param>
/// <returns>操作成功返回 true反之则返回 false</returns>
private async Task<bool> DownloadGithubAssets(string url, JObject assetsObject)
private static async Task<bool> 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<LogItemViewModel> _logItemViewModels;
private static async Task<bool> 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<LogItemViewModel>? _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;
}
/*
/// <summary>
/// 复制文件夹内容并覆盖已存在的相同名字的文件
/// </summary>
/// <param name="sourcePath">源文件夹</param>
/// <param name="targetPath">目标文件夹</param>
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);
}
}
*/
}

View File

@@ -210,22 +210,6 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase
}
}
/// <summary>
/// Gets a value indicating whether to update nightly.
/// </summary>
public bool UpdateNightly
{
get => _versionType == UpdateVersionType.Nightly;
}
/// <summary>
/// Gets a value indicating whether to update beta version.
/// </summary>
public bool UpdateBeta
{
get => _versionType == UpdateVersionType.Beta;
}
private bool _updateCheck = Convert.ToBoolean(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.UpdateCheck, bool.TrueString));
public List<GenericCombinedData<string>> 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);
}
}