From 0f1da3669654345fffc2eacf4423cbd69014a261 Mon Sep 17 00:00:00 2001 From: Liam Sho Date: Sat, 25 Mar 2023 19:21:03 +0800 Subject: [PATCH] refactor: web request --- src/MaaWpfGui/Helper/ConfigurationHelper.cs | 22 +- src/MaaWpfGui/Main/Bootstrapper.cs | 3 + src/MaaWpfGui/Services/HttpService.cs | 224 ++++++++++++++++++ src/MaaWpfGui/Services/IHttpService.cs | 34 +++ src/MaaWpfGui/Services/IMaaApiService.cs | 25 ++ src/MaaWpfGui/Services/MaaApiService.cs | 81 +++++++ src/MaaWpfGui/Services/StageManager.cs | 18 +- src/MaaWpfGui/Services/WebService.cs | 214 ----------------- .../ViewModels/UI/CopilotViewModel.cs | 9 +- .../ViewModels/UI/SettingsViewModel.cs | 11 +- .../ViewModels/UI/VersionUpdateViewModel.cs | 182 +------------- 11 files changed, 419 insertions(+), 404 deletions(-) create mode 100644 src/MaaWpfGui/Services/HttpService.cs create mode 100644 src/MaaWpfGui/Services/IHttpService.cs create mode 100644 src/MaaWpfGui/Services/IMaaApiService.cs create mode 100644 src/MaaWpfGui/Services/MaaApiService.cs delete mode 100644 src/MaaWpfGui/Services/WebService.cs diff --git a/src/MaaWpfGui/Helper/ConfigurationHelper.cs b/src/MaaWpfGui/Helper/ConfigurationHelper.cs index 24493b640f..158a5a2020 100644 --- a/src/MaaWpfGui/Helper/ConfigurationHelper.cs +++ b/src/MaaWpfGui/Helper/ConfigurationHelper.cs @@ -15,7 +15,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Text.Json; -using System.Threading.Tasks; using Serilog; namespace MaaWpfGui.Helper @@ -28,6 +27,10 @@ namespace MaaWpfGui.Helper private static readonly ILogger _logger = Log.ForContext(); + public delegate void ConfigurationUpdateEventHandler(string key, string oldValue, string newValue); + + public static event ConfigurationUpdateEventHandler ConfigurationUpdateEvent; + private static bool Released { get; set; } /// @@ -55,8 +58,10 @@ namespace MaaWpfGui.Helper /// The return value of public static bool SetValue(string key, string value) { + var old = string.Empty; if (_kvs.ContainsKey(key)) { + old = _kvs[key]; _kvs[key] = value; } else @@ -67,6 +72,7 @@ namespace MaaWpfGui.Helper var result = Save(); if (result) { + ConfigurationUpdateEvent?.Invoke(key, old, value); _logger.Information("Configuration {Key} has been set to {Value}", key, value); } else @@ -84,10 +90,17 @@ namespace MaaWpfGui.Helper /// The return value of . public static bool DeleteValue(string key) { + var old = string.Empty; + if (_kvs.ContainsKey(key)) + { + old = _kvs[key]; + } + _kvs.Remove(key); var result = Save(); if (result) { + ConfigurationUpdateEvent?.Invoke(key, old, string.Empty); _logger.Information("Configuration {Key} has been deleted", key); } else @@ -136,8 +149,11 @@ namespace MaaWpfGui.Helper var parsed = ParseJsonFile(_configurationFile); if (parsed is null) { - File.Copy(_configurationBakFile, _configurationFile, true); - parsed = ParseJsonFile(_configurationFile); + if (File.Exists(_configurationBakFile)) + { + File.Copy(_configurationBakFile, _configurationFile, true); + parsed = ParseJsonFile(_configurationFile); + } } if (parsed is null) diff --git a/src/MaaWpfGui/Main/Bootstrapper.cs b/src/MaaWpfGui/Main/Bootstrapper.cs index 4d260b5959..f067fba6fe 100644 --- a/src/MaaWpfGui/Main/Bootstrapper.cs +++ b/src/MaaWpfGui/Main/Bootstrapper.cs @@ -143,6 +143,9 @@ namespace MaaWpfGui.Main builder.Bind().To().InSingletonScope(); builder.Bind().To().InSingletonScope(); + + builder.Bind().To().InSingletonScope(); + builder.Bind().To().InSingletonScope(); } /// diff --git a/src/MaaWpfGui/Services/HttpService.cs b/src/MaaWpfGui/Services/HttpService.cs new file mode 100644 index 0000000000..329adaa755 --- /dev/null +++ b/src/MaaWpfGui/Services/HttpService.cs @@ -0,0 +1,224 @@ +// +// 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 General Public License 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 +// + +using System; +using System.Collections.Generic; +using System.Drawing.Printing; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using MaaWpfGui.Constants; +using MaaWpfGui.Helper; +using MaaWpfGui.ViewModels.UI; +using Serilog; +using Serilog.Core; + +namespace MaaWpfGui.Services +{ + public class HttpService : IHttpService + { + public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.76"; + + private static string Proxy => ConfigurationHelper.GetValue(ConfigurationKeys.UpdateProxy, string.Empty); + + private readonly ILogger _logger = Log.ForContext(); + + private HttpClient _client; + + public HttpService() + { + ConfigurationHelper.ConfigurationUpdateEvent += (key, old, value) => + { + if (key != ConfigurationKeys.UpdateProxy) + { + return; + } + + if (old != value) + { + BuildHttpClient(); + } + }; + + BuildHttpClient(); + } + + public async Task GetStringAsync(Uri uri, Dictionary extraHeader = null) + { + var response = await GetAsync(uri, extraHeader); + + if (response != null) + { + return await response.Content.ReadAsStringAsync(); + } + + return null; + } + + public async Task GetStreamAsync(Uri uri, Dictionary extraHeader = null) + { + var response = await GetAsync(uri, extraHeader); + + if (response != null) + { + return await response.Content.ReadAsStreamAsync(); + } + + return null; + } + + public async Task GetAsync(Uri uri, Dictionary extraHeader = null) + { + try + { + var request = new HttpRequestMessage + { + RequestUri = uri, + Method = HttpMethod.Get, + }; + + if (extraHeader != null) + { + foreach (var kvp in extraHeader) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + var response = await _client.SendAsync(request); + + return response.IsSuccessStatusCode is false ? null : response; + } + catch (Exception e) + { + _logger.Error(e, "Failed to send GET request to {Uri}", uri); + return null; + } + } + + public async Task PostAsJsonAsync(Uri uri, T content, Dictionary extraHeader = null) + { + try + { + var body = JsonSerializer.Serialize(content); + var message = new HttpRequestMessage(HttpMethod.Post, uri); + message.Headers.Accept.ParseAdd("application/json"); + message.Content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await _client.SendAsync(message); + return await response.Content.ReadAsStringAsync(); + } + catch (Exception e) + { + _logger.Error(e, "Failed to send POST request to {Uri}", uri); + return null; + } + } + + public async Task DownloadFileAsync(Uri uri, string fileName, string contentType = null) + { + string fileDir = Directory.GetCurrentDirectory(); + string fileNameWithTemp = fileName + ".temp"; + string fullFilePath = Path.Combine(fileDir, fileName); + string fullFilePathWithTemp = Path.Combine(fileDir, fileNameWithTemp); + + var response = GetAsync(uri, extraHeader: new Dictionary + { + { "Accept", contentType }, + }).ConfigureAwait(false).GetAwaiter().GetResult(); + + if (response is null) + { + return false; + } + + var success = true; + try + { + var stream = response.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + using var fileStream = new FileStream(fullFilePathWithTemp, FileMode.Create, FileAccess.Write); + + // 记录初始化 + long value = 0; + int valueInOneSecond = 0; + long fileMaximum = response.Content.Headers.ContentLength ?? 1; + DateTime beforeDt = DateTime.Now; + + // Dangerous action + VersionUpdateViewModel.OutputDownloadProgress(); + + byte[] buffer = new byte[81920]; + int byteLen = await stream.ReadAsync(buffer, 0, buffer.Length); + + while (byteLen > 0) + { + valueInOneSecond += byteLen; + double ts = DateTime.Now.Subtract(beforeDt).TotalSeconds; + if (ts > 1) + { + beforeDt = DateTime.Now; + value += valueInOneSecond; + + // Dangerous action + VersionUpdateViewModel.OutputDownloadProgress(value, fileMaximum, valueInOneSecond, ts); + valueInOneSecond = 0; + } + + // 输入输出 + fileStream.Write(buffer, 0, byteLen); + byteLen = await stream.ReadAsync(buffer, 0, buffer.Length); + } + + File.Copy(fullFilePathWithTemp, fullFilePath, true); + } + catch (Exception e) + { + _logger.Error("Failed to download file from {Uri}", uri); + success = false; + } + finally + { + if (File.Exists(fullFilePathWithTemp)) + { + File.Delete(fullFilePathWithTemp); + } + } + + return success; + } + + private void BuildHttpClient() + { + var proxyIsUri = Uri.TryCreate(Proxy, UriKind.RelativeOrAbsolute, out var uri); + if (!string.IsNullOrEmpty(Proxy) && (!(_client is null))) + { + return; + } + + var handler = new HttpClientHandler { AllowAutoRedirect = true, }; + + if (proxyIsUri) + { + handler.Proxy = new WebProxy(uri); + handler.UseProxy = true; + } + + _client?.Dispose(); + _client = new HttpClient(handler); + _client.DefaultRequestHeaders.Add("User-Agent", UserAgent); + } + } +} diff --git a/src/MaaWpfGui/Services/IHttpService.cs b/src/MaaWpfGui/Services/IHttpService.cs new file mode 100644 index 0000000000..3778bf2146 --- /dev/null +++ b/src/MaaWpfGui/Services/IHttpService.cs @@ -0,0 +1,34 @@ +// +// 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 General Public License 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 +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace MaaWpfGui.Services +{ + public interface IHttpService + { + Task GetStringAsync(Uri uri, Dictionary extraHeader = null); + + Task GetStreamAsync(Uri uri, Dictionary extraHeader = null); + + Task GetAsync(Uri uri, Dictionary extraHeader = null); + + Task PostAsJsonAsync(Uri uri, T content, Dictionary extraHeader = null); + + Task DownloadFileAsync(Uri uri, string fileName, string contentType = null); + } +} diff --git a/src/MaaWpfGui/Services/IMaaApiService.cs b/src/MaaWpfGui/Services/IMaaApiService.cs new file mode 100644 index 0000000000..75494ed121 --- /dev/null +++ b/src/MaaWpfGui/Services/IMaaApiService.cs @@ -0,0 +1,25 @@ +// +// 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 General Public License 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 +// + +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace MaaWpfGui.Services +{ + public interface IMaaApiService + { + Task RequestMaaApiWithCache(string api); + + JObject LoadApiCache(string api); + } +} diff --git a/src/MaaWpfGui/Services/MaaApiService.cs b/src/MaaWpfGui/Services/MaaApiService.cs new file mode 100644 index 0000000000..e8af980b19 --- /dev/null +++ b/src/MaaWpfGui/Services/MaaApiService.cs @@ -0,0 +1,81 @@ +// +// 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 General Public License 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 +// + +using System; +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MaaWpfGui.Services +{ + public class MaaApiService : IMaaApiService + { + private readonly IHttpService _httpService; + + private const string CacheDir = "cache/gui/"; + private const string MaaApi = "https://ota.maa.plus/MaaAssistantArknights/api/"; + + public MaaApiService(IHttpService httpService) + { + _httpService = httpService; + } + + public async Task RequestMaaApiWithCache(string api) + { + if (!Directory.Exists(CacheDir)) + { + Directory.CreateDirectory(CacheDir); + } + + var url = MaaApi + api; + + var response = await _httpService.GetStringAsync(new Uri(url)); + if (response == null) + { + return LoadApiCache(api); + } + + try + { + var json = (JObject)JsonConvert.DeserializeObject(response); + var cache = CacheDir + api.Replace('/', '_'); + File.WriteAllText(cache, response); + + return json; + } + catch + { + return null; + } + } + + public JObject LoadApiCache(string api) + { + var cache = CacheDir + api.Replace('/', '_'); + if (!File.Exists(cache)) + { + return null; + } + + try + { + return (JObject)JsonConvert.DeserializeObject(File.ReadAllText(cache)); + } + catch + { + return null; + } + } + } +} diff --git a/src/MaaWpfGui/Services/StageManager.cs b/src/MaaWpfGui/Services/StageManager.cs index 20fc9968a7..0fb4cd9bd3 100644 --- a/src/MaaWpfGui/Services/StageManager.cs +++ b/src/MaaWpfGui/Services/StageManager.cs @@ -41,6 +41,7 @@ namespace MaaWpfGui.Services // model references private readonly TaskQueueViewModel _taskQueueViewModel; + private readonly IMaaApiService _maaApiService; private static readonly ILogger _logger = Log.ForContext(); @@ -54,6 +55,7 @@ namespace MaaWpfGui.Services public StageManager(IContainer container) { _taskQueueViewModel = container.Get(); + _maaApiService = container.Get(); UpdateStage(false); Execute.OnUIThread(async () => @@ -77,8 +79,8 @@ namespace MaaWpfGui.Services { // Check if we need to update from the web string lastUpdateTimeFile = "lastUpdateTime.json"; - JObject localLastUpdatedJson = WebService.LoadApiCache(lastUpdateTimeFile); - JObject webLastUpdatedJson = WebService.RequestMaaApiWithCache(lastUpdateTimeFile); + JObject localLastUpdatedJson = _maaApiService.LoadApiCache(lastUpdateTimeFile); + JObject webLastUpdatedJson = _maaApiService.RequestMaaApiWithCache(lastUpdateTimeFile).ConfigureAwait(false).GetAwaiter().GetResult(); if (localLastUpdatedJson != null && webLastUpdatedJson != null) { long localTimestamp = localLastUpdatedJson["timestamp"].ToObject(); @@ -106,12 +108,16 @@ namespace MaaWpfGui.Services } // Download the activities - var stageApi = "gui/StageActivity.json"; - JObject activity = fromWeb ? WebService.RequestMaaApiWithCache(stageApi) : WebService.LoadApiCache(stageApi); + const string StageApi = "gui/StageActivity.json"; + JObject activity = fromWeb + ? _maaApiService.RequestMaaApiWithCache(StageApi).ConfigureAwait(false).GetAwaiter().GetResult() + : _maaApiService.LoadApiCache(StageApi); // Download the tasks resources into cache so MaaCore can load them later var tasksPath = "resource/tasks.json"; - JObject tasksJson = fromWeb ? WebService.RequestMaaApiWithCache(tasksPath) : WebService.LoadApiCache(tasksPath); + JObject tasksJson = fromWeb + ? _maaApiService.RequestMaaApiWithCache(tasksPath).ConfigureAwait(false).GetAwaiter().GetResult() + : _maaApiService.LoadApiCache(tasksPath); if (clientType != "Official" && tasksJson != null) { @@ -120,7 +126,7 @@ namespace MaaWpfGui.Services // Download the client specific resources only when the Official ones are successfully downloaded so that the client specific resource version is the actual version // TODO: There may be an issue when the CN resource is loaded from cache (e.g. network down) while global resource is downloaded (e.g. network up again) // var tasksJsonClient = fromWeb ? WebService.RequestMaaApiWithCache(tasksPath) : WebService.RequestMaaApiWithCache(tasksPath); - _ = fromWeb ? WebService.RequestMaaApiWithCache(tasksPath) : WebService.RequestMaaApiWithCache(tasksPath); + _ = _maaApiService.RequestMaaApiWithCache(tasksPath); } bool isDebugVersion = Marshal.PtrToStringAnsi(AsstGetVersion()) == "DEBUG VERSION"; diff --git a/src/MaaWpfGui/Services/WebService.cs b/src/MaaWpfGui/Services/WebService.cs deleted file mode 100644 index 9eddd9cecb..0000000000 --- a/src/MaaWpfGui/Services/WebService.cs +++ /dev/null @@ -1,214 +0,0 @@ -// -// 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 General Public License 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 -// - -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using MaaWpfGui.Constants; -using MaaWpfGui.Helper; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace MaaWpfGui.Services -{ - public static class WebService - { - public const string RequestUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.76"; - - private static string s_proxy = ConfigurationHelper.GetValue(ConfigurationKeys.UpdateProxy, string.Empty); - - public static string Proxy - { - get => s_proxy; set - { - s_proxy = value; - NormalClient = BuildNormalClient(); - } - } - -#nullable enable - public static HttpClient NormalClient { get; private set; } = BuildNormalClient(); - - private static HttpClient BuildNormalClient() => new HttpClient(new HttpClientHandler() - { - Proxy = string.IsNullOrWhiteSpace(s_proxy) ? null : new WebProxy(s_proxy), - }); - - public static async Task GetJsonAsync(string url) - { - try - { - using var message = new HttpRequestMessage(HttpMethod.Get, url); - message.Headers.Accept.ParseAdd("application/json"); - using var response = await NormalClient.SendAsync(message); - return await response.Content.ReadAsStringAsync(); - } - catch (Exception e) - { - // Refactor pending - return null; - } - } - - public static async Task PostJsonAsync(string url, T content) - { - return await PostJsonAsync(url, JsonConvert.SerializeObject(content)); - } - - public static async Task PostJsonAsync(string url, string body) - { - try - { - using var message = new HttpRequestMessage(HttpMethod.Post, url); - message.Headers.Accept.ParseAdd("application/json"); - message.Content = new StringContent(body, Encoding.UTF8, "application/json"); - using var response = await NormalClient.SendAsync(message); - return await response.Content.ReadAsStringAsync(); - } - catch (Exception e) - { - // Refactor pending - return null; - } - } - -#nullable disable - - public static string RequestGet(string url) - { - try - { - HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url); - httpWebRequest.Method = "GET"; - httpWebRequest.UserAgent = RequestUserAgent; - httpWebRequest.Accept = "application/vnd.github.v3+json"; - if (!string.IsNullOrWhiteSpace(Proxy)) - { - httpWebRequest.Proxy = new WebProxy(Proxy); - } - - var httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse; - if (httpWebResponse.StatusCode != HttpStatusCode.OK) - { - return null; - } - - var streamReader = new StreamReader(httpWebResponse.GetResponseStream(), Encoding.UTF8); - var responseContent = streamReader.ReadToEnd(); - streamReader.Close(); - httpWebResponse.Close(); - return responseContent; - } - catch (Exception e) - { - // Refactor pending - return null; - } - } - - public static string RequestPost(string url, string body) - { - try - { - HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url); - httpWebRequest.Method = "POST"; - httpWebRequest.UserAgent = RequestUserAgent; - httpWebRequest.Accept = "application/vnd.github.v3+json"; - if (!string.IsNullOrWhiteSpace(Proxy)) - { - httpWebRequest.Proxy = new WebProxy(Proxy); - } - - var bytes = Encoding.UTF8.GetBytes(body); - httpWebRequest.ContentType = "application/json"; - httpWebRequest.ContentLength = bytes.Length; - using (var requestStream = httpWebRequest.GetRequestStream()) - { - requestStream.Write(bytes, 0, bytes.Length); - } - - var httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse; - if (httpWebResponse.StatusCode != HttpStatusCode.OK) - { - return null; - } - - var streamReader = new StreamReader(httpWebResponse.GetResponseStream(), Encoding.UTF8); - var responseContent = streamReader.ReadToEnd(); - streamReader.Close(); - httpWebResponse.Close(); - return responseContent; - } - catch (Exception e) - { - // Refactor pending - return null; - } - } - - private const string CacheDir = "cache/"; - private const string MaaApi = "https://ota.maa.plus/MaaAssistantArknights/api/"; - - // 如果请求失败,则读取缓存。否则写入缓存 - public static JObject RequestMaaApiWithCache(string api) - { - if (!Directory.Exists(CacheDir)) - { - Directory.CreateDirectory(CacheDir); - } - - var url = MaaApi + api; - - var response = RequestGet(url); - if (response == null) - { - return LoadApiCache(api); - } - - try - { - var json = (JObject)JsonConvert.DeserializeObject(response); - var cache = CacheDir + api; - Directory.CreateDirectory(Path.GetDirectoryName(cache)); - File.WriteAllText(cache, response); - - return json; - } - catch - { - return null; - } - } - - public static JObject LoadApiCache(string api) - { - var cache = CacheDir + api; - if (!File.Exists(cache)) - { - return null; - } - - try - { - return (JObject)JsonConvert.DeserializeObject(File.ReadAllText(cache)); - } - catch - { - return null; - } - } - } -} diff --git a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs index df9724f4be..645463ccda 100644 --- a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs @@ -40,6 +40,7 @@ namespace MaaWpfGui.ViewModels.UI public class CopilotViewModel : Screen { private readonly IWindowManager _windowManager; + private readonly IHttpService _httpService; private readonly IContainer _container; private AsstProxy _asstProxy; @@ -55,10 +56,12 @@ namespace MaaWpfGui.ViewModels.UI /// /// The IoC container. /// The window manager. - public CopilotViewModel(IContainer container, IWindowManager windowManager) + /// The http service. + public CopilotViewModel(IContainer container, IWindowManager windowManager, IHttpService httpService) { _container = container; _windowManager = windowManager; + _httpService = httpService; DisplayName = LocalizationHelper.GetString("Copilot"); LogItemViewModels = new ObservableCollection(); AddLog(LocalizationHelper.GetString("CopilotTip"), UiLogColor.Message); @@ -233,7 +236,7 @@ namespace MaaWpfGui.ViewModels.UI { try { - var jsonResponse = await WebService.GetJsonAsync($@"https://prts.maa.plus/copilot/get/{copilotID}"); + var jsonResponse = await _httpService.GetStringAsync(new Uri($@"https://prts.maa.plus/copilot/get/{copilotID}")); var json = (JObject)JsonConvert.DeserializeObject(jsonResponse); if (json != null && json.ContainsKey("status_code") && json["status_code"].ToString() == "200") { @@ -587,7 +590,7 @@ namespace MaaWpfGui.ViewModels.UI rating = rating, }); - var response = await WebService.PostJsonAsync(_copilotRatingUrl, jsonParam); + var response = await _httpService.PostAsJsonAsync(new Uri(_copilotRatingUrl), jsonParam); if (response == null) { AddLog(LocalizationHelper.GetString("FailedToLikeWebJson"), UiLogColor.Error); diff --git a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs index 68dbff8f55..cd7c4bd943 100644 --- a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs @@ -52,6 +52,7 @@ namespace MaaWpfGui.ViewModels.UI private TaskQueueViewModel _taskQueueViewModel; private AsstProxy _asstProxy; private VersionUpdateViewModel _versionUpdateViewModel; + private readonly IHttpService _httpService; [DllImport("MaaCore.dll")] private static extern IntPtr AsstGetVersion(); @@ -92,6 +93,7 @@ namespace MaaWpfGui.ViewModels.UI { _container = container; _windowManager = windowManager; + _httpService = container.Get(); DisplayName = LocalizationHelper.GetString("Settings"); _listTitle.Add(LocalizationHelper.GetString("GameSettings")); @@ -1705,7 +1707,6 @@ namespace MaaWpfGui.ViewModels.UI get => _proxy; set { - WebService.Proxy = value; SetAndNotify(ref _proxy, value); ConfigurationHelper.SetValue(ConfigurationKeys.UpdateProxy, value); } @@ -2147,14 +2148,10 @@ namespace MaaWpfGui.ViewModels.UI if (!System.IO.File.Exists(GoogleAdbFilename)) { - var downloadTask = Task.Run(() => - { - return VersionUpdateViewModel.DownloadFile(GoogleAdbDownloadUrl, GoogleAdbFilename); - }); - var downloadResult = await downloadTask; + var downloadResult = await _httpService.DownloadFileAsync(new Uri(GoogleAdbDownloadUrl), GoogleAdbFilename); if (!downloadResult) { - Execute.OnUIThread(() => + await Execute.OnUIThreadAsync(() => { using var toast = new ToastNotification(LocalizationHelper.GetString("AdbDownloadFailedTitle")); toast.AppendContentText(LocalizationHelper.GetString("AdbDownloadFailedDesc")).Show(); diff --git a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs index b250db1574..4faf12f8b3 100644 --- a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs @@ -41,6 +41,7 @@ namespace MaaWpfGui.ViewModels.UI { private readonly SettingsViewModel _settingsViewModel; private readonly TaskQueueViewModel _taskQueueViewModel; + private readonly IHttpService _httpService; /// /// Initializes a new instance of the class. @@ -50,6 +51,7 @@ namespace MaaWpfGui.ViewModels.UI { _settingsViewModel = container.Get(); _taskQueueViewModel = container.Get(); + _httpService = container.Get(); } [DllImport("MaaCore.dll")] @@ -478,7 +480,7 @@ namespace MaaWpfGui.ViewModels.UI url = url.Replace(repTuple.Item1, repTuple.Item2); } - if (DownloadGithubAssets(url, _assetsObject, Downloader.Native)) + if (DownloadGithubAssets(url, _assetsObject)) { OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadCompletedTitle")); downloaded = true; @@ -675,7 +677,7 @@ namespace MaaWpfGui.ViewModels.UI { for (var i = 0; i < requestSource.Length; i++) { - response = WebService.RequestGet(requestSource[i] + url); + response = _httpService.GetStringAsync(new Uri(requestSource[i] + url)).ConfigureAwait(false).GetAwaiter().GetResult(); if (!string.IsNullOrEmpty(response)) { break; @@ -691,182 +693,20 @@ namespace MaaWpfGui.ViewModels.UI /// /// 下载链接 /// Github Assets 对象 - /// 下载方式,如为空则使用 CSharp 原生方式下载 - /// 保存至的文件夹,如为空则使用当前位置 /// 操作成功返回 true,反之则返回 false - private bool DownloadGithubAssets(string url, JObject assetsObject, - Downloader downloader = Downloader.Native, string saveTo = null) + private bool DownloadGithubAssets(string url, JObject assetsObject) { _logItemViewModels = _taskQueueViewModel.LogItemViewModels; - return DownloadFile( - url: url, - fileName: assetsObject["name"].ToString(), contentType: - assetsObject["content_type"].ToString(), - downloader: downloader, - saveTo: saveTo, - proxy: _settingsViewModel.Proxy); - } - - /// - /// 通过网络获取文件资源 - /// - /// 网络资源地址 - /// 保存此文件使用的文件名 - /// 获取对象的物联网通用类型 - /// 下载方式,如为空则使用 CSharp 原生方式下载 - /// 保存至的文件夹,如为空则使用当前位置 - /// http proxy - /// 操作成功返回 ,反之则返回 - public static bool DownloadFile(string url, string fileName, string contentType = null, - Downloader downloader = Downloader.Native, string saveTo = null, string proxy = "") - { - string fileDir = (saveTo == null) ? Directory.GetCurrentDirectory() : Path.GetFullPath(saveTo); - string fileNameWithTemp = fileName + ".temp"; - string fullFilePath = Path.Combine(fileDir, fileName); - string fullFilePathWithTemp = Path.Combine(fileDir, fileNameWithTemp); - bool returned = false; - try - { - switch (downloader) - { - case Downloader.Native: - returned = DownloadFileForCSharpNative(url: url, filePath: fullFilePathWithTemp, contentType: contentType, proxy); - break; - } - - OutputDownloadProgress(string.Empty); - } - catch (WebException) - { - returned = false; - } - catch (Exception e) - { - // ! REMOVE - returned = false; - } - - if (returned) - { - File.Copy(fullFilePathWithTemp, fullFilePath, true); - } - - // 删除临时文件 - if (File.Exists(fullFilePathWithTemp)) - { - File.Delete(fullFilePathWithTemp); - } - - return returned; - } - - private static bool DownloadFileForAria2(string url, string saveTo, string fileName, string proxy = "") - { - var aria2FilePath = Path.GetFullPath(Directory.GetCurrentDirectory() + "/aria2c.exe"); - var aria2Args = "\"" + url + "\" --continue=true --dir=\"" + saveTo + "\" --out=\"" + fileName + "\" --user-agent=\"" + WebService.RequestUserAgent + "\""; - - if (proxy.Length > 0) - { - aria2Args += " --all-proxy=\"" + proxy + "\""; - } - - var aria2Process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = aria2FilePath, - Arguments = aria2Args, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - }, - EnableRaisingEvents = true, - }; - aria2Process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => - { - if (e.Data != null && e.Data.StartsWith("[")) - { - OutputDownloadProgress(e.Data); - } - }); - - aria2Process.Start(); - aria2Process.BeginOutputReadLine(); - aria2Process.WaitForExit(); - var exit_code = aria2Process.ExitCode; - aria2Process.Close(); - return exit_code == 0; - } - - /// - /// 使用 CSharp 原生方式下载文件 - /// - /// 下载地址 - /// 文件路径 - /// HTTP ContentType - /// http proxy - /// 是否成功 - private static bool DownloadFileForCSharpNative(string url, string filePath, string contentType = null, string proxy = "") - { - bool downloaded = false; - - // 创建 Http 请求 - var httpWebRequest = WebRequest.Create(url) as HttpWebRequest; - - // 设定相关属性 - httpWebRequest.Method = "GET"; - httpWebRequest.UserAgent = WebService.RequestUserAgent; - httpWebRequest.Accept = contentType; - if (!string.IsNullOrEmpty(proxy)) - { - httpWebRequest.Proxy = new WebProxy(proxy); - } - - // 获取输入输出流 - using (var response = httpWebRequest.GetResponse()) - { - using var responseStream = response.GetResponseStream(); - using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); - - // 记录初始化 - long value = 0; - int valueInOneSecond = 0; - long fileMaximum = response.ContentLength; - DateTime beforDT = DateTime.Now; - OutputDownloadProgress(); - - // 输入输出初始化 - byte[] buffer = new byte[81920]; - int byteLen = responseStream.Read(buffer, 0, buffer.Length); - - while (byteLen > 0) - { - // 记录 - valueInOneSecond += byteLen; - double ts = DateTime.Now.Subtract(beforDT).TotalSeconds; - if (ts > 1) - { - beforDT = DateTime.Now; - value += valueInOneSecond; - OutputDownloadProgress(value, fileMaximum, valueInOneSecond, ts); - valueInOneSecond = 0; - } - - // 输入输出 - fileStream.Write(buffer, 0, byteLen); - byteLen = responseStream.Read(buffer, 0, buffer.Length); - } - - downloaded = true; - } - - return downloaded; + return _httpService.DownloadFileAsync( + new Uri(url), + assetsObject["name"].ToString(), + assetsObject["content_type"].ToString()) + .ConfigureAwait(false).GetAwaiter().GetResult(); } private static System.Collections.ObjectModel.ObservableCollection _logItemViewModels = null; - private static void OutputDownloadProgress(long value = 0, long maximum = 1, int len = 0, double ts = 1) + public static void OutputDownloadProgress(long value = 0, long maximum = 1, int len = 0, double ts = 1) { OutputDownloadProgress( string.Format("[{0:F}MiB/{1:F}MiB({2}%) {3:F} KiB/s]",