refactor: web request

This commit is contained in:
Liam Sho
2023-03-25 19:21:03 +08:00
parent 9cbff58681
commit 0f1da36696
11 changed files with 419 additions and 404 deletions

View File

@@ -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<ConfigurationHelper>();
public delegate void ConfigurationUpdateEventHandler(string key, string oldValue, string newValue);
public static event ConfigurationUpdateEventHandler ConfigurationUpdateEvent;
private static bool Released { get; set; }
/// <summary>
@@ -55,8 +58,10 @@ namespace MaaWpfGui.Helper
/// <returns>The return value of <see cref="Save"/></returns>
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
/// <returns>The return value of <see cref="Save"/>.</returns>
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)

View File

@@ -143,6 +143,9 @@ namespace MaaWpfGui.Main
builder.Bind<IMaaHotKeyActionHandler>().To<MaaHotKeyActionHandler>().InSingletonScope();
builder.Bind<IMainWindowManager>().To<MainWindowManager>().InSingletonScope();
builder.Bind<IHttpService>().To<HttpService>().InSingletonScope();
builder.Bind<IMaaApiService>().To<MaaApiService>().InSingletonScope();
}
/// <inheritdoc/>

View File

@@ -0,0 +1,224 @@
// <copyright file="HttpService.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 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
// </copyright>
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<HttpService>();
private HttpClient _client;
public HttpService()
{
ConfigurationHelper.ConfigurationUpdateEvent += (key, old, value) =>
{
if (key != ConfigurationKeys.UpdateProxy)
{
return;
}
if (old != value)
{
BuildHttpClient();
}
};
BuildHttpClient();
}
public async Task<string> GetStringAsync(Uri uri, Dictionary<string, string> extraHeader = null)
{
var response = await GetAsync(uri, extraHeader);
if (response != null)
{
return await response.Content.ReadAsStringAsync();
}
return null;
}
public async Task<Stream> GetStreamAsync(Uri uri, Dictionary<string, string> extraHeader = null)
{
var response = await GetAsync(uri, extraHeader);
if (response != null)
{
return await response.Content.ReadAsStreamAsync();
}
return null;
}
public async Task<HttpResponseMessage> GetAsync(Uri uri, Dictionary<string, string> 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<string> PostAsJsonAsync<T>(Uri uri, T content, Dictionary<string, string> 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<bool> 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<string, string>
{
{ "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);
}
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="IHttpService.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 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
// </copyright>
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace MaaWpfGui.Services
{
public interface IHttpService
{
Task<string> GetStringAsync(Uri uri, Dictionary<string, string> extraHeader = null);
Task<Stream> GetStreamAsync(Uri uri, Dictionary<string, string> extraHeader = null);
Task<HttpResponseMessage> GetAsync(Uri uri, Dictionary<string, string> extraHeader = null);
Task<string> PostAsJsonAsync<T>(Uri uri, T content, Dictionary<string, string> extraHeader = null);
Task<bool> DownloadFileAsync(Uri uri, string fileName, string contentType = null);
}
}

View File

@@ -0,0 +1,25 @@
// <copyright file="IMaaApiService.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 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
// </copyright>
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace MaaWpfGui.Services
{
public interface IMaaApiService
{
Task<JObject> RequestMaaApiWithCache(string api);
JObject LoadApiCache(string api);
}
}

View File

@@ -0,0 +1,81 @@
// <copyright file="MaaApiService.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 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
// </copyright>
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<JObject> 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;
}
}
}
}

View File

@@ -41,6 +41,7 @@ namespace MaaWpfGui.Services
// model references
private readonly TaskQueueViewModel _taskQueueViewModel;
private readonly IMaaApiService _maaApiService;
private static readonly ILogger _logger = Log.ForContext<StageManager>();
@@ -54,6 +55,7 @@ namespace MaaWpfGui.Services
public StageManager(IContainer container)
{
_taskQueueViewModel = container.Get<TaskQueueViewModel>();
_maaApiService = container.Get<IMaaApiService>();
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<long>();
@@ -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";

View File

@@ -1,214 +0,0 @@
// <copyright file="WebService.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 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
// </copyright>
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<string?> 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<string?> PostJsonAsync<T>(string url, T content)
{
return await PostJsonAsync(url, JsonConvert.SerializeObject(content));
}
public static async Task<string?> 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;
}
}
}
}

View File

@@ -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
/// </summary>
/// <param name="container">The IoC container.</param>
/// <param name="windowManager">The window manager.</param>
public CopilotViewModel(IContainer container, IWindowManager windowManager)
/// <param name="httpService">The http service.</param>
public CopilotViewModel(IContainer container, IWindowManager windowManager, IHttpService httpService)
{
_container = container;
_windowManager = windowManager;
_httpService = httpService;
DisplayName = LocalizationHelper.GetString("Copilot");
LogItemViewModels = new ObservableCollection<LogItemViewModel>();
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);

View File

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

View File

@@ -41,6 +41,7 @@ namespace MaaWpfGui.ViewModels.UI
{
private readonly SettingsViewModel _settingsViewModel;
private readonly TaskQueueViewModel _taskQueueViewModel;
private readonly IHttpService _httpService;
/// <summary>
/// Initializes a new instance of the <see cref="VersionUpdateViewModel"/> class.
@@ -50,6 +51,7 @@ namespace MaaWpfGui.ViewModels.UI
{
_settingsViewModel = container.Get<SettingsViewModel>();
_taskQueueViewModel = container.Get<TaskQueueViewModel>();
_httpService = container.Get<IHttpService>();
}
[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
/// </summary>
/// <param name="url">下载链接</param>
/// <param name="assetsObject">Github Assets 对象</param>
/// <param name="downloader">下载方式,如为空则使用 CSharp 原生方式下载</param>
/// <param name="saveTo">保存至的文件夹,如为空则使用当前位置</param>
/// <returns>操作成功返回 true反之则返回 false</returns>
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);
}
/// <summary>
/// 通过网络获取文件资源
/// </summary>
/// <param name="url">网络资源地址</param>
/// <param name="fileName">保存此文件使用的文件名</param>
/// <param name="contentType">获取对象的物联网通用类型</param>
/// <param name="downloader">下载方式,如为空则使用 CSharp 原生方式下载</param>
/// <param name="saveTo">保存至的文件夹,如为空则使用当前位置</param>
/// <param name="proxy">http proxy</param>
/// <returns>操作成功返回 <see langword="true"/>,反之则返回 <see langword="false"/>。</returns>
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;
}
/// <summary>
/// 使用 CSharp 原生方式下载文件
/// </summary>
/// <param name="url">下载地址</param>
/// <param name="filePath">文件路径</param>
/// <param name="contentType">HTTP ContentType</param>
/// <param name="proxy">http proxy</param>
/// <returns>是否成功</returns>
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<LogItemViewModel> _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]",