diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs index 8b89ac41..ad691d54 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs @@ -35,7 +35,7 @@ internal sealed class CachedImage : Implementation.ImageEx try { - HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), HutaoExceptionKind.ImageCacheInvalidUri, SH.ControlImageCachedImageInvalidResourceUri); + HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri); string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread. token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled. return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed. diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs index 460e5b3c..f46622a7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs @@ -5,34 +5,26 @@ namespace Snap.Hutao.Core.ExceptionService; internal sealed class HutaoException : Exception { - public HutaoException(HutaoExceptionKind kind, string message, Exception? innerException) - : this(message, innerException) - { - Kind = kind; - } - - private HutaoException(string message, Exception? innerException) + public HutaoException(string message, Exception? innerException) : base($"{message}\n{innerException?.Message}", innerException) { } - public HutaoExceptionKind Kind { get; private set; } - [DoesNotReturn] - public static HutaoException Throw(HutaoExceptionKind kind, string message, Exception? innerException = default) + public static HutaoException Throw(string message, Exception? innerException = default) { - throw new HutaoException(kind, message, innerException); + throw new HutaoException(message, innerException); } - public static void ThrowIf(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default) + public static void ThrowIf(bool condition, string message, Exception? innerException = default) { if (condition) { - throw new HutaoException(kind, message, innerException); + throw new HutaoException(message, innerException); } } - public static void ThrowIfNot(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default) + public static void ThrowIfNot(bool condition, string message, Exception? innerException = default) { if (!condition) { @@ -60,6 +52,12 @@ internal sealed class HutaoException : Exception throw new InvalidCastException(message, innerException); } + [DoesNotReturn] + public static NotSupportedException NotSupported(string? message = default, Exception? innerException = default) + { + throw new NotSupportedException(message, innerException); + } + [DoesNotReturn] public static OperationCanceledException OperationCanceled(string message, Exception? innerException = default) { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs index 02d9347c..f70daead 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs @@ -29,7 +29,7 @@ internal readonly struct TempFile : IDisposable } catch (UnauthorizedAccessException ex) { - HutaoException.Throw(HutaoExceptionKind.FileSystemCreateFileInsufficientPermissions, SH.CoreIOTempFileCreateFail, ex); + HutaoException.Throw(SH.CoreIOTempFileCreateFail, ex); } if (delete) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/InterProcess/PrivateNamedPipeServer.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/InterProcess/PrivateNamedPipeServer.cs index 1c35891c..2d61d09b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/InterProcess/PrivateNamedPipeServer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/InterProcess/PrivateNamedPipeServer.cs @@ -49,7 +49,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable { byte[] content = new byte[header->ContentLength]; serverStream.ReadAtLeast(content, header->ContentLength, false); - HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, HutaoExceptionKind.PrivateNamedPipeContentHashIncorrect, "PipePacket Content Hash incorrect"); + HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect"); return content; } diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs index 40d24286..14cab77b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs @@ -118,14 +118,6 @@ internal static partial class EnumerableExtension collection.RemoveAt(collection.Count - 1); } - /// - /// 转换到新类型的列表 - /// - /// 原始类型 - /// 新类型 - /// 列表 - /// 选择器 - /// 新类型的列表 [Pure] public static List SelectList(this List list, Func selector) { diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntity.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntity.cs index 3e90f714..3a055fb4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntity.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntity.cs @@ -5,5 +5,5 @@ namespace Snap.Hutao.Model.Entity.Abstraction; internal interface IAppDbEntity { - Guid InnerId { get; set; } + Guid InnerId { get; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntityHasArchive.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntityHasArchive.cs new file mode 100644 index 00000000..086eb1df --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Abstraction/IAppDbEntityHasArchive.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model.Entity.Abstraction; + +internal interface IAppDbEntityHasArchive : IAppDbEntity +{ + Guid ArchiveId { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs index 0c587661..be9ace7a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs @@ -16,7 +16,7 @@ namespace Snap.Hutao.Model.Entity; [HighQuality] [Table("achievements")] [SuppressMessage("", "SA1124")] -internal sealed class Achievement : IAppDbEntity, +internal sealed class Achievement : IAppDbEntityHasArchive, IEquatable, IDbMappingForeignKeyFrom, IDbMappingForeignKeyFrom diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs index 2dfb7a1a..404b8580 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity.Abstraction; @@ -19,4 +20,16 @@ internal static class AppDbServiceAppDbEntityExtension { return service.ExecuteAsync((dbset, token) => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId, token), token); } + + public static List ListByArchiveId(this IAppDbService service, Guid archiveId) + where TEntity : class, IAppDbEntityHasArchive + { + return service.Query(query => query.Where(e => e.ArchiveId == archiveId).ToList()); + } + + public static ValueTask> ListByArchiveIdAsync(this IAppDbService service, Guid archiveId, CancellationToken token = default) + where TEntity : class, IAppDbEntityHasArchive + { + return service.QueryAsync((query, token) => query.Where(e => e.ArchiveId == archiveId).ToListAsync(token), token); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs new file mode 100644 index 00000000..1965ba4d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs @@ -0,0 +1,47 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; +using System.Collections.ObjectModel; +using System.Linq.Expressions; + +namespace Snap.Hutao.Service.Abstraction; + +internal static class AppDbServiceCollectionExtension +{ + public static List List(this IAppDbService service) + where TEntity : class + { + return service.Query(query => query.ToList()); + } + + public static List List(this IAppDbService service, Expression> predicate) + where TEntity : class + { + return service.Query(query => query.Where(predicate).ToList()); + } + + public static ValueTask> ListAsync(this IAppDbService service, CancellationToken token = default) + where TEntity : class + { + return service.QueryAsync((query, token) => query.ToListAsync(token), token); + } + + public static ValueTask> ListAsync(this IAppDbService service, Expression> predicate, CancellationToken token = default) + where TEntity : class + { + return service.QueryAsync((query, token) => query.Where(predicate).ToListAsync(token), token); + } + + public static ObservableCollection ObservableCollection(this IAppDbService service) + where TEntity : class + { + return service.Query(query => query.ToObservableCollection()); + } + + public static ObservableCollection ObservableCollection(this IAppDbService service, Expression> predicate) + where TEntity : class + { + return service.Query(query => query.Where(predicate).ToObservableCollection()); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs index fdf0d8a1..798da431 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs @@ -84,18 +84,6 @@ internal static class AppDbServiceExtension return service.ExecuteAsync((dbset, token) => dbset.AddRangeAndSaveAsync(entities, token), token); } - public static TEntity Single(this IAppDbService service, Expression> predicate) - where TEntity : class - { - return service.Execute(dbset => dbset.AsNoTracking().Single(predicate)); - } - - public static ValueTask SingleAsync(this IAppDbService service, Expression> predicate, CancellationToken token = default) - where TEntity : class - { - return service.ExecuteAsync((dbset, token) => dbset.AsNoTracking().SingleAsync(predicate, token), token); - } - public static TResult Query(this IAppDbService service, Func, TResult> func) where TEntity : class { @@ -126,6 +114,18 @@ internal static class AppDbServiceExtension return service.ExecuteAsync((dbset, token) => func(dbset.AsNoTracking(), token), token); } + public static TEntity Single(this IAppDbService service, Expression> predicate) + where TEntity : class + { + return service.Query(query => query.Single(predicate)); + } + + public static ValueTask SingleAsync(this IAppDbService service, Expression> predicate, CancellationToken token = default) + where TEntity : class + { + return service.QueryAsync((query, token) => query.SingleAsync(predicate, token), token); + } + public static int Update(this IAppDbService service, TEntity entity) where TEntity : class { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs index 1bed7625..683c7dc4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs @@ -11,9 +11,6 @@ using EntityAchievement = Snap.Hutao.Model.Entity.Achievement; namespace Snap.Hutao.Service.Achievement; -/// -/// 成就数据库服务 -/// [ConstructorGenerated] [Injection(InjectAs.Singleton, typeof(IAchievementDbService))] internal sealed partial class AchievementDbService : IAchievementDbService @@ -79,7 +76,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService public ObservableCollection GetAchievementArchiveCollection() { - return this.Query>(query => query.ToObservableCollection()); + return this.ObservableCollection(); } public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive, CancellationToken token = default) @@ -90,25 +87,21 @@ internal sealed partial class AchievementDbService : IAchievementDbService public List GetAchievementListByArchiveId(Guid archiveId) { - return this.Query>(query => [.. query.Where(a => a.ArchiveId == archiveId)]); + return this.ListByArchiveId(archiveId); } public ValueTask> GetAchievementListByArchiveIdAsync(Guid archiveId, CancellationToken token = default) { - return this.QueryAsync>( - (query, token) => query - .Where(a => a.ArchiveId == archiveId) - .ToListAsync(token), - token); + return this.ListByArchiveIdAsync(archiveId, token); } public List GetAchievementArchiveList() { - return this.Query>(query => [.. query]); + return this.List(); } - public async ValueTask> GetAchievementArchiveListAsync(CancellationToken token = default) + public ValueTask> GetAchievementArchiveListAsync(CancellationToken token = default) { - return await this.QueryAsync>(query => query.ToListAsync()).ConfigureAwait(false); + return this.ListAsync(token); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.Archive.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.Archive.cs deleted file mode 100644 index 347cbe40..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.Archive.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Core.Database; -using Snap.Hutao.Model.Entity; -using System.Collections.ObjectModel; - -namespace Snap.Hutao.Service.Achievement; - -/// -/// 集合部分 -/// -internal sealed partial class AchievementService -{ - private ObservableCollection? archiveCollection; - - /// - public AchievementArchive? CurrentArchive - { - get => dbCurrent.Current; - set => dbCurrent.Current = value; - } - - /// - public ObservableCollection ArchiveCollection - { - get - { - if (archiveCollection is null) - { - archiveCollection = achievementDbService.GetAchievementArchiveCollection(); - CurrentArchive = archiveCollection.SelectedOrDefault(); - } - - return archiveCollection; - } - } - - /// - public async ValueTask AddArchiveAsync(AchievementArchive newArchive) - { - if (string.IsNullOrWhiteSpace(newArchive.Name)) - { - return ArchiveAddResult.InvalidName; - } - - ArgumentNullException.ThrowIfNull(archiveCollection); - - // 查找是否有相同的名称 - if (archiveCollection.Any(a => a.Name == newArchive.Name)) - { - return ArchiveAddResult.AlreadyExists; - } - - // Sync cache - await taskContext.SwitchToMainThreadAsync(); - archiveCollection.Add(newArchive); - - // Sync database - await taskContext.SwitchToBackgroundAsync(); - CurrentArchive = newArchive; - - return ArchiveAddResult.Added; - } - - /// - public async ValueTask RemoveArchiveAsync(AchievementArchive archive) - { - ArgumentNullException.ThrowIfNull(archiveCollection); - - // Sync cache - await taskContext.SwitchToMainThreadAsync(); - archiveCollection.Remove(archive); - - // Sync database - await taskContext.SwitchToBackgroundAsync(); - await achievementDbService.RemoveAchievementArchiveAsync(archive).ConfigureAwait(false); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.Interchange.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.Interchange.cs deleted file mode 100644 index 85b32656..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.Interchange.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.InterChange.Achievement; -using EntityAchievement = Snap.Hutao.Model.Entity.Achievement; - -namespace Snap.Hutao.Service.Achievement; - -/// -/// 数据交换部分 -/// -internal sealed partial class AchievementService -{ - /// - public async ValueTask ImportFromUIAFAsync(AchievementArchive archive, List list, ImportStrategy strategy) - { - await taskContext.SwitchToBackgroundAsync(); - - Guid archiveId = archive.InnerId; - - switch (strategy) - { - case ImportStrategy.AggressiveMerge: - { - IOrderedEnumerable orederedUIAF = list.OrderBy(a => a.Id); - return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, true); - } - - case ImportStrategy.LazyMerge: - { - IOrderedEnumerable orederedUIAF = list.OrderBy(a => a.Id); - return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, false); - } - - case ImportStrategy.Overwrite: - { - IEnumerable orederedUIAF = list - .Select(uiaf => EntityAchievement.From(archiveId, uiaf)) - .OrderBy(a => a.Id); - return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF); - } - - default: - throw Must.NeverHappen(); - } - } - - /// - public async ValueTask ExportToUIAFAsync(AchievementArchive archive) - { - await taskContext.SwitchToBackgroundAsync(); - List entities = await achievementDbService - .GetAchievementListByArchiveIdAsync(archive.InnerId) - .ConfigureAwait(false); - List list = entities.SelectList(UIAFItem.From); - - return new() - { - Info = UIAFInfo.From(runtimeOptions), - List = list, - }; - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index d7a22a94..55e9083d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -3,17 +3,16 @@ using Snap.Hutao.Core; using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Model.Entity; +using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.Primitive; using Snap.Hutao.ViewModel.Achievement; +using System.Collections.ObjectModel; using EntityAchievement = Snap.Hutao.Model.Entity.Achievement; -using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement; namespace Snap.Hutao.Service.Achievement; -/// -/// 成就服务 -/// [HighQuality] [ConstructorGenerated] [Injection(InjectAs.Scoped, typeof(IAchievementService))] @@ -25,21 +24,127 @@ internal sealed partial class AchievementService : IAchievementService private readonly RuntimeOptions runtimeOptions; private readonly ITaskContext taskContext; - /// - public List GetAchievementViewList(AchievementArchive archive, List metadata) + private ObservableCollection? archiveCollection; + + public AchievementArchive? CurrentArchive + { + get => dbCurrent.Current; + set => dbCurrent.Current = value; + } + + public ObservableCollection ArchiveCollection + { + get + { + if (archiveCollection is null) + { + archiveCollection = achievementDbService.GetAchievementArchiveCollection(); + CurrentArchive = archiveCollection.SelectedOrDefault(); + } + + return archiveCollection; + } + } + + public List GetAchievementViewList(AchievementArchive archive, AchievementServiceMetadataContext context) { Dictionary entities = achievementDbService.GetAchievementMapByArchiveId(archive.InnerId); - return metadata.SelectList(meta => + return context.Achievements.SelectList(meta => { EntityAchievement entity = entities.GetValueOrDefault(meta.Id) ?? EntityAchievement.From(archive.InnerId, meta.Id); return new AchievementView(entity, meta); }); } - /// public void SaveAchievement(AchievementView achievement) { achievementDbService.OverwriteAchievement(achievement.Entity); } + + public async ValueTask AddArchiveAsync(AchievementArchive newArchive) + { + if (string.IsNullOrWhiteSpace(newArchive.Name)) + { + return ArchiveAddResultKind.InvalidName; + } + + ArgumentNullException.ThrowIfNull(archiveCollection); + + if (archiveCollection.Any(a => a.Name == newArchive.Name)) + { + return ArchiveAddResultKind.AlreadyExists; + } + + // Sync cache + await taskContext.SwitchToMainThreadAsync(); + archiveCollection.Add(newArchive); + + // Sync database + await taskContext.SwitchToBackgroundAsync(); + CurrentArchive = newArchive; + + return ArchiveAddResultKind.Added; + } + + public async ValueTask RemoveArchiveAsync(AchievementArchive archive) + { + ArgumentNullException.ThrowIfNull(archiveCollection); + + // Sync cache + await taskContext.SwitchToMainThreadAsync(); + archiveCollection.Remove(archive); + + // Sync database + await taskContext.SwitchToBackgroundAsync(); + await achievementDbService.RemoveAchievementArchiveAsync(archive).ConfigureAwait(false); + } + + public async ValueTask ImportFromUIAFAsync(AchievementArchive archive, List list, ImportStrategyKind strategy) + { + await taskContext.SwitchToBackgroundAsync(); + + Guid archiveId = archive.InnerId; + + switch (strategy) + { + case ImportStrategyKind.AggressiveMerge: + { + IOrderedEnumerable orederedUIAF = list.OrderBy(a => a.Id); + return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, true); + } + + case ImportStrategyKind.LazyMerge: + { + IOrderedEnumerable orederedUIAF = list.OrderBy(a => a.Id); + return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, false); + } + + case ImportStrategyKind.Overwrite: + { + IEnumerable orederedUIAF = list + .SelectList(uiaf => EntityAchievement.From(archiveId, uiaf)) + .SortBy(a => a.Id); + return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF); + } + + default: + throw HutaoException.NotSupported(); + } + } + + public async ValueTask ExportToUIAFAsync(AchievementArchive archive) + { + await taskContext.SwitchToBackgroundAsync(); + List entities = await achievementDbService + .GetAchievementListByArchiveIdAsync(archive.InnerId) + .ConfigureAwait(false); + List list = entities.SelectList(UIAFItem.From); + + return new() + { + Info = UIAFInfo.From(runtimeOptions), + List = list, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementServiceMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementServiceMetadataContext.cs new file mode 100644 index 00000000..d9f366f5 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementServiceMetadataContext.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; +using Snap.Hutao.Service.Metadata.ContextAbstraction; +using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement; + +namespace Snap.Hutao.Service.Achievement; + +internal sealed class AchievementServiceMetadataContext : IMetadataContext, + IMetadataListAchievementSource, + IMetadataDictionaryIdAchievementSource +{ + public List Achievements { get; set; } = default!; + + public Dictionary IdAchievementMap { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs index f588b786..75d2ef23 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementStatisticsService.cs @@ -13,32 +13,34 @@ namespace Snap.Hutao.Service.Achievement; [Injection(InjectAs.Scoped, typeof(IAchievementStatisticsService))] internal sealed partial class AchievementStatisticsService : IAchievementStatisticsService { + private const int AchievementCardTakeCount = 2; + private readonly IAchievementDbService achievementDbService; private readonly ITaskContext taskContext; /// - public async ValueTask> GetAchievementStatisticsAsync(Dictionary achievementMap) + public async ValueTask> GetAchievementStatisticsAsync(AchievementServiceMetadataContext context, CancellationToken token = default) { await taskContext.SwitchToBackgroundAsync(); List results = []; - foreach (AchievementArchive archive in await achievementDbService.GetAchievementArchiveListAsync().ConfigureAwait(false)) + foreach (AchievementArchive archive in await achievementDbService.GetAchievementArchiveListAsync(token).ConfigureAwait(false)) { int finishedCount = await achievementDbService - .GetFinishedAchievementCountByArchiveIdAsync(archive.InnerId) + .GetFinishedAchievementCountByArchiveIdAsync(archive.InnerId, token) .ConfigureAwait(false); - int totalCount = achievementMap.Count; + int totalCount = context.IdAchievementMap.Count; List achievements = await achievementDbService - .GetLatestFinishedAchievementListByArchiveIdAsync(archive.InnerId, 2) + .GetLatestFinishedAchievementListByArchiveIdAsync(archive.InnerId, AchievementCardTakeCount, token) .ConfigureAwait(false); results.Add(new() { DisplayName = archive.Name, FinishDescription = AchievementStatistics.Format(finishedCount, totalCount, out _), - Achievements = achievements.SelectList(entity => new AchievementView(entity, achievementMap[entity.Id])), + Achievements = achievements.SelectList(entity => new AchievementView(entity, context.IdAchievementMap[entity.Id])), }); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResultKind.cs similarity index 92% rename from src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResultKind.cs index d8b31c16..9d2a1b70 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResultKind.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Achievement; /// 存档添加结果 /// [HighQuality] -internal enum ArchiveAddResult +internal enum ArchiveAddResultKind { /// /// 添加成功 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs index e30735ee..adbfca29 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs @@ -32,13 +32,7 @@ internal interface IAchievementService /// UIAF ValueTask ExportToUIAFAsync(EntityArchive selectedArchive); - /// - /// 获取整合的成就 - /// - /// 用户 - /// 元数据 - /// 整合的成就 - List GetAchievementViewList(EntityArchive archive, List metadata); + List GetAchievementViewList(EntityArchive archive, AchievementServiceMetadataContext context); /// /// 异步导入UIAF数据 @@ -47,7 +41,7 @@ internal interface IAchievementService /// UIAF数据 /// 选项 /// 导入结果 - ValueTask ImportFromUIAFAsync(EntityArchive archive, List list, ImportStrategy strategy); + ValueTask ImportFromUIAFAsync(EntityArchive archive, List list, ImportStrategyKind strategy); /// /// 异步移除存档 @@ -67,5 +61,5 @@ internal interface IAchievementService /// /// 新存档 /// 存档添加结果 - ValueTask AddArchiveAsync(EntityArchive newArchive); + ValueTask AddArchiveAsync(EntityArchive newArchive); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementStatisticsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementStatisticsService.cs index b74e763b..73eb1bf0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementStatisticsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementStatisticsService.cs @@ -9,5 +9,5 @@ namespace Snap.Hutao.Service.Achievement; internal interface IAchievementStatisticsService { - ValueTask> GetAchievementStatisticsAsync(Dictionary achievementMap); + ValueTask> GetAchievementStatisticsAsync(AchievementServiceMetadataContext context, CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportStrategy.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportStrategyKind.cs similarity index 92% rename from src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportStrategy.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportStrategyKind.cs index 1451dae8..6b519aa3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportStrategy.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ImportStrategyKind.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Achievement; /// 导入策略 /// [HighQuality] -internal enum ImportStrategy +internal enum ImportStrategyKind { /// /// 贪婪合并 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsAddress.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsAddress.cs index 24243004..4d87306f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsAddress.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsAddress.cs @@ -19,12 +19,12 @@ internal static class GameFpsAddress public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerContext state, in RequiredGameModule requiredGameModule) { bool readOk = UnsafeReadModulesMemory(state.GameProcess, requiredGameModule, out VirtualMemory localMemory); - HutaoException.ThrowIfNot(readOk, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed); + HutaoException.ThrowIfNot(readOk, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed); using (localMemory) { int offset = IndexOfPattern(localMemory.AsSpan()[(int)requiredGameModule.UnityPlayer.Size..]); - HutaoException.ThrowIfNot(offset >= 0, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerInterestedPatternNotFound); + HutaoException.ThrowIfNot(offset >= 0, SH.ServiceGameUnlockerInterestedPatternNotFound); byte* pLocalMemory = (byte*)localMemory.Pointer; ref readonly Module unityPlayer = ref requiredGameModule.UnityPlayer; @@ -76,7 +76,7 @@ internal static class GameFpsAddress { value = 0; bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, ref value, out _); - HutaoException.ThrowIfNot(result, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed); + HutaoException.ThrowIfNot(result, SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed); return result; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs index 04b62d3f..ec38da02 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs @@ -30,10 +30,10 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker /// public async ValueTask UnlockAsync(CancellationToken token = default) { - HutaoException.ThrowIfNot(context.IsUnlockerValid, HutaoExceptionKind.GameFpsUnlockingFailed, "This Unlocker is invalid"); + HutaoException.ThrowIfNot(context.IsUnlockerValid, "This Unlocker is invalid"); (FindModuleResult result, RequiredGameModule gameModule) = await GameProcessModule.FindModuleAsync(context).ConfigureAwait(false); - HutaoException.ThrowIfNot(result != FindModuleResult.TimeLimitExeeded, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded); - HutaoException.ThrowIfNot(result != FindModuleResult.NoModuleFound, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleNoModuleFound); + HutaoException.ThrowIfNot(result != FindModuleResult.TimeLimitExeeded, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded); + HutaoException.ThrowIfNot(result != FindModuleResult.NoModuleFound, SH.ServiceGameUnlockerFindModuleNoModuleFound); GameFpsAddress.UnsafeFindFpsAddress(context, gameModule); context.Report(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAchievementSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAchievementSource.cs new file mode 100644 index 00000000..42821881 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAchievementSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryIdAchievementSource +{ + public Dictionary IdAchievementMap { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListAchievementSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListAchievementSource.cs new file mode 100644 index 00000000..5908fb0f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListAchievementSource.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataListAchievementSource +{ + public List Achievements { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs index 529fda39..d39d5c2f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs @@ -18,15 +18,20 @@ internal static class MetadataServiceContextExtension // List { - if (context is IMetadataListMaterialSource listMaterialSource) + if (context is IMetadataListAchievementSource listAchievementSource) { - listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); + listAchievementSource.Achievements = await metadataService.GetAchievementListAsync(token).ConfigureAwait(false); } if (context is IMetadataListGachaEventSource listGachaEventSource) { listGachaEventSource.GachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false); } + + if (context is IMetadataListMaterialSource listMaterialSource) + { + listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); + } } // Dictionary diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs index 8ed6953f..9c454d4f 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs @@ -34,11 +34,11 @@ internal sealed partial class AchievementImportDialog : ContentDialog /// 异步获取导入选项 /// /// 导入选项 - public async ValueTask> GetImportStrategyAsync() + public async ValueTask> GetImportStrategyAsync() { await taskContext.SwitchToMainThreadAsync(); ContentDialogResult result = await ShowAsync(); - ImportStrategy strategy = (ImportStrategy)ImportModeSelector.SelectedIndex; + ImportStrategyKind strategy = (ImportStrategyKind)ImportModeSelector.SelectedIndex; return new(result == ContentDialogResult.Primary, strategy); } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs index 34cae197..8779cd21 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs @@ -89,7 +89,7 @@ internal sealed partial class AchievementImporter AchievementImportDialog importDialog = await dependencies.ContentDialogFactory .CreateInstanceAsync(uiaf).ConfigureAwait(false); - (bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false); + (bool isOk, ImportStrategyKind strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false); if (!isOk) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs index 768a9a10..4373e600 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs @@ -3,11 +3,13 @@ using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control.Collection.AdvancedCollectionView; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO; using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Service.Achievement; using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification; using Snap.Hutao.View.Dialog; @@ -158,19 +160,19 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav if (isOk) { - ArchiveAddResult result = await dependencies.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false); + ArchiveAddResultKind result = await dependencies.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false); switch (result) { - case ArchiveAddResult.Added: + case ArchiveAddResultKind.Added: await dependencies.TaskContext.SwitchToMainThreadAsync(); SelectedArchive = dependencies.AchievementService.CurrentArchive; dependencies.InfoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name)); break; - case ArchiveAddResult.InvalidName: + case ArchiveAddResultKind.InvalidName: dependencies.InfoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName); break; - case ArchiveAddResult.AlreadyExists: + case ArchiveAddResultKind.AlreadyExists: dependencies.InfoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name)); break; default: @@ -264,9 +266,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav return; } - List achievements = await dependencies.MetadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false); + AchievementServiceMetadataContext context = await dependencies.MetadataService + .GetContextAsync(CancellationToken) + .ConfigureAwait(false); - if (TryGetAchievements(archive, achievements, out List? combined)) + if (TryGetAchievements(archive, context, out List? combined)) { await dependencies.TaskContext.SwitchToMainThreadAsync(); @@ -277,14 +281,14 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav } } - private bool TryGetAchievements(EntityAchievementArchive archive, List achievements, [NotNullWhen(true)] out List? combined) + private bool TryGetAchievements(EntityAchievementArchive archive, AchievementServiceMetadataContext context, [NotNullWhen(true)] out List? combined) { try { - combined = dependencies.AchievementService.GetAchievementViewList(archive, achievements); + combined = dependencies.AchievementService.GetAchievementViewList(archive, context); return true; } - catch (Core.ExceptionService.UserdataCorruptedException ex) + catch (HutaoException ex) { dependencies.InfoBarService.Error(ex); combined = default; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs index 567711ae..714cd624 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs @@ -41,7 +41,7 @@ internal sealed partial class LaunchGameShared if (!IgnoredInvalidChannelOptions.Contains(options)) { // 后台收集 - HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}"); + HutaoException.Throw($"不支持的 ChannelOptions: {options}"); } }