mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor options
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
@@ -12,28 +11,16 @@ using Windows.Storage;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 存储环境相关的选项
|
||||
/// 运行时运算得到的选项,无数据库交互
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
|
||||
internal sealed class RuntimeOptions
|
||||
{
|
||||
private readonly ILogger<RuntimeOptions> logger;
|
||||
|
||||
private readonly bool isWebView2Supported;
|
||||
private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected;
|
||||
|
||||
private bool? isElevated;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃选项
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
public RuntimeOptions(ILogger<RuntimeOptions> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
AppLaunchTime = DateTimeOffset.UtcNow;
|
||||
|
||||
DataFolder = GetDataFolderPath();
|
||||
@@ -45,117 +32,95 @@ internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
|
||||
UserAgent = $"Snap Hutao/{Version}";
|
||||
|
||||
DeviceId = GetUniqueUserId();
|
||||
DetectWebView2Environment(ref webView2Version, ref isWebView2Supported);
|
||||
DetectWebView2Environment(logger, out webView2Version, out isWebView2Supported);
|
||||
|
||||
static string GetDataFolderPath()
|
||||
{
|
||||
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
|
||||
|
||||
if (!string.IsNullOrEmpty(preferredPath))
|
||||
{
|
||||
Directory.CreateDirectory(preferredPath);
|
||||
return preferredPath;
|
||||
}
|
||||
|
||||
// Fallback to MyDocuments
|
||||
string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
|
||||
#if RELEASE
|
||||
// 将测试版与正式版的文件目录分离
|
||||
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
|
||||
#else
|
||||
// 使得迁移能正常生成
|
||||
string folderName = "Hutao";
|
||||
#endif
|
||||
string path = Path.GetFullPath(Path.Combine(myDocuments, folderName));
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
static string GetUniqueUserId()
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
|
||||
return Convert.ToMd5HexString($"{userName}{machineGuid}");
|
||||
}
|
||||
|
||||
static void DetectWebView2Environment(ILogger<RuntimeOptions> logger, out string webView2Version, out bool isWebView2Supported)
|
||||
{
|
||||
try
|
||||
{
|
||||
webView2Version = CoreWebView2Environment.GetAvailableBrowserVersionString();
|
||||
isWebView2Supported = true;
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
webView2Version = SH.CoreWebView2HelperVersionUndetected;
|
||||
isWebView2Supported = false;
|
||||
logger.LogError(ex, "WebView2 Runtime not installed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前版本
|
||||
/// </summary>
|
||||
public Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 标准UA
|
||||
/// </summary>
|
||||
public string UserAgent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 安装位置
|
||||
/// </summary>
|
||||
public string InstalledLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹路径
|
||||
/// </summary>
|
||||
public string DataFolder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 本地缓存
|
||||
/// </summary>
|
||||
public string LocalCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 包家族名称
|
||||
/// </summary>
|
||||
public string FamilyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备Id
|
||||
/// </summary>
|
||||
public string DeviceId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// WebView2 版本
|
||||
/// </summary>
|
||||
public string WebView2Version { get => webView2Version; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持 WebView2
|
||||
/// </summary>
|
||||
public bool IsWebView2Supported { get => isWebView2Supported; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为提升的权限
|
||||
/// </summary>
|
||||
public bool IsElevated { get => isElevated ??= GetElevated(); }
|
||||
public bool IsElevated
|
||||
{
|
||||
get
|
||||
{
|
||||
return isElevated ??= GetElevated();
|
||||
|
||||
static bool GetElevated()
|
||||
{
|
||||
if (LocalSetting.Get(SettingKeys.OverrideElevationRequirement, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset AppLaunchTime { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RuntimeOptions Value { get => this; }
|
||||
|
||||
private static string GetDataFolderPath()
|
||||
{
|
||||
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
|
||||
|
||||
if (!string.IsNullOrEmpty(preferredPath) && Directory.Exists(preferredPath))
|
||||
{
|
||||
return preferredPath;
|
||||
}
|
||||
|
||||
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
#if RELEASE
|
||||
// 将测试版与正式版的文件目录分离
|
||||
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
|
||||
#else
|
||||
// 使得迁移能正常生成
|
||||
string folderName = "Hutao";
|
||||
#endif
|
||||
string path = Path.GetFullPath(Path.Combine(myDocument, folderName));
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static string GetUniqueUserId()
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
|
||||
return Convert.ToMd5HexString($"{userName}{machineGuid}");
|
||||
}
|
||||
|
||||
private static bool GetElevated()
|
||||
{
|
||||
if (LocalSetting.Get(SettingKeys.OverrideElevationRequirement, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
}
|
||||
|
||||
private void DetectWebView2Environment(ref string webView2Version, ref bool isWebView2Supported)
|
||||
{
|
||||
try
|
||||
{
|
||||
webView2Version = CoreWebView2Environment.GetAvailableBrowserVersionString();
|
||||
isWebView2Supported = true;
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
logger.LogError(ex, "WebView2 Runtime not installed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,35 +11,27 @@ internal static class DateTimeOffsetExtension
|
||||
{
|
||||
public static readonly DateTimeOffset DatebaseDefaultTime = new(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0));
|
||||
|
||||
/// <summary>
|
||||
/// 从Unix时间戳转换
|
||||
/// </summary>
|
||||
/// <param name="timestamp">时间戳</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>转换的时间</returns>
|
||||
public static DateTimeOffset FromUnixTime(long? timestamp, in DateTimeOffset defaultValue)
|
||||
public static DateTimeOffset UnsafeRelaxedFromUnixTime(long? timestamp, in DateTimeOffset defaultValue)
|
||||
{
|
||||
if (timestamp is { } value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(value);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(value);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (timestamp is not { } value)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(value);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(value);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,19 +17,6 @@ internal static partial class EnumerableExtension
|
||||
return source.ElementAtOrDefault(index) ?? source.LastOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果传入集合不为空则原路返回,
|
||||
/// 如果传入集合为空返回一个集合的空集
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>源集合或空集</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IEnumerable<TSource> EmptyIfNull<TSource>(this IEnumerable<TSource>? source)
|
||||
{
|
||||
return source ?? Enumerable.Empty<TSource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找枚举中唯一的值,找不到时
|
||||
/// 回退到首个或默认值
|
||||
|
||||
@@ -17,4 +17,22 @@ internal static class NullableExtension
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string ToStringOrEmpty<T>(this in T? nullable)
|
||||
where T : struct
|
||||
{
|
||||
string? result = default;
|
||||
|
||||
if (nullable.HasValue)
|
||||
{
|
||||
result = nullable.Value.ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(result))
|
||||
{
|
||||
result = string.Empty;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,9 @@ internal static class SpanExtension
|
||||
/// <param name="span">Span</param>
|
||||
/// <returns>最大值的下标</returns>
|
||||
public static int IndexOfMax<T>(this in ReadOnlySpan<T> span)
|
||||
where T : INumber<T>
|
||||
where T : INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
T max = T.Zero;
|
||||
T max = T.MinValue;
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ internal sealed class UIAFInfo : IMappingFrom<UIAFInfo, RuntimeOptions>
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed class UIIFInfo
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2250,7 +2250,7 @@
|
||||
<value>设备 ID</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingDeviceIpDescription" xml:space="preserve">
|
||||
<value>IP:{0},归属:{1}</value>
|
||||
<value>IP:{0} 归属服务器:{1}</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>设备 IP</value>
|
||||
|
||||
@@ -11,25 +11,10 @@ using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序选项
|
||||
/// 存储服务相关的选项
|
||||
/// </summary>
|
||||
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class AppOptions : DbStoreOptions
|
||||
{
|
||||
private readonly List<NameValue<BackdropType>> supportedBackdropTypesInner = CollectionsNameValue.FromEnum<BackdropType>();
|
||||
|
||||
private readonly List<NameValue<CultureInfo>> supportedCulturesInner =
|
||||
[
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("en")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ko")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ja")),
|
||||
];
|
||||
|
||||
private string? gamePath;
|
||||
private string? powerShellPath;
|
||||
private bool? isEmptyHistoryWishVisible;
|
||||
@@ -38,75 +23,67 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
private bool? isAdvancedLaunchOptionsEnabled;
|
||||
private string? geetestCustomCompositeUrl;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public string GamePath
|
||||
{
|
||||
get => GetOption(ref gamePath, SettingEntry.GamePath);
|
||||
set => SetOption(ref gamePath, SettingEntry.GamePath, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PowerShell 路径
|
||||
/// </summary>
|
||||
public string PowerShellPath
|
||||
{
|
||||
get => GetOption(ref powerShellPath, SettingEntry.PowerShellPath, GetPowerShellLocationOrEmpty);
|
||||
get
|
||||
{
|
||||
return GetOption(ref powerShellPath, SettingEntry.PowerShellPath, GetDefaultPowerShellLocationOrEmpty);
|
||||
|
||||
static string GetDefaultPowerShellLocationOrEmpty()
|
||||
{
|
||||
string? paths = Environment.GetEnvironmentVariable("Path");
|
||||
if (!string.IsNullOrEmpty(paths))
|
||||
{
|
||||
foreach (StringSegment path in new StringTokenizer(paths, [';']))
|
||||
{
|
||||
if (path is { HasValue: true, Length: > 0 })
|
||||
{
|
||||
if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Path.Combine(path.Value, "powershell.exe");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
set => SetOption(ref powerShellPath, SettingEntry.PowerShellPath, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public bool IsEmptyHistoryWishVisible
|
||||
{
|
||||
get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible);
|
||||
set => SetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所有支持的背景样式
|
||||
/// </summary>
|
||||
public List<NameValue<BackdropType>> BackdropTypes { get => supportedBackdropTypesInner; }
|
||||
public List<NameValue<BackdropType>> BackdropTypes { get; } = CollectionsNameValue.FromEnum<BackdropType>();
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型 默认 Mica
|
||||
/// </summary>
|
||||
public BackdropType BackdropType
|
||||
{
|
||||
get => GetOption(ref backdropType, SettingEntry.SystemBackdropType, v => Enum.Parse<BackdropType>(v), BackdropType.Mica).Value;
|
||||
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, value => value.ToString()!);
|
||||
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, value => value.ToStringOrEmpty());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所有支持的语言
|
||||
/// </summary>
|
||||
public List<NameValue<CultureInfo>> Cultures { get => supportedCulturesInner; }
|
||||
public List<NameValue<CultureInfo>> Cultures { get; } = SupportedCultures.Get();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化前的语言
|
||||
/// 通过设置与获取此属性,就可以获取到与系统同步的语言
|
||||
/// </summary>
|
||||
public CultureInfo PreviousCulture { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 当前语言
|
||||
/// 默认为系统语言
|
||||
/// </summary>
|
||||
public CultureInfo CurrentCulture
|
||||
{
|
||||
get => GetOption(ref currentCulture, SettingEntry.Culture, CultureInfo.GetCultureInfo, CultureInfo.CurrentCulture);
|
||||
set => SetOption(ref currentCulture, SettingEntry.Culture, value, value => value.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用高级功能
|
||||
/// DO NOT MOVE TO OTHER CLASS
|
||||
/// We are binding this property in SettingPage
|
||||
/// </summary>
|
||||
public bool IsAdvancedLaunchOptionsEnabled
|
||||
{
|
||||
// DO NOT MOVE TO OTHER CLASS
|
||||
// We use this property in SettingPage binding
|
||||
get => GetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled);
|
||||
set => SetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled, value);
|
||||
}
|
||||
@@ -117,28 +94,5 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
set => SetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl, value);
|
||||
}
|
||||
|
||||
private static NameValue<CultureInfo> ToNameValue(CultureInfo info)
|
||||
{
|
||||
return new(info.NativeName, info);
|
||||
}
|
||||
|
||||
private static string GetPowerShellLocationOrEmpty()
|
||||
{
|
||||
string? paths = Environment.GetEnvironmentVariable("Path");
|
||||
if (!string.IsNullOrEmpty(paths))
|
||||
{
|
||||
foreach (StringSegment path in new StringTokenizer(paths, [';']))
|
||||
{
|
||||
if (path is { HasValue: true, Length: > 0 })
|
||||
{
|
||||
if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Path.Combine(path.Value, "powershell.exe");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
internal CultureInfo PreviousCulture { get; set; } = default!;
|
||||
}
|
||||
@@ -33,7 +33,7 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP
|
||||
if (query.TryGetValue("auth_appid", out string? appId) && appId is "webview_gacha")
|
||||
{
|
||||
string? queryLanguageCode = query["lang"];
|
||||
if (metadataOptions.IsCurrentLocale(queryLanguageCode))
|
||||
if (metadataOptions.LanguageCodeFitsCurrentLocale(queryLanguageCode))
|
||||
{
|
||||
return new(true, new(queryString));
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv
|
||||
NameValueCollection query = HttpUtility.ParseQueryString(result.TrimEnd("#/log"));
|
||||
string? queryLanguageCode = query["lang"];
|
||||
|
||||
if (metadataOptions.IsCurrentLocale(queryLanguageCode))
|
||||
if (metadataOptions.LanguageCodeFitsCurrentLocale(queryLanguageCode))
|
||||
{
|
||||
return new(true, new(result));
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService
|
||||
// v2.1 only support CHS
|
||||
if (version is UIGFVersion.Major2Minor2OrLower)
|
||||
{
|
||||
if (!metadataOptions.IsCurrentLocale(uigf.Info.Language))
|
||||
if (!metadataOptions.LanguageCodeFitsCurrentLocale(uigf.Info.Language))
|
||||
{
|
||||
string message = SH.FormatServiceGachaUIGFImportLanguageNotMatch(uigf.Info.Language, metadataOptions.LanguageCode);
|
||||
ThrowHelper.InvalidOperation(message);
|
||||
|
||||
@@ -40,10 +40,6 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
private bool? useStarwardPlayTimeStatistics;
|
||||
private bool? setDiscordActivityWhenPlaying;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的启动游戏选项
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public LaunchOptions(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
@@ -53,47 +49,66 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
|
||||
InitializeMonitors(Monitors);
|
||||
InitializeScreenFps(out primaryScreenFps);
|
||||
|
||||
static void InitializeMonitors(List<NameValue<int>> monitors)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This list can't use foreach
|
||||
// https://github.com/microsoft/CsWinRT/issues/747
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
DisplayArea displayArea = displayAreas[i];
|
||||
int index = i + 1;
|
||||
monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
monitors.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void InitializeScreenFps(out int fps)
|
||||
{
|
||||
HDC hDC = default;
|
||||
try
|
||||
{
|
||||
hDC = GetDC(HWND.Null);
|
||||
fps = GetDeviceCaps(hDC, GET_DEVICE_CAPS_INDEX.VREFRESH);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = ReleaseDC(HWND.Null, hDC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用启动参数
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => GetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, true);
|
||||
set => SetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否全屏
|
||||
/// </summary>
|
||||
public bool IsFullScreen
|
||||
{
|
||||
get => GetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen);
|
||||
set => SetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否无边框
|
||||
/// </summary>
|
||||
public bool IsBorderless
|
||||
{
|
||||
get => GetOption(ref isBorderless, SettingEntry.LaunchIsBorderless);
|
||||
set => SetOption(ref isBorderless, SettingEntry.LaunchIsBorderless, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否独占全屏
|
||||
/// </summary>
|
||||
public bool IsExclusive
|
||||
{
|
||||
get => GetOption(ref isExclusive, SettingEntry.LaunchIsExclusive);
|
||||
set => SetOption(ref isExclusive, SettingEntry.LaunchIsExclusive, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 屏幕宽度
|
||||
/// </summary>
|
||||
public int ScreenWidth
|
||||
{
|
||||
get => GetOption(ref screenWidth, SettingEntry.LaunchScreenWidth, primaryScreenWidth);
|
||||
@@ -106,9 +121,6 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
set => SetOption(ref isScreenWidthEnabled, SettingEntry.LaunchIsScreenWidthEnabled, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 屏幕高度
|
||||
/// </summary>
|
||||
public int ScreenHeight
|
||||
{
|
||||
get => GetOption(ref screenHeight, SettingEntry.LaunchScreenHeight, primaryScreenHeight);
|
||||
@@ -121,32 +133,20 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
set => SetOption(ref isScreenHeightEnabled, SettingEntry.LaunchIsScreenHeightEnabled, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否全屏
|
||||
/// </summary>
|
||||
public bool UnlockFps
|
||||
{
|
||||
get => GetOption(ref unlockFps, SettingEntry.LaunchUnlockFps);
|
||||
set => SetOption(ref unlockFps, SettingEntry.LaunchUnlockFps, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 目标帧率
|
||||
/// </summary>
|
||||
public int TargetFps
|
||||
{
|
||||
get => GetOption(ref targetFps, SettingEntry.LaunchTargetFps, primaryScreenFps);
|
||||
set => SetOption(ref targetFps, SettingEntry.LaunchTargetFps, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所有监视器
|
||||
/// </summary>
|
||||
public List<NameValue<int>> Monitors { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 目标帧率
|
||||
/// </summary>
|
||||
[AllowNull]
|
||||
public NameValue<int> Monitor
|
||||
{
|
||||
@@ -196,31 +196,4 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
get => GetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, true);
|
||||
set => SetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, value);
|
||||
}
|
||||
|
||||
private static void InitializeMonitors(List<NameValue<int>> monitors)
|
||||
{
|
||||
// This list can't use foreach
|
||||
// https://github.com/microsoft/CsWinRT/issues/747
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
DisplayArea displayArea = displayAreas[i];
|
||||
int index = i + 1;
|
||||
monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index));
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeScreenFps(out int fps)
|
||||
{
|
||||
HDC hDC = default;
|
||||
try
|
||||
{
|
||||
hDC = GetDC(HWND.Null);
|
||||
fps = GetDeviceCaps(hDC, GET_DEVICE_CAPS_INDEX.VREFRESH);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = ReleaseDC(HWND.Null, hDC);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,141 +2,38 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Service.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃用户选项
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed class HutaoUserOptions : ObservableObject, IOptions<HutaoUserOptions>
|
||||
internal sealed class HutaoUserOptions : ObservableObject
|
||||
{
|
||||
private readonly TaskCompletionSource initializedTaskCompletionSource = new();
|
||||
private readonly TaskCompletionSource initialization = new();
|
||||
|
||||
private string? userName = SH.ViewServiceHutaoUserLoginOrRegisterHint;
|
||||
private string? token;
|
||||
private bool isLoggedIn;
|
||||
private bool isHutaoCloudServiceAllowed;
|
||||
private bool isCloudServiceAllowed;
|
||||
private bool isLicensedDeveloper;
|
||||
private bool isMaintainer;
|
||||
private string? gachaLogExpireAt;
|
||||
private string? gachaLogExpireAtSlim;
|
||||
private bool isMaintainer;
|
||||
private string? token;
|
||||
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string? UserName { get => userName; set => SetProperty(ref userName, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 真正的用户名
|
||||
/// </summary>
|
||||
public string? ActualUserName { get => IsLoggedIn ? UserName : null; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已登录
|
||||
/// </summary>
|
||||
public bool IsLoggedIn { get => isLoggedIn; set => SetProperty(ref isLoggedIn, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃云服务是否可用
|
||||
/// </summary>
|
||||
public bool IsCloudServiceAllowed { get => isHutaoCloudServiceAllowed; set => SetProperty(ref isHutaoCloudServiceAllowed, value); }
|
||||
public bool IsCloudServiceAllowed { get => isCloudServiceAllowed; set => SetProperty(ref isCloudServiceAllowed, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为开发者
|
||||
/// </summary>
|
||||
public bool IsLicensedDeveloper { get => isLicensedDeveloper; set => SetProperty(ref isLicensedDeveloper, value); }
|
||||
|
||||
public bool IsMaintainer { get => isMaintainer; set => SetProperty(ref isMaintainer, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录服务到期时间
|
||||
/// </summary>
|
||||
public string? GachaLogExpireAt { get => gachaLogExpireAt; set => SetProperty(ref gachaLogExpireAt, value); }
|
||||
|
||||
public string? GachaLogExpireAtSlim { get => gachaLogExpireAtSlim; set => SetProperty(ref gachaLogExpireAtSlim, value); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public HutaoUserOptions Value { get => this; }
|
||||
internal string? Token { get => token; set => token = value; }
|
||||
|
||||
public async ValueTask<bool> PostLoginSucceedAsync(HutaoPassportClient passportClient, ITaskContext taskContext, string username, string password, string? token)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.PassportUserName, username);
|
||||
LocalSetting.Set(SettingKeys.PassportPassword, password);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
UserName = username;
|
||||
this.token = token;
|
||||
IsLoggedIn = true;
|
||||
initializedTaskCompletionSource.TrySetResult();
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
Web.Response.Response<UserInfo> userInfoResponse = await passportClient.GetUserInfoAsync(default).ConfigureAwait(false);
|
||||
if (userInfoResponse.IsOk())
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
UpdateUserInfo(userInfoResponse.Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void LogoutOrUnregister()
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.PassportUserName, string.Empty);
|
||||
LocalSetting.Set(SettingKeys.PassportPassword, string.Empty);
|
||||
|
||||
UserName = null;
|
||||
token = null;
|
||||
IsLoggedIn = false;
|
||||
ClearUserInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录失败
|
||||
/// </summary>
|
||||
public void LoginFailed()
|
||||
{
|
||||
UserName = SH.ViewServiceHutaoUserLoginFailHint;
|
||||
initializedTaskCompletionSource.TrySetResult();
|
||||
}
|
||||
|
||||
public void SkipLogin()
|
||||
{
|
||||
initializedTaskCompletionSource.TrySetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新用户信息
|
||||
/// </summary>
|
||||
/// <param name="userInfo">用户信息</param>
|
||||
public void UpdateUserInfo(UserInfo userInfo)
|
||||
{
|
||||
IsLicensedDeveloper = userInfo.IsLicensedDeveloper;
|
||||
IsMaintainer = userInfo.IsMaintainer;
|
||||
string unescaped = Regex.Unescape(SH.ServiceHutaoUserGachaLogExpiredAt);
|
||||
GachaLogExpireAt = string.Format(CultureInfo.CurrentCulture, unescaped, userInfo.GachaLogExpireAt);
|
||||
GachaLogExpireAtSlim = $"{userInfo.GachaLogExpireAt:yyyy.MM.dd HH:mm:ss}";
|
||||
IsCloudServiceAllowed = IsLicensedDeveloper || userInfo.GachaLogExpireAt > DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
public async ValueTask<string?> GetTokenAsync()
|
||||
{
|
||||
await initializedTaskCompletionSource.Task.ConfigureAwait(false);
|
||||
return token;
|
||||
}
|
||||
|
||||
private void ClearUserInfo()
|
||||
{
|
||||
IsLicensedDeveloper = false;
|
||||
IsMaintainer = false;
|
||||
GachaLogExpireAt = null;
|
||||
GachaLogExpireAtSlim = null;
|
||||
IsCloudServiceAllowed = false;
|
||||
}
|
||||
internal TaskCompletionSource Initialization { get => initialization; }
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Service.Hutao;
|
||||
|
||||
internal static class HutaoUserOptionsExtension
|
||||
{
|
||||
public static string? GetActualUserName(this HutaoUserOptions options)
|
||||
{
|
||||
return options.IsLoggedIn ? options.UserName : null;
|
||||
}
|
||||
|
||||
public static async ValueTask<string?> GetTokenAsync(this HutaoUserOptions options)
|
||||
{
|
||||
await options.Initialization.Task.ConfigureAwait(false);
|
||||
return options.Token;
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> PostLoginSucceedAsync(this HutaoUserOptions options, HutaoPassportClient passportClient, ITaskContext taskContext, string username, string password, string? token)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.PassportUserName, username);
|
||||
LocalSetting.Set(SettingKeys.PassportPassword, password);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
options.UserName = username;
|
||||
options.Token = token;
|
||||
options.IsLoggedIn = true;
|
||||
options.Initialization.TrySetResult();
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
Web.Response.Response<UserInfo> userInfoResponse = await passportClient.GetUserInfoAsync(default).ConfigureAwait(false);
|
||||
if (userInfoResponse.IsOk())
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
UpdateUserInfo(options, userInfoResponse.Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
static void UpdateUserInfo(HutaoUserOptions options, UserInfo userInfo)
|
||||
{
|
||||
options.IsLicensedDeveloper = userInfo.IsLicensedDeveloper;
|
||||
options.IsMaintainer = userInfo.IsMaintainer;
|
||||
|
||||
options.IsCloudServiceAllowed = options.IsLicensedDeveloper || userInfo.GachaLogExpireAt > DateTimeOffset.UtcNow;
|
||||
string unescaped = Regex.Unescape(SH.ServiceHutaoUserGachaLogExpiredAt);
|
||||
options.GachaLogExpireAt = string.Format(CultureInfo.CurrentCulture, unescaped, userInfo.GachaLogExpireAt);
|
||||
options.GachaLogExpireAtSlim = $"{userInfo.GachaLogExpireAt:yyyy.MM.dd HH:mm:ss}";
|
||||
}
|
||||
}
|
||||
|
||||
public static void PostLogoutOrUnregister(this HutaoUserOptions options)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.PassportUserName, string.Empty);
|
||||
LocalSetting.Set(SettingKeys.PassportPassword, string.Empty);
|
||||
|
||||
options.UserName = null;
|
||||
options.Token = null;
|
||||
options.IsLoggedIn = false;
|
||||
ClearUserInfo(options);
|
||||
|
||||
static void ClearUserInfo(HutaoUserOptions options)
|
||||
{
|
||||
options.IsLicensedDeveloper = false;
|
||||
options.IsMaintainer = false;
|
||||
options.GachaLogExpireAt = null;
|
||||
options.GachaLogExpireAtSlim = null;
|
||||
options.IsCloudServiceAllowed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void PostLoginFailed(this HutaoUserOptions options)
|
||||
{
|
||||
options.UserName = SH.ViewServiceHutaoUserLoginFailHint;
|
||||
options.Initialization.TrySetResult();
|
||||
}
|
||||
|
||||
public static void PostLoginSkipped(this HutaoUserOptions options)
|
||||
{
|
||||
options.Initialization.TrySetResult();
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe
|
||||
|
||||
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
options.SkipLogin();
|
||||
options.PostLoginSkipped();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -52,7 +52,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe
|
||||
else
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
options.LoginFailed();
|
||||
options.PostLoginFailed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using Snap.Hutao.Core;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
/// <summary>
|
||||
/// 元数据选项
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
internal sealed partial class MetadataOptions
|
||||
{
|
||||
private readonly AppOptions appOptions;
|
||||
private readonly RuntimeOptions hutaoOptions;
|
||||
@@ -22,9 +17,6 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
private string? fallbackDataFolder;
|
||||
private string? localizedDataFolder;
|
||||
|
||||
/// <summary>
|
||||
/// 中文数据文件夹
|
||||
/// </summary>
|
||||
public string FallbackDataFolder
|
||||
{
|
||||
get
|
||||
@@ -39,9 +31,6 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地化数据文件夹
|
||||
/// </summary>
|
||||
public string LocalizedDataFolder
|
||||
{
|
||||
get
|
||||
@@ -56,17 +45,11 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前使用的元数据本地化名称
|
||||
/// </summary>
|
||||
public string LocaleName
|
||||
{
|
||||
get => localeName ??= GetLocaleName(appOptions.CurrentCulture);
|
||||
get => localeName ??= MetadataOptionsExtension.GetLocaleName(appOptions.CurrentCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前语言代码
|
||||
/// </summary>
|
||||
public string LanguageCode
|
||||
{
|
||||
get
|
||||
@@ -79,63 +62,4 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
throw new KeyNotFoundException($"Invalid localeName: '{LocaleName}'");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MetadataOptions Value { get => this; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取语言名称
|
||||
/// </summary>
|
||||
/// <param name="cultureInfo">语言信息</param>
|
||||
/// <returns>元数据语言名称</returns>
|
||||
public static string GetLocaleName(CultureInfo cultureInfo)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (LocaleNames.TryGetLocaleNameFromLanguageName(cultureInfo.Name, out string? localeName))
|
||||
{
|
||||
return localeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
cultureInfo = cultureInfo.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否为当前语言名称
|
||||
/// </summary>
|
||||
/// <param name="languageCode">语言代码</param>
|
||||
/// <returns>是否为当前语言名称</returns>
|
||||
public bool IsCurrentLocale(string? languageCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CultureInfo cultureInfo = CultureInfo.GetCultureInfo(languageCode);
|
||||
return GetLocaleName(cultureInfo) == LocaleName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地的本地化元数据文件
|
||||
/// </summary>
|
||||
/// <param name="fileNameWithExtension">文件名</param>
|
||||
/// <returns>本地的本地化元数据文件</returns>
|
||||
public string GetLocalizedLocalFile(string fileNameWithExtension)
|
||||
{
|
||||
return Path.Combine(LocalizedDataFolder, fileNameWithExtension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取服务器上的本地化元数据文件
|
||||
/// </summary>
|
||||
/// <param name="fileNameWithExtension">文件名</param>
|
||||
/// <returns>服务器上的本地化元数据文件</returns>
|
||||
public string GetLocalizedRemoteFile(string fileNameWithExtension)
|
||||
{
|
||||
return Web.HutaoEndpoints.Metadata(LocaleName, fileNameWithExtension);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
internal static class MetadataOptionsExtension
|
||||
{
|
||||
public static string GetLocalizedLocalFile(this MetadataOptions options, string fileNameWithExtension)
|
||||
{
|
||||
return Path.Combine(options.LocalizedDataFolder, fileNameWithExtension);
|
||||
}
|
||||
|
||||
public static string GetLocalizedRemoteFile(this MetadataOptions options, string fileNameWithExtension)
|
||||
{
|
||||
return Web.HutaoEndpoints.Metadata(options.LocaleName, fileNameWithExtension);
|
||||
}
|
||||
|
||||
public static bool LanguageCodeFitsCurrentLocale(this MetadataOptions options, string? languageCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to make sure code fits in 1 of 15 metadata locales
|
||||
CultureInfo cultureInfo = CultureInfo.GetCultureInfo(languageCode);
|
||||
return GetLocaleName(cultureInfo) == options.LocaleName;
|
||||
}
|
||||
|
||||
internal static string GetLocaleName(CultureInfo cultureInfo)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (LocaleNames.TryGetLocaleNameFromLanguageName(cultureInfo.Name, out string? localeName))
|
||||
{
|
||||
return localeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
cultureInfo = cultureInfo.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs
Normal file
29
src/Snap.Hutao/Snap.Hutao/Service/SupportedCultures.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
internal static class SupportedCultures
|
||||
{
|
||||
private static readonly List<NameValue<CultureInfo>> Cultures =
|
||||
[
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("en")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ko")),
|
||||
ToNameValue(CultureInfo.GetCultureInfo("ja")),
|
||||
];
|
||||
|
||||
public static List<NameValue<CultureInfo>> Get()
|
||||
{
|
||||
return Cultures;
|
||||
}
|
||||
|
||||
private static NameValue<CultureInfo> ToNameValue(CultureInfo info)
|
||||
{
|
||||
return new(info.NativeName, info);
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
hutaoUserOptions.LogoutOrUnregister();
|
||||
hutaoUserOptions.PostLogoutOrUnregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
|
||||
[Command("LogoutCommand")]
|
||||
private void LogoutAsync()
|
||||
{
|
||||
hutaoUserOptions.LogoutOrUnregister();
|
||||
hutaoUserOptions.PostLogoutOrUnregister();
|
||||
}
|
||||
|
||||
[Command("ResetPasswordCommand")]
|
||||
|
||||
@@ -8,7 +8,7 @@ internal sealed class IPInformation
|
||||
public static IPInformation Default { get; } = new()
|
||||
{
|
||||
Ip = "Unknown",
|
||||
Division = "Unknown"
|
||||
Division = "Unknown",
|
||||
};
|
||||
|
||||
[JsonPropertyName("ip")]
|
||||
|
||||
@@ -233,7 +233,7 @@ internal sealed partial class HutaoSpiralAbyssClient
|
||||
if (spiralAbyssResponse.IsOk())
|
||||
{
|
||||
HutaoUserOptions options = serviceProvider.GetRequiredService<HutaoUserOptions>();
|
||||
return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data, options.ActualUserName);
|
||||
return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data, options.GetActualUserName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user