mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor gacha service
This commit is contained in:
@@ -5,6 +5,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// An asynchronous barrier that blocks the signaler until all other participants have signaled.
|
||||
/// FIFO
|
||||
/// </summary>
|
||||
internal class AsyncBarrier
|
||||
{
|
||||
@@ -16,7 +17,7 @@ internal class AsyncBarrier
|
||||
/// <summary>
|
||||
/// The set of participants who have reached the barrier, with their awaiters that can resume those participants.
|
||||
/// </summary>
|
||||
private readonly Stack<TaskCompletionSource> waiters;
|
||||
private readonly Queue<TaskCompletionSource> waiters;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AsyncBarrier"/> class.
|
||||
@@ -24,7 +25,7 @@ internal class AsyncBarrier
|
||||
/// <param name="participants">The number of participants.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,19 +22,9 @@ internal static class StringExtension
|
||||
return new(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除结尾可能存在的字符串
|
||||
/// </summary>
|
||||
/// <param name="source">源</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>新的字符串</returns>
|
||||
[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();
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
|
||||
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
|
||||
{
|
||||
if (items.Count == 0)
|
||||
{
|
||||
|
||||
@@ -81,7 +81,7 @@ internal interface ICultivationService
|
||||
/// <param name="itemId">主Id</param>
|
||||
/// <param name="items">待存物品</param>
|
||||
/// <returns>是否保存成功</returns>
|
||||
Task<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Item> items);
|
||||
ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Item> items);
|
||||
|
||||
/// <summary>
|
||||
/// 保存养成物品状态
|
||||
|
||||
@@ -26,11 +26,11 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
private readonly AppOptions options;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
|
||||
public async ValueTask<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
|
||||
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(g => new HistoryWishBuilder(g, context));
|
||||
List<HistoryWishBuilder> 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
|
||||
public async ValueTask<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
|
||||
@@ -18,5 +18,5 @@ internal interface IGachaStatisticsFactory
|
||||
/// <param name="items">物品列表</param>
|
||||
/// <param name="context">祈愿记录上下文</param>
|
||||
/// <returns>祈愿统计对象</returns>
|
||||
Task<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
|
||||
ValueTask<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
|
||||
}
|
||||
@@ -17,5 +17,5 @@ internal interface IGachaStatisticsSlimFactory
|
||||
/// <param name="items">排序的物品</param>
|
||||
/// <param name="context">祈愿记录服务上下文</param>
|
||||
/// <returns>简化的祈愿统计</returns>
|
||||
Task<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
|
||||
ValueTask<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
|
||||
}
|
||||
@@ -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<HomaGachaLogClient>();
|
||||
@@ -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;
|
||||
|
||||
@@ -54,15 +54,6 @@ internal sealed class TypedWishSummaryBuilder
|
||||
private DateTimeOffset fromTimeTracker = DateTimeOffset.MaxValue;
|
||||
private DateTimeOffset toTimeTracker = DateTimeOffset.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的类型化祈愿统计信息构建器
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="name">祈愿配置</param>
|
||||
/// <param name="typeEvaluator">祈愿类型判断器</param>
|
||||
/// <param name="distributionType">分布类型</param>
|
||||
/// <param name="guaranteeOrangeThreshold">五星保底</param>
|
||||
/// <param name="guaranteePurpleThreshold">四星保底</param>
|
||||
public TypedWishSummaryBuilder(
|
||||
IServiceProvider serviceProvider,
|
||||
string name,
|
||||
@@ -140,7 +131,7 @@ internal sealed class TypedWishSummaryBuilder
|
||||
/// 转换到类型化祈愿统计信息
|
||||
/// </summary>
|
||||
/// <returns>类型化祈愿统计信息</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// 初始化存档集合
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="collection">集合</param>
|
||||
public static void Initialize(AppDbContext appDbContext, out ObservableCollection<GachaArchive> collection)
|
||||
public static void Initialize(IServiceProvider serviceProvider, out ObservableCollection<GachaArchive> collection)
|
||||
{
|
||||
try
|
||||
{
|
||||
collection = appDbContext.GachaArchives.ToObservableCollection();
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
collection = appDbContext.GachaArchives.AsNoTracking().ToObservableCollection();
|
||||
}
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ObservableCollection<GachaArchive> ArchiveCollection
|
||||
public ObservableCollection<GachaArchive>? ArchiveCollection
|
||||
{
|
||||
get => context.ArchiveCollection;
|
||||
}
|
||||
@@ -67,14 +67,10 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
Dictionary<string, Model.Metadata.Avatar.Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<string, Model.Metadata.Weapon.Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
GachaArchives.Initialize(appDbContext, out ObservableCollection<GachaArchive> collection);
|
||||
GachaArchives.Initialize(serviceProvider, out ObservableCollection<GachaArchive> 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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GachaStatistics> GetStatisticsAsync(GachaArchive? archive)
|
||||
public async ValueTask<GachaStatistics> 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<AppDbContext>();
|
||||
IOrderedQueryable<GachaItem> items = appDbContext.GachaItems
|
||||
.Where(i => i.ArchiveId == archive.InnerId)
|
||||
.OrderBy(i => i.Id);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
IOrderedQueryable<GachaItem> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
/// <returns>物品 Id</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ internal interface IGachaLogService
|
||||
/// <summary>
|
||||
/// 获取可用于绑定的存档集合
|
||||
/// </summary>
|
||||
ObservableCollection<GachaArchive> ArchiveCollection { get; }
|
||||
ObservableCollection<GachaArchive>? ArchiveCollection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 导出为一个新的UIGF对象
|
||||
@@ -37,7 +37,7 @@ internal interface IGachaLogService
|
||||
/// </summary>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <returns>祈愿统计</returns>
|
||||
Task<GachaStatistics> GetStatisticsAsync(GachaArchive? archive);
|
||||
ValueTask<GachaStatistics> GetStatisticsAsync(GachaArchive? archive);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取简化的祈愿统计列表
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录query
|
||||
/// 祈愿记录 query
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal readonly struct GachaLogQuery
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))]
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryProvider
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录Url提供器拓展
|
||||
/// </summary>
|
||||
internal static class GachaLogQueryProviderExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 选出对应的祈愿 Url 提供器
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="option">刷新选项</param>
|
||||
/// <returns>对应的祈愿 Url 提供器</returns>
|
||||
public static IGachaLogQueryProvider? PickProvider(this IServiceProvider serviceProvider, RefreshOption option)
|
||||
{
|
||||
IEnumerable<IGachaLogQueryProvider> providers = serviceProvider.GetServices<IGachaLogQueryProvider>();
|
||||
|
||||
string? name = option switch
|
||||
{
|
||||
RefreshOption.WebCache => nameof(GachaLogQueryWebCacheProvider),
|
||||
RefreshOption.SToken => nameof(GachaLogQuerySTokenProvider),
|
||||
RefreshOption.ManualInput => nameof(GachaLogQueryManualInputProvider),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return providers.SingleOrDefault(p => p.Name == name);
|
||||
}
|
||||
}
|
||||
@@ -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<GachaLogQuerySTokenProvider>(),
|
||||
RefreshOption.WebCache => serviceProvider.GetRequiredService<GachaLogQueryWebCacheProvider>(),
|
||||
RefreshOption.ManualInput => serviceProvider.GetRequiredService<GachaLogQueryManualInputProvider>(),
|
||||
_ => throw Must.NeverHappen("不支持的刷新选项"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))]
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvider
|
||||
{
|
||||
private readonly BindingClient2 bindingClient2;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
/// </summary>
|
||||
[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");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<IGachaLogQueryProviderFactory>().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<GachaLogRefreshProgressDialog>();
|
||||
IDisposable dialogHider = await dialog.BlockAsync(taskContext).ConfigureAwait(false);
|
||||
Progress<GachaLogFetchStatus> 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<GachaLogRefreshProgressDialog>();
|
||||
IDisposable dialogHider = await dialog.BlockAsync(taskContext).ConfigureAwait(false);
|
||||
Progress<GachaLogFetchStatus> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ internal readonly struct QueryString
|
||||
}
|
||||
}
|
||||
|
||||
private static QueryString Parse(ReadOnlySpan<char> value)
|
||||
{
|
||||
// TODO: .NET 8 ReadOnlySpan Split
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a query string into a <see cref="QueryString"/> object. Keys/values are automatically URL decoded.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user