mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
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:
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
37
src/MaaWpfGui/Extensions/StringExtensions.cs
Normal file
37
src/MaaWpfGui/Extensions/StringExtensions.cs
Normal 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..];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user