From 53044b0dda6a7469ff5087b95a13b792cd367976 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Thu, 27 Jul 2023 17:23:28 +0800 Subject: [PATCH] refactor gacha service --- .../Snap.Hutao/Core/Threading/AsyncBarrier.cs | 9 +- .../Snap.Hutao/Extension/StringExtension.cs | 14 +-- .../Service/Cultivation/CultivationService.cs | 2 +- .../Cultivation/ICultivationService.cs | 2 +- .../Factory/GachaStatisticsFactory.cs | 12 ++- .../Factory/GachaStatisticsSlimFactory.cs | 2 +- .../Factory/IGachaStatisticsFactory.cs | 2 +- .../Factory/IGachaStatisticsSlimFactory.cs | 2 +- .../GachaLog/Factory/PullPrediction.cs | 4 +- .../Factory/TypedWishSummaryBuilder.cs | 13 +-- .../Service/GachaLog/GachaArchives.cs | 11 ++- .../Service/GachaLog/GachaLogService.cs | 47 +++++----- .../GachaLog/GachaLogServiceContext.cs | 29 ++++-- .../Service/GachaLog/IGachaLogService.cs | 4 +- .../GachaLog/QueryProvider/GachaLogQuery.cs | 2 +- .../GachaLogQueryManualInputProvider.cs | 2 +- .../GachaLogQueryProviderExtension.cs | 31 ------- .../GachaLogQueryProviderFactory.cs | 22 +++++ .../GachaLogQuerySTokenProvider.cs | 2 +- .../GachaLogQueryWebCacheProvider.cs | 16 +++- .../IGachaLogQueryProviderFactory.cs | 9 ++ .../ViewModel/GachaLog/GachaLogViewModel.cs | 91 +++++++++---------- .../Web/Request/QueryString/QueryString.cs | 6 ++ 23 files changed, 173 insertions(+), 161 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderFactory.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProviderFactory.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs index 13dfab76..f05dab8d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs @@ -5,6 +5,7 @@ namespace Snap.Hutao.Core.Threading; /// /// An asynchronous barrier that blocks the signaler until all other participants have signaled. +/// FIFO /// internal class AsyncBarrier { @@ -16,7 +17,7 @@ internal class AsyncBarrier /// /// The set of participants who have reached the barrier, with their awaiters that can resume those participants. /// - private readonly Stack waiters; + private readonly Queue waiters; /// /// Initializes a new instance of the class. @@ -24,7 +25,7 @@ internal class AsyncBarrier /// The number of participants. public AsyncBarrier(int participants) { - Requires.Range(participants > 0, nameof(participants)); + Must.Range(participants >= 1, "Participants of AsyncBarrier can not be less than 1"); participantCount = participants; // Allocate the stack so no resizing is necessary. @@ -47,7 +48,7 @@ internal class AsyncBarrier // Unleash everyone that preceded this one. while (waiters.Count > 0) { - _ = Task.Factory.StartNew(state => ((TaskCompletionSource)state!).SetResult(), waiters.Pop(), default, TaskCreationOptions.None, TaskScheduler.Default); + _ = Task.Factory.StartNew(state => ((TaskCompletionSource)state!).SetResult(), waiters.Dequeue(), default, TaskCreationOptions.None, TaskScheduler.Default); } // And allow this one to continue immediately. @@ -57,7 +58,7 @@ internal class AsyncBarrier { // We need more folks. So suspend this caller. TaskCompletionSource tcs = new(); - waiters.Push(tcs); + waiters.Enqueue(tcs); return tcs.Task; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs index a972547a..02d523aa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/StringExtension.cs @@ -22,19 +22,9 @@ internal static class StringExtension return new(value); } - /// - /// 移除结尾可能存在的字符串 - /// - /// 源 - /// 值 - /// 新的字符串 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string TrimEnd(this string source, string value) { - while (source.EndsWith(value)) - { - source = source[..^value.Length]; - } - - return source; + return source.AsSpan().TrimEnd(value).ToString(); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index 78ec27cf..b72833b8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -159,7 +159,7 @@ internal sealed partial class CultivationService : ICultivationService } /// - public async Task SaveConsumptionAsync(CultivateType type, uint itemId, List items) + public async ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items) { if (items.Count == 0) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs index 9b212bd1..2859254a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -81,7 +81,7 @@ internal interface ICultivationService /// 主Id /// 待存物品 /// 是否保存成功 - Task SaveConsumptionAsync(CultivateType type, uint itemId, List items); + ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items); /// /// 保存养成物品状态 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs index c5e8d203..064080db 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -26,11 +26,11 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory private readonly AppOptions options; /// - public async Task CreateAsync(IOrderedQueryable items, GachaLogServiceContext context) + public async ValueTask CreateAsync(IOrderedQueryable items, GachaLogServiceContext context) { await taskContext.SwitchToBackgroundAsync(); List gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false); - List historyWishBuilders = gachaEvents.SelectList(g => new HistoryWishBuilder(g, context)); + List historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context)); return CreateCore(serviceProvider, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible); } @@ -137,6 +137,8 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory } } + AsyncBarrier barrier = new(3); + return new() { // history @@ -157,9 +159,9 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory BlueWeapons = blueWeaponCounter.ToStatisticsList(), // typed wish summary - StandardWish = standardWishBuilder.ToTypedWishSummary(), - AvatarWish = avatarWishBuilder.ToTypedWishSummary(), - WeaponWish = weaponWishBuilder.ToTypedWishSummary(), + StandardWish = standardWishBuilder.ToTypedWishSummary(barrier), + AvatarWish = avatarWishBuilder.ToTypedWishSummary(barrier), + WeaponWish = weaponWishBuilder.ToTypedWishSummary(barrier), }; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsSlimFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsSlimFactory.cs index cd537599..b87fd8e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsSlimFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsSlimFactory.cs @@ -19,7 +19,7 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF private readonly ITaskContext taskContext; /// - public async Task CreateAsync(IOrderedQueryable items, GachaLogServiceContext context) + public async ValueTask CreateAsync(IOrderedQueryable items, GachaLogServiceContext context) { await taskContext.SwitchToBackgroundAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsFactory.cs index 0d100e87..1f1c7dbb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsFactory.cs @@ -18,5 +18,5 @@ internal interface IGachaStatisticsFactory /// 物品列表 /// 祈愿记录上下文 /// 祈愿统计对象 - Task CreateAsync(IOrderedQueryable items, GachaLogServiceContext context); + ValueTask CreateAsync(IOrderedQueryable items, GachaLogServiceContext context); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsSlimFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsSlimFactory.cs index c7e8df33..7ffad2be 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsSlimFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/IGachaStatisticsSlimFactory.cs @@ -17,5 +17,5 @@ internal interface IGachaStatisticsSlimFactory /// 排序的物品 /// 祈愿记录服务上下文 /// 简化的祈愿统计 - Task CreateAsync(IOrderedQueryable items, GachaLogServiceContext context); + ValueTask CreateAsync(IOrderedQueryable items, GachaLogServiceContext context); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/PullPrediction.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/PullPrediction.cs index bca1b2ca..1b06a2c8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/PullPrediction.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/PullPrediction.cs @@ -34,7 +34,7 @@ internal sealed class PullPrediction this.distributionType = distributionType; } - public async Task PredictAsync() + public async Task PredictAsync(AsyncBarrier barrier) { await taskContext.SwitchToBackgroundAsync(); HomaGachaLogClient gachaLogClient = serviceProvider.GetRequiredService(); @@ -43,9 +43,9 @@ internal sealed class PullPrediction if (response.IsOk()) { PredictResult result = PredictCore(response.Data.Distribution, typedWishSummary); + await barrier.SignalAndWaitAsync().ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync(); - typedWishSummary.ProbabilityOfNextPullIsOrange = result.ProbabilityOfNextPullIsOrange; typedWishSummary.ProbabilityOfPredictedPullLeftToOrange = result.ProbabilityOfPredictedPullLeftToOrange; typedWishSummary.PredictedPullLeftToOrange = result.PredictedPullLeftToOrange; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs index 051271bf..d028e159 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs @@ -54,15 +54,6 @@ internal sealed class TypedWishSummaryBuilder private DateTimeOffset fromTimeTracker = DateTimeOffset.MaxValue; private DateTimeOffset toTimeTracker = DateTimeOffset.MinValue; - /// - /// 构造一个新的类型化祈愿统计信息构建器 - /// - /// 服务提供器 - /// 祈愿配置 - /// 祈愿类型判断器 - /// 分布类型 - /// 五星保底 - /// 四星保底 public TypedWishSummaryBuilder( IServiceProvider serviceProvider, string name, @@ -140,7 +131,7 @@ internal sealed class TypedWishSummaryBuilder /// 转换到类型化祈愿统计信息 /// /// 类型化祈愿统计信息 - public TypedWishSummary ToTypedWishSummary() + public TypedWishSummary ToTypedWishSummary(AsyncBarrier barrier) { summaryItems.CompleteAdding(guaranteeOrangeThreshold); double totalCount = totalCountTracker; @@ -172,7 +163,7 @@ internal sealed class TypedWishSummaryBuilder }; // TODO: barrier all predictions. - new PullPrediction(serviceProvider, summary, distributionType).PredictAsync().SafeForget(); + new PullPrediction(serviceProvider, summary, distributionType).PredictAsync(barrier).SafeForget(); return summary; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchives.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchives.cs index 7958da53..930418b4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchives.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaArchives.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; @@ -17,13 +18,17 @@ internal static class GachaArchives /// /// 初始化存档集合 /// - /// 数据库上下文 + /// 服务提供器 /// 集合 - public static void Initialize(AppDbContext appDbContext, out ObservableCollection collection) + public static void Initialize(IServiceProvider serviceProvider, out ObservableCollection collection) { try { - collection = appDbContext.GachaArchives.ToObservableCollection(); + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + collection = appDbContext.GachaArchives.AsNoTracking().ToObservableCollection(); + } } catch (SqliteException ex) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index e79abc52..48e94181 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -46,7 +46,7 @@ internal sealed partial class GachaLogService : IGachaLogService } /// - public ObservableCollection ArchiveCollection + public ObservableCollection? ArchiveCollection { get => context.ArchiveCollection; } @@ -67,14 +67,10 @@ internal sealed partial class GachaLogService : IGachaLogService Dictionary nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false); Dictionary nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false); - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - GachaArchives.Initialize(appDbContext, out ObservableCollection collection); + GachaArchives.Initialize(serviceProvider, out ObservableCollection collection); - context = new(idAvatarMap, idWeaponMap, nameAvatarMap, nameWeaponMap, collection); - return true; - } + context = new(idAvatarMap, idWeaponMap, nameAvatarMap, nameWeaponMap, collection); + return true; } else { @@ -83,30 +79,24 @@ internal sealed partial class GachaLogService : IGachaLogService } /// - public async Task GetStatisticsAsync(GachaArchive? archive) + public async ValueTask GetStatisticsAsync(GachaArchive? archive) { archive ??= CurrentArchive; + ArgumentNullException.ThrowIfNull(archive); // Return statistics - if (archive != null) + using (ValueStopwatch.MeasureExecution(logger)) { - using (ValueStopwatch.MeasureExecution(logger)) + using (IServiceScope scope = serviceProvider.CreateScope()) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - IOrderedQueryable items = appDbContext.GachaItems - .Where(i => i.ArchiveId == archive.InnerId) - .OrderBy(i => i.Id); + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + IOrderedQueryable items = appDbContext.GachaItems + .Where(i => i.ArchiveId == archive.InnerId) + .OrderBy(i => i.Id); - return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false); - } + return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false); } } - else - { - throw Must.NeverHappen(); - } } /// @@ -250,4 +240,15 @@ internal sealed partial class GachaLogService : IGachaLogService return new(!fetchContext.FetchStatus.AuthKeyTimeout, fetchContext.TargetArchive); } +} + +[ConstructorGenerated] +[Injection(InjectAs.Scoped, typeof(IGachaLogDbService))] +internal sealed partial class GachaLogDbService : IGachaLogDbService +{ + +} + +internal interface IGachaLogDbService +{ } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs index 02980689..363e3a4b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs @@ -86,13 +86,17 @@ internal readonly struct GachaLogServiceContext { if (!ItemCache.TryGetValue(name, out Item? result)) { - result = type switch + if (type == SH.ModelInterchangeUIGFItemTypeAvatar) { - "角色" => NameAvatarMap[name].ToItem(), - "武器" => NameWeaponMap[name].ToItem(), - _ => throw Must.NeverHappen(), - }; + result = NameAvatarMap[name].ToItem(); + } + if (type == SH.ModelInterchangeUIGFItemTypeWeapon) + { + result = NameWeaponMap[name].ToItem(); + } + + ArgumentNullException.ThrowIfNull(result); ItemCache[name] = result; } @@ -123,11 +127,16 @@ internal readonly struct GachaLogServiceContext /// 物品 Id public uint GetItemId(GachaLogItem item) { - return item.ItemType switch + if (item.ItemType == SH.ModelInterchangeUIGFItemTypeAvatar) { - "角色" => NameAvatarMap!.GetValueOrDefault(item.Name)?.Id ?? 0, - "武器" => NameWeaponMap!.GetValueOrDefault(item.Name)?.Id ?? 0, - _ => 0U, - }; + return NameAvatarMap!.GetValueOrDefault(item.Name)?.Id ?? 0; + } + + if (item.ItemType == SH.ModelInterchangeUIGFItemTypeWeapon) + { + return NameWeaponMap!.GetValueOrDefault(item.Name)?.Id ?? 0; + } + + return 0U; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs index 23b6961d..38ad34fe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs @@ -23,7 +23,7 @@ internal interface IGachaLogService /// /// 获取可用于绑定的存档集合 /// - ObservableCollection ArchiveCollection { get; } + ObservableCollection? ArchiveCollection { get; } /// /// 导出为一个新的UIGF对象 @@ -37,7 +37,7 @@ internal interface IGachaLogService /// /// 存档 /// 祈愿统计 - Task GetStatisticsAsync(GachaArchive? archive); + ValueTask GetStatisticsAsync(GachaArchive? archive); /// /// 异步获取简化的祈愿统计列表 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs index 0808c2f6..95c6347c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs @@ -4,7 +4,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// -/// 祈愿记录query +/// 祈愿记录 query /// [HighQuality] internal readonly struct GachaLogQuery diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs index bba11860..064e52ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryManualInputProvider.cs @@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))] +[Injection(InjectAs.Transient)] internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryProvider { private readonly IServiceProvider serviceProvider; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderExtension.cs deleted file mode 100644 index 57642db0..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderExtension.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Service.GachaLog.QueryProvider; - -/// -/// 祈愿记录Url提供器拓展 -/// -internal static class GachaLogQueryProviderExtension -{ - /// - /// 选出对应的祈愿 Url 提供器 - /// - /// 服务提供器 - /// 刷新选项 - /// 对应的祈愿 Url 提供器 - public static IGachaLogQueryProvider? PickProvider(this IServiceProvider serviceProvider, RefreshOption option) - { - IEnumerable providers = serviceProvider.GetServices(); - - string? name = option switch - { - RefreshOption.WebCache => nameof(GachaLogQueryWebCacheProvider), - RefreshOption.SToken => nameof(GachaLogQuerySTokenProvider), - RefreshOption.ManualInput => nameof(GachaLogQueryManualInputProvider), - _ => null, - }; - - return providers.SingleOrDefault(p => p.Name == name); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderFactory.cs new file mode 100644 index 00000000..e26a43b4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryProviderFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.GachaLog.QueryProvider; + +[ConstructorGenerated] +[Injection(InjectAs.Transient, typeof(IGachaLogQueryProviderFactory))] +internal sealed partial class GachaLogQueryProviderFactory : IGachaLogQueryProviderFactory +{ + private readonly IServiceProvider serviceProvider; + + public IGachaLogQueryProvider Create(RefreshOption option) + { + return option switch + { + RefreshOption.SToken => serviceProvider.GetRequiredService(), + RefreshOption.WebCache => serviceProvider.GetRequiredService(), + RefreshOption.ManualInput => serviceProvider.GetRequiredService(), + _ => throw Must.NeverHappen("不支持的刷新选项"), + }; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs index 11ec1b07..482f09c0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuerySTokenProvider.cs @@ -15,7 +15,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))] +[Injection(InjectAs.Transient)] internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvider { private readonly BindingClient2 bindingClient2; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs index 832e066c..f8464653 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs @@ -17,7 +17,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))] +[Injection(InjectAs.Transient)] internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProvider { private readonly IGameService gameService; @@ -38,8 +38,15 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv ? GameConstants.GenshinImpactData : GameConstants.YuanShenData; - // TODO: make sure how the cache file located. - return Path.Combine(Path.GetDirectoryName(path)!, dataFolder, @"webCaches\2.13.0.1\Cache\Cache_Data\data_2"); + DirectoryInfo webCacheFolder = new(Path.Combine(Path.GetDirectoryName(path)!, dataFolder, "webCaches")); + Regex versionRegex = VersionRegex(); + DirectoryInfo? lastestVersionCacheFolder = webCacheFolder + .EnumerateDirectories() + .Where(dir => versionRegex.IsMatch(dir.Name)) + .MaxBy(dir => new Version(dir.Name)); + + lastestVersionCacheFolder ??= webCacheFolder; + return Path.Combine(lastestVersionCacheFolder.FullName, @"Cache\Cache_Data\data_2"); } /// @@ -112,4 +119,7 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv return null; } + + [GeneratedRegex("^[1-9]+?\\.[0-9]+?\\.[0-9]+?\\.[0-9]+?$")] + private static partial Regex VersionRegex(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProviderFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProviderFactory.cs new file mode 100644 index 00000000..93aa61e6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProviderFactory.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.GachaLog.QueryProvider; + +internal interface IGachaLogQueryProviderFactory +{ + IGachaLogQueryProvider Create(RefreshOption option); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs index 72d45ed6..5431503d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs @@ -146,65 +146,62 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel private async Task RefreshInternalAsync(RefreshOption option) { - IGachaLogQueryProvider? provider = serviceProvider.PickProvider(option); + IGachaLogQueryProvider provider = serviceProvider.GetRequiredService().Create(option); - if (provider != null) + (bool isOk, GachaLogQuery query) = await provider.GetQueryAsync().ConfigureAwait(false); + + if (isOk) { - (bool isOk, GachaLogQuery query) = await provider.GetQueryAsync().ConfigureAwait(false); + RefreshStrategy strategy = IsAggressiveRefresh ? RefreshStrategy.AggressiveMerge : RefreshStrategy.LazyMerge; - if (isOk) + // ContentDialog must be created by main thread. + await taskContext.SwitchToMainThreadAsync(); + + GachaLogRefreshProgressDialog dialog = serviceProvider.CreateInstance(); + IDisposable dialogHider = await dialog.BlockAsync(taskContext).ConfigureAwait(false); + Progress progress = new(dialog.OnReport); + bool authkeyValid; + + try { - RefreshStrategy strategy = IsAggressiveRefresh ? RefreshStrategy.AggressiveMerge : RefreshStrategy.LazyMerge; - - // ContentDialog must be created by main thread. - await taskContext.SwitchToMainThreadAsync(); - - GachaLogRefreshProgressDialog dialog = serviceProvider.CreateInstance(); - IDisposable dialogHider = await dialog.BlockAsync(taskContext).ConfigureAwait(false); - Progress progress = new(dialog.OnReport); - bool authkeyValid; - - try + using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) { - using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) + try { - try - { - authkeyValid = await gachaLogService.RefreshGachaLogAsync(query, strategy, progress, CancellationToken).ConfigureAwait(false); - } - catch (UserdataCorruptedException ex) - { - authkeyValid = false; - infoBarService.Error(ex); - } + authkeyValid = await gachaLogService.RefreshGachaLogAsync(query, strategy, progress, CancellationToken).ConfigureAwait(false); + } + catch (UserdataCorruptedException ex) + { + authkeyValid = false; + infoBarService.Error(ex); } } - catch (OperationCanceledException) - { - // We set true here in order to hide the dialog. - authkeyValid = true; - infoBarService.Warning(SH.ViewModelGachaLogRefreshOperationCancel); - } + } + catch (OperationCanceledException) + { + // We set true here in order to hide the dialog. + authkeyValid = true; + infoBarService.Warning(SH.ViewModelGachaLogRefreshOperationCancel); + } - await taskContext.SwitchToMainThreadAsync(); - if (authkeyValid) - { - SetSelectedArchiveAndUpdateStatistics(gachaLogService.CurrentArchive, true); - dialogHider.Dispose(); - } - else - { - dialog.Title = SH.ViewModelGachaLogRefreshFail; - dialog.PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText; - dialog.DefaultButton = ContentDialogButton.Primary; - } + await taskContext.SwitchToMainThreadAsync(); + if (authkeyValid) + { + SetSelectedArchiveAndUpdateStatistics(gachaLogService.CurrentArchive, true); + dialogHider.Dispose(); } else { - if (!string.IsNullOrEmpty(query.Message)) - { - infoBarService.Warning(query.Message); - } + dialog.Title = SH.ViewModelGachaLogRefreshFail; + dialog.PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText; + dialog.DefaultButton = ContentDialogButton.Primary; + } + } + else + { + if (!string.IsNullOrEmpty(query.Message)) + { + infoBarService.Warning(query.Message); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs index c076cb0f..06e4e18f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/QueryString/QueryString.cs @@ -38,6 +38,12 @@ internal readonly struct QueryString } } + private static QueryString Parse(ReadOnlySpan value) + { + // TODO: .NET 8 ReadOnlySpan Split + return default; + } + /// /// Parses a query string into a object. Keys/values are automatically URL decoded. ///