mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor welcome view model
This commit is contained in:
@@ -17,9 +17,9 @@ namespace Snap.Hutao.SourceGeneration.DependencyInjection;
|
||||
internal sealed class InjectionGenerator : IIncrementalGenerator
|
||||
{
|
||||
public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute";
|
||||
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
|
||||
public const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
|
||||
public const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
|
||||
private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
|
||||
public const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
|
||||
|
||||
private static readonly DiagnosticDescriptor invalidInjectionDescriptor = new("SH101", "无效的 InjectAs 枚举值", "尚未支持生成 {0} 配置", "Quality", DiagnosticSeverity.Error, true);
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Snap.Hutao.SourceGeneration.Primitive;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.DependencyInjection;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
internal class ServiceAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private static readonly DiagnosticDescriptor NonSingletonUseServiceProviderDescriptor = new("SH301", "Non Singleton service should avoid direct use of IServiceProvider", "Non Singleton service should avoid direct use of IServiceProvider", "Quality", DiagnosticSeverity.Info, true);
|
||||
private static readonly DiagnosticDescriptor SingletonServiceCaptureNonSingletonServiceDescriptor = new("SH302", "Singleton service should avoid keep reference of non singleton service", "Singleton service should avoid keep reference of non singleton service", "Quality", DiagnosticSeverity.Info, true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get => new DiagnosticDescriptor[]
|
||||
{
|
||||
NonSingletonUseServiceProviderDescriptor,
|
||||
SingletonServiceCaptureNonSingletonServiceDescriptor,
|
||||
}.ToImmutableArray(); }
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterCompilationStartAction(CompilationStart);
|
||||
}
|
||||
|
||||
private static void CompilationStart(CompilationStartAnalysisContext context)
|
||||
{
|
||||
context.RegisterSyntaxNodeAction(HandleNonSingletonUseServiceProvider, SyntaxKind.ClassDeclaration);
|
||||
context.RegisterSyntaxNodeAction(HandleSingletonServiceCaptureNonSingletonService, SyntaxKind.ClassDeclaration);
|
||||
}
|
||||
|
||||
private static void HandleNonSingletonUseServiceProvider(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
ClassDeclarationSyntax classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
|
||||
if (classDeclarationSyntax.HasAttributeLists())
|
||||
{
|
||||
INamedTypeSymbol? classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
|
||||
if (classSymbol is not null)
|
||||
{
|
||||
foreach (AttributeData attributeData in classSymbol.GetAttributes())
|
||||
{
|
||||
if (attributeData.AttributeClass!.ToDisplayString() is InjectionGenerator.AttributeName)
|
||||
{
|
||||
string serviceType = attributeData.ConstructorArguments[0].ToCSharpString();
|
||||
if (serviceType is InjectionGenerator.InjectAsTransientName or InjectionGenerator.InjectAsScopedName)
|
||||
{
|
||||
HandleNonSingletonUseServiceProviderActual(context, classSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleNonSingletonUseServiceProviderActual(SyntaxNodeAnalysisContext context, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
ISymbol? symbol = classSymbol.GetMembers().Where(m => m is IFieldSymbol f && f.Type.ToDisplayString() == "System.IServiceProvider").SingleOrDefault();
|
||||
|
||||
if (symbol is not null)
|
||||
{
|
||||
Diagnostic diagnostic = Diagnostic.Create(NonSingletonUseServiceProviderDescriptor, symbol.Locations.FirstOrDefault());
|
||||
context.ReportDiagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleSingletonServiceCaptureNonSingletonService(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
//classSymbol.GetMembers().Where(m => m is IFieldSymbol { IsReadOnly: true, DeclaredAccessibility: Accessibility.Private } f);
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
CA1501: 6
|
||||
CA1501: 8
|
||||
@@ -40,4 +40,10 @@ internal static class StringExtension
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Format(this string value, object? arg0, object? arg1, object? arg2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1, arg2);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
/// UIAF格式的信息
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class UIAFInfo : IMappingFrom<UIAFInfo, IServiceProvider>
|
||||
internal sealed class UIAFInfo : IMappingFrom<UIAFInfo, RuntimeOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// 导出的 App 名称
|
||||
@@ -45,15 +45,8 @@ internal sealed class UIAFInfo : IMappingFrom<UIAFInfo, IServiceProvider>
|
||||
[JsonPropertyName("uiaf_version")]
|
||||
public string? UIAFVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的专用 UIAF 信息
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <returns>专用 UIAF 信息</returns>
|
||||
public static UIAFInfo From(IServiceProvider serviceProvider)
|
||||
public static UIAFInfo From(RuntimeOptions runtimeOptions)
|
||||
{
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
return new()
|
||||
{
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
|
||||
@@ -23,7 +23,7 @@ internal sealed class ItemIconConverter : ValueConverter<string, Uri>
|
||||
return default!;
|
||||
}
|
||||
|
||||
return name.StartsWith("UI_RelicIcon_")
|
||||
return name.StartsWith("UI_RelicIcon_", StringComparison.Ordinal)
|
||||
? RelicIconConverter.IconNameToUri(name)
|
||||
: Web.HutaoEndpoints.StaticFile("ItemIcon", $"{name}.png").ToUri();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ internal sealed class SkillIconConverter : ValueConverter<string, Uri>
|
||||
return Web.HutaoEndpoints.UIIconNone;
|
||||
}
|
||||
|
||||
return name.StartsWith("UI_Talent_")
|
||||
return name.StartsWith("UI_Talent_", StringComparison.Ordinal)
|
||||
? Web.HutaoEndpoints.StaticFile("Talent", $"{name}.png").ToUri()
|
||||
: Web.HutaoEndpoints.StaticFile("Skill", $"{name}.png").ToUri();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed class LevelDescription
|
||||
/// 格式化的等级
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string LevelFormatted { get => string.Format(SH.ModelWeaponAffixFormat, Level + 1); }
|
||||
public string LevelFormatted { get => SH.ModelWeaponAffixFormat.Format(Level + 1); }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
|
||||
@@ -39,6 +39,6 @@ internal sealed unsafe class IdentityConverter<TWrapper> : JsonConverter<TWrappe
|
||||
/// <inheritdoc/>
|
||||
public override void WriteAsPropertyName(Utf8JsonWriter writer, TWrapper value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WritePropertyName((*(uint*)&value).ToString());
|
||||
writer.WritePropertyName($"{*(uint*)&value}");
|
||||
}
|
||||
}
|
||||
@@ -5819,5 +5819,14 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
return ResourceManager.GetString("WebResponseFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 [{0}] 中的 [{1}] 网络请求异常,请稍后再试 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string WebResponseRequestExceptionFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("WebResponseRequestExceptionFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2093,4 +2093,7 @@
|
||||
<data name="WebResponseFormat" xml:space="preserve">
|
||||
<value>状态:{0} | 信息:{1}</value>
|
||||
</data>
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{0}] 中的 [{1}] 网络请求异常,请稍后再试</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
@@ -85,7 +86,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == key)?.Value;
|
||||
storage = value is null ? defaultValue : int.Parse(value);
|
||||
storage = value is null ? defaultValue : int.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return storage.Value;
|
||||
@@ -211,7 +212,7 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
appDbContext.Settings.ExecuteDeleteWhere(e => e.Key == key);
|
||||
appDbContext.Settings.AddAndSave(new(key, value.ToString()));
|
||||
appDbContext.Settings.AddAndSave(new(key, $"{value}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class AchievementDbBulkOperation
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
@@ -76,7 +76,10 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity!.Id < uiaf!.Id)
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
ArgumentNullException.ThrowIfNull(uiaf);
|
||||
|
||||
if (entity.Id < uiaf.Id)
|
||||
{
|
||||
moveEntity = true;
|
||||
moveUIAF = false;
|
||||
@@ -164,7 +167,10 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldEntity!.Id < newEntity!.Id)
|
||||
ArgumentNullException.ThrowIfNull(oldEntity);
|
||||
ArgumentNullException.ThrowIfNull(newEntity);
|
||||
|
||||
if (oldEntity.Id < newEntity.Id)
|
||||
{
|
||||
moveOld = true;
|
||||
moveNew = false;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
/// 成就数据库服务
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped, typeof(IAchievementDbService))]
|
||||
[Injection(InjectAs.Singleton, typeof(IAchievementDbService))]
|
||||
internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
@@ -66,7 +66,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.OrderByDescending(a => a.Time.ToString())
|
||||
.OrderByDescending(a => a.Time)
|
||||
.Take(take)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -51,19 +51,14 @@ internal sealed partial class AchievementService
|
||||
public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
List<UIAFItem> list = achievementDbService
|
||||
.GetAchievementListByArchiveId(archive.InnerId)
|
||||
.SelectList(UIAFItem.From);
|
||||
|
||||
return new()
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
List<UIAFItem> list = achievementDbService
|
||||
.GetAchievementListByArchiveId(archive.InnerId)
|
||||
.SelectList(UIAFItem.From);
|
||||
|
||||
return new()
|
||||
{
|
||||
Info = UIAFInfo.From(scope.ServiceProvider),
|
||||
List = list,
|
||||
};
|
||||
}
|
||||
Info = UIAFInfo.From(runtimeOptions),
|
||||
List = list,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
@@ -23,7 +24,7 @@ internal sealed partial class AchievementService : IAchievementService
|
||||
private readonly ScopedDbCurrent<AchievementArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
|
||||
private readonly AchievementDbBulkOperation achievementDbBulkOperation;
|
||||
private readonly IAchievementDbService achievementDbService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -40,6 +40,6 @@ internal readonly struct ImportResult
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(SH.ServiceAchievementImportResultFormat, Add, Update, Remove);
|
||||
return SH.ServiceAchievementImportResultFormat.Format(Add, Update, Remove);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,9 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
// 缓存中存在记录,直接返回
|
||||
if (memoryCache.TryGetValue(CacheKey, out object? cache))
|
||||
{
|
||||
return (AnnouncementWrapper)cache!;
|
||||
AnnouncementWrapper? wrapper = (AnnouncementWrapper?)cache;
|
||||
ArgumentNullException.ThrowIfNull(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
@@ -60,7 +62,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
}
|
||||
}
|
||||
|
||||
return null!;
|
||||
return default!;
|
||||
}
|
||||
|
||||
private static void JoinAnnouncements(Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Snap.Hutao.Service.AvatarInfo;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class AvatarInfoDbBulkOperation
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
@@ -10,7 +10,7 @@ using ModelAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
|
||||
namespace Snap.Hutao.Service.AvatarInfo;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped, typeof(IAvatarInfoDbService))]
|
||||
[Injection(InjectAs.Singleton, typeof(IAvatarInfoDbService))]
|
||||
internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
@@ -117,7 +117,8 @@ internal sealed class SummaryAvatarFactory
|
||||
MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId];
|
||||
|
||||
// AffixMap can be null when it's a white weapon.
|
||||
uint affixLevel = equip.Weapon!.AffixMap?.SingleOrDefault().Value ?? 0U;
|
||||
ArgumentNullException.ThrowIfNull(equip.Weapon);
|
||||
uint affixLevel = equip.Weapon.AffixMap?.SingleOrDefault().Value ?? 0U;
|
||||
|
||||
WeaponStat? mainStat = equip.Flat.WeaponStats?.ElementAtOrDefault(0);
|
||||
WeaponStat? subStat = equip.Flat.WeaponStats?.ElementAtOrDefault(1);
|
||||
|
||||
@@ -121,7 +121,8 @@ internal sealed class SummaryReliquaryFactory
|
||||
// 从喵插件抓取的圣遗物评分权重
|
||||
// 部分复杂的角色暂时使用了默认值
|
||||
ReliquaryAffixWeight affixWeight = metadataContext.IdReliquaryAffixWeightMap.GetValueOrDefault(avatarInfo.AvatarId, ReliquaryAffixWeight.Default);
|
||||
ReliquaryMainAffixLevel maxRelicLevel = metadataContext.ReliquaryLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level)!;
|
||||
ReliquaryMainAffixLevel? maxRelicLevel = metadataContext.ReliquaryLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level);
|
||||
ArgumentNullException.ThrowIfNull(maxRelicLevel);
|
||||
|
||||
float percent = relicLevel.PropertyMap[property] / maxRelicLevel.PropertyMap[property];
|
||||
float baseScore = 8 * percent * affixWeight[property];
|
||||
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Service.DailyNote;
|
||||
|
||||
@@ -52,14 +53,14 @@ internal sealed class DailyNoteOptions : DbStoreOptions
|
||||
/// </summary>
|
||||
public NameValue<int>? SelectedRefreshTime
|
||||
{
|
||||
get => GetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, time => RefreshTimes.Single(t => t.Value == int.Parse(time)), RefreshTimes[1]);
|
||||
get => GetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, time => RefreshTimes.Single(t => t.Value == int.Parse(time, CultureInfo.InvariantCulture)), RefreshTimes[1]);
|
||||
set
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
if (scheduleTaskInterop.RegisterForDailyNoteRefresh(value.Value))
|
||||
{
|
||||
SetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, value, value => value.Value.ToString());
|
||||
SetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, value, value => $"{value.Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -133,7 +133,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
|
||||
default:
|
||||
// ItemId string length not correct.
|
||||
ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.ItemId), null!);
|
||||
ThrowHelper.UserdataCorrupted(SH.ServiceGachaStatisticsFactoryItemIdInvalid.Format(item.ItemId), default!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ internal sealed class HutaoStatisticsFactory
|
||||
{
|
||||
8U => context.IdAvatarMap[item.Item],
|
||||
5U => context.IdWeaponMap[item.Item],
|
||||
_ => throw ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.Item), null!),
|
||||
_ => throw ThrowHelper.UserdataCorrupted(SH.ServiceGachaStatisticsFactoryItemIdInvalid.Format(item.Item), default!),
|
||||
};
|
||||
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ internal sealed partial class GameService : IGameService
|
||||
{
|
||||
string gamePath = appOptions.GamePath;
|
||||
string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFileName);
|
||||
bool isOversea = string.Equals(Path.GetFileName(gamePath), GenshinImpactFileName, StringComparison.OrdinalIgnoreCase);
|
||||
bool isOversea = string.Equals(Path.GetFileName(gamePath), GenshinImpactFileName, StringComparison.Ordinal);
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
|
||||
@@ -9,6 +9,8 @@ internal interface IUserDbService
|
||||
|
||||
ValueTask DeleteUserByIdAsync(Guid id);
|
||||
|
||||
ValueTask DeleteUsersAsync();
|
||||
|
||||
ValueTask<List<Model.Entity.User>> GetUserListAsync();
|
||||
|
||||
ValueTask UpdateUserAsync(Model.Entity.User user);
|
||||
|
||||
@@ -63,4 +63,9 @@ internal interface IUserService
|
||||
/// <param name="user">待移除的用户</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask RemoveUserAsync(BindingUser user);
|
||||
}
|
||||
|
||||
internal interface IUserServiceUnsafe
|
||||
{
|
||||
ValueTask UnsafeRemoveUsersAsync();
|
||||
}
|
||||
@@ -59,4 +59,13 @@ internal sealed partial class UserDbService : IUserDbService
|
||||
await appDbContext.Users.AddAndSaveAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DeleteUsersAsync()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.Users.ExecuteDeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace Snap.Hutao.Service.User;
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IUserService))]
|
||||
internal sealed partial class UserService : IUserService
|
||||
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
{
|
||||
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||
private readonly IUserInitializationService userInitializationService;
|
||||
@@ -57,6 +57,12 @@ internal sealed partial class UserService : IUserService
|
||||
messenger.Send(new UserRemovedMessage(user.Entity));
|
||||
}
|
||||
|
||||
public async ValueTask UnsafeRemoveUsersAsync()
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
await userDbService.DeleteUsersAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.ViewModel;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
namespace Snap.Hutao.View;
|
||||
|
||||
|
||||
130
src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/DownloadSummary.cs
Normal file
130
src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/DownloadSummary.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
internal sealed class DownloadSummary : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly string fileName;
|
||||
private readonly string fileUrl;
|
||||
private readonly Progress<StreamCopyStatus> progress;
|
||||
private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
|
||||
private double progressValue;
|
||||
private long updateCount;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的下载信息
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="fileName">压缩文件名称</param>
|
||||
public DownloadSummary(IServiceProvider serviceProvider, string fileName)
|
||||
{
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
httpClient = serviceProvider.GetRequiredService<HttpClient>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
DisplayName = fileName;
|
||||
this.fileName = fileName;
|
||||
fileUrl = Web.HutaoEndpoints.StaticZip(fileName);
|
||||
|
||||
progress = new(UpdateProgressStatus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示名称
|
||||
/// </summary>
|
||||
public string DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get => description; private set => SetProperty(ref description, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 进度值,最大1
|
||||
/// </summary>
|
||||
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 异步下载并解压
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public async Task<bool> DownloadAndExtractAsync()
|
||||
{
|
||||
ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>();
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
logger.LogInformation("Begin download, length: {length}", contentLength);
|
||||
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
|
||||
ExtractFiles(temp);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ProgressValue = 1;
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Download Static Zip failed");
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Description = ex is HttpRequestException httpRequestException
|
||||
? $"{SH.ViewModelWelcomeDownloadSummaryException} - HTTP {httpRequestException.StatusCode:D}"
|
||||
: SH.ViewModelWelcomeDownloadSummaryException;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProgressStatus(StreamCopyStatus status)
|
||||
{
|
||||
if (Interlocked.Increment(ref updateCount) % 40 == 0)
|
||||
{
|
||||
Description = $"{Converters.ToFileSizeString(status.BytesCopied)}/{Converters.ToFileSizeString(status.TotalBytes)}";
|
||||
ProgressValue = status.TotalBytes == 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractFiles(Stream stream)
|
||||
{
|
||||
IImageCacheFilePathOperation imageCache = serviceProvider.GetRequiredService<IImageCache>().As<IImageCacheFilePathOperation>()!;
|
||||
|
||||
using (ZipArchive archive = new(stream))
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
string destPath = imageCache.GetFileFromCategoryAndName(fileName, entry.FullName);
|
||||
entry.ExtractToFile(destPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/WelcomeViewModel.cs
Normal file
112
src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/WelcomeViewModel.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
/// <summary>
|
||||
/// 欢迎视图模型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class WelcomeViewModel : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private ObservableCollection<DownloadSummary>? downloadSummaries;
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
public ObservableCollection<DownloadSummary>? DownloadSummaries { get => downloadSummaries; set => SetProperty(ref downloadSummaries, value); }
|
||||
|
||||
[Command("OpenUICommand")]
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
IEnumerable<DownloadSummary> downloadSummaries = GenerateStaticResourceDownloadTasks();
|
||||
|
||||
DownloadSummaries = downloadSummaries.ToObservableCollection();
|
||||
|
||||
await Parallel.ForEachAsync(downloadSummaries, async (summary, token) =>
|
||||
{
|
||||
if (await summary.DownloadAndExtractAsync().ConfigureAwait(false))
|
||||
{
|
||||
taskContext.InvokeOnMainThread(() => DownloadSummaries.Remove(summary));
|
||||
}
|
||||
}).ConfigureAwait(true);
|
||||
|
||||
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
|
||||
StaticResource.FulfillAllContracts();
|
||||
|
||||
try
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText(SH.ViewModelWelcomeDownloadCompleteTitle)
|
||||
.AddText(SH.ViewModelWelcomeDownloadCompleteMessage)
|
||||
.Show();
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// 0x803E0105
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<DownloadSummary> GenerateStaticResourceDownloadTasks()
|
||||
{
|
||||
Dictionary<string, DownloadSummary> downloadSummaries = new();
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V1Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("Bg", new(serviceProvider, "Bg"));
|
||||
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
|
||||
downloadSummaries.TryAdd("GachaAvatarIcon", new(serviceProvider, "GachaAvatarIcon"));
|
||||
downloadSummaries.TryAdd("GachaAvatarImg", new(serviceProvider, "GachaAvatarImg"));
|
||||
downloadSummaries.TryAdd("EquipIcon", new(serviceProvider, "EquipIcon"));
|
||||
downloadSummaries.TryAdd("GachaEquipIcon", new(serviceProvider, "GachaEquipIcon"));
|
||||
downloadSummaries.TryAdd("NameCardPic", new(serviceProvider, "NameCardPic"));
|
||||
downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill"));
|
||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V2Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("AchievementIcon", new(serviceProvider, "AchievementIcon"));
|
||||
downloadSummaries.TryAdd("ItemIcon", new(serviceProvider, "ItemIcon"));
|
||||
downloadSummaries.TryAdd("IconElement", new(serviceProvider, "IconElement"));
|
||||
downloadSummaries.TryAdd("RelicIcon", new(serviceProvider, "RelicIcon"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V3Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill"));
|
||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V4Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V5Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("MonsterIcon", new(serviceProvider, "MonsterIcon"));
|
||||
}
|
||||
|
||||
return downloadSummaries.Select(x => x.Value);
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,13 @@ using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
using Snap.Hutao.Service.Game.Locator;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.Service.User;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -34,7 +33,13 @@ namespace Snap.Hutao.ViewModel;
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IGameLocatorFactory gameLocatorFactory;
|
||||
private readonly IClipboardInterop clipboardInterop;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly IPickerFactory pickerFactory;
|
||||
private readonly IUserService userService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AppOptions options;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
@@ -89,10 +94,17 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ResetStaticResourceCommand")]
|
||||
private static void ResetStaticResource()
|
||||
{
|
||||
StaticResource.FailAllContracts();
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
|
||||
[Command("SetGamePathCommand")]
|
||||
private async Task SetGamePathAsync()
|
||||
{
|
||||
IGameLocator locator = serviceProvider.GetRequiredService<IGameLocatorFactory>().Create(GameLocationSource.Manual);
|
||||
IGameLocator locator = gameLocatorFactory.Create(GameLocationSource.Manual);
|
||||
|
||||
(bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false);
|
||||
if (isOk)
|
||||
@@ -110,9 +122,8 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
if (!string.IsNullOrEmpty(gamePath))
|
||||
{
|
||||
string cacheFilePath = GachaLogQueryWebCacheProvider.GetCacheFile(gamePath);
|
||||
string cacheFolder = Path.GetDirectoryName(cacheFilePath)!;
|
||||
string? cacheFolder = Path.GetDirectoryName(cacheFilePath);
|
||||
|
||||
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
try
|
||||
@@ -129,7 +140,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(string.Format(SH.ViewModelSettingClearWebCachePathInvalid, cacheFolder));
|
||||
infoBarService.Warning(SH.ViewModelSettingClearWebCachePathInvalid.Format(cacheFolder));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,17 +150,18 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
SignInWebViewDialog dialog = serviceProvider.CreateInstance<SignInWebViewDialog>();
|
||||
await dialog.ShowAsync();
|
||||
|
||||
// TODO remove this.
|
||||
// SignInWebViewDialog dialog = serviceProvider.CreateInstance<SignInWebViewDialog>();
|
||||
// await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
[Command("UpdateCheckCommand")]
|
||||
private async Task CheckUpdateAsync()
|
||||
{
|
||||
#if DEBUG
|
||||
await serviceProvider
|
||||
.GetRequiredService<Service.Navigation.INavigationService>()
|
||||
.NavigateAsync<View.Page.TestPage>(Service.Navigation.INavigationAwaiter.Default)
|
||||
await navigationService
|
||||
.NavigateAsync<View.Page.TestPage>(INavigationAwaiter.Default)
|
||||
.ConfigureAwait(false);
|
||||
#else
|
||||
await Windows.System.Launcher.LaunchUriAsync(new(@"ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
|
||||
@@ -159,8 +171,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
[Command("SetDataFolderCommand")]
|
||||
private async Task SetDataFolderAsync()
|
||||
{
|
||||
(bool isOk, string folder) = await serviceProvider
|
||||
.GetRequiredService<IPickerFactory>()
|
||||
(bool isOk, string folder) = await pickerFactory
|
||||
.GetFolderPicker()
|
||||
.TryPickSingleFolderAsync()
|
||||
.ConfigureAwait(false);
|
||||
@@ -168,24 +179,16 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
if (isOk)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.DataFolderPath, folder);
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Success(SH.ViewModelSettingSetDataFolderSuccess);
|
||||
infoBarService.Success(SH.ViewModelSettingSetDataFolderSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ResetStaticResourceCommand")]
|
||||
private void ResetStaticResource()
|
||||
{
|
||||
StaticResource.FailAllContracts();
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
|
||||
[Command("CopyDeviceIdCommand")]
|
||||
private void CopyDeviceId()
|
||||
{
|
||||
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
try
|
||||
{
|
||||
serviceProvider.GetRequiredService<IClipboardInterop>().SetText(HutaoOptions.DeviceId);
|
||||
clipboardInterop.SetText(HutaoOptions.DeviceId);
|
||||
infoBarService.Success(SH.ViewModelSettingCopyDeviceIdSuccess);
|
||||
}
|
||||
catch (COMException ex)
|
||||
@@ -197,7 +200,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
[Command("NavigateToHutaoPassportCommand")]
|
||||
private void NavigateToHutaoPassport()
|
||||
{
|
||||
serviceProvider.GetRequiredService<INavigationService>().Navigate<View.Page.HutaoPassportPage>(INavigationAwaiter.Default);
|
||||
navigationService.Navigate<View.Page.HutaoPassportPage>(INavigationAwaiter.Default);
|
||||
}
|
||||
|
||||
[Command("OpenCacheFolderCommand")]
|
||||
@@ -215,18 +218,15 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
[Command("DeleteUsersCommand")]
|
||||
private async Task DangerousDeleteUsersAsync()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
if (userService is IUserServiceUnsafe @unsafe)
|
||||
{
|
||||
ContentDialogResult result = await scope.ServiceProvider
|
||||
.GetRequiredService<IContentDialogFactory>()
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
.CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
if (result is ContentDialogResult.Primary)
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.Users.ExecuteDeleteAsync().ConfigureAwait(false);
|
||||
|
||||
await @unsafe.UnsafeRemoveUsersAsync().ConfigureAwait(false);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ namespace Snap.Hutao.ViewModel;
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OpenUIAsync()
|
||||
{
|
||||
@@ -24,7 +21,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
}
|
||||
|
||||
[Command("RestartAppCommand")]
|
||||
private void RestartApp(bool elevated)
|
||||
private static void RestartApp(bool elevated)
|
||||
{
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 欢迎视图模型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class WelcomeViewModel : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private ObservableCollection<DownloadSummary>? downloadSummaries;
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
public ObservableCollection<DownloadSummary>? DownloadSummaries { get => downloadSummaries; set => SetProperty(ref downloadSummaries, value); }
|
||||
|
||||
[Command("OpenUICommand")]
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
IEnumerable<DownloadSummary> downloadSummaries = GenerateStaticResourceDownloadTasks();
|
||||
|
||||
DownloadSummaries = downloadSummaries.ToObservableCollection();
|
||||
|
||||
await Parallel.ForEachAsync(downloadSummaries, async (summary, token) =>
|
||||
{
|
||||
if (await summary.DownloadAndExtractAsync().ConfigureAwait(false))
|
||||
{
|
||||
taskContext.InvokeOnMainThread(() => DownloadSummaries.Remove(summary));
|
||||
}
|
||||
}).ConfigureAwait(true);
|
||||
|
||||
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
|
||||
StaticResource.FulfillAllContracts();
|
||||
|
||||
try
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText(SH.ViewModelWelcomeDownloadCompleteTitle)
|
||||
.AddText(SH.ViewModelWelcomeDownloadCompleteMessage)
|
||||
.Show();
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// 0x803E0105
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<DownloadSummary> GenerateStaticResourceDownloadTasks()
|
||||
{
|
||||
Dictionary<string, DownloadSummary> downloadSummaries = new();
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V1Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("Bg", new(serviceProvider, "Bg"));
|
||||
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
|
||||
downloadSummaries.TryAdd("GachaAvatarIcon", new(serviceProvider, "GachaAvatarIcon"));
|
||||
downloadSummaries.TryAdd("GachaAvatarImg", new(serviceProvider, "GachaAvatarImg"));
|
||||
downloadSummaries.TryAdd("EquipIcon", new(serviceProvider, "EquipIcon"));
|
||||
downloadSummaries.TryAdd("GachaEquipIcon", new(serviceProvider, "GachaEquipIcon"));
|
||||
downloadSummaries.TryAdd("NameCardPic", new(serviceProvider, "NameCardPic"));
|
||||
downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill"));
|
||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V2Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("AchievementIcon", new(serviceProvider, "AchievementIcon"));
|
||||
downloadSummaries.TryAdd("ItemIcon", new(serviceProvider, "ItemIcon"));
|
||||
downloadSummaries.TryAdd("IconElement", new(serviceProvider, "IconElement"));
|
||||
downloadSummaries.TryAdd("RelicIcon", new(serviceProvider, "RelicIcon"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V3Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("Skill", new(serviceProvider, "Skill"));
|
||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "Talent"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V4Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "AvatarIcon"));
|
||||
}
|
||||
|
||||
if (StaticResource.IsContractUnfulfilled(StaticResource.V5Contract))
|
||||
{
|
||||
downloadSummaries.TryAdd("MonsterIcon", new(serviceProvider, "MonsterIcon"));
|
||||
}
|
||||
|
||||
return downloadSummaries.Select(x => x.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
internal sealed class DownloadSummary : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly string fileName;
|
||||
private readonly string fileUrl;
|
||||
private readonly Progress<StreamCopyStatus> progress;
|
||||
private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
|
||||
private double progressValue;
|
||||
private long updateCount;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的下载信息
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="fileName">压缩文件名称</param>
|
||||
public DownloadSummary(IServiceProvider serviceProvider, string fileName)
|
||||
{
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
httpClient = serviceProvider.GetRequiredService<HttpClient>();
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
DisplayName = fileName;
|
||||
this.fileName = fileName;
|
||||
fileUrl = Web.HutaoEndpoints.StaticZip(fileName);
|
||||
|
||||
progress = new(UpdateProgressStatus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示名称
|
||||
/// </summary>
|
||||
public string DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get => description; private set => SetProperty(ref description, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 进度值,最大1
|
||||
/// </summary>
|
||||
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 异步下载并解压
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public async Task<bool> DownloadAndExtractAsync()
|
||||
{
|
||||
ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>();
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
logger.LogInformation("Begin download, length: {length}", contentLength);
|
||||
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
|
||||
ExtractFiles(temp);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ProgressValue = 1;
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Download Static Zip failed");
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Description = ex is HttpRequestException httpRequestException
|
||||
? $"{SH.ViewModelWelcomeDownloadSummaryException} - HTTP {httpRequestException.StatusCode:D}"
|
||||
: SH.ViewModelWelcomeDownloadSummaryException;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProgressStatus(StreamCopyStatus status)
|
||||
{
|
||||
if (Interlocked.Increment(ref updateCount) % 40 == 0)
|
||||
{
|
||||
Description = $"{Converters.ToFileSizeString(status.BytesCopied)}/{Converters.ToFileSizeString(status.TotalBytes)}";
|
||||
ProgressValue = status.TotalBytes == 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractFiles(Stream stream)
|
||||
{
|
||||
IImageCacheFilePathOperation imageCache = serviceProvider.GetRequiredService<IImageCache>().As<IImageCacheFilePathOperation>()!;
|
||||
|
||||
using (ZipArchive archive = new(stream))
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
string destPath = imageCache.GetFileFromCategoryAndName(fileName, entry.FullName);
|
||||
entry.ExtractToFile(destPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>当前是否上传了数据</returns>
|
||||
public async Task<Response<bool>> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
|
||||
public async ValueTask<Response<bool>> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
Response<bool>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<bool>>(HutaoEndpoints.RecordCheck(uid.Value), options, logger, token)
|
||||
@@ -52,7 +52,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>排行信息</returns>
|
||||
public async Task<Response<RankInfo>> GetRankAsync(PlayerUid uid, CancellationToken token = default)
|
||||
public async ValueTask<Response<RankInfo>> GetRankAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
Response<RankInfo>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<RankInfo>>(HutaoEndpoints.RecordRank(uid.Value), options, logger, token)
|
||||
@@ -67,7 +67,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>总览信息</returns>
|
||||
public async Task<Response<Overview>> GetOverviewAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<Overview>> GetOverviewAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<Overview>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<Overview>>(HutaoEndpoints.StatisticsOverview, options, logger, token)
|
||||
@@ -82,7 +82,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<Response<List<AvatarAppearanceRank>>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<List<AvatarAppearanceRank>>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarAppearanceRank>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarAppearanceRank>>>(HutaoEndpoints.StatisticsAvatarAttendanceRate, options, logger, token)
|
||||
@@ -97,7 +97,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色出场率</returns>
|
||||
public async Task<Response<List<AvatarUsageRank>>> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<List<AvatarUsageRank>>> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarUsageRank>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarUsageRank>>>(HutaoEndpoints.StatisticsAvatarUtilizationRate, options, logger, token)
|
||||
@@ -112,7 +112,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色/武器/圣遗物搭配</returns>
|
||||
public async Task<Response<List<AvatarCollocation>>> GetAvatarCollocationsAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<List<AvatarCollocation>>> GetAvatarCollocationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarCollocation>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarCollocation>>>(HutaoEndpoints.StatisticsAvatarAvatarCollocation, options, logger, token)
|
||||
@@ -127,7 +127,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色/武器/圣遗物搭配</returns>
|
||||
public async Task<Response<List<WeaponCollocation>>> GetWeaponCollocationsAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<List<WeaponCollocation>>> GetWeaponCollocationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<WeaponCollocation>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<WeaponCollocation>>>(HutaoEndpoints.StatisticsWeaponWeaponCollocation, options, logger, token)
|
||||
@@ -142,7 +142,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色图片列表</returns>
|
||||
public async Task<Response<List<AvatarConstellationInfo>>> GetAvatarHoldingRatesAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<List<AvatarConstellationInfo>>> GetAvatarHoldingRatesAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<AvatarConstellationInfo>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<AvatarConstellationInfo>>>(HutaoEndpoints.StatisticsAvatarHoldingRate, options, logger, token)
|
||||
@@ -157,7 +157,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>队伍出场列表</returns>
|
||||
public async Task<Response<List<TeamAppearance>>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
public async ValueTask<Response<List<TeamAppearance>>> GetTeamCombinationsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<List<TeamAppearance>>? resp = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<List<TeamAppearance>>>(HutaoEndpoints.StatisticsTeamCombination, options, logger, token)
|
||||
@@ -172,7 +172,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>玩家记录</returns>
|
||||
public async Task<SimpleRecord?> GetPlayerRecordAsync(UserAndUid userAndUid, CancellationToken token = default)
|
||||
public async ValueTask<SimpleRecord?> GetPlayerRecordAsync(UserAndUid userAndUid, CancellationToken token = default)
|
||||
{
|
||||
IGameRecordClient gameRecordClient = serviceProvider
|
||||
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
|
||||
@@ -212,7 +212,7 @@ internal sealed partial class HomaSpiralAbyssClient
|
||||
/// <param name="playerRecord">玩家记录</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
public async Task<Response<string>> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
|
||||
public async ValueTask<Response<string>> UploadRecordAsync(SimpleRecord playerRecord, CancellationToken token = default)
|
||||
{
|
||||
Response<string>? resp = await httpClient
|
||||
.TryCatchPostAsJsonAsync<SimpleRecord, Response<string>>(HutaoEndpoints.RecordUpload, playerRecord, options, logger, token)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model.Converter;
|
||||
|
||||
/// <summary>
|
||||
@@ -14,10 +16,18 @@ internal sealed class ReliquarySetsConverter : JsonConverter<ReliquarySets>
|
||||
/// <inheritdoc/>
|
||||
public override ReliquarySets? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.GetString() is string source)
|
||||
if (reader.GetString() is { } source)
|
||||
{
|
||||
string[] sets = source.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new(sets.Select(set => new ReliquarySet(set)));
|
||||
List<ReliquarySet> sets = new();
|
||||
foreach (StringSegment segment in new StringTokenizer(source, Separator.ToArray()))
|
||||
{
|
||||
if (segment.HasValue)
|
||||
{
|
||||
sets.Add(new(segment.Value));
|
||||
}
|
||||
}
|
||||
|
||||
return new(sets);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed class SimpleRank
|
||||
/// <returns>新的简单数值</returns>
|
||||
public static SimpleRank? FromRank(Rank? rank)
|
||||
{
|
||||
if (rank == null)
|
||||
if (rank is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
@@ -19,8 +20,8 @@ internal sealed class ReliquarySet
|
||||
{
|
||||
string[]? deconstructed = set.Split('-');
|
||||
|
||||
EquipAffixId = uint.Parse(deconstructed[0]);
|
||||
Count = int.Parse(deconstructed[1]);
|
||||
EquipAffixId = uint.Parse(deconstructed[0], CultureInfo.InvariantCulture);
|
||||
Count = int.Parse(deconstructed[1], CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,11 +15,11 @@ internal sealed class Team
|
||||
/// 上半
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SeparatorCommaInt32EnumerableConverter))]
|
||||
public IEnumerable<int> UpHalf { get; set; } = null!;
|
||||
public IEnumerable<int> UpHalf { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 下半
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SeparatorCommaInt32EnumerableConverter))]
|
||||
public IEnumerable<int> DownHalf { get; set; } = null!;
|
||||
public IEnumerable<int> DownHalf { get; set; } = default!;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ internal readonly struct QueryString
|
||||
{
|
||||
string name;
|
||||
string? value;
|
||||
int indexOfEquals = pair.IndexOf('=');
|
||||
int indexOfEquals = pair.IndexOf('=', StringComparison.Ordinal);
|
||||
if (indexOfEquals == -1)
|
||||
{
|
||||
name = pair;
|
||||
|
||||
@@ -31,7 +31,7 @@ internal struct QueryStringParameter
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return $"{Name}={Value}";
|
||||
}
|
||||
|
||||
@@ -80,23 +80,22 @@ internal class Response
|
||||
/// <returns>本体或默认值,当本体为 null 时 返回默认值</returns>
|
||||
public static Response<TData> DefaultIfNull<TData, TOther>(Response<TOther>? response, [CallerMemberName] string callerName = default!)
|
||||
{
|
||||
if (response != null)
|
||||
if (response is not null)
|
||||
{
|
||||
Must.Argument(response.ReturnCode != 0, "返回代码必须为0");
|
||||
|
||||
// 0x26F19335 is a magic number that hashed from "Snap.Hutao"
|
||||
return new(response.ReturnCode, response.Message, default);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(0x26F19335, $"[{callerName}] 中的 [{typeof(TData).Name}] 网络请求异常,请稍后再试", default);
|
||||
// Magic number that hashed from "Snap.Hutao"
|
||||
return new(0x26F19335, SH.WebResponseRequestExceptionFormat.Format(callerName, typeof(TData).Name), default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(SH.WebResponseFormat, ReturnCode, Message);
|
||||
return SH.WebResponseFormat.Format(ReturnCode, Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +162,8 @@ internal sealed class Response<TData> : Response, IJsResult
|
||||
{
|
||||
if (ReturnCode == 0)
|
||||
{
|
||||
data = Data!;
|
||||
ArgumentNullException.ThrowIfNull(Data);
|
||||
data = Data;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user