mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor achievement
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,14 +118,6 @@ internal static partial class EnumerableExtension
|
||||
collection.RemoveAt(collection.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到新类型的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">原始类型</typeparam>
|
||||
/// <typeparam name="TResult">新类型</typeparam>
|
||||
/// <param name="list">列表</param>
|
||||
/// <param name="selector">选择器</param>
|
||||
/// <returns>新类型的列表</returns>
|
||||
[Pure]
|
||||
public static List<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, TResult> selector)
|
||||
{
|
||||
|
||||
@@ -5,5 +5,5 @@ namespace Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
internal interface IAppDbEntity
|
||||
{
|
||||
Guid InnerId { get; set; }
|
||||
Guid InnerId { get; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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<Achievement>,
|
||||
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
|
||||
|
||||
@@ -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<TEntity> ListByArchiveId<TEntity>(this IAppDbService<TEntity> service, Guid archiveId)
|
||||
where TEntity : class, IAppDbEntityHasArchive
|
||||
{
|
||||
return service.Query(query => query.Where(e => e.ArchiveId == archiveId).ToList());
|
||||
}
|
||||
|
||||
public static ValueTask<List<TEntity>> ListByArchiveIdAsync<TEntity>(this IAppDbService<TEntity> service, Guid archiveId, CancellationToken token = default)
|
||||
where TEntity : class, IAppDbEntityHasArchive
|
||||
{
|
||||
return service.QueryAsync((query, token) => query.Where(e => e.ArchiveId == archiveId).ToListAsync(token), token);
|
||||
}
|
||||
}
|
||||
@@ -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<TEntity> List<TEntity>(this IAppDbService<TEntity> service)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Query(query => query.ToList());
|
||||
}
|
||||
|
||||
public static List<TEntity> List<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Query(query => query.Where(predicate).ToList());
|
||||
}
|
||||
|
||||
public static ValueTask<List<TEntity>> ListAsync<TEntity>(this IAppDbService<TEntity> service, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.QueryAsync((query, token) => query.ToListAsync(token), token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<TEntity>> ListAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.QueryAsync((query, token) => query.Where(predicate).ToListAsync(token), token);
|
||||
}
|
||||
|
||||
public static ObservableCollection<TEntity> ObservableCollection<TEntity>(this IAppDbService<TEntity> service)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Query(query => query.ToObservableCollection());
|
||||
}
|
||||
|
||||
public static ObservableCollection<TEntity> ObservableCollection<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Query(query => query.Where(predicate).ToObservableCollection());
|
||||
}
|
||||
}
|
||||
@@ -84,18 +84,6 @@ internal static class AppDbServiceExtension
|
||||
return service.ExecuteAsync((dbset, token) => dbset.AddRangeAndSaveAsync(entities, token), token);
|
||||
}
|
||||
|
||||
public static TEntity Single<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AsNoTracking().Single(predicate));
|
||||
}
|
||||
|
||||
public static ValueTask<TEntity> SingleAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.AsNoTracking().SingleAsync(predicate, token), token);
|
||||
}
|
||||
|
||||
public static TResult Query<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, 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<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Query(query => query.Single(predicate));
|
||||
}
|
||||
|
||||
public static ValueTask<TEntity> SingleAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.QueryAsync((query, token) => query.SingleAsync(predicate, token), token);
|
||||
}
|
||||
|
||||
public static int Update<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
|
||||
@@ -11,9 +11,6 @@ using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就数据库服务
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IAchievementDbService))]
|
||||
internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
@@ -79,7 +76,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
|
||||
public ObservableCollection<AchievementArchive> GetAchievementArchiveCollection()
|
||||
{
|
||||
return this.Query<AchievementArchive, ObservableCollection<AchievementArchive>>(query => query.ToObservableCollection());
|
||||
return this.ObservableCollection<AchievementArchive>();
|
||||
}
|
||||
|
||||
public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive, CancellationToken token = default)
|
||||
@@ -90,25 +87,21 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
|
||||
public List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId)
|
||||
{
|
||||
return this.Query<EntityAchievement, List<EntityAchievement>>(query => [.. query.Where(a => a.ArchiveId == archiveId)]);
|
||||
return this.ListByArchiveId<EntityAchievement>(archiveId);
|
||||
}
|
||||
|
||||
public ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId, CancellationToken token = default)
|
||||
{
|
||||
return this.QueryAsync<EntityAchievement, List<EntityAchievement>>(
|
||||
(query, token) => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToListAsync(token),
|
||||
token);
|
||||
return this.ListByArchiveIdAsync<EntityAchievement>(archiveId, token);
|
||||
}
|
||||
|
||||
public List<AchievementArchive> GetAchievementArchiveList()
|
||||
{
|
||||
return this.Query<AchievementArchive, List<AchievementArchive>>(query => [.. query]);
|
||||
return this.List<AchievementArchive>();
|
||||
}
|
||||
|
||||
public async ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync(CancellationToken token = default)
|
||||
public ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync(CancellationToken token = default)
|
||||
{
|
||||
return await this.QueryAsync<AchievementArchive, List<AchievementArchive>>(query => query.ToListAsync()).ConfigureAwait(false);
|
||||
return this.ListAsync<AchievementArchive>(token);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 集合部分
|
||||
/// </summary>
|
||||
internal sealed partial class AchievementService
|
||||
{
|
||||
private ObservableCollection<AchievementArchive>? archiveCollection;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AchievementArchive? CurrentArchive
|
||||
{
|
||||
get => dbCurrent.Current;
|
||||
set => dbCurrent.Current = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ObservableCollection<AchievementArchive> ArchiveCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (archiveCollection is null)
|
||||
{
|
||||
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
|
||||
CurrentArchive = archiveCollection.SelectedOrDefault();
|
||||
}
|
||||
|
||||
return archiveCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ArchiveAddResult> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 数据交换部分
|
||||
/// </summary>
|
||||
internal sealed partial class AchievementService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ImportResult> ImportFromUIAFAsync(AchievementArchive archive, List<UIAFItem> list, ImportStrategy strategy)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
Guid archiveId = archive.InnerId;
|
||||
|
||||
switch (strategy)
|
||||
{
|
||||
case ImportStrategy.AggressiveMerge:
|
||||
{
|
||||
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, true);
|
||||
}
|
||||
|
||||
case ImportStrategy.LazyMerge:
|
||||
{
|
||||
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, false);
|
||||
}
|
||||
|
||||
case ImportStrategy.Overwrite:
|
||||
{
|
||||
IEnumerable<EntityAchievement> orederedUIAF = list
|
||||
.Select(uiaf => EntityAchievement.From(archiveId, uiaf))
|
||||
.OrderBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF);
|
||||
}
|
||||
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<EntityAchievement> entities = await achievementDbService
|
||||
.GetAchievementListByArchiveIdAsync(archive.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
List<UIAFItem> list = entities.SelectList(UIAFItem.From);
|
||||
|
||||
return new()
|
||||
{
|
||||
Info = UIAFInfo.From(runtimeOptions),
|
||||
List = list,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 成就服务
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<AchievementView> GetAchievementViewList(AchievementArchive archive, List<MetadataAchievement> metadata)
|
||||
private ObservableCollection<AchievementArchive>? archiveCollection;
|
||||
|
||||
public AchievementArchive? CurrentArchive
|
||||
{
|
||||
get => dbCurrent.Current;
|
||||
set => dbCurrent.Current = value;
|
||||
}
|
||||
|
||||
public ObservableCollection<AchievementArchive> ArchiveCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (archiveCollection is null)
|
||||
{
|
||||
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
|
||||
CurrentArchive = archiveCollection.SelectedOrDefault();
|
||||
}
|
||||
|
||||
return archiveCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public List<AchievementView> GetAchievementViewList(AchievementArchive archive, AchievementServiceMetadataContext context)
|
||||
{
|
||||
Dictionary<AchievementId, EntityAchievement> 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);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SaveAchievement(AchievementView achievement)
|
||||
{
|
||||
achievementDbService.OverwriteAchievement(achievement.Entity);
|
||||
}
|
||||
|
||||
public async ValueTask<ArchiveAddResultKind> 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<ImportResult> ImportFromUIAFAsync(AchievementArchive archive, List<UIAFItem> list, ImportStrategyKind strategy)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
Guid archiveId = archive.InnerId;
|
||||
|
||||
switch (strategy)
|
||||
{
|
||||
case ImportStrategyKind.AggressiveMerge:
|
||||
{
|
||||
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, true);
|
||||
}
|
||||
|
||||
case ImportStrategyKind.LazyMerge:
|
||||
{
|
||||
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, false);
|
||||
}
|
||||
|
||||
case ImportStrategyKind.Overwrite:
|
||||
{
|
||||
IEnumerable<EntityAchievement> orederedUIAF = list
|
||||
.SelectList(uiaf => EntityAchievement.From(archiveId, uiaf))
|
||||
.SortBy(a => a.Id);
|
||||
return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF);
|
||||
}
|
||||
|
||||
default:
|
||||
throw HutaoException.NotSupported();
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<EntityAchievement> entities = await achievementDbService
|
||||
.GetAchievementListByArchiveIdAsync(archive.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
List<UIAFItem> list = entities.SelectList(UIAFItem.From);
|
||||
|
||||
return new()
|
||||
{
|
||||
Info = UIAFInfo.From(runtimeOptions),
|
||||
List = list,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<MetadataAchievement> Achievements { get; set; } = default!;
|
||||
|
||||
public Dictionary<AchievementId, MetadataAchievement> IdAchievementMap { get; set; } = default!;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap)
|
||||
public async ValueTask<List<AchievementStatistics>> GetAchievementStatisticsAsync(AchievementServiceMetadataContext context, CancellationToken token = default)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
List<AchievementStatistics> 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<EntityAchievement> 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])),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
/// 存档添加结果
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal enum ArchiveAddResult
|
||||
internal enum ArchiveAddResultKind
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加成功
|
||||
@@ -32,13 +32,7 @@ internal interface IAchievementService
|
||||
/// <returns>UIAF</returns>
|
||||
ValueTask<UIAF> ExportToUIAFAsync(EntityArchive selectedArchive);
|
||||
|
||||
/// <summary>
|
||||
/// 获取整合的成就
|
||||
/// </summary>
|
||||
/// <param name="archive">用户</param>
|
||||
/// <param name="metadata">元数据</param>
|
||||
/// <returns>整合的成就</returns>
|
||||
List<AchievementView> GetAchievementViewList(EntityArchive archive, List<MetadataAchievement> metadata);
|
||||
List<AchievementView> GetAchievementViewList(EntityArchive archive, AchievementServiceMetadataContext context);
|
||||
|
||||
/// <summary>
|
||||
/// 异步导入UIAF数据
|
||||
@@ -47,7 +41,7 @@ internal interface IAchievementService
|
||||
/// <param name="list">UIAF数据</param>
|
||||
/// <param name="strategy">选项</param>
|
||||
/// <returns>导入结果</returns>
|
||||
ValueTask<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy);
|
||||
ValueTask<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportStrategyKind strategy);
|
||||
|
||||
/// <summary>
|
||||
/// 异步移除存档
|
||||
@@ -67,5 +61,5 @@ internal interface IAchievementService
|
||||
/// </summary>
|
||||
/// <param name="newArchive">新存档</param>
|
||||
/// <returns>存档添加结果</returns>
|
||||
ValueTask<ArchiveAddResult> AddArchiveAsync(EntityArchive newArchive);
|
||||
ValueTask<ArchiveAddResultKind> AddArchiveAsync(EntityArchive newArchive);
|
||||
}
|
||||
@@ -9,5 +9,5 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
|
||||
internal interface IAchievementStatisticsService
|
||||
{
|
||||
ValueTask<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap);
|
||||
ValueTask<List<AchievementStatistics>> GetAchievementStatisticsAsync(AchievementServiceMetadataContext context, CancellationToken token = default);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
/// 导入策略
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal enum ImportStrategy
|
||||
internal enum ImportStrategyKind
|
||||
{
|
||||
/// <summary>
|
||||
/// 贪婪合并
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,10 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> 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();
|
||||
|
||||
@@ -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<AchievementId, Model.Metadata.Achievement.Achievement> IdAchievementMap { get; set; }
|
||||
}
|
||||
@@ -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<Model.Metadata.Achievement.Achievement> Achievements { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -34,11 +34,11 @@ internal sealed partial class AchievementImportDialog : ContentDialog
|
||||
/// 异步获取导入选项
|
||||
/// </summary>
|
||||
/// <returns>导入选项</returns>
|
||||
public async ValueTask<ValueResult<bool, ImportStrategy>> GetImportStrategyAsync()
|
||||
public async ValueTask<ValueResult<bool, ImportStrategyKind>> GetImportStrategyAsync()
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
ImportStrategy strategy = (ImportStrategy)ImportModeSelector.SelectedIndex;
|
||||
ImportStrategyKind strategy = (ImportStrategyKind)ImportModeSelector.SelectedIndex;
|
||||
|
||||
return new(result == ContentDialogResult.Primary, strategy);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ internal sealed partial class AchievementImporter
|
||||
|
||||
AchievementImportDialog importDialog = await dependencies.ContentDialogFactory
|
||||
.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategyKind strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
|
||||
@@ -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<MetadataAchievement> achievements = await dependencies.MetadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
|
||||
AchievementServiceMetadataContext context = await dependencies.MetadataService
|
||||
.GetContextAsync<AchievementServiceMetadataContext>(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
||||
if (TryGetAchievements(archive, context, out List<AchievementView>? combined))
|
||||
{
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
@@ -277,14 +281,14 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetAchievements(EntityAchievementArchive archive, List<MetadataAchievement> achievements, [NotNullWhen(true)] out List<AchievementView>? combined)
|
||||
private bool TryGetAchievements(EntityAchievementArchive archive, AchievementServiceMetadataContext context, [NotNullWhen(true)] out List<AchievementView>? 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;
|
||||
|
||||
@@ -41,7 +41,7 @@ internal sealed partial class LaunchGameShared
|
||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||
{
|
||||
// 后台收集
|
||||
HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}");
|
||||
HutaoException.Throw($"不支持的 ChannelOptions: {options}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user