diff --git a/src/MaaWpfGui/Services/Web/HttpService.cs b/src/MaaWpfGui/Services/Web/HttpService.cs index 08ae8bd751..97f5a78678 100644 --- a/src/MaaWpfGui/Services/Web/HttpService.cs +++ b/src/MaaWpfGui/Services/Web/HttpService.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Net; using System.Net.Http; @@ -72,6 +73,34 @@ namespace MaaWpfGui.Services.Web BuildDownloaderHttpClient(); } + public async Task HeadAsync(Uri uri, Dictionary extraHeader = null) + { + try + { + var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Head, }; + + if (extraHeader != null) + { + foreach (var kvp in extraHeader) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + var stopwatch = Stopwatch.StartNew(); + var response = await _client.SendAsync(request); + stopwatch.Stop(); + response.Log(); + + return response.IsSuccessStatusCode is false ? -1.0 : stopwatch.Elapsed.TotalMilliseconds; + } + catch (Exception e) + { + _logger.Error(e, "Failed to send GET request to {Uri}", uri); + return -1.0; + } + } + public async Task GetStringAsync(Uri uri, Dictionary extraHeader = null) { var response = await GetAsync(uri, extraHeader); @@ -96,15 +125,11 @@ namespace MaaWpfGui.Services.Web return null; } - public async Task GetAsync(Uri uri, Dictionary extraHeader = null) + public async Task GetAsync(Uri uri, Dictionary extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) { try { - var request = new HttpRequestMessage - { - RequestUri = uri, - Method = HttpMethod.Get, - }; + var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get, }; if (extraHeader != null) { @@ -114,7 +139,7 @@ namespace MaaWpfGui.Services.Web } } - var response = await _client.SendAsync(request); + var response = await _client.SendAsync(request, httpCompletionOption); response.Log(); return response.IsSuccessStatusCode is false ? null : response; @@ -145,7 +170,7 @@ namespace MaaWpfGui.Services.Web } } - public async Task DownloadFileAsync(Uri uri, string fileName, string contentType = null) + public async Task DownloadFileAsync(Uri uri, string fileName, string contentType = "application/octet-stream") { string fileDir = Directory.GetCurrentDirectory(); string fileNameWithTemp = fileName + ".temp"; @@ -153,7 +178,7 @@ namespace MaaWpfGui.Services.Web string fullFilePathWithTemp = Path.Combine(fileDir, fileNameWithTemp); _logger.Information("Start to download file from {Uri} and save to {TempPath}", uri, fullFilePathWithTemp); - var response = await GetAsync(uri, extraHeader: new Dictionary { { "Accept", contentType } }); + var response = await GetAsync(uri, extraHeader: new Dictionary { { "Accept", contentType } }, httpCompletionOption: HttpCompletionOption.ResponseHeadersRead); if (response is null) { diff --git a/src/MaaWpfGui/Services/Web/IHttpService.cs b/src/MaaWpfGui/Services/Web/IHttpService.cs index 6afbf87dc3..97996081a8 100644 --- a/src/MaaWpfGui/Services/Web/IHttpService.cs +++ b/src/MaaWpfGui/Services/Web/IHttpService.cs @@ -21,6 +21,14 @@ namespace MaaWpfGui.Services.Web { public interface IHttpService { + /// + /// Test url available and return legacy + /// + /// Target Uri + /// Extra HTTP Request Headers + /// Legacy in ms, -1 when response code not equal 200 + Task HeadAsync(Uri uri, Dictionary extraHeader = null); + /// /// Send HTTP GET request and get a string response /// @@ -43,7 +51,7 @@ namespace MaaWpfGui.Services.Web /// Target Uri /// Extra HTTP Request Headers /// object - Task GetAsync(Uri uri, Dictionary extraHeader = null); + Task GetAsync(Uri uri, Dictionary extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead); /// /// Send HTTP POST request and a string reponse diff --git a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs index de2d1692a8..a33ec53886 100644 --- a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs @@ -117,7 +117,7 @@ namespace MaaWpfGui.ViewModels.UI } public FlowDocument UpdateInfoDoc => Markdig.Wpf.Markdown.ToFlowDocument(UpdateInfo, - new MarkdownPipelineBuilder().UseSupportedExtensions().Build()); + new MarkdownPipelineBuilder().UseSupportedExtensions().Build()); private string _updateUrl; @@ -199,8 +199,8 @@ namespace MaaWpfGui.ViewModels.UI { using var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionZipFileFoundTitle")); toast.AppendContentText(LocalizationHelper.GetString("NewVersionZipFileFoundDescDecompressing")) - .AppendContentText(UpdateTag) - .ShowUpdateVersion(row: 2); + .AppendContentText(UpdateTag) + .ShowUpdateVersion(row: 2); }); string curDir = Directory.GetCurrentDirectory(); @@ -224,8 +224,8 @@ namespace MaaWpfGui.ViewModels.UI { using var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionZipFileBrokenTitle")); toast.AppendContentText(LocalizationHelper.GetString("NewVersionZipFileBrokenDescFilename") + UpdatePackageName) - .AppendContentText(LocalizationHelper.GetString("NewVersionZipFileBrokenDescDeleted")) - .ShowUpdateVersion(); + .AppendContentText(LocalizationHelper.GetString("NewVersionZipFileBrokenDescDeleted")) + .ShowUpdateVersion(); }); return false; } @@ -422,7 +422,7 @@ namespace MaaWpfGui.ViewModels.UI return version; } else if (SemVersion.TryParse(version, SemVersionStyles.AllowLowerV, out var semVersion) && - isNightlyVersion(semVersion)) + isNightlyVersion(semVersion)) { // v4.6.6-1.g{Hash} // v4.6.7-beta.2.8.g{Hash} @@ -465,9 +465,7 @@ namespace MaaWpfGui.ViewModels.UI #pragma warning restore IDE0042 Execute.OnUIThread(() => { - using var toast = new ToastNotification((otaFound ? - LocalizationHelper.GetString("NewVersionFoundTitle") : - LocalizationHelper.GetString("NewVersionFoundButNoPackageTitle")) + " : " + UpdateTag); + using var toast = new ToastNotification((otaFound ? LocalizationHelper.GetString("NewVersionFoundTitle") : LocalizationHelper.GetString("NewVersionFoundButNoPackageTitle")) + " : " + UpdateTag); if (goDownload) { OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadPreparing")); @@ -526,17 +524,45 @@ namespace MaaWpfGui.ViewModels.UI urls.Add(rawUrl); } - foreach (var url in urls) + _logger.Information("Start test legacy download urls"); + + // run legacy test parallel + var tasks = urls.ConvertAll(url => Instances.HttpService.HeadAsync(new Uri(url))); + var legacies = await Task.WhenAll(tasks); + + // select the fastest mirror + _logger.Information("Selecting the fastest mirror:"); + var selected = 0; + for (int i = 0; i < legacies.Length; i++) { - downloaded = await DownloadGithubAssets(url, _assetsObject); - if (downloaded) + if (legacies[i].Equals(-1.0)) { - OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadCompletedTitle")); - break; + _logger.Warning("\turl: {0} not available", urls[i]); + continue; + } + + _logger.Information("\turl: {0}, legacy: {1:0.00}ms", urls[i], legacies[i]); + if (legacies[i] < legacies[selected]) + { + selected = i; } } - if (!downloaded) + if (legacies[selected].Equals(-1.0)) + { + _logger.Error("All mirrors are not available"); + return CheckUpdateRetT.NetworkError; + } + + _logger.Information("Selected mirror: {0}", urls[selected]); + + + downloaded = await DownloadGithubAssets(urls[selected], _assetsObject); + if (downloaded) + { + OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadCompletedTitle")); + } + else { OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); Execute.OnUIThread(() => @@ -544,8 +570,8 @@ namespace MaaWpfGui.ViewModels.UI using var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionDownloadFailedTitle")); toast.ButtonSystemUrl = UpdateUrl; toast.AppendContentText(LocalizationHelper.GetString("NewVersionDownloadFailedDesc")) - .AddButtonLeft(openUrlToastButton.text, openUrlToastButton.action) - .Show(); + .AddButtonLeft(openUrlToastButton.text, openUrlToastButton.action) + .Show(); }); return CheckUpdateRetT.NoNeedToUpdate; } @@ -728,8 +754,8 @@ namespace MaaWpfGui.ViewModels.UI break; } } - } - while (string.IsNullOrEmpty(response) && retryTimes-- > 0); + } while (string.IsNullOrEmpty(response) && retryTimes-- > 0); + return response; } #pragma warning restore IDE0051 // 删除未使用的私有成员 @@ -746,9 +772,9 @@ namespace MaaWpfGui.ViewModels.UI try { return await Instances.HttpService.DownloadFileAsync( - new Uri(url), - assetsObject["name"].ToString(), - assetsObject["content_type"]?.ToString()) + new Uri(url), + assetsObject["name"].ToString(), + assetsObject["content_type"]?.ToString()) .ConfigureAwait(false); } catch (Exception)