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}");
}
}