From c0d80084b71b81e2a68e18df56f8eceb3f5d37fe Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 5 May 2022 22:53:07 +0800 Subject: [PATCH] add database intergrations --- src/Snap.Hutao/Snap.Hutao/App.xaml.cs | 24 +-- .../Context/Database/AppDbContext.cs | 21 +++ .../Context/FileSystem/FileSystemContext.cs | 153 ++++++++++++++++++ .../Location/IFileSystemLocation.cs | 16 ++ .../Context/FileSystem/Location/MyDocument.cs | 27 ++++ .../Context/FileSystem/MyDocumentContext.cs | 19 +++ .../Snap.Hutao/Core/CoreEnvironment.cs | 48 ++++++ .../Core/Json/Converter/DateTimeConverter.cs | 30 ++++ .../Snap.Hutao/Core/{ => Json}/HttpJson.cs | 2 +- .../Snap.Hutao/Core/{ => Json}/Json.cs | 26 ++- .../Snap.Hutao/Core/Setting/LocalSetting.cs | 113 +++++++++++++ .../Snap.Hutao/Core/Setting/SettingKeys.cs | 15 ++ .../Snap.Hutao/Core/Threading/Watcher.cs | 18 ++- .../Extension/PackageVersionExtensions.cs | 22 +++ .../Factory/Abstraction/IPickerFactory.cs | 30 ++++ .../Snap.Hutao/Factory/PickerFactory.cs | 54 +++++++ src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs | 109 +++++++++++++ src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs | 2 +- .../Snap.Hutao/{Core => Model}/Observable.cs | 2 +- .../Service/Abstraction/IInfoBarService.cs | 7 - .../Service/Abstraction/INavigationService.cs | 2 +- .../Snap.Hutao/Service/InfoBarService.cs | 46 +++--- .../Snap.Hutao/Service/NavigationService.cs | 7 +- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 16 +- .../View/Converter/BoolToObjectConverter.cs | 86 ---------- .../Converter/BoolToVisibilityConverter.cs | 21 --- .../BoolToVisibilityRevertConverter.cs | 1 + src/Snap.Hutao/Snap.Hutao/View/MainView.xaml | 25 ++- .../Snap.Hutao/View/MainView.xaml.cs | 6 +- .../View/Page/AnnouncementPage.xaml | 47 +++--- .../Snap.Hutao/View/Page/SettingPage.xaml | 5 +- .../Snap.Hutao/View/Page/SettingPage.xaml.cs | 3 + .../Snap.Hutao/ViewModel/SettingViewModel.cs | 19 +++ .../Hk4e/Common/Announcement/Announcement.cs | 91 ++++++----- .../Announcement/AnnouncementContent.cs | 14 +- .../Announcement/AnnouncementListWrapper.cs | 6 +- .../Common/Announcement/AnnouncementType.cs | 8 +- .../Announcement/AnnouncementWrapper.cs | 16 +- .../Snap.Hutao/Web/Request/AuthRequester.cs | 8 +- .../Snap.Hutao/Web/Request/Requester.cs | 114 ++++++------- .../Web/Response/KnownReturnCode.cs | 7 +- .../Snap.Hutao/Web/Response/ListWrapper.cs | 5 +- .../Snap.Hutao/Web/Response/Response.cs | 8 +- .../Web/Response/Response{TData}.cs | 19 ++- 44 files changed, 939 insertions(+), 379 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Context/FileSystem/FileSystemContext.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/IFileSystemLocation.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/MyDocument.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Context/FileSystem/MyDocumentContext.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeConverter.cs rename src/Snap.Hutao/Snap.Hutao/Core/{ => Json}/HttpJson.cs (97%) rename src/Snap.Hutao/Snap.Hutao/Core/{ => Json}/Json.cs (85%) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Extension/PackageVersionExtensions.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IPickerFactory.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs rename src/Snap.Hutao/Snap.Hutao/{Core => Model}/Observable.cs (97%) delete mode 100644 src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToObjectConverter.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityConverter.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs index 746c88a0..713e2e08 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs @@ -3,8 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; -using Snap.Hutao.Core; -using Snap.Hutao.Web.Request; namespace Snap.Hutao; @@ -40,29 +38,13 @@ public partial class App : Application private static void InitializeDependencyInjection() { - // prepare DI IServiceProvider services = new ServiceCollection() .AddLogging(builder => builder.AddDebug()) - - // http json - .AddHttpClient() - .ConfigureHttpClient(client => - { - client.Timeout = Timeout.InfiniteTimeSpan; - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Snap Hutao"); - }) - .Services - - // requester & auth reuqester - .AddHttpClient(nameof(Requester)) - .AddTypedClient() - .ConfigureHttpClient(client => client.Timeout = Timeout.InfiniteTimeSpan) - .Services - - // inject app wide services + .AddHttpClients() + .AddDefaultJsonSerializerOptions() .AddInjections(typeof(App)) .BuildServiceProvider(); Ioc.Default.ConfigureServices(services); } -} \ No newline at end of file +} diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs new file mode 100644 index 00000000..c604c7de --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs @@ -0,0 +1,21 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; + +namespace Snap.Hutao.Context.Database; + +/// +/// 应用程序数据库上下文 +/// +internal class AppDbContext : DbContext +{ + /// + /// 构造一个新的应用程序数据库上下文 + /// + /// 选项 + public AppDbContext(DbContextOptions options) + : base(options) + { + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/FileSystemContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/FileSystemContext.cs new file mode 100644 index 00000000..52e69700 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/FileSystemContext.cs @@ -0,0 +1,153 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Context.FileSystem.Location; +using System.IO; + +namespace Snap.Hutao.Context.FileSystem; + +/// +/// 文件系统上下文 +/// +/// 路径位置类型 +internal abstract class FileSystemContext +{ + private readonly IFileSystemLocation location; + + /// + /// 初始化文件系统上下文 + /// + /// 指定的文件系统位置 + public FileSystemContext(IFileSystemLocation location) + { + this.location = location; + } + + /// + /// 检查根目录 + /// + /// 是否创建了路径 + public bool EnsureDirectory() + { + string folder = location.GetPath(); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + return true; + } + + return false; + } + + /// + /// 检查文件是否存在 + /// + /// 文件名称 + /// 是否存在 + public bool FileExists(string file) + { + return File.Exists(Locate(file)); + } + + /// + /// 检查文件是否存在 + /// + /// 文件夹名称 + /// 文件名称 + /// 是否存在 + public bool FileExists(string folder, string file) + { + return File.Exists(Locate(folder, file)); + } + + /// + /// 检查文件是否存在 + /// + /// 文件夹名称 + /// 是否存在 + public bool FolderExists(string folder) + { + return Directory.Exists(Locate(folder)); + } + + /// + /// 定位根目录中的文件或文件夹 + /// + /// 文件或文件夹 + /// 绝对路径 + public string Locate(string fileOrFolder) + { + return Path.GetFullPath(fileOrFolder, location.GetPath()); + } + + /// + /// 定位根目录下子文件夹中的文件 + /// + /// 文件夹 + /// 文件 + /// 绝对路径 + public string Locate(string folder, string file) + { + return Path.GetFullPath(Path.Combine(folder, file), location.GetPath()); + } + + /// + /// 将文件移动到指定的子目录 + /// + /// 文件 + /// 文件夹 + /// 是否覆盖 + /// 是否成功 当文件不存在时会失败 + public bool MoveToFolderOrIgnore(string file, string folder, bool overwrite = true) + { + string target = Locate(folder, file); + file = Locate(file); + + if (File.Exists(file)) + { + File.Move(file, target, overwrite); + return true; + } + + return false; + } + + /// + /// 创建文件,若已存在文件,则不会创建 + /// + /// 文件 + public void CreateFileOrIgnore(string file) + { + file = Locate(file); + if (!File.Exists(file)) + { + File.Create(file).Dispose(); + } + } + + /// + /// 创建文件夹,若已存在文件,则不会创建 + /// + /// 文件夹 + public void CreateFolderOrIgnore(string folder) + { + folder = Locate(folder); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + } + + /// + /// 尝试删除文件夹 + /// + /// 文件夹 + public void DeleteFolderOrIgnore(string folder) + { + folder = Locate(folder); + if (Directory.Exists(folder)) + { + Directory.Delete(folder, true); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/IFileSystemLocation.cs b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/IFileSystemLocation.cs new file mode 100644 index 00000000..5baf2b2b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/IFileSystemLocation.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Context.FileSystem.Location; + +/// +/// 文件系统位置 +/// +public interface IFileSystemLocation +{ + /// + /// 获取路径 + /// + /// 路径 + string GetPath(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/MyDocument.cs b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/MyDocument.cs new file mode 100644 index 00000000..e0678e46 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/Location/MyDocument.cs @@ -0,0 +1,27 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.IO; + +namespace Snap.Hutao.Context.FileSystem.Location; + +/// +/// 我的文档位置 +/// +[Injection(InjectAs.Transient)] +public class MyDocument : IFileSystemLocation +{ + private string? path; + + /// + public string GetPath() + { + if (string.IsNullOrEmpty(path)) + { + string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + path = Path.GetFullPath(Path.Combine(myDocument, "Hutao")); + } + + return path; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/MyDocumentContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/MyDocumentContext.cs new file mode 100644 index 00000000..ada240d7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Context/FileSystem/MyDocumentContext.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Context.FileSystem.Location; + +namespace Snap.Hutao.Context.FileSystem; + +/// +/// 我的文档上下文 +/// +[Injection(InjectAs.Transient)] +internal class MyDocumentContext : FileSystemContext +{ + /// + public MyDocumentContext(MyDocument myDocument) + : base(myDocument) + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs new file mode 100644 index 00000000..2853a4df --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs @@ -0,0 +1,48 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Win32; +using Snap.Hutao.Extension; +using System.Security.Cryptography; +using System.Text; +using Windows.ApplicationModel; + +namespace Snap.Hutao.Core; + +/// +/// 核心环境参数 +/// +internal static class CoreEnvironment +{ + /// + /// 当前版本 + /// + public static readonly Version Version; + + /// + /// 设备Id + /// + public static readonly string DeviceId; + + private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography"; + private const string MachineGuidValue = "MachineGuid"; + + static CoreEnvironment() + { + Version = Package.Current.Id.Version.ToVersion(); + DeviceId = GetDeviceId(); + } + + /// + /// 获取设备的UUID + /// + /// 设备的UUID + private static string GetDeviceId() + { + string userName = Environment.UserName; + object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName); + byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}"); + byte[] hash = MD5.HashData(bytes); + return Convert.ToHexString(hash); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeConverter.cs new file mode 100644 index 00000000..7ab58381 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Snap.Hutao.Core.Json.Converter; + +/// +/// 实现日期的转换 +/// +internal class DateTimeConverter : JsonConverter +{ + /// + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.GetString() is string dataTimeString) + { + return DateTime.Parse(dataTimeString); + } + + return default(DateTime); + } + + /// + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/HttpJson.cs similarity index 97% rename from src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs rename to src/Snap.Hutao/Snap.Hutao/Core/Json/HttpJson.cs index b0df8fb2..b8751c80 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/HttpJson.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/HttpJson.cs @@ -3,7 +3,7 @@ using System.Net.Http; -namespace Snap.Hutao.Core; +namespace Snap.Hutao.Core.Json; /// /// Http Json 处理 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Json.cs similarity index 85% rename from src/Snap.Hutao/Snap.Hutao/Core/Json.cs rename to src/Snap.Hutao/Snap.Hutao/Core/Json/Json.cs index eb470f2a..af02fe38 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Json.cs @@ -1,10 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; using System.IO; +using System.Text.Json; -namespace Snap.Hutao.Core; +namespace Snap.Hutao.Core.Json; /// /// Json操作 @@ -12,20 +12,17 @@ namespace Snap.Hutao.Core; [Injection(InjectAs.Transient)] public class Json { + private readonly JsonSerializerOptions jsonSerializerOptions; private readonly ILogger logger; - private readonly JsonSerializerSettings jsonSerializerSettings = new() - { - DateFormatString = "yyyy'-'MM'-'dd' 'HH':'mm':'ss.FFFFFFFK", - Formatting = Formatting.Indented, - }; - /// /// 初始化一个新的 Json操作 实例 /// + /// 配置 /// 日志器 - public Json(ILogger logger) + public Json(JsonSerializerOptions jsonSerializerOptions, ILogger logger) { + this.jsonSerializerOptions = jsonSerializerOptions; this.logger = logger; } @@ -34,19 +31,20 @@ public class Json /// /// 要反序列化的对象的类型 /// 要反序列化的JSON - /// Json字符串中的反序列化对象, 如果反序列化失败会抛出异常 + /// Json字符串中的反序列化对象, 如果反序列化失败会返回 public T? ToObject(string value) { try { - return JsonConvert.DeserializeObject(value); + T? result = JsonSerializer.Deserialize(value); + return result; } catch (Exception ex) { - logger.LogError("反序列化Json时遇到问题:{ex}", ex); + logger.LogError("反序列化Json时遇到问题\n{ex}", ex); } - return default; + return default(T); } /// @@ -69,7 +67,7 @@ public class Json /// 对象的JSON字符串表示形式 public string Stringify(object? value) { - return JsonConvert.SerializeObject(value, jsonSerializerSettings); + return JsonSerializer.Serialize(value, jsonSerializerOptions); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs new file mode 100644 index 00000000..80c64e09 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs @@ -0,0 +1,113 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; +using Windows.Storage; + +namespace Snap.Hutao.Core.Setting; + +/// +/// 本地设置 +/// +internal static class LocalSetting +{ + /// + /// 由于 没有启用 nullable, + /// 在处理引用类型时需要格外小心 + /// 将值类型的操作与引用类型区分开,可以提升一定的性能 + /// + private static readonly ApplicationDataContainer Container; + + static LocalSetting() + { + Container = ApplicationData.Current.LocalSettings; + } + + /// + /// 获取设置项的值 + /// + /// 设置项的类型 + /// 键 + /// 默认值 + /// 获取的值 + public static T? Get(string key, T? defaultValue = default) + where T : class + { + if (Container.Values.TryGetValue(key, out object? value)) + { + return value is null ? defaultValue : value as T; + } + else + { + Set(key, defaultValue); + return defaultValue; + } + } + + /// + /// 获取设置项的值 + /// + /// 设置项的类型 + /// 键 + /// 默认值 + /// 获取的值 + public static T GetValueType(string key, T defaultValue = default) + where T : struct + { + if (Container.Values.TryGetValue(key, out object? value)) + { + if (value is null) + { + return defaultValue; + } + else + { + // 无法避免的拆箱操作 + return (T)value; + } + } + else + { + Set(key, defaultValue); + return defaultValue; + } + } + + /// + /// 设置设置项的值 + /// + /// 设置项的类型 + /// 键 + /// 值 + public static void Set(string key, T? value) + where T : class + { + try + { + Container.Values[key] = value; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + + /// + /// 设置设置项的值 + /// + /// 设置项的类型 + /// 键 + /// 值 + public static void SetValueType(string key, T value) + where T : struct + { + try + { + Container.Values[key] = value; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs new file mode 100644 index 00000000..ebee4966 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Setting; + +/// +/// 设置键 +/// +internal static class SettingKeys +{ + /// + /// 上次打开时App的版本 + /// + public static readonly string LastAppVersion = "LastAppVersion"; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs index 39ee0715..ed17be97 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft; +using Snap.Hutao.Model; namespace Snap.Hutao.Core.Threading; @@ -13,6 +14,7 @@ namespace Snap.Hutao.Core.Threading; public class Watcher : Observable { private readonly bool isReusable; + private bool hasUsed; private bool isWorking; private bool isCompleted; @@ -48,33 +50,33 @@ public class Watcher : Observable /// /// 对某个操作进行监视, - /// 无法防止代码重入 /// /// 一个可释放的对象,用于在操作完成时自动提示监视器工作已经完成 /// 重用了一个不可重用的监视器 public IDisposable Watch() { + Verify.Operation(!IsWorking, $"此 {nameof(Watcher)} 已经处于检查状态"); Verify.Operation(isReusable || !hasUsed, $"此 {nameof(Watcher)} 不允许多次使用"); hasUsed = true; IsWorking = true; - return new WorkDisposable(this); + return new WatchDisposable(this); } - private struct WorkDisposable : IDisposable + private struct WatchDisposable : IDisposable { - private readonly Watcher work; + private readonly Watcher watcher; - public WorkDisposable(Watcher work) + public WatchDisposable(Watcher watcher) { - this.work = work; + this.watcher = watcher; } public void Dispose() { - work.IsWorking = false; - work.IsCompleted = true; + watcher.IsWorking = false; + watcher.IsCompleted = true; } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/PackageVersionExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/PackageVersionExtensions.cs new file mode 100644 index 00000000..770e5c36 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/PackageVersionExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Windows.ApplicationModel; + +namespace Snap.Hutao.Extension; + +/// +/// 包版本扩展 +/// +public static class PackageVersionExtensions +{ + /// + /// 将包版本转换为版本 + /// + /// 包版本 + /// 版本 + public static Version ToVersion(this PackageVersion packageVersion) + { + return new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IPickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IPickerFactory.cs new file mode 100644 index 00000000..e34d9787 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Abstraction/IPickerFactory.cs @@ -0,0 +1,30 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Windows.Storage.Pickers; + +namespace Snap.Hutao.Factory.Abstraction; + +/// +/// 文件选择器工厂 +/// +internal interface IPickerFactory +{ + /// + /// 获取 经过初始化的 + /// + /// 经过初始化的 + FileOpenPicker GetFileOpenPicker(); + + /// + /// 获取 经过初始化的 + /// + /// 经过初始化的 + FileSavePicker GetFileSavePicker(); + + /// + /// 获取 经过初始化的 + /// + /// 经过初始化的 + FolderPicker GetFolderPicker(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs new file mode 100644 index 00000000..d890e524 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs @@ -0,0 +1,54 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Factory.Abstraction; +using Windows.Storage.Pickers; +using WinRT.Interop; + +namespace Snap.Hutao.Factory; + +/// +[Injection(InjectAs.Transient)] +internal class PickerFactory : IPickerFactory +{ + private readonly MainWindow mainWindow; + + /// + /// 构造一个新的文件选择器工厂 + /// + /// 主窗体的引用注入 + public PickerFactory(MainWindow mainWindow) + { + this.mainWindow = mainWindow; + } + + /// + public FileOpenPicker GetFileOpenPicker() + { + return GetInitializedPicker(); + } + + /// + public FileSavePicker GetFileSavePicker() + { + return GetInitializedPicker(); + } + + /// + public FolderPicker GetFolderPicker() + { + return GetInitializedPicker(); + } + + private T GetInitializedPicker() + where T : new() + { + // Create a folder picker. + T picker = new(); + + IntPtr hWnd = WindowNative.GetWindowHandle(mainWindow); + InitializeWithWindow.Initialize(picker, hWnd); + + return picker; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs new file mode 100644 index 00000000..44ca25e9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/IocConfiguration.cs @@ -0,0 +1,109 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Snap.Hutao.Context.Database; +using Snap.Hutao.Context.FileSystem; +using Snap.Hutao.Core; +using Snap.Hutao.Core.Json; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Web.Request; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Snap.Hutao; + +/// +/// 配置 +/// +internal static class IocConfiguration +{ + /// + /// 添加 + /// + /// 集合 + /// 可继续操作的集合 + public static IServiceCollection AddHttpClients(this IServiceCollection services) + { + // http json + services + .AddHttpClient() + .ConfigureHttpClient(client => + { + client.Timeout = Timeout.InfiniteTimeSpan; + client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Snap Hutao"); + }); + + // requester & auth reuqester + services + .AddHttpClient(nameof(Requester)) + .AddTypedClient() + .ConfigureHttpClient(client => client.Timeout = Timeout.InfiniteTimeSpan); + + return services; + } + + /// + /// 添加默认的 配置 + /// + /// 集合 + /// 可继续操作的集合 + public static IServiceCollection AddDefaultJsonSerializerOptions(this IServiceCollection services) + { + // default json options, global configuration + return services + .AddSingleton(new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + WriteIndented = true, + }); + } + + /// + /// 添加数据库 + /// + /// 集合 + /// 可继续操作的集合 + public static IServiceCollection AddDatebase(this IServiceCollection services) + { + MyDocumentContext myDocument = new(new()); + myDocument.EnsureDirectory(); + + string dbFile = myDocument.Locate("Userdata.db"); + string sqlConnectionString = $"Data Source={dbFile}"; + + bool shouldMigrate = false; + + if (!myDocument.FileExists(dbFile)) + { + shouldMigrate = true; + } + else + { + string? versionString = LocalSetting.Get(SettingKeys.LastAppVersion); + if (Version.TryParse(versionString, out Version? lastVersion)) + { + if (lastVersion < CoreEnvironment.Version) + { + shouldMigrate = true; + } + } + } + + if (shouldMigrate) + { + // temporarily create a context + using (AppDbContext context = new(new DbContextOptionsBuilder().UseSqlite(sqlConnectionString).Options)) + { + context.Database.Migrate(); + } + } + + LocalSetting.Set(SettingKeys.LastAppVersion, CoreEnvironment.Version.ToString()); + + return services + .AddPooledDbContextFactory(builder => builder.UseSqlite(sqlConnectionString)); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs index 68944a48..602122e5 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao; /// /// 主窗体 /// -[Injection(InjectAs.Transient)] +[Injection(InjectAs.Singleton)] public sealed partial class MainWindow : Window { /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Observable.cs b/src/Snap.Hutao/Snap.Hutao/Model/Observable.cs similarity index 97% rename from src/Snap.Hutao/Snap.Hutao/Core/Observable.cs rename to src/Snap.Hutao/Snap.Hutao/Model/Observable.cs index 9dc24c49..5f23609c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Observable.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Observable.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace Snap.Hutao.Core; +namespace Snap.Hutao.Model; /// /// 简单的实现了 接口 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs index 5878871c..243d4f72 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IInfoBarService.cs @@ -61,13 +61,6 @@ public interface IInfoBarService /// 信息条的目标容器 void Initialize(StackPanel container); - /// - /// 显示特定的信息条 - /// - /// 信息条 - /// 关闭延迟 - void Show(InfoBar infoBar, int delay = 0); - /// /// 显示成功信息 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs index cc4cca4d..2e5416e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/INavigationService.cs @@ -61,5 +61,5 @@ public interface INavigationService /// /// 同步的页面类型 /// 是否同步成功 - bool SyncTabWith(Type pageType); + bool SyncSelectedNavigationViewItemWith(Type pageType); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs index 001793c7..b7e6cbfb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; -using Microsoft.VisualStudio.Threading; using Snap.Hutao.Service.Abstraction; namespace Snap.Hutao.Service; @@ -79,25 +78,25 @@ internal class InfoBarService : IInfoBarService PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay); } - /// - public void Show(InfoBar infoBar, int delay = 0) - { - Must.NotNull(infoBarStack!).DispatcherQueue.TryEnqueue(ShowInfoBarOnUIThreadAsync(infoBarStack, infoBar, delay).Forget); - } - - private async Task ShowInfoBarOnUIThreadAsync(StackPanel stack, InfoBar infoBar, int delay) - { - infoBar.Closed += OnInfoBarClosed; - stack.Children.Add(infoBar); - - if (delay > 0) - { - await Task.Delay(delay); - infoBar.IsOpen = false; - } - } - private void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay) + { + if (infoBarStack is null) + { + return; + } + + infoBarStack.DispatcherQueue.TryEnqueue(() => PrepareInfoBarAndShowInternal(severity, title, message, delay)); + } + + /// + /// 此方法应在主线程上运行 + /// + /// 严重程度 + /// 标题 + /// 消息 + /// 关闭延迟 + [SuppressMessage("", "VSTHRD100", Justification ="只能通过 async void 方法使控件在主线程创建")] + private async void PrepareInfoBarAndShowInternal(InfoBarSeverity severity, string? title, string? message, int delay) { InfoBar infoBar = new() { @@ -107,7 +106,14 @@ internal class InfoBarService : IInfoBarService IsOpen = true, }; - Show(infoBar, delay); + infoBar.Closed += OnInfoBarClosed; + Must.NotNull(infoBarStack!)!.Children.Add(infoBar); + + if (delay > 0) + { + await Task.Delay(delay); + infoBar.IsOpen = false; + } } private void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/NavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/NavigationService.cs index 7e0aff4a..4bbf8e05 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/NavigationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/NavigationService.cs @@ -66,9 +66,9 @@ internal class NavigationService : INavigationService public bool HasEverNavigated { get; set; } /// - public bool SyncTabWith(Type pageType) + public bool SyncSelectedNavigationViewItemWith(Type? pageType) { - if (NavigationView is null) + if (NavigationView is null || pageType is null) { return false; } @@ -98,7 +98,7 @@ internal class NavigationService : INavigationService return false; } - _ = isSyncTabRequested && SyncTabWith(pageType); + _ = isSyncTabRequested && SyncSelectedNavigationViewItemWith(pageType); bool result = false; try @@ -151,6 +151,7 @@ internal class NavigationService : INavigationService if (Frame != null && Frame.CanGoBack) { Frame.GoBack(); + SyncSelectedNavigationViewItemWith(Frame.Content.GetType()); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 1ef2f3aa..b8b9b842 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -13,7 +13,7 @@ enable true zh-CN - zh-CN + zh-cn False True @@ -48,14 +48,16 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -63,10 +65,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -83,9 +82,6 @@ - - - diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToObjectConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToObjectConverter.cs deleted file mode 100644 index 3307a7f9..00000000 --- a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToObjectConverter.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Data; - -namespace Snap.Hutao.View.Converter; - -/// -/// This class converts a boolean value into an other object. -/// Can be used to convert true/false to visibility, a couple of colors, couple of images, etc. -/// -public class BoolToObjectConverter : DependencyObject, IValueConverter -{ - /// - /// Identifies the property. - /// - public static readonly DependencyProperty TrueValueProperty = - DependencyProperty.Register(nameof(TrueValue), typeof(object), typeof(BoolToObjectConverter), new PropertyMetadata(null)); - - /// - /// Identifies the property. - /// - public static readonly DependencyProperty FalseValueProperty = - DependencyProperty.Register(nameof(FalseValue), typeof(object), typeof(BoolToObjectConverter), new PropertyMetadata(null)); - - /// - /// Gets or sets the value to be returned when the boolean is true - /// - public object TrueValue - { - get => GetValue(TrueValueProperty); - set => SetValue(TrueValueProperty, value); - } - - /// - /// Gets or sets the value to be returned when the boolean is false - /// - public object FalseValue - { - get => GetValue(FalseValueProperty); - set => SetValue(FalseValueProperty, value); - } - - /// - /// Convert a boolean value to an other object. - /// - /// The source data being passed to the target. - /// The type of the target property, as a type reference. - /// An optional parameter to be used to invert the converter logic. - /// The language of the conversion. - /// The value to be passed to the target dependency property. - public object Convert(object value, Type targetType, object parameter, string language) - { - bool boolValue = value is bool valid && valid; - - // Negate if needed - if (ConvertHelper.TryParseBool(parameter)) - { - boolValue = !boolValue; - } - - return ConvertHelper.Convert(boolValue ? TrueValue : FalseValue, targetType); - } - - /// - /// Convert back the value to a boolean - /// - /// If the parameter is a reference type, must match its reference to return true. - /// The target data being passed to the source. - /// The type of the target property, as a type reference (System.Type for Microsoft .NET, a TypeName helper struct for Visual C++ component extensions (C++/CX)). - /// An optional parameter to be used to invert the converter logic. - /// The language of the conversion. - /// The value to be passed to the source object. - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - bool result = Equals(value, ConvertHelper.Convert(TrueValue, value.GetType())); - - if (ConvertHelper.TryParseBool(parameter)) - { - result = !result; - } - - return result; - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityConverter.cs deleted file mode 100644 index 3884dfc1..00000000 --- a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityConverter.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.UI.Xaml; - -namespace Snap.Hutao.View.Converter; - -/// -/// This class converts a boolean value into a Visibility enumeration. -/// -public class BoolToVisibilityConverter : BoolToObjectConverter -{ - /// - /// Initializes a new instance of the class. - /// - public BoolToVisibilityConverter() - { - TrueValue = Visibility.Visible; - FalseValue = Visibility.Collapsed; - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs index 42cf1fbb..349502aa 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.WinUI.UI.Converters; using Microsoft.UI.Xaml; namespace Snap.Hutao.View.Converter; diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml index 5672b9fb..70137523 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml @@ -10,26 +10,28 @@ + IsPaneOpen="True" + IsBackEnabled="{Binding ElementName=ContentFrame,Path=CanGoBack}"> + - + + + + - - - - - + + + @@ -61,9 +63,6 @@ TintColor="#34424d" FallbackColor="#34424d"/> - - - diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs index 9857fdab..b19adabe 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs @@ -21,10 +21,12 @@ public sealed partial class MainView : UserControl { InitializeComponent(); + infoBarService = Ioc.Default.GetRequiredService(); + infoBarService.Initialize(InfoBarStack); + navigationService = Ioc.Default.GetRequiredService(); navigationService.Initialize(NavView, ContentFrame); - infoBarService = Ioc.Default.GetRequiredService(); - infoBarService.Initialize(InfoBarStack); + navigationService.Navigate(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml index 21c91799..53c5cd4d 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml @@ -59,7 +59,7 @@ @@ -105,31 +105,20 @@ HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}"> - + - - - + + + + + + + + - diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index e36cc0d9..28f14f08 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -17,9 +17,8 @@ - - + + @@ -13,6 +15,7 @@ public sealed partial class SettingPage : Microsoft.UI.Xaml.Controls.Page /// public SettingPage() { + DataContext = Ioc.Default.GetRequiredService(); InitializeComponent(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs new file mode 100644 index 00000000..bc798c0e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.ViewModel; + +/// +/// 测试视图模型 +/// +[Injection(InjectAs.Transient)] +internal class SettingViewModel +{ + /// + /// 构造一个新的测试视图模型 + /// + /// 异步命令工厂 + public SettingViewModel() + { + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs index 89ee03c7..8fe42e99 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs @@ -1,7 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; +using Snap.Hutao.Core.Json.Converter; +using System.Text.Json.Serialization; using System.Windows.Input; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; @@ -13,42 +14,6 @@ public class Announcement : AnnouncementContent { private double timePercent; - /// - /// 类型标签 - /// - [JsonProperty("type_label")] - public string? TypeLabel { get; set; } - - /// - /// 标签文本 - /// - [JsonProperty("tag_label")] - public string? TagLabel { get; set; } - - /// - /// 标签图标 - /// - [JsonProperty("tag_icon")] - public string? TagIcon { get; set; } - - /// - /// 登录提醒 - /// - [JsonProperty("login_alert")] - public int LoginAlert { get; set; } - - /// - /// 开始时间 - /// - [JsonProperty("start_time")] - public DateTime StartTime { get; set; } - - /// - /// 结束时间 - /// - [JsonProperty("end_time")] - public DateTime EndTime { get; set; } - /// /// 启动展示窗口的命令 /// @@ -131,45 +96,83 @@ public class Announcement : AnnouncementContent get => $"{StartTime:yyyy.MM.dd HH:mm} - {EndTime:yyyy.MM.dd HH:mm}"; } + /// + /// 类型标签 + /// + [JsonPropertyName("type_label")] + public string? TypeLabel { get; set; } + + /// + /// 标签文本 + /// + [JsonPropertyName("tag_label")] + public string? TagLabel { get; set; } + + /// + /// 标签图标 + /// + [JsonPropertyName("tag_icon")] + public string? TagIcon { get; set; } + + /// + /// 登录提醒 + /// + [JsonPropertyName("login_alert")] + public int LoginAlert { get; set; } + + /// + /// 开始时间 + /// + [JsonPropertyName("start_time")] + [JsonConverter(typeof(DateTimeConverter))] + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + [JsonPropertyName("end_time")] + [JsonConverter(typeof(DateTimeConverter))] + public DateTime EndTime { get; set; } + /// /// 类型 /// - [JsonProperty("type")] + [JsonPropertyName("type")] public int Type { get; set; } /// /// 提醒 /// - [JsonProperty("remind")] + [JsonPropertyName("remind")] public int Remind { get; set; } /// /// 通知 /// - [JsonProperty("alert")] + [JsonPropertyName("alert")] public int Alert { get; set; } /// /// 标签开始时间 /// - [JsonProperty("tag_start_time")] + [JsonPropertyName("tag_start_time")] public string? TagStartTime { get; set; } /// /// 标签结束时间 /// - [JsonProperty("tag_end_time")] + [JsonPropertyName("tag_end_time")] public string? TagEndTime { get; set; } /// /// 提醒版本 /// - [JsonProperty("remind_ver")] + [JsonPropertyName("remind_ver")] public int RemindVer { get; set; } /// /// 是否含有内容 /// - [JsonProperty("has_content")] + [JsonPropertyName("has_content")] public bool HasContent { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs index 3bce5e46..3698463e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementContent.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; @@ -13,37 +13,37 @@ public class AnnouncementContent /// /// 公告Id /// - [JsonProperty("ann_id")] + [JsonPropertyName("ann_id")] public int AnnId { get; set; } /// /// 公告标题 /// - [JsonProperty("title")] + [JsonPropertyName("title")] public string? Title { get; set; } /// /// 副标题 /// - [JsonProperty("subtitle")] + [JsonPropertyName("subtitle")] public string? Subtitle { get; set; } /// /// 横幅Url /// - [JsonProperty("banner")] + [JsonPropertyName("banner")] public string? Banner { get; set; } /// /// 内容字符串 /// 可能包含了一些html格式 /// - [JsonProperty("content")] + [JsonPropertyName("content")] public string? Content { get; set; } /// /// 语言 /// - [JsonProperty("lang")] + [JsonPropertyName("lang")] public string? Lang { get; set; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs index 22c150a5..bd9ab6a9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementListWrapper.cs @@ -1,8 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; using Snap.Hutao.Web.Response; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; @@ -14,12 +14,12 @@ public class AnnouncementListWrapper : ListWrapper /// /// 类型Id /// - [JsonProperty("type_id")] + [JsonPropertyName("type_id")] public int TypeId { get; set; } /// /// 类型标签 /// - [JsonProperty("type_label")] + [JsonPropertyName("type_label")] public string? TypeLabel { get; set; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs index d624d918..d37c1d73 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementType.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; @@ -13,18 +13,18 @@ public class AnnouncementType /// /// Id /// - [JsonProperty("id")] + [JsonPropertyName("id")] public int Id { get; set; } /// /// 名称 /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string? Name { get; set; } /// /// 国际化名称 /// - [JsonProperty("mi18n_name")] + [JsonPropertyName("mi18n_name")] public string? MI18NName { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs index f20085eb..bf374452 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs @@ -1,9 +1,9 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; using Snap.Hutao.Web.Response; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; @@ -15,36 +15,36 @@ public class AnnouncementWrapper : ListWrapper /// /// 总数 /// - [JsonProperty("total")] + [JsonPropertyName("total")] public int Total { get; set; } /// /// 类型列表 /// - [JsonProperty("type_list")] + [JsonPropertyName("type_list")] public List? TypeList { get; set; } /// /// 提醒 /// - [JsonProperty("alert")] + [JsonPropertyName("alert")] public bool Alert { get; set; } /// /// 提醒Id /// - [JsonProperty("alert_id")] + [JsonPropertyName("alert_id")] public int AlertId { get; set; } /// /// 时区 /// - [JsonProperty("timezone")] + [JsonPropertyName("timezone")] public int TimeZone { get; set; } /// /// 时间戳 /// - [JsonProperty("t")] - public long TimeStamp { get; set; } + [JsonPropertyName("t")] + public string? TimeStamp { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/AuthRequester.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/AuthRequester.cs index 1d420f79..d73c4560 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/AuthRequester.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/AuthRequester.cs @@ -1,7 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core; +using Snap.Hutao.Core.Json; +using Snap.Hutao.Service.Abstraction; using System.Net.Http; namespace Snap.Hutao.Web.Request; @@ -16,9 +17,10 @@ public class AuthRequester : Requester /// /// Http 客户端 /// Json 处理器 + /// 信息条服务 /// 消息器 - public AuthRequester(HttpClient httpClient, Json json, ILogger logger) - : base(httpClient, json, logger) + public AuthRequester(HttpClient httpClient, Json json, IInfoBarService infoBarService, ILogger logger) + : base(httpClient, json, infoBarService, logger) { } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs index 3b4bd4fb..e902de79 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/Requester.cs @@ -1,7 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core; +using Snap.Hutao.Core.Json; +using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Web.Response; using System.Net.Http; using System.Text; @@ -16,6 +17,7 @@ public class Requester { private readonly HttpClient httpClient; private readonly Json json; + private readonly IInfoBarService infoBarService; private readonly ILogger logger; /// @@ -23,11 +25,13 @@ public class Requester /// /// Http 客户端 /// Json 处理器 + /// 信息条服务 /// 消息器 - public Requester(HttpClient httpClient, Json json, ILogger logger) + public Requester(HttpClient httpClient, Json json, IInfoBarService infoBarService, ILogger logger) { this.httpClient = httpClient; this.json = json; + this.infoBarService = infoBarService; this.logger = logger; } @@ -37,7 +41,7 @@ public class Requester public RequestOptions Headers { get; set; } = new RequestOptions(); /// - /// 内部使用的 + /// 内部使用的 /// protected HttpClient HttpClient { get => httpClient; } @@ -50,32 +54,15 @@ public class Requester /// 响应 public async Task?> GetAsync(string? url, CancellationToken cancellationToken = default) { - logger.LogInformation("GET {urlbase}", url?.Split('?')[0]); - return url is null - ? null - : await RequestAsync( - client => new RequestInfo(url, () => client.GetAsync(url, cancellationToken)), - cancellationToken) - .ConfigureAwait(false); - } + if (url is null) + { + return Response.CreateForEmptyUrl(); + } - /// - /// GET 操作 - /// - /// 返回的类类型 - /// 地址 - /// 编码 - /// 取消令牌 - /// 响应 - public async Task?> GetAsync(string? url, Encoding encoding, CancellationToken cancellationToken = default) - { - logger.LogInformation("GET {urlbase}", url?.Split('?')[0]); - return url is null - ? null - : await RequestAsync( - client => new RequestInfo(url, () => client.GetAsync(url, cancellationToken), encoding), - cancellationToken) - .ConfigureAwait(false); + Task GetMethod(HttpClient client, CancellationToken token) => client.GetAsync(url, token); + + return await RequestAsync(GetMethod, cancellationToken) + .ConfigureAwait(false); } /// @@ -88,13 +75,17 @@ public class Requester /// 响应 public async Task?> PostAsync(string? url, object data, CancellationToken cancellationToken = default) { + if (url is null) + { + return Response.CreateForEmptyUrl(); + } + string dataString = json.Stringify(data); - logger.LogInformation("POST {urlbase} with\n{dataString}", url?.Split('?')[0], dataString); - return url is null - ? null - : await RequestAsync( - client => new RequestInfo(url, () => client.PostAsync(url, new StringContent(dataString), cancellationToken)), - cancellationToken) + HttpContent content = new StringContent(dataString); + + Task PostMethod(HttpClient client, CancellationToken token) => client.PostAsync(url, content, token); + + return await RequestAsync(PostMethod, cancellationToken) .ConfigureAwait(false); } @@ -109,13 +100,17 @@ public class Requester /// 响应 public async Task?> PostAsync(string? url, object data, string contentType, CancellationToken cancellationToken = default) { + if (url is null) + { + return Response.CreateForEmptyUrl(); + } + string dataString = json.Stringify(data); - logger.LogInformation("POST {urlbase} with\n{dataString}", url?.Split('?')[0], dataString); - return url is null - ? null - : await RequestAsync( - client => new RequestInfo(url, () => client.PostAsync(url, new StringContent(dataString, Encoding.UTF8, contentType), cancellationToken)), - cancellationToken) + HttpContent content = new StringContent(dataString, Encoding.UTF8, contentType); + + Task PostMethod(HttpClient client, CancellationToken token) => client.PostAsync(url, content, token); + + return await RequestAsync(PostMethod, cancellationToken) .ConfigureAwait(false); } @@ -155,53 +150,38 @@ public class Requester } } - private async Task?> RequestAsync(Func requestFunc, CancellationToken cancellationToken = default) + private async Task?> RequestAsync( + Func> requestFunc, + CancellationToken cancellationToken = default) { PrepareHttpClient(); - RequestInfo? info = requestFunc(HttpClient); try { - HttpResponseMessage response = await info.RequestAsyncFunc.Invoke() + HttpResponseMessage response = await requestFunc + .Invoke(HttpClient, cancellationToken) .ConfigureAwait(false); - string contentString = await response.Content.ReadAsStringAsync(cancellationToken) + string contentString = await response.Content + .ReadAsStringAsync(cancellationToken) .ConfigureAwait(false); - if (info.Encoding is not null) + Response? resp = json.ToObject>(contentString); + if (resp?.ToString() is string representable) { - byte[] bytes = Encoding.UTF8.GetBytes(contentString); - info.Encoding.GetString(bytes); + infoBarService.Information(representable); } - logger.LogInformation("Response String :{contentString}", contentString); - - return json.ToObject>(contentString); + return resp; } catch (Exception ex) { logger.LogError(ex, "请求时遇到问题"); - return Response.CreateFail($"{ex.Message}"); + return Response.CreateForException($"{ex.Message}"); } finally { logger.LogInformation("Request Completed"); } } - - private record RequestInfo - { - public RequestInfo(string url, Func> httpResponseMessage, Encoding? encoding = null) - { - Url = url; - RequestAsyncFunc = httpResponseMessage; - Encoding = encoding; - } - - public string Url { get; set; } - - public Func> RequestAsyncFunc { get; set; } - - public Encoding? Encoding { get; set; } - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs index 0f289b12..47cf998a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs @@ -8,10 +8,15 @@ namespace Snap.Hutao.Web.Response; /// public enum KnownReturnCode { + /// + /// Url为 空 + /// + UrlIsEmpty = -2000000001, + /// /// 内部错误 /// - InternalFailure = int.MinValue, + InternalFailure = -2000000000, /// /// 已经签到过了 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs index 5aeedd84..3c977a3c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/ListWrapper.cs @@ -1,8 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Response; @@ -15,5 +15,6 @@ public class ListWrapper /// /// 列表 /// - [JsonProperty("list")] public List? List { get; set; } + [JsonPropertyName("list")] + public List? List { get; set; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs index c9d472df..fbb8fae5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Response; @@ -13,13 +13,13 @@ public class Response /// /// 返回代码 /// - [JsonProperty("retcode")] + [JsonPropertyName("retcode")] public int ReturnCode { get; set; } /// /// 消息 /// - [JsonProperty("message")] + [JsonPropertyName("message")] public string? Message { get; set; } /// @@ -37,7 +37,7 @@ public class Response /// /// 消息 /// 响应 - public static Response CreateFail(string message) + public static Response CreateForException(string message) { return new Response() { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs index 28f7492e..6effbe96 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/Response{TData}.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Snap.Hutao.Web.Response; @@ -14,7 +14,7 @@ public class Response : Response /// /// 数据 /// - [JsonProperty("data")] + [JsonPropertyName("data")] public TData? Data { get; set; } /// @@ -22,7 +22,7 @@ public class Response : Response /// /// 消息 /// 响应 - public static new Response CreateFail(string message) + public static new Response CreateForException(string message) { return new Response() { @@ -30,4 +30,17 @@ public class Response : Response Message = message, }; } + + /// + /// 构造一个空Url的响应 + /// + /// 响应 + public static Response CreateForEmptyUrl() + { + return new Response() + { + ReturnCode = (int)KnownReturnCode.UrlIsEmpty, + Message = "请求的 Url 不应为空", + }; + } }