diff --git a/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/HttpClientBehaviorTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/HttpClientBehaviorTest.cs new file mode 100644 index 00000000..c6fbbe9c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao.Test/RuntimeBehavior/HttpClientBehaviorTest.cs @@ -0,0 +1,45 @@ +using System.Drawing; +using System.Net.Http; +using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Snap.Hutao.Test.RuntimeBehavior; + +[TestClass] +public sealed class HttpClientBehaviorTest +{ + private const int MessageNotYetSent = 0; + + [TestMethod] + public async Task RetrySendHttpRequestMessage() + { + using (HttpClient httpClient = new()) + { + HttpRequestMessage requestMessage = new(HttpMethod.Post, "https://jsonplaceholder.typicode.com/posts"); + JsonContent content = JsonContent.Create(new Point(12, 34)); + requestMessage.Content = content; + using (requestMessage) + { + await httpClient.SendAsync(requestMessage).ConfigureAwait(false); + } + + Interlocked.Exchange(ref GetPrivateSendStatus(requestMessage), MessageNotYetSent); + Volatile.Write(ref GetPrivateDisposed(content), false); + await httpClient.SendAsync(requestMessage).ConfigureAwait(false); + } + } + + // private int _sendStatus + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_sendStatus")] + private static extern ref int GetPrivateSendStatus(HttpRequestMessage message); + + // private bool _disposed + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")] + private static extern ref bool GetPrivateDisposed(HttpRequestMessage message); + + // private bool _disposed + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")] + private static extern ref bool GetPrivateDisposed(HttpContent content); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs b/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs index 38b6f64a..9a4d02c7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Markup; namespace Snap.Hutao.Control; @@ -36,9 +37,18 @@ internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl private static void IsLoadingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Loading control = (Loading)d; - control.presenter ??= control.GetTemplateChild("ContentGrid") as FrameworkElement; - control?.Update(); + if ((bool)e.NewValue) + { + control.presenter ??= control.GetTemplateChild("ContentGrid") as FrameworkElement; + } + else if (control.presenter is not null) + { + XamlMarkupHelper.UnloadObject(control.presenter); + control.presenter = null; + } + + control.Update(); } private void Update() diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml index aa45b4ab..d7567bf5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml @@ -23,7 +23,8 @@ + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + x:Load="False"> @@ -84,4 +85,4 @@ - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index af792574..9c939791 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -11,16 +11,13 @@ using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; using System.Collections.Concurrent; using System.Collections.Frozen; +using System.Diagnostics; using System.IO; using System.Net; using System.Net.Http; namespace Snap.Hutao.Core.Caching; -/// -/// Provides methods and tools to cache files in a folder -/// The class's name will become the cache folder's name -/// [HighQuality] [ConstructorGenerated] [Injection(InjectAs.Singleton, typeof(IImageCache))] @@ -28,10 +25,9 @@ namespace Snap.Hutao.Core.Caching; [PrimaryHttpMessageHandler(MaxConnectionsPerServer = 8)] internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOperation { - private const string CacheFolderName = nameof(ImageCache); private const string CacheFailedDownloadTasksName = $"{nameof(ImageCache)}.FailedDownloadTasks"; - private readonly FrozenDictionary retryCountToDelay = FrozenDictionary.ToFrozenDictionary( + private static readonly FrozenDictionary DelayFromRetryCount = FrozenDictionary.ToFrozenDictionary( [ KeyValuePair.Create(0, TimeSpan.FromSeconds(4)), KeyValuePair.Create(1, TimeSpan.FromSeconds(16)), @@ -46,16 +42,13 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera private readonly ILogger logger; private readonly IMemoryCache memoryCache; - private string? baseFolder; private string? cacheFolder; private string CacheFolder { get => LazyInitializer.EnsureInitialized(ref cacheFolder, () => { - baseFolder ??= serviceProvider.GetRequiredService().LocalCache; - DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName)); - return info.FullName; + return serviceProvider.GetRequiredService().GetLocalCacheImageCacheFolder(); }); } @@ -149,8 +142,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera return treatNullFileAsInvalid; } - FileInfo fileInfo = new(file); - return fileInfo.Length == 0; + return new FileInfo(file).Length == 0; } private void RemoveCore(IEnumerable filePaths) @@ -172,80 +164,76 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera [SuppressMessage("", "SH003")] private async Task DownloadFileAsync(Uri uri, string baseFile) { - int retryCount = 0; - HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache)); - while (retryCount < 3) + using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache))) { + int retryCount = 0; + HttpRequestMessageBuilder requestMessageBuilder = httpRequestMessageBuilderFactory .Create() .SetRequestUri(uri) - - // These headers are only available for our own api - .SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase)) + .SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase)) // These headers are only available for our own api .Get(); - using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage) + while (retryCount < 3) { - using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) - { - if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri) - { - logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target); - } + requestMessageBuilder.Resurrect(); - if (responseMessage.IsSuccessStatusCode) + using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage) + { + using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) { - if (responseMessage.Content.Headers.ContentType?.MediaType is "application/json") + // Redirect detection + if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri) { -#if DEBUG - DebugTrack(uri); -#endif - string raw = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - logger.LogColorizedCritical("Failed to download '{Uri}' with unexpected body '{Raw}'", (uri, ConsoleColor.Red), (raw, ConsoleColor.DarkYellow)); - return; + logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target); } - using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false)) + if (responseMessage.IsSuccessStatusCode) { - using (FileStream fileStream = File.Create(baseFile)) + if (responseMessage.Content.Headers.ContentType?.MediaType is "application/json") { - await httpStream.CopyToAsync(fileStream).ConfigureAwait(false); + DebugTrackFailedUri(uri); + string raw = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); + logger.LogColorizedCritical("Failed to download '{Uri}' with unexpected body '{Raw}'", (uri, ConsoleColor.Red), (raw, ConsoleColor.DarkYellow)); return; } - } - } - switch (responseMessage.StatusCode) - { - case HttpStatusCode.TooManyRequests: + using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false)) { - retryCount++; - TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount]; - logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay); - await Task.Delay(delay).ConfigureAwait(false); - break; + using (FileStream fileStream = File.Create(baseFile)) + { + await httpStream.CopyToAsync(fileStream).ConfigureAwait(false); + return; + } } + } - default: -#if DEBUG - DebugTrack(uri); -#endif - logger.LogColorizedCritical("Failed to download '{Uri}' with status code '{StatusCode}'", (uri, ConsoleColor.Red), (responseMessage.StatusCode, ConsoleColor.DarkYellow)); - return; + switch (responseMessage.StatusCode) + { + case HttpStatusCode.TooManyRequests: + { + retryCount++; + TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? DelayFromRetryCount[retryCount]; + logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay); + await Task.Delay(delay).ConfigureAwait(false); + break; + } + + default: + DebugTrackFailedUri(uri); + logger.LogColorizedCritical("Failed to download '{Uri}' with status code '{StatusCode}'", (uri, ConsoleColor.Red), (responseMessage.StatusCode, ConsoleColor.DarkYellow)); + return; + } } } } } } -} -#if DEBUG -internal partial class ImageCache -{ - private void DebugTrack(Uri uri) + [Conditional("DEBUG")] + private void DebugTrackFailedUri(Uri uri) { - HashSet? set = memoryCache.GetOrCreate(CacheFailedDownloadTasksName, entry => entry.Value ??= new HashSet()) as HashSet; + HashSet? set = memoryCache.GetOrCreate(CacheFailedDownloadTasksName, entry => new HashSet()); set?.Add(uri.ToString()); } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs index 7caba81f..6059d92a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs @@ -70,7 +70,7 @@ internal sealed class ObservableReorderableDbCollection : ObservableCol [SuppressMessage("", "SA1402")] internal sealed class ObservableReorderableDbCollection : ObservableCollection - where TEntityOnly : class, IEntityOnly + where TEntityOnly : class, IEntityAccess where TEntity : class, IReorderable { private readonly IServiceProvider serviceProvider; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs index fe550299..90e04cfc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs @@ -73,7 +73,7 @@ internal sealed partial class ScopedDbCurrent [ConstructorGenerated] internal sealed partial class ScopedDbCurrent - where TEntityOnly : class, IEntityOnly + where TEntityOnly : class, IEntityAccess where TEntity : class, ISelectable where TMessage : Message.ValueChangedMessage, new() { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index d32e7276..ee49a41f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -41,7 +41,7 @@ internal static class DependencyInjection .AddJsonOptions() .AddDatabase() .AddInjections() - .AddAllHttpClients() + .AddConfiguredHttpClients() // Discrete services .AddSingleton() diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs index fc3baa03..ac0cc302 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs @@ -34,27 +34,27 @@ internal static class IocConfiguration .AddTransient(typeof(Database.ScopedDbCurrent<,>)) .AddTransient(typeof(Database.ScopedDbCurrent<,,>)) .AddDbContextPool(AddDbContextCore); - } - private static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder) - { - RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService(); - string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db"); - string sqlConnectionString = $"Data Source={dbFile}"; - - // Temporarily create a context - using (AppDbContext context = AppDbContext.Create(serviceProvider, sqlConnectionString)) + static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder) { - if (context.Database.GetPendingMigrations().Any()) - { - System.Diagnostics.Debug.WriteLine("[Database] Performing AppDbContext Migrations"); - context.Database.Migrate(); - } - } + RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService(); + string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db"); + string sqlConnectionString = $"Data Source={dbFile}"; - builder - .EnableSensitiveDataLogging() - .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) - .UseSqlite(sqlConnectionString); + // Temporarily create a context + using (AppDbContext context = AppDbContext.Create(serviceProvider, sqlConnectionString)) + { + if (context.Database.GetPendingMigrations().Any()) + { + System.Diagnostics.Debug.WriteLine("[Database] Performing AppDbContext Migrations"); + context.Database.Migrate(); + } + } + + builder + .EnableSensitiveDataLogging() + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .UseSqlite(sqlConnectionString); + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs index de8ef22b..dfe5fe0e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocHttpClientConfiguration.cs @@ -15,7 +15,7 @@ internal static partial class IocHttpClientConfiguration { private const string ApplicationJson = "application/json"; - public static IServiceCollection AddAllHttpClients(this IServiceCollection services) + public static IServiceCollection AddConfiguredHttpClients(this IServiceCollection services) { services .ConfigureHttpClientDefaults(clientBuilder => @@ -27,7 +27,7 @@ internal static partial class IocHttpClientConfiguration HttpClientHandler clientHandler = (HttpClientHandler)handler; clientHandler.AllowAutoRedirect = true; clientHandler.UseProxy = true; - clientHandler.Proxy = provider.GetRequiredService(); + clientHandler.Proxy = provider.GetRequiredService(); }); }) .AddHttpClients(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs deleted file mode 100644 index 5cdf3e36..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Runtime.CompilerServices; - -namespace Snap.Hutao.Core.ExceptionService; - -/// -/// 帮助更好的抛出异常 -/// -[HighQuality] -[System.Diagnostics.StackTraceHidden] -[Obsolete("Use HutaoException instead")] -internal static class ThrowHelper -{ - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - public static ArgumentException Argument(string message, string? paramName) - { - throw new ArgumentException(message, paramName); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/DirectoryOperation.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/DirectoryOperation.cs index 39132ba2..2e3894ad 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/DirectoryOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/DirectoryOperation.cs @@ -2,7 +2,12 @@ // Licensed under the MIT license. using Microsoft.VisualBasic.FileIO; +using Snap.Hutao.Win32.System.Com; +using Snap.Hutao.Win32.UI.Shell; using System.IO; +using static Snap.Hutao.Win32.Macros; +using static Snap.Hutao.Win32.Ole32; +using static Snap.Hutao.Win32.Shell32; namespace Snap.Hutao.Core.IO; @@ -18,4 +23,29 @@ internal static class DirectoryOperation FileSystem.MoveDirectory(sourceDirName, destDirName, true); return true; } + + public static unsafe bool UnsafeRename(string path, string name, FILEOPERATION_FLAGS flags = FILEOPERATION_FLAGS.FOF_ALLOWUNDO | FILEOPERATION_FLAGS.FOF_NOCONFIRMMKDIR) + { + bool result = false; + + if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation))) + { + if (SUCCEEDED(SHCreateItemFromParsingName(path, default, in IShellItem.IID, out IShellItem* pShellItem))) + { + pFileOperation->SetOperationFlags(flags); + pFileOperation->RenameItem(pShellItem, name, default); + + if (SUCCEEDED(pFileOperation->PerformOperations())) + { + result = true; + } + + IUnknownMarshal.Release(pShellItem); + } + + IUnknownMarshal.Release(pFileOperation); + } + + return result; + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs index 00b97aa5..83f8b2b4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs @@ -45,6 +45,30 @@ internal static class FileOperation return true; } + public static unsafe bool UnsafeDelete(string path) + { + bool result = false; + + if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation))) + { + if (SUCCEEDED(SHCreateItemFromParsingName(path, default, in IShellItem.IID, out IShellItem* pShellItem))) + { + pFileOperation->DeleteItem(pShellItem, default); + + if (SUCCEEDED(pFileOperation->PerformOperations())) + { + result = true; + } + + IUnknownMarshal.Release(pShellItem); + } + + IUnknownMarshal.Release(pFileOperation); + } + + return result; + } + public static unsafe bool UnsafeMove(string sourceFileName, string destFileName) { bool result = false; @@ -73,28 +97,4 @@ internal static class FileOperation return result; } - - public static unsafe bool UnsafeDelete(string path) - { - bool result = false; - - if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation))) - { - if (SUCCEEDED(SHCreateItemFromParsingName(path, default, in IShellItem.IID, out IShellItem* pShellItem))) - { - pFileOperation->DeleteItem(pShellItem, default); - - if (SUCCEEDED(pFileOperation->PerformOperations())) - { - result = true; - } - - IUnknownMarshal.Release(pShellItem); - } - - IUnknownMarshal.Release(pFileOperation); - } - - return result; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs index 86f4b96a..435597fc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs @@ -6,6 +6,9 @@ using System.Text; namespace Snap.Hutao.Core.IO.Hashing; +#if NET9_0_OR_GREATER +[Obsolete] +#endif internal static class Hash { public static unsafe string SHA1HexString(string input) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/DynamicHttpProxy.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/HttpProxyUsingSystemProxy.cs similarity index 93% rename from src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/DynamicHttpProxy.cs rename to src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/HttpProxyUsingSystemProxy.cs index f40b8a4f..b0169e6c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/DynamicHttpProxy.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/HttpProxyUsingSystemProxy.cs @@ -3,13 +3,14 @@ using CommunityToolkit.Mvvm.ComponentModel; using Snap.Hutao.Win32.Registry; +using System.Linq.Expressions; using System.Net; using System.Reflection; namespace Snap.Hutao.Core.IO.Http.Proxy; [Injection(InjectAs.Singleton)] -internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, IDisposable +internal sealed partial class HttpProxyUsingSystemProxy : ObservableObject, IWebProxy, IDisposable { private const string ProxySettingPath = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections"; @@ -20,7 +21,7 @@ internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, ID private IWebProxy innerProxy = default!; - public DynamicHttpProxy(IServiceProvider serviceProvider) + public HttpProxyUsingSystemProxy(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; UpdateInnerProxy(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs index acc10562..629a74b5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs @@ -27,4 +27,11 @@ internal static class RuntimeOptionsExtension Directory.CreateDirectory(directory); return directory; } + + public static string GetLocalCacheImageCacheFolder(this RuntimeOptions options) + { + string directory = Path.Combine(options.LocalCache, "ImageCache"); + Directory.CreateDirectory(directory); + return directory; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs index 347f2638..67123d28 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs @@ -67,7 +67,7 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop IUnknownMarshal.Release(pPersistFile); } - uint value = IUnknownMarshal.Release(pShellLink); + IUnknownMarshal.Release(pShellLink); } return result; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/RectInt16.cs similarity index 55% rename from src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs rename to src/Snap.Hutao/Snap.Hutao/Core/Windowing/RectInt16.cs index 9996fa4e..59f569e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/RectInt16.cs @@ -1,19 +1,18 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using System.Runtime.CompilerServices; using Windows.Graphics; namespace Snap.Hutao.Core.Windowing; -internal readonly struct CompactRect +internal readonly struct RectInt16 { private readonly short x; private readonly short y; private readonly short width; private readonly short height; - private CompactRect(int x, int y, int width, int height) + private RectInt16(int x, int y, int width, int height) { this.x = (short)x; this.y = (short)y; @@ -21,24 +20,22 @@ internal readonly struct CompactRect this.height = (short)height; } - public static implicit operator RectInt32(CompactRect rect) + public static implicit operator RectInt32(RectInt16 rect) { return new(rect.x, rect.y, rect.width, rect.height); } - public static explicit operator CompactRect(RectInt32 rect) + public static explicit operator RectInt16(RectInt32 rect) { return new(rect.X, rect.Y, rect.Width, rect.Height); } - public static unsafe explicit operator CompactRect(ulong value) + public static unsafe explicit operator RectInt16(ulong value) { - Unsafe.SkipInit(out CompactRect rect); - *(ulong*)&rect = value; - return rect; + return *(RectInt16*)&value; } - public static unsafe implicit operator ulong(CompactRect rect) + public static unsafe implicit operator ulong(RectInt16 rect) { return *(ulong*)▭ } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs index 564b73f4..98ab3d69 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs @@ -241,7 +241,7 @@ internal sealed class XamlWindowController if (window is IXamlWindowRectPersisted rectPersisted) { - RectInt32 nonDpiPersistedRect = (CompactRect)LocalSetting.Get(rectPersisted.PersistRectKey, (CompactRect)rect); + RectInt32 nonDpiPersistedRect = (RectInt16)LocalSetting.Get(rectPersisted.PersistRectKey, (RectInt16)rect); RectInt32 persistedRect = nonDpiPersistedRect.Scale(scale); // If the persisted size is less than min size, we want to reset to the init size. @@ -266,7 +266,7 @@ internal sealed class XamlWindowController { // We save the non-dpi rect here double scale = 1.0 / window.GetRasterizationScale(); - LocalSetting.Set(rectPersisted.PersistRectKey, (CompactRect)window.AppWindow.GetRect().Scale(scale)); + LocalSetting.Set(rectPersisted.PersistRectKey, (RectInt16)window.AppWindow.GetRect().Scale(scale)); } } #endregion diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index 85fb49f9..5b2e9993 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -104,7 +104,7 @@ internal static partial class EnumerableExtension [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ObservableReorderableDbCollection ToObservableReorderableDbCollection(this IEnumerable source, IServiceProvider serviceProvider) - where TEntityOnly : class, IEntityOnly + where TEntityOnly : class, IEntityAccess where TEntity : class, IReorderable { return source is List list diff --git a/src/Snap.Hutao/Snap.Hutao/Model/IEntityAccessWithMetadata.cs b/src/Snap.Hutao/Snap.Hutao/Model/IEntityAccessWithMetadata.cs new file mode 100644 index 00000000..83bc4e37 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/IEntityAccessWithMetadata.cs @@ -0,0 +1,14 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model; + +internal interface IEntityAccessWithMetadata : IEntityAccess +{ + TMetadata Inner { get; } +} + +internal interface IEntityAccess +{ + TEntity Entity { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/IEntityWithMetadata.cs b/src/Snap.Hutao/Snap.Hutao/Model/IEntityWithMetadata.cs deleted file mode 100644 index 3eedb828..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Model/IEntityWithMetadata.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Model; - -/// -/// 实体与元数据 -/// -/// 实体 -/// 元数据 -[HighQuality] -internal interface IEntityWithMetadata : IEntityOnly -{ - /// - /// 元数据 - /// - TMetadata Inner { get; } -} - -internal interface IEntityOnly -{ - /// - /// 实体 - /// - TEntity Entity { get; } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs index 580a1fea..0709e1b8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs @@ -82,31 +82,10 @@ internal abstract partial class DbStoreOptions : ObservableObject return storage.Value; } - [return: NotNull] - protected T GetOption(ref T? storage, string key, Func deserializer, [DisallowNull] T defaultValue) + [return: NotNullIfNotNull(nameof(defaultValue))] + protected T GetOption(ref T? storage, string key, Func deserializer, T defaultValue) { - if (storage is not null) - { - return storage; - } - - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value; - if (value is null) - { - storage = defaultValue; - } - else - { - T targetValue = deserializer(value); - ArgumentNullException.ThrowIfNull(targetValue); - storage = targetValue; - } - } - - return storage; + return GetOption(ref storage, key, deserializer, () => defaultValue); } protected T GetOption(ref T? storage, string key, Func deserializer, Func defaultValueFactory) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteWebhookOperation.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteWebhookOperation.cs index 0f008b7a..377c4fbc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteWebhookOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteWebhookOperation.cs @@ -32,6 +32,6 @@ internal sealed partial class DailyNoteWebhookOperation .SetHeader("x-uid", $"{playerUid}") .PostJson(dailyNote); - await builder.TryCatchSendAsync(httpClient, logger, token).ConfigureAwait(false); + await builder.SendAsync(httpClient, logger, token).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/DirectX.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/DirectX.cs index 2fdd8016..679c764f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/DirectX.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/DirectX.cs @@ -80,7 +80,6 @@ internal static class DirectX return false; } - //IUnknownMarshal.Release(swapChain); return true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs index 3e2be4bf..0369aeae 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs @@ -122,7 +122,8 @@ internal sealed class GameScreenCaptureSession : IDisposable try { captureContext.UpdatePreview(previewWindow, frame.Surface); - //UnsafeProcessFrameSurface(frame.Surface); + + // UnsafeProcessFrameSurface(frame.Surface); } catch (Exception ex) { diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs index ef5deb5a..84f10397 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs @@ -49,7 +49,7 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control HttpClient httpClient = serviceProvider.GetRequiredService(); WebApiResponse? resp = await builder - .TryCatchSendAsync>(httpClient, logger, token) + .SendAsync>(httpClient, logger, token) .ConfigureAwait(false); return $"{resp?.Data?.AccountInfo?.AccountId}"; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml index 75d81062..16227a0a 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml @@ -124,6 +124,11 @@ Header="Screen Capture Test" IsClickEnabled="True"/> + +