mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix #819
This commit is contained in:
@@ -16,7 +16,7 @@ namespace Snap.Hutao.SourceGeneration.Automation;
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
internal sealed class CommandGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeName = "Snap.Hutao.Core.Annotation.CommandAttribute";
|
||||
public const string AttributeName = "Snap.Hutao.Core.Annotation.CommandAttribute";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,12 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor readOnlyStructRefDescriptor = new("SH002", "ReadOnly struct should be passed with ref-like key word", "ReadOnly Struct [{0}] should be passed with ref-like key word", "Quality", DiagnosticSeverity.Info, true);
|
||||
private static readonly DiagnosticDescriptor useValueTaskIfPossibleDescriptor = new("SH003", "Use ValueTask instead of Task whenever possible", "Use ValueTask instead of Task", "Quality", DiagnosticSeverity.Info, true);
|
||||
|
||||
private static readonly ImmutableHashSet<string> RefLikeKeySkipTypes = new HashSet<string>()
|
||||
{
|
||||
"System.Threading.CancellationToken",
|
||||
"System.Guid"
|
||||
}.ToImmutableHashSet();
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
|
||||
{
|
||||
get
|
||||
@@ -51,7 +57,6 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
};
|
||||
|
||||
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
|
||||
|
||||
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
|
||||
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
|
||||
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
|
||||
@@ -102,6 +107,12 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
// ICommand can only use Task or Task<T>
|
||||
if (methodSymbol.GetAttributes().Any(attr=>attr.AttributeClass!.ToDisplayString() == Automation.CommandGenerator.AttributeName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task"))
|
||||
{
|
||||
Location location = methodSyntax.ReturnType.GetLocation();
|
||||
@@ -113,7 +124,15 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
private static void HandleMethodParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node;
|
||||
|
||||
// 跳过方法定义 如 接口
|
||||
if (methodSyntax.Body == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!;
|
||||
|
||||
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task"))
|
||||
{
|
||||
return;
|
||||
@@ -124,22 +143,19 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳过异步方法,因为异步方法无法使用 ref in out
|
||||
if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.AsyncKeyword)))
|
||||
foreach (SyntaxToken token in methodSyntax.Modifiers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 跳过异步方法,因为异步方法无法使用 ref/in/out
|
||||
if (token.IsKind(SyntaxKind.AsyncKeyword))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳过重载方法
|
||||
if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.OverrideKeyword)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳过方法定义 如 接口
|
||||
if (methodSyntax.Body == null)
|
||||
{
|
||||
return;
|
||||
// 跳过重载方法
|
||||
if (token.IsKind(SyntaxKind.OverrideKeyword))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ParameterSyntax parameter in methodSyntax.ParameterList.Parameters)
|
||||
@@ -151,8 +167,7 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳过 CancellationToken
|
||||
if (symbol.Type.ToDisplayString() == "System.Threading.CancellationToken")
|
||||
if (RefLikeKeySkipTypes.Contains(symbol.Type.ToDisplayString()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ internal static class ThrowHelper
|
||||
/// <exception cref="OperationCanceledException">操作取消异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner)
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
|
||||
{
|
||||
throw new OperationCanceledException(message, inner);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ internal static class ThrowHelper
|
||||
/// <exception cref="InvalidOperationException">无效操作异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static InvalidOperationException InvalidOperation(string message, Exception? inner)
|
||||
public static InvalidOperationException InvalidOperation(string message, Exception? inner = default)
|
||||
{
|
||||
throw new InvalidOperationException(message, inner);
|
||||
}
|
||||
|
||||
20
src/Snap.Hutao/Snap.Hutao/Core/IO/DirectoryOperation.cs
Normal file
20
src/Snap.Hutao/Snap.Hutao/Core/IO/DirectoryOperation.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
internal static class DirectoryOperation
|
||||
{
|
||||
public static bool Move(string sourceDirName, string destDirName)
|
||||
{
|
||||
if (!Directory.Exists(sourceDirName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Directory.Move(sourceDirName, destDirName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,15 @@ internal readonly struct Delay
|
||||
/// <param name="minMilliSeconds">最小,闭</param>
|
||||
/// <param name="maxMilliSeconds">最小,开</param>
|
||||
/// <returns>任务</returns>
|
||||
public static ValueTask RandomAsync(int minMilliSeconds, int maxMilliSeconds)
|
||||
[SuppressMessage("", "VSTHRD200")]
|
||||
public static ValueTask Random(int minMilliSeconds, int maxMilliSeconds)
|
||||
{
|
||||
return Task.Delay((int)(Random.Shared.NextDouble() * (maxMilliSeconds - minMilliSeconds)) + minMilliSeconds).AsValueTask();
|
||||
return Task.Delay((int)(System.Random.Shared.NextDouble() * (maxMilliSeconds - minMilliSeconds)) + minMilliSeconds).AsValueTask();
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD200")]
|
||||
public static ValueTask FromSeconds(int seconds)
|
||||
{
|
||||
return Task.Delay(TimeSpan.FromSeconds(seconds)).AsValueTask();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 元素类型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1602")]
|
||||
internal enum ElementType
|
||||
{
|
||||
None = 0, // 无元素
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 生长曲线
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1602")]
|
||||
internal enum GrowCurveType
|
||||
{
|
||||
GROW_CURVE_NONE = 0,
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// <summary>
|
||||
/// 材料类型
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1602")]
|
||||
internal enum MaterialType
|
||||
{
|
||||
MATERIAL_NONE = 0,
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// 怪物种类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1602")]
|
||||
internal enum MonsterType
|
||||
{
|
||||
MONSTER_NONE = 0,
|
||||
|
||||
@@ -13,9 +13,4 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||
/// </summary>
|
||||
internal sealed class AvatarBaseValue : BaseValue
|
||||
{
|
||||
public PropertyCurveValue GetPropertyCurveValue(FightProperty fightProperty)
|
||||
{
|
||||
// TODO: impl
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ namespace Snap.Hutao.Model.Metadata;
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
internal static class AvatarIds
|
||||
{
|
||||
// 此处的变量名称以 UI_AvatarIcon 为准
|
||||
|
||||
@@ -1410,6 +1410,15 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 UIGF 文件的语言:{0} 与胡桃的语言:{1} 不匹配,请切换到对应语言重试 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGachaUIGFImportLanguageNotMatch {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceGachaUIGFImportLanguageNotMatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 存在多个匹配账号,请删除重复的账号 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -2075,4 +2075,7 @@
|
||||
<data name="CoreExceptionServiceDatabaseCorruptedMessage" xml:space="preserve">
|
||||
<value>数据库已损坏:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGachaUIGFImportLanguageNotMatch" xml:space="preserve">
|
||||
<value>UIGF 文件的语言:{0} 与胡桃的语言:{1} 不匹配,请切换到对应语言重试</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -28,7 +28,6 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
/// <param name="items">待合并的项</param>
|
||||
/// <param name="aggressive">是否贪婪</param>
|
||||
/// <returns>导入结果</returns>
|
||||
[SuppressMessage("", "SH002")]
|
||||
public ImportResult Merge(Guid archiveId, IEnumerable<UIAFItem> items, bool aggressive)
|
||||
{
|
||||
logger.LogInformation("Perform {Method} Operation for archive: {Id}, Aggressive: {Aggressive}", nameof(Merge), archiveId, aggressive);
|
||||
@@ -119,7 +118,6 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
/// <param name="archiveId">成就id</param>
|
||||
/// <param name="items">待覆盖的项</param>
|
||||
/// <returns>导入结果</returns>
|
||||
[SuppressMessage("", "SH002")]
|
||||
public ImportResult Overwrite(Guid archiveId, IEnumerable<EntityAchievement> items)
|
||||
{
|
||||
logger.LogInformation("Perform {Method} Operation for archive: {Id}", nameof(Overwrite), archiveId);
|
||||
|
||||
@@ -15,39 +15,34 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
internal sealed partial class AchievementStatisticsService : IAchievementStatisticsService
|
||||
{
|
||||
private readonly IAchievementDbService achievementDbService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap)
|
||||
public async ValueTask<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
|
||||
List<AchievementStatistics> results = new();
|
||||
foreach (AchievementArchive archive in achievementDbService.GetAchievementArchiveList())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
int finishedCount = await achievementDbService
|
||||
.GetFinishedAchievementCountByArchiveIdAsync(archive.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
List<AchievementStatistics> results = new();
|
||||
foreach (AchievementArchive archive in achievementDbService.GetAchievementArchiveList())
|
||||
int totalCount = achievementMap.Count;
|
||||
|
||||
List<EntityAchievement> achievements = await achievementDbService
|
||||
.GetLatestFinishedAchievementListByArchiveIdAsync(archive.InnerId, 2)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
results.Add(new()
|
||||
{
|
||||
int finishedCount = await achievementDbService
|
||||
.GetFinishedAchievementCountByArchiveIdAsync(archive.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
int totalCount = achievementMap.Count;
|
||||
|
||||
List<EntityAchievement> achievements = await achievementDbService
|
||||
.GetLatestFinishedAchievementListByArchiveIdAsync(archive.InnerId, 2)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
results.Add(new()
|
||||
{
|
||||
DisplayName = archive.Name,
|
||||
FinishDescription = AchievementStatistics.Format(finishedCount, totalCount, out _),
|
||||
Achievements = achievements.SelectList(entity => new AchievementView(entity, achievementMap[entity.Id])),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
DisplayName = archive.Name,
|
||||
FinishDescription = AchievementStatistics.Format(finishedCount, totalCount, out _),
|
||||
Achievements = achievements.SelectList(entity => new AchievementView(entity, achievementMap[entity.Id])),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,5 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
|
||||
internal interface IAchievementStatisticsService
|
||||
{
|
||||
Task<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap);
|
||||
ValueTask<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ internal sealed partial class CultivationService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ProjectAddResult> TryAddProjectAsync(CultivateProject project)
|
||||
public async ValueTask<ProjectAddResult> TryAddProjectAsync(CultivateProject project)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(project.Name))
|
||||
{
|
||||
@@ -69,7 +69,7 @@ internal sealed partial class CultivationService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task RemoveProjectAsync(CultivateProject project)
|
||||
public async ValueTask RemoveProjectAsync(CultivateProject project)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(projects);
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ internal interface ICultivationService
|
||||
/// </summary>
|
||||
/// <param name="project">项目</param>
|
||||
/// <returns>任务</returns>
|
||||
Task RemoveProjectAsync(CultivateProject project);
|
||||
ValueTask RemoveProjectAsync(CultivateProject project);
|
||||
|
||||
/// <summary>
|
||||
/// 异步保存养成物品
|
||||
@@ -100,5 +100,5 @@ internal interface ICultivationService
|
||||
/// </summary>
|
||||
/// <param name="project">项目</param>
|
||||
/// <returns>添加操作的结果</returns>
|
||||
Task<ProjectAddResult> TryAddProjectAsync(CultivateProject project);
|
||||
ValueTask<ProjectAddResult> TryAddProjectAsync(CultivateProject project);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task AddDailyNoteAsync(UserAndUid role)
|
||||
public async ValueTask AddDailyNoteAsync(UserAndUid role)
|
||||
{
|
||||
string roleUid = role.Uid.Value;
|
||||
|
||||
@@ -69,7 +69,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntriesAsync()
|
||||
public async ValueTask<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntryCollectionAsync()
|
||||
{
|
||||
if (entries == null)
|
||||
{
|
||||
@@ -111,7 +111,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task RemoveDailyNoteAsync(DailyNoteEntry entry)
|
||||
public async ValueTask RemoveDailyNoteAsync(DailyNoteEntry entry)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ArgumentNullException.ThrowIfNull(entries);
|
||||
|
||||
@@ -18,13 +18,13 @@ internal interface IDailyNoteService
|
||||
/// </summary>
|
||||
/// <param name="role">角色</param>
|
||||
/// <returns>任务</returns>
|
||||
Task AddDailyNoteAsync(UserAndUid role);
|
||||
ValueTask AddDailyNoteAsync(UserAndUid role);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取实时便笺列表
|
||||
/// </summary>
|
||||
/// <returns>实时便笺列表</returns>
|
||||
Task<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntriesAsync();
|
||||
ValueTask<ObservableCollection<DailyNoteEntry>> GetDailyNoteEntryCollectionAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步刷新实时便笺
|
||||
@@ -37,5 +37,5 @@ internal interface IDailyNoteService
|
||||
/// </summary>
|
||||
/// <param name="entry">指定的实时便笺</param>
|
||||
/// <returns>任务</returns>
|
||||
Task RemoveDailyNoteAsync(DailyNoteEntry entry);
|
||||
ValueTask RemoveDailyNoteAsync(DailyNoteEntry entry);
|
||||
}
|
||||
@@ -48,7 +48,7 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
|
||||
}
|
||||
}
|
||||
|
||||
private static GachaStatisticsSlim CreateCore(GachaLogServiceMetadataContext context, List<GachaItem> items, string uid)
|
||||
private static GachaStatisticsSlim CreateCore(in GachaLogServiceMetadataContext context, List<GachaItem> items, string uid)
|
||||
{
|
||||
int standardOrangeTracker = 0;
|
||||
int standardPurpleTracker = 0;
|
||||
|
||||
@@ -34,7 +34,7 @@ internal sealed class PullPrediction
|
||||
this.distributionType = distributionType;
|
||||
}
|
||||
|
||||
public async Task PredictAsync(AsyncBarrier barrier)
|
||||
public async ValueTask PredictAsync(AsyncBarrier barrier)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
HomaGachaLogClient gachaLogClient = serviceProvider.GetRequiredService<HomaGachaLogClient>();
|
||||
|
||||
@@ -144,7 +144,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.GachaArchives.AddAndSaveAsync(archive);
|
||||
await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ internal struct GachaLogFetchContext
|
||||
/// </summary>
|
||||
/// <param name="item">物品</param>
|
||||
/// <returns>是否应添加</returns>
|
||||
public bool ShouldAddItem(GachaLogItem item)
|
||||
public readonly bool ShouldAddItem(GachaLogItem item)
|
||||
{
|
||||
return !isLazy || item.Id > DbEndId;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ internal struct GachaLogFetchContext
|
||||
/// </summary>
|
||||
/// <param name="items">物品集合</param>
|
||||
/// <returns>当前类型已经处理完成</returns>
|
||||
public bool ItemsHaveReachEnd(List<GachaLogItem> items)
|
||||
public readonly bool ItemsHaveReachEnd(List<GachaLogItem> items)
|
||||
{
|
||||
return CurrentTypeAddingCompleted || items.Count < GachaLogQueryOptions.Size;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ internal struct GachaLogFetchContext
|
||||
/// <summary>
|
||||
/// 保存物品
|
||||
/// </summary>
|
||||
public void SaveItems()
|
||||
public readonly void SaveItems()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
@@ -167,7 +167,7 @@ internal struct GachaLogFetchContext
|
||||
/// </summary>
|
||||
/// <param name="progress">进度</param>
|
||||
/// <param name="isAuthKeyTimeout">验证密钥是否过期</param>
|
||||
public void Report(IProgress<GachaLogFetchStatus> progress, bool isAuthKeyTimeout = false)
|
||||
public readonly void Report(IProgress<GachaLogFetchStatus> progress, bool isAuthKeyTimeout = false)
|
||||
{
|
||||
FetchStatus.AuthKeyTimeout = isAuthKeyTimeout;
|
||||
progress.Report(FetchStatus);
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, GachaArchive?>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default)
|
||||
public async ValueTask<ValueResult<bool, GachaArchive?>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
GachaArchive? archive = await gachaLogDbService
|
||||
.GetGachaArchiveByUidAsync(uid, token)
|
||||
@@ -81,13 +81,13 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> DeleteGachaItemsAsync(string uid, CancellationToken token = default)
|
||||
public async ValueTask<ValueResult<bool, string>> DeleteGachaItemsAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
return await homaGachaLogClient.DeleteGachaItemsAsync(uid, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, HutaoStatistics>> GetCurrentEventStatisticsAsync(CancellationToken token = default)
|
||||
public async ValueTask<ValueResult<bool, HutaoStatistics>> GetCurrentEventStatisticsAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<GachaEventStatistics> response = await homaGachaLogClient.GetGachaEventStatisticsAsync(token).ConfigureAwait(false);
|
||||
if (response.IsOk())
|
||||
@@ -110,7 +110,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
return new(false, default!);
|
||||
}
|
||||
|
||||
private async Task<EndIds?> GetEndIdsFromCloudAsync(string uid, CancellationToken token = default)
|
||||
private async ValueTask<EndIds?> GetEndIdsFromCloudAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
Response<EndIds> resp = await homaGachaLogClient.GetEndIdsAsync(uid, token).ConfigureAwait(false);
|
||||
return resp.IsOk() ? resp.Data : default;
|
||||
|
||||
@@ -206,7 +206,7 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
break;
|
||||
}
|
||||
|
||||
await Delay.RandomAsync(1000, 2000).ConfigureAwait(false);
|
||||
await Delay.Random(1000, 2000).ConfigureAwait(false);
|
||||
}
|
||||
while (true);
|
||||
|
||||
@@ -217,7 +217,7 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
fetchContext.SaveItems();
|
||||
await Delay.RandomAsync(1000, 2000).ConfigureAwait(false);
|
||||
await Delay.Random(1000, 2000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new(!fetchContext.FetchStatus.AuthKeyTimeout, fetchContext.TargetArchive);
|
||||
|
||||
@@ -19,14 +19,14 @@ internal interface IGachaLogHutaoCloudService
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>是否删除成功</returns>
|
||||
Task<ValueResult<bool, string>> DeleteGachaItemsAsync(string uid, CancellationToken token = default);
|
||||
ValueTask<ValueResult<bool, string>> DeleteGachaItemsAsync(string uid, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取祈愿统计信息
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>祈愿统计信息</returns>
|
||||
Task<ValueResult<bool, HutaoStatistics>> GetCurrentEventStatisticsAsync(CancellationToken token = default);
|
||||
ValueTask<ValueResult<bool, HutaoStatistics>> GetCurrentEventStatisticsAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask<Response<List<GachaEntry>>> GetGachaEntriesAsync(CancellationToken token = default);
|
||||
|
||||
@@ -36,7 +36,7 @@ internal interface IGachaLogHutaoCloudService
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>是否获取成功</returns>
|
||||
Task<ValueResult<bool, GachaArchive?>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default);
|
||||
ValueTask<ValueResult<bool, GachaArchive?>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传祈愿记录
|
||||
|
||||
@@ -42,7 +42,7 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP
|
||||
string message = string.Format(
|
||||
SH.ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale,
|
||||
queryLanguageCode,
|
||||
metadataOptions.LocaleName);
|
||||
metadataOptions.LanguageCode);
|
||||
return new(false, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv
|
||||
string message = string.Format(
|
||||
SH.ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale,
|
||||
queryLanguageCode,
|
||||
metadataOptions.LocaleName);
|
||||
metadataOptions.LanguageCode);
|
||||
return new(false, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
@@ -15,6 +17,7 @@ namespace Snap.Hutao.Service.GachaLog;
|
||||
internal sealed partial class UIGFImportService : IUIGFImportService
|
||||
{
|
||||
private readonly ILogger<UIGFImportService> logger;
|
||||
private readonly MetadataOptions metadataOptions;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IGachaLogDbService gachaLogDbService;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -24,6 +27,12 @@ internal sealed partial class UIGFImportService : IUIGFImportService
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
if (!metadataOptions.IsCurrentLocale(uigf.Info.Language))
|
||||
{
|
||||
string message = string.Format(SH.ServiceGachaUIGFImportLanguageNotMatch, uigf.Info.Language, metadataOptions.LanguageCode);
|
||||
ThrowHelper.InvalidOperation(message, null);
|
||||
}
|
||||
|
||||
GachaArchiveOperation.GetOrAdd(serviceProvider, uigf.Info.Uid, archives, out GachaArchive? archive);
|
||||
Guid archiveId = archive.InnerId;
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ internal sealed partial class GameService : IGameService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
|
||||
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
|
||||
{
|
||||
string gamePath = appOptions.GamePath;
|
||||
string gameFolder = Path.GetDirectoryName(gamePath)!;
|
||||
|
||||
@@ -76,7 +76,7 @@ internal interface IGameService
|
||||
/// <param name="launchScheme">目标启动方案</param>
|
||||
/// <param name="progress">进度</param>
|
||||
/// <returns>是否替换成功</returns>
|
||||
Task<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress);
|
||||
ValueTask<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress);
|
||||
|
||||
/// <summary>
|
||||
/// 修改注册表中的账号信息
|
||||
|
||||
@@ -76,7 +76,7 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator
|
||||
|
||||
private static string Unescape(string str)
|
||||
{
|
||||
string hex4Result = UTF16Regex().Replace(str, @"\u$1");
|
||||
string hex4Result = UTF16Regex().Replace(str, @"\u");
|
||||
|
||||
// 不包含中文
|
||||
// Some one's folder might begin with 'u'
|
||||
@@ -89,6 +89,6 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator
|
||||
return Regex.Unescape(hex4Result);
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\\\x([0-9a-f]{4})")]
|
||||
[GeneratedRegex(@"\\x(?=[0-9a-f]{4})")]
|
||||
private static partial Regex UTF16Regex();
|
||||
}
|
||||
@@ -42,12 +42,12 @@ internal readonly struct ItemOperationInfo
|
||||
/// </summary>
|
||||
/// <param name="type">操作类型</param>
|
||||
/// <param name="target">目标</param>
|
||||
/// <param name="cache">缓存</param>
|
||||
public ItemOperationInfo(ItemOperationType type, VersionItem target, VersionItem cache)
|
||||
/// <param name="moveTo">缓存</param>
|
||||
public ItemOperationInfo(ItemOperationType type, VersionItem target, VersionItem moveTo)
|
||||
{
|
||||
Type = type;
|
||||
Target = target.RemoteName;
|
||||
MoveTo = cache.RemoteName;
|
||||
MoveTo = moveTo.RemoteName;
|
||||
Md5 = target.Md5;
|
||||
TotalBytes = target.FileSize;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Snap.Hutao.Service.Game.Package;
|
||||
internal enum ItemOperationType
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除
|
||||
/// 添加
|
||||
/// </summary>
|
||||
Remove = 0,
|
||||
Add = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 替换
|
||||
@@ -20,7 +20,7 @@ internal enum ItemOperationType
|
||||
Replace = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 添加
|
||||
/// 需要备份
|
||||
/// </summary>
|
||||
Add = 2,
|
||||
Backup = 2,
|
||||
}
|
||||
@@ -22,6 +22,8 @@ namespace Snap.Hutao.Service.Game.Package;
|
||||
[HttpClient(HttpClientConfiguration.Default)]
|
||||
internal sealed partial class PackageConverter
|
||||
{
|
||||
private const string PackageVersion = "pkg_version";
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly HttpClient httpClient;
|
||||
@@ -37,15 +39,33 @@ internal sealed partial class PackageConverter
|
||||
/// <returns>替换结果与资源</returns>
|
||||
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResource, string gameFolder, IProgress<PackageReplaceStatus> progress)
|
||||
{
|
||||
// 以 国服 => 国际 为例
|
||||
// 1. 下载国际服的 pkg_version 文件,转换为索引字典
|
||||
// 获取本地对应 pkg_version 文件,转换为索引字典
|
||||
//
|
||||
// 2. 对比两者差异,
|
||||
// 国际服有 & 国服没有的 为新增
|
||||
// 国际服有 & 国服也有的 为替换
|
||||
// 剩余国际服没有 & 国服有的 为备份
|
||||
// 生成对应的操作信息项,对比文件的尺寸与MD5
|
||||
//
|
||||
// 3. 根据操作信息项,提取其中需要下载的项进行缓存对比或下载
|
||||
// 若缓存中文件的尺寸与MD5与操作信息项中的一致则直接跳过
|
||||
// 每个文件下载后需要验证文件文件的尺寸与MD5
|
||||
// 若出现下载失败的情况,终止转换进程,此时国服文件尚未替换
|
||||
//
|
||||
// 4. 全部资源下载完成后,根据操作信息项,进行文件替换
|
||||
// 处理顺序:备份/替换/新增
|
||||
// 替换操作等于 先备份国服文件,随后新增国际服文件
|
||||
string scatteredFilesUrl = gameResource.Game.Latest.DecompressedPath;
|
||||
Uri pkgVersionUri = $"{scatteredFilesUrl}/pkg_version".ToUri();
|
||||
Uri pkgVersionUri = $"{scatteredFilesUrl}/{PackageVersion}".ToUri();
|
||||
ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese;
|
||||
|
||||
progress.Report(new(SH.ServiceGamePackageRequestPackageVerion));
|
||||
Dictionary<string, VersionItem> remoteItems = await TryGetRemoteItemsAsync(pkgVersionUri).ConfigureAwait(false);
|
||||
Dictionary<string, VersionItem> localItems = await TryGetLocalItemsAsync(gameFolder, direction).ConfigureAwait(false);
|
||||
Dictionary<string, VersionItem> remoteItems = await GetRemoteItemsAsync(pkgVersionUri).ConfigureAwait(false);
|
||||
Dictionary<string, VersionItem> localItems = await GetLocalItemsAsync(gameFolder, direction).ConfigureAwait(false);
|
||||
|
||||
IEnumerable<ItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems).OrderBy(i => (int)i.Type);
|
||||
IEnumerable<ItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems).OrderBy(i => i.Type);
|
||||
return await ReplaceGameResourceAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -116,12 +136,13 @@ internal sealed partial class PackageConverter
|
||||
{
|
||||
if (local.TryGetValue(remoteName, out VersionItem? localItem))
|
||||
{
|
||||
if (remoteItem.Md5 != localItem.Md5)
|
||||
if (!remoteItem.Md5.Equals(localItem.Md5, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 本地发现了同名且不同MD5的项,需要替换为服务器上的项
|
||||
// 本地发现了同名且不同 MD5 的项,需要替换为服务器上的项
|
||||
yield return new(ItemOperationType.Replace, remoteItem, localItem);
|
||||
}
|
||||
|
||||
// 同名同MD5,跳过
|
||||
local.Remove(remoteName);
|
||||
}
|
||||
else
|
||||
@@ -131,30 +152,28 @@ internal sealed partial class PackageConverter
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ItemOperationInfo item in local.Select(kvp => new ItemOperationInfo(ItemOperationType.Remove, kvp.Value, kvp.Value)))
|
||||
foreach ((_, VersionItem localItem) in local)
|
||||
{
|
||||
yield return item;
|
||||
yield return new(ItemOperationType.Backup, localItem, localItem);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenameDataFolder(string gameFolder, ConvertDirection direction)
|
||||
private static void TryRenameDataFolder(string gameFolder, ConvertDirection direction)
|
||||
{
|
||||
string yuanShenData = Path.Combine(gameFolder, YuanShenData);
|
||||
string genshinImpactData = Path.Combine(gameFolder, GenshinImpactData);
|
||||
|
||||
if (direction == ConvertDirection.ChineseToOversea)
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(yuanShenData))
|
||||
{
|
||||
Directory.Move(yuanShenData, genshinImpactData);
|
||||
}
|
||||
_ = direction == ConvertDirection.ChineseToOversea
|
||||
? DirectoryOperation.Move(yuanShenData, genshinImpactData)
|
||||
: DirectoryOperation.Move(genshinImpactData, yuanShenData);
|
||||
}
|
||||
else
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (Directory.Exists(genshinImpactData))
|
||||
{
|
||||
Directory.Move(genshinImpactData, yuanShenData);
|
||||
}
|
||||
// Access to the path is denied.
|
||||
// When user install the game in special folder like 'Program Files'
|
||||
throw ThrowHelper.GameFileOperation(SH.ServiceGamePackageRenameDataFolderFailed, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,15 +183,15 @@ internal sealed partial class PackageConverter
|
||||
File.Move(targetFullPath, cacheFilePath, true);
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, VersionItem>> TryGetLocalItemsAsync(string gameFolder, ConvertDirection direction)
|
||||
private async ValueTask<Dictionary<string, VersionItem>> GetLocalItemsAsync(string gameFolder, ConvertDirection direction)
|
||||
{
|
||||
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version")))
|
||||
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, PackageVersion)))
|
||||
{
|
||||
return await GetLocalVersionItemsAsync(localSteam, direction).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, VersionItem>> TryGetRemoteItemsAsync(Uri pkgVersionUri)
|
||||
private async ValueTask<Dictionary<string, VersionItem>> GetRemoteItemsAsync(Uri pkgVersionUri)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -190,20 +209,11 @@ internal sealed partial class PackageConverter
|
||||
private async ValueTask<bool> ReplaceGameResourceAsync(IEnumerable<ItemOperationInfo> operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress<PackageReplaceStatus> progress)
|
||||
{
|
||||
// 重命名 _Data 目录
|
||||
try
|
||||
{
|
||||
RenameDataFolder(gameFolder, direction);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Access to the path is denied.
|
||||
// When user install the game in special folder like 'Program Files'
|
||||
throw ThrowHelper.GameFileOperation(SH.ServiceGamePackageRenameDataFolderFailed, ex);
|
||||
}
|
||||
TryRenameDataFolder(gameFolder, direction);
|
||||
|
||||
// Cache folder
|
||||
Core.RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<Core.RuntimeOptions>();
|
||||
string cacheFolder = Path.Combine(hutaoOptions.DataFolder, "ServerCache");
|
||||
Core.RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<Core.RuntimeOptions>();
|
||||
string cacheFolder = Path.Combine(runtimeOptions.DataFolder, "ServerCache");
|
||||
|
||||
// 执行下载与移动操作
|
||||
foreach (ItemOperationInfo info in operations)
|
||||
@@ -216,7 +226,7 @@ internal sealed partial class PackageConverter
|
||||
|
||||
switch (info.Type)
|
||||
{
|
||||
case ItemOperationType.Remove:
|
||||
case ItemOperationType.Backup:
|
||||
MoveToCache(moveToFilePath, targetFilePath);
|
||||
break;
|
||||
case ItemOperationType.Replace:
|
||||
@@ -232,7 +242,7 @@ internal sealed partial class PackageConverter
|
||||
}
|
||||
|
||||
// 重新下载所有 *pkg_version 文件
|
||||
await ReplacePackageVersionsAsync(scatteredFilesUrl, gameFolder).ConfigureAwait(false);
|
||||
await ReplacePackageVersionFilesAsync(scatteredFilesUrl, gameFolder).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -269,7 +279,7 @@ internal sealed partial class PackageConverter
|
||||
{
|
||||
StreamCopyWorker<PackageReplaceStatus> streamCopyWorker = new(webStream, fileStream, bytesRead => new(info.Target, bytesRead, totalBytes));
|
||||
await streamCopyWorker.CopyAsync(progress).ConfigureAwait(false);
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
fileStream.Position = 0;
|
||||
string remoteMd5 = await MD5.HashAsync(fileStream).ConfigureAwait(false);
|
||||
if (string.Equals(info.Md5, remoteMd5, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -283,7 +293,7 @@ internal sealed partial class PackageConverter
|
||||
|
||||
// We want to retry forever.
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Error(ex);
|
||||
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||
await Delay.FromSeconds(2).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,7 +301,7 @@ internal sealed partial class PackageConverter
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask ReplacePackageVersionsAsync(string scatteredFilesUrl, string gameFolder)
|
||||
private async ValueTask ReplacePackageVersionFilesAsync(string scatteredFilesUrl, string gameFolder)
|
||||
{
|
||||
foreach (string versionFilePath in Directory.EnumerateFiles(gameFolder, "*pkg_version"))
|
||||
{
|
||||
@@ -340,14 +350,14 @@ internal sealed partial class PackageConverter
|
||||
|
||||
using (StreamReader reader = new(stream))
|
||||
{
|
||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } raw)
|
||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } row)
|
||||
{
|
||||
if (string.IsNullOrEmpty(raw))
|
||||
if (string.IsNullOrEmpty(row))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
||||
VersionItem item = JsonSerializer.Deserialize<VersionItem>(row, options)!;
|
||||
|
||||
string remoteName = item.RemoteName;
|
||||
|
||||
|
||||
@@ -103,6 +103,11 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
|
||||
/// <returns>是否为当前语言名称</returns>
|
||||
public bool IsCurrentLocale(string languageCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CultureInfo cultureInfo = CultureInfo.GetCultureInfo(languageCode);
|
||||
return GetLocaleName(cultureInfo) == LocaleName;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ internal sealed partial class DailyNoteViewModel : Abstraction.ViewModel
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ObservableCollection<UserAndUid> roles = await userService.GetRoleCollectionAsync().ConfigureAwait(false);
|
||||
ObservableCollection<DailyNoteEntry> entries = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false);
|
||||
ObservableCollection<DailyNoteEntry> entries = await dailyNoteService.GetDailyNoteEntryCollectionAsync().ConfigureAwait(false);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
UserAndUids = roles;
|
||||
|
||||
@@ -45,7 +45,7 @@ internal sealed class DailyNoteViewModelSlim : Abstraction.ViewModelSlim<View.Pa
|
||||
.ConfigureAwait(false);
|
||||
ObservableCollection<DailyNoteEntry> entries = await ServiceProvider
|
||||
.GetRequiredService<IDailyNoteService>()
|
||||
.GetDailyNoteEntriesAsync()
|
||||
.GetDailyNoteEntryCollectionAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// We have to create a copy here,
|
||||
|
||||
@@ -362,9 +362,17 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogImportProgress).ConfigureAwait(true);
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false);
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
infoBarService.Success(SH.ViewModelGachaLogImportComplete);
|
||||
|
||||
Reference in New Issue
Block a user