diff --git a/SteamToolsV2+.sln b/SteamToolsV2+.sln index 11e39a9f6..7937d97c8 100644 --- a/SteamToolsV2+.sln +++ b/SteamToolsV2+.sln @@ -81,6 +81,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "refer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Application.SteamTools.Tools.OpenSourceLibraryList", "System.Application.SteamTools.Tools.OpenSourceLibraryList\System.Application.SteamTools.Tools.OpenSourceLibraryList.csproj", "{CF7C2B80-D393-45EA-A67D-DF46FF17B0F8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3_Test", "3_Test", "{EA0809CE-2CE7-4173-B915-A6BB01522EB5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Application.SteamTools.Client.Test", "System.Application.SteamTools.Client.Test\System.Application.SteamTools.Client.Test.csproj", "{8EDF4429-251A-416D-BB68-93F227191BCF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -293,6 +297,14 @@ Global {CF7C2B80-D393-45EA-A67D-DF46FF17B0F8}.Release|Any CPU.Build.0 = Release|Any CPU {CF7C2B80-D393-45EA-A67D-DF46FF17B0F8}.Release|x86.ActiveCfg = Release|Any CPU {CF7C2B80-D393-45EA-A67D-DF46FF17B0F8}.Release|x86.Build.0 = Release|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Debug|x86.ActiveCfg = Debug|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Debug|x86.Build.0 = Debug|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Release|Any CPU.Build.0 = Release|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Release|x86.ActiveCfg = Release|Any CPU + {8EDF4429-251A-416D-BB68-93F227191BCF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -331,6 +343,8 @@ Global {3C05CED6-7712-40F0-AF17-8DD3F4C0A224} = {8EB91F53-1062-4E9B-8E4A-096F35EA688D} {35D60129-6652-4B6E-A04A-9766C3CF1869} = {8EB91F53-1062-4E9B-8E4A-096F35EA688D} {CF7C2B80-D393-45EA-A67D-DF46FF17B0F8} = {CC6FF77A-6E6B-4D42-B12B-0177745E9D22} + {EA0809CE-2CE7-4173-B915-A6BB01522EB5} = {B43459A1-709B-4623-9677-AAC2FEE4FBA3} + {8EDF4429-251A-416D-BB68-93F227191BCF} = {EA0809CE-2CE7-4173-B915-A6BB01522EB5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3833E7C5-69BC-4CB3-92CE-A8854AA35D8A} diff --git a/System.Application.SteamTools.Client.Desktop/ServiceCollectionExtensions.cs b/System.Application.SteamTools.Client.Desktop/ServiceCollectionExtensions.cs index 7382dcd3f..da38f9fa9 100644 --- a/System.Application.SteamTools.Client.Desktop/ServiceCollectionExtensions.cs +++ b/System.Application.SteamTools.Client.Desktop/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; using System.Application.Services; using System.Application.Services.Implementation; using System.Application.UI; @@ -15,7 +16,12 @@ namespace Microsoft.Extensions.DependencyInjection /// public static IServiceCollection AddDesktopLogging(this IServiceCollection services) { - services.AddLogging(AppHelper.Configure); + var (minLevel, cfg) = AppHelper.Configure(); + services.AddLogging(cfg); + services.Configure(o => + { + o.MinLevel = minLevel; + }); return services; } @@ -41,18 +47,6 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - /// - /// 添加 通用 Http 服务 - /// - /// - /// - public static IServiceCollection AddHttpService(this IServiceCollection services) - { - services.AddHttpClient(); // 添加 System.Net.Http.HttpClient / HttpClientFactory - services.AddSingleton(); - return services; - } - /// /// 添加 Steam 相关助手、工具类服务 /// diff --git a/System.Application.SteamTools.Client.Desktop/Services/IHttpService.cs b/System.Application.SteamTools.Client.Desktop/Services/IHttpService.cs deleted file mode 100644 index 7df4725db..000000000 --- a/System.Application.SteamTools.Client.Desktop/Services/IHttpService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace System.Application.Services -{ - public interface IHttpService - { - public static IHttpService Instance => DI.Get(); - - Task GetAsync(string requestUri, CancellationToken cancellationToken = default) where T : notnull; - } - -#if DEBUG - - [Obsolete("use IHttpService.Instance", true)] - public class HttpServices - { - } - -#endif -} \ No newline at end of file diff --git a/System.Application.SteamTools.Client.Desktop/Services/Implementation/HttpServiceImpl.cs b/System.Application.SteamTools.Client.Desktop/Services/Implementation/HttpServiceImpl.cs deleted file mode 100644 index 8c919d5f1..000000000 --- a/System.Application.SteamTools.Client.Desktop/Services/Implementation/HttpServiceImpl.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using System.IO; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Serialization; - -namespace System.Application.Services.Implementation -{ - internal sealed class HttpServiceImpl : HttpService, IHttpService - { - const string TAG = "HttpS"; - const string Accept = MediaTypeNames.JSON; - readonly JsonSerializer jsonSerializer = new JsonSerializer(); - - public HttpServiceImpl(ILoggerFactory loggerFactory, IHttpPlatformHelper http_helper, IHttpClientFactory clientFactory) : base(loggerFactory.CreateLogger(TAG), http_helper, clientFactory) - { - } - - public async Task GetAsync(string requestUri, CancellationToken cancellationToken) where T : notnull - { - HttpRequestMessage? request = null; - HttpResponseMessage? response = null; - bool notDispose = false; - try - { - request = new HttpRequestMessage(HttpMethod.Get, requestUri); - request.Headers.Accept.ParseAdd(Accept); - request.Headers.UserAgent.ParseAdd(http_helper.UserAgent); - var client = CreateClient(); - response = await client.SendAsync(request, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken) - .ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - if (response.Content != null) - { - var rspContentClrType = typeof(T); - if (rspContentClrType == typeof(string)) - { - return (T)(object)await response.Content.ReadAsStringAsync(); - } - else if (rspContentClrType == typeof(byte[])) - { - return (T)(object)await response.Content.ReadAsByteArrayAsync(); - } - else if (rspContentClrType == typeof(Stream)) - { - notDispose = true; - return (T)(object)await response.Content.ReadAsStreamAsync(); - } - var mime = response.Content.Headers.ContentType?.MediaType ?? Accept; - switch (mime) - { - case MediaTypeNames.JSON: - case MediaTypeNames.XML: - case MediaTypeNames.XML_APP: - { - using var stream = await response.Content - .ReadAsStreamAsync().ConfigureAwait(false); - using var reader = new StreamReader(stream, Encoding.UTF8); - switch (mime) - { - case MediaTypeNames.JSON: - { - using var json = new JsonTextReader(reader); - return jsonSerializer.Deserialize(json); - } - case MediaTypeNames.XML: - case MediaTypeNames.XML_APP: - { - var xmlSerializer = new XmlSerializer(typeof(T)); - return (T)xmlSerializer.Deserialize(reader); - } - } - } - break; - } - } - } - } - catch (Exception e) - { - Log.Error(TAG, e, "GetAsync Fail."); - } - finally - { - if (!notDispose) - { - request?.Dispose(); - response?.Dispose(); - } - } - return default; - } - } -} \ No newline at end of file diff --git a/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs b/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs index be2911e45..96555e9fc 100644 --- a/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs +++ b/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs @@ -76,15 +76,21 @@ namespace System.Application.UI /// static LoggerFilterOptions LoggerFilterOptions => DI.Get>().Value; - internal static void Configure(ILoggingBuilder builder) + internal static (LogLevel minLevel, Action cfg) Configure() { - var logLevel = DefaultLoggerMinLevel; - builder.SetMinimumLevel(logLevel); - SetNLoggerMinLevel(logLevel); + var minLevel = DefaultLoggerMinLevel; - // 可以多个日志提供同时用,比如还可以在 Win 平台再添加一个 Windows 事件日志 + Action cfg = builder => + { + builder.SetMinimumLevel(minLevel); + SetNLoggerMinLevel(minLevel); - builder.AddNLog(); // 添加 NLog 日志 + // 可以多个日志提供同时用,比如还可以在 Win 平台再添加一个 Windows 事件日志 + + builder.AddNLog(); // 添加 NLog 日志 + }; + + return (minLevel, cfg); } /// diff --git a/System.Application.SteamTools.Client.Desktop/UI/ViewModels/Pages/SteamAccountPageViewModel.cs b/System.Application.SteamTools.Client.Desktop/UI/ViewModels/Pages/SteamAccountPageViewModel.cs index 4a2c0953d..ccd529524 100644 --- a/System.Application.SteamTools.Client.Desktop/UI/ViewModels/Pages/SteamAccountPageViewModel.cs +++ b/System.Application.SteamTools.Client.Desktop/UI/ViewModels/Pages/SteamAccountPageViewModel.cs @@ -4,7 +4,6 @@ using System.Application.Services; using System.Application.UI.Resx; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Threading.Tasks; @@ -32,11 +31,10 @@ namespace System.Application.UI.ViewModels set => this.RaiseAndSetIfChanged(ref _steamUsers, value); } - internal async override Task Initialize() { SteamUsers = new ObservableCollection(steamService.GetRememberUserList()); - if (SteamUsers == null || SteamUsers.Count < 1) + if (!SteamUsers.Any_Nullable()) { return; } @@ -55,9 +53,9 @@ namespace System.Application.UI.ViewModels users[i].WantsOfflineMode = temp.WantsOfflineMode; users[i].SkipOfflineModeWarning = temp.SkipOfflineModeWarning; users[i].OriginVdfString = temp.OriginVdfString; - users[i].AvatarMediumStream = string.IsNullOrEmpty(users[i].AvatarMedium) ? null : await httpService.GetAsync(users[i].AvatarMedium); + users[i].AvatarMediumStream = string.IsNullOrEmpty(users[i].AvatarMedium) ? null : await httpService.GetImageAsync(users[i].AvatarMedium, ImageChannelType.SteamAvatars); } SteamUsers = new ObservableCollection(users.OrderByDescending(o => o.RememberPassword).ThenByDescending(o => o.LastLoginTime).ToList()); } } -} +} \ No newline at end of file diff --git a/System.Application.SteamTools.Client.Test/HttpTest.cs b/System.Application.SteamTools.Client.Test/HttpTest.cs new file mode 100644 index 000000000..20030ecd1 --- /dev/null +++ b/System.Application.SteamTools.Client.Test/HttpTest.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using System.Application.Services; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Application +{ + [TestFixture] + public class HttpTest + { + [Test] + public void GetImage() + { + const string api_base = "https://localhost:5001/"; + var images = new[] { + "images/icons/company.png", + "images/icons/GitHub-Mark-120px-plus.png", + "images/icons/icon.png", + "images/company.png", + "images/icons/company.png", + "images/icons/GitHub-Mark-120px-plus.png", + "images/icons/logo.png", + "images/company.png", + "images/icons/icon.png", + "images/icons/icon.png", + "images/icons/steam.png" }; + + var tasks = images.Select(Handle).ToArray(); + Task.WaitAll(tasks); + TestContext.WriteLine("OK"); + + static async Task Handle(string requestUri) + { + var tid = Thread.CurrentThread.ManagedThreadId; + var result = await IHttpService.Instance.GetImageAsync( + api_base + requestUri, ImageChannelType.SteamAvatars); + var tid2 = Thread.CurrentThread.ManagedThreadId; + TestContext.WriteLine($"({tid}-{tid2})requestUri: {requestUri}, length: {result?.Length}"); + } + } + } +} \ No newline at end of file diff --git a/System.Application.SteamTools.Client.Test/SetupFixture.cs b/System.Application.SteamTools.Client.Test/SetupFixture.cs new file mode 100644 index 000000000..e5959551d --- /dev/null +++ b/System.Application.SteamTools.Client.Test/SetupFixture.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using System.IO; +using System.Logging; + +namespace System.Application +{ + [SetUpFixture] + public class SetupFixture + { + public static bool DIInit { get; set; } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + // TODO: Add code here that is run before + // all tests in the assembly are run + if (!DIInit) + { + DI.Init(ConfigureServices); + + var path = AppContext.BaseDirectory; + var path1 = Path.Combine(path, "AppData"); + IOPath.DirCreateByNotExists(path1); + var path2 = Path.Combine(path, "Cache"); + IOPath.DirCreateByNotExists(path2); + string GetAppDataDirectory() => path1; + string GetCacheDirectory() => path2; + IOPath.InitFileSystem(GetAppDataDirectory, GetCacheDirectory); + } + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + // TODO: Add code here that is run after + // all tests in the assembly have been run + } + + static void ConfigureServices(IServiceCollection services) + { + services.AddLogging(l => l.AddProvider(NUnitLoggerProvider.Instance)); + services.Configure(o => + { + o.MinLevel = LogLevel.Trace; + }); + services.TryAddHttpPlatformHelper(); + services.AddHttpService(); + } + } +} \ No newline at end of file diff --git a/System.Application.SteamTools.Client.Test/System.Application.SteamTools.Client.Test.csproj b/System.Application.SteamTools.Client.Test/System.Application.SteamTools.Client.Test.csproj new file mode 100644 index 000000000..b591281b0 --- /dev/null +++ b/System.Application.SteamTools.Client.Test/System.Application.SteamTools.Client.Test.csproj @@ -0,0 +1,32 @@ + + + + net5.0 + System.Application + UnitTest;$(DefineConstants) + + + + + Properties\AssemblyInfo.cs + + + Logging\NUnitLogger.cs + + + Logging\NUnitLoggerProvider.cs + + + + + + + + + + + + + + + diff --git a/System.Application.SteamTools.Client/ImageChannelType.cs b/System.Application.SteamTools.Client/ImageChannelType.cs new file mode 100644 index 000000000..74c8b8100 --- /dev/null +++ b/System.Application.SteamTools.Client/ImageChannelType.cs @@ -0,0 +1,40 @@ +using System.Application; +using System.Application.Services; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Application +{ + /// + /// 图片下载渠道类型 + /// + public enum ImageChannelType + { + /// + /// Steam 头像图片 + /// + SteamAvatars, + + /// + /// Steam 游戏图片 + /// + SteamGames, + } +} + +namespace System +{ + public static class ImageChannelTypeEnumExtensions + { + /// + public static Task GetImageAsync(this IHttpService httpService, + string requestUri, + ImageChannelType channelType, + CancellationToken cancellationToken = default) + { + var channelType_ = channelType.ToString(); + return httpService.GetImageAsync(requestUri, channelType_, cancellationToken); + } + } +} \ No newline at end of file diff --git a/System.Application.SteamTools.Client/ServiceCollectionExtensions.cs b/System.Application.SteamTools.Client/ServiceCollectionExtensions.cs index 0c359b304..b4be2b635 100644 --- a/System.Application.SteamTools.Client/ServiceCollectionExtensions.cs +++ b/System.Application.SteamTools.Client/ServiceCollectionExtensions.cs @@ -1,15 +1,24 @@ using Microsoft.Extensions.DependencyInjection.Extensions; -using System; using System.Application.Services; using System.Application.Services.Implementation; -using System.Collections.Generic; -using System.Text; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection { public static class IdentityServiceCollectionExtensions { + /// + /// 添加 通用 Http 服务 + /// + /// + /// + public static IServiceCollection AddHttpService(this IServiceCollection services) + { + services.AddHttpClient(); // 添加 System.Net.Http.HttpClient / HttpClientFactory + services.AddSingleton(); + return services; + } + public static IServiceCollection TryAddUserManager(this IServiceCollection services) { services.TryAddSingleton(); @@ -17,4 +26,4 @@ namespace Microsoft.Extensions.DependencyInjection return services; } } -} +} \ No newline at end of file diff --git a/System.Application.SteamTools.Client/Services/IHttpService.cs b/System.Application.SteamTools.Client/Services/IHttpService.cs new file mode 100644 index 000000000..a21c25475 --- /dev/null +++ b/System.Application.SteamTools.Client/Services/IHttpService.cs @@ -0,0 +1,44 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Application.Services +{ + public interface IHttpService + { + public static IHttpService Instance => DI.Get(); + + /// + /// 通过 Get 请求 API 内容 + /// + /// + /// + /// + /// + /// + Task GetAsync(string requestUri, + string accept = MediaTypeNames.JSON, + CancellationToken cancellationToken = default) where T : notnull; + + /// + /// 通过 Get 请求 Image 内容 + /// + /// + /// 渠道类型,根据不同的类型建立不同的缓存文件夹 + /// + /// + Task GetImageAsync( + string requestUri, + string channelType, + CancellationToken cancellationToken = default); + } + +#if DEBUG + + [Obsolete("use IHttpService.Instance", true)] + public class HttpServices + { + } + +#endif +} \ No newline at end of file diff --git a/System.Application.SteamTools.Client/Services/Implementation/HttpServiceImpl.cs b/System.Application.SteamTools.Client/Services/Implementation/HttpServiceImpl.cs new file mode 100644 index 000000000..daafc25a3 --- /dev/null +++ b/System.Application.SteamTools.Client/Services/Implementation/HttpServiceImpl.cs @@ -0,0 +1,255 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System.IO; +using System.IO.FileFormats; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Serialization; +using System.Collections.Concurrent; +using System.Security.Cryptography; + +namespace System.Application.Services.Implementation +{ + internal sealed class HttpServiceImpl : GeneralHttpClientFactory, IHttpService + { + const string TAG = "HttpS"; + readonly JsonSerializer jsonSerializer = new(); + + public HttpServiceImpl( + ILoggerFactory loggerFactory, + IHttpPlatformHelper http_helper, + IHttpClientFactory clientFactory) + : base(loggerFactory.CreateLogger(TAG), http_helper, clientFactory) + { + } + + public async Task GetAsync( + string requestUri, + string accept, + CancellationToken cancellationToken) where T : notnull + { + HttpRequestMessage? request = null; + HttpResponseMessage? response = null; + bool notDispose = false; + try + { + request = new HttpRequestMessage(HttpMethod.Get, requestUri); + request.Headers.Accept.ParseAdd(accept); + request.Headers.UserAgent.ParseAdd(http_helper.UserAgent); + var client = CreateClient(); + response = await client.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken) + .ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + if (response.Content != null) + { + var rspContentClrType = typeof(T); + if (rspContentClrType == typeof(string)) + { + return (T)(object)await response.Content.ReadAsStringAsync(); + } + else if (rspContentClrType == typeof(byte[])) + { + return (T)(object)await response.Content.ReadAsByteArrayAsync(); + } + else if (rspContentClrType == typeof(Stream)) + { + notDispose = true; + return (T)(object)await response.Content.ReadAsStreamAsync(); + } + var mime = response.Content.Headers.ContentType?.MediaType ?? accept; + switch (mime) + { + case MediaTypeNames.JSON: + case MediaTypeNames.XML: + case MediaTypeNames.XML_APP: + { + using var stream = await response.Content + .ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(stream, Encoding.UTF8); + switch (mime) + { + case MediaTypeNames.JSON: + { + using var json = new JsonTextReader(reader); + return jsonSerializer.Deserialize(json); + } + case MediaTypeNames.XML: + case MediaTypeNames.XML_APP: + { + var xmlSerializer = new XmlSerializer(typeof(T)); + return (T)xmlSerializer.Deserialize(reader); + } + } + } + break; + } + } + } + } + catch (Exception e) + { + Log.Error(TAG, e, "GetAsync Fail."); + } + finally + { + if (!notDispose) + { + request?.Dispose(); + response?.Dispose(); + } + } + return default; + } + + async Task GetImageAsync_( + string requestUri, + string localCacheFilePath, + CancellationToken cancellationToken) + { + var localCacheFilePathExists = File.Exists(localCacheFilePath); + + try + { + if (localCacheFilePathExists) // 存在缓存文件 + { + var fileStream = File.OpenRead(localCacheFilePath); + if (FileFormat.IsImage(fileStream, out var format)) + { + if (http_helper.SupportedImageFormats.Contains(format)) // 读取缓存并且格式符合要求 + { + fileStream.Close(); + Log.Debug(TAG, "GetImageAsync localCacheFilePath: {0}", localCacheFilePath); + return localCacheFilePath; + } + else // 格式不准确 + { + Log.Error(TAG, "GetImageAsync Not Supported ImageFormat: {0}.", format); + } + } + else // 未知的图片格式 + { + Log.Error(TAG, "GetImageAsync Unknown ImageFormat."); + } + fileStream.Close(); + } + } + catch (Exception e) + { + Log.Error(TAG, e, "GetImageAsync Load LocalFile Fail."); + } + + if (localCacheFilePathExists) + { + File.Delete(localCacheFilePath); // 必须删除文件,重新下载覆盖 + } + + try + { + Log.Debug(TAG, "GetImageAsync requestUri: {0}", requestUri); + //#if DEBUG + // var now = DateTime.Now; + // var filePath = Path.Combine(IOPath.CacheDirectory, $"{now.ToString(DateTimeFormat.Connect)}-{Hashs.String.Crc32(requestUri)}"); + // File.WriteAllText(filePath, now.ToString()); + //#endif + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + request.Headers.Accept.ParseAdd(http_helper.AcceptImages); + request.Headers.UserAgent.ParseAdd(http_helper.UserAgent); + var client = CreateClient(); + var response = await client.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken) + .ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + var stream = await response.Content.ReadAsStreamAsync(); + var localCacheFilePath2 = localCacheFilePath + ".download-cache"; + var fileStream = File.Create(localCacheFilePath2); + cancellationToken.Register(() => + { + fileStream.Close(); + IOPath.FileTryDelete(localCacheFilePath2); + }); + await stream.CopyToAsync(fileStream, cancellationToken); + await fileStream.FlushAsync(); + bool isSupportedImageFormat; + if (FileFormat.IsImage(fileStream, out var format)) + { + if (http_helper.SupportedImageFormats.Contains(format)) // 读取缓存并且格式符合要求 + { + isSupportedImageFormat = true; + } + else // 格式不准确 + { + Log.Error(TAG, + "GetImageAsync Download Not Supported ImageFormat: {0}.", format); + isSupportedImageFormat = false; + } + } + else // 未知的图片格式 + { + Log.Error(TAG, "GetImageAsync Download Unknown ImageFormat."); + isSupportedImageFormat = false; + } + fileStream.Close(); + if (isSupportedImageFormat) + { + File.Move(localCacheFilePath2, localCacheFilePath); + return localCacheFilePath; + } + else + { + IOPath.FileTryDelete(localCacheFilePath2); + } + } + } + catch (Exception e) + { + Log.Error(TAG, e, "GetImageAsync Fail."); + } + + return default; + } + + readonly ConcurrentDictionary>> get_image_pipeline = new(); + + public async Task GetImageAsync( + string requestUri, + string channelType, + CancellationToken cancellationToken) + { + if (get_image_pipeline.ContainsKey(channelType)) + { + var pairs = get_image_pipeline[channelType]; + if (pairs.ContainsKey(requestUri)) + { + var findResult = await pairs[requestUri]; + return FileOpenRead(findResult); + } + } + else + { + get_image_pipeline.TryAdd(channelType, new ConcurrentDictionary>()); + } + + var dirPath = Path.Combine(IOPath.CacheDirectory, "Images", channelType); + IOPath.DirCreateByNotExists(dirPath); + var fileName = Hashs.String.SHA256(requestUri) + FileEx.ImageSource; + var localCacheFilePath = Path.Combine(dirPath, fileName); + + var pairs2 = get_image_pipeline[channelType]; + var value = GetImageAsync_(requestUri, localCacheFilePath, cancellationToken); + pairs2.TryAdd(requestUri, value); + var result = await value; + pairs2.TryRemove(requestUri, out var _); + return FileOpenRead(result); + + static Stream? FileOpenRead(string? p) => p == null ? null : File.OpenRead(p); + } + } +} \ No newline at end of file diff --git a/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs b/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs index 6bd43a98a..45692c9a6 100644 --- a/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs +++ b/System.Application.SteamTools.Services.CloudService/Services/CloudService/CloudServiceClientBase.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace System.Application.Services.CloudService { - public abstract class CloudServiceClientBase : HttpService, ICloudServiceClient, IApiConnectionPlatformHelper + public abstract class CloudServiceClientBase : GeneralHttpClientFactory, ICloudServiceClient, IApiConnectionPlatformHelper { public const string ClientName_ = "CloudServiceClient"; internal const string DefaultApiBaseUrl = Constants.Prefix_HTTPS + "api.steamtool.net"; diff --git a/System.Common.CoreLib/Net/Http/HttpService.cs b/System.Common.CoreLib/Net/Http/GeneralHttpClientFactory.cs similarity index 87% rename from System.Common.CoreLib/Net/Http/HttpService.cs rename to System.Common.CoreLib/Net/Http/GeneralHttpClientFactory.cs index 40bfd6a73..26f0092f3 100644 --- a/System.Common.CoreLib/Net/Http/HttpService.cs +++ b/System.Common.CoreLib/Net/Http/GeneralHttpClientFactory.cs @@ -2,13 +2,16 @@ namespace System.Net.Http { - public abstract class HttpService + /// + /// 通用 工厂 + /// + public abstract class GeneralHttpClientFactory { protected readonly ILogger logger; protected readonly IHttpPlatformHelper http_helper; readonly IHttpClientFactory _clientFactory; - public HttpService( + public GeneralHttpClientFactory( ILogger logger, IHttpPlatformHelper http_helper, IHttpClientFactory clientFactory) diff --git a/System.Common.CoreLib/Net/Http/HttpPlatformHelper.cs b/System.Common.CoreLib/Net/Http/HttpPlatformHelper.cs index 04b7614be..24d3e8b24 100644 --- a/System.Common.CoreLib/Net/Http/HttpPlatformHelper.cs +++ b/System.Common.CoreLib/Net/Http/HttpPlatformHelper.cs @@ -1,4 +1,5 @@ using System.IO; +using System.IO.FileFormats; namespace System.Net.Http { @@ -9,6 +10,17 @@ namespace System.Net.Http public virtual string UserAgent => DefaultUserAgent; + public virtual string AcceptImages => "image/png, image/*; q=0.8"; + + static readonly Lazy mSupportedImageFormats = new(() => new ImageFormat[] + { + ImageFormat.JPEG, + ImageFormat.PNG, + ImageFormat.GIF, + }); + + public virtual ImageFormat[] SupportedImageFormats => mSupportedImageFormats.Value; + public (string filePath, string mime)? TryHandleUploadFile(Stream fileStream) => null; } } \ No newline at end of file diff --git a/System.Common.CoreLib/Net/Http/IHttpPlatformHelper.cs b/System.Common.CoreLib/Net/Http/IHttpPlatformHelper.cs index c57aebe6a..156bab0fd 100644 --- a/System.Common.CoreLib/Net/Http/IHttpPlatformHelper.cs +++ b/System.Common.CoreLib/Net/Http/IHttpPlatformHelper.cs @@ -1,4 +1,5 @@ using System.IO; +using System.IO.FileFormats; namespace System.Net.Http { @@ -14,6 +15,9 @@ namespace System.Net.Http /// string UserAgent { get; } + string AcceptImages { get; } + + ImageFormat[] SupportedImageFormats { get; } /// /// 尝试将需要上传的文件流[处理]后保存到临时路径中并返回信息 /// [处理]由客户端平台原生实现 diff --git a/System.Common.UnitTest/SetupFixture.cs b/System.Common.UnitTest/SetupFixture.cs index 8ffc17741..b544cb067 100644 --- a/System.Common.UnitTest/SetupFixture.cs +++ b/System.Common.UnitTest/SetupFixture.cs @@ -29,6 +29,10 @@ namespace System static void ConfigureServices(IServiceCollection services) { services.AddLogging(l => l.AddProvider(NUnitLoggerProvider.Instance)); + services.Configure(o => + { + o.MinLevel = LogLevel.Trace; + }); //services.AddPinyin(); } }