Compare commits

..

5 Commits

Author SHA1 Message Date
qhy040404
5182fd8577 trigger gc after viewmodel dispose 2024-04-28 14:19:24 +08:00
DismissedLight
ff2521c02c Merge pull request #1581 from DGP-Studio/feat/custom_sa_homa_not_login_dialog 2024-04-28 10:25:15 +08:00
Lightczx
5954c1a0ab fix build failed 2024-04-26 14:49:50 +08:00
Lightczx
4dc753bf5a Merge branch 'develop' of https://github.com/DGP-Studio/Snap.Hutao into develop 2024-04-26 14:47:43 +08:00
Lightczx
bd3617c15a refactor cultivation 2024-04-26 14:47:40 +08:00
24 changed files with 241 additions and 315 deletions

View File

@@ -104,6 +104,7 @@ internal class ScopedPage : Page
// Dispose the scope
pageScope.Dispose();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("cultivate_entries")]
internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry, CultivateType, uint>
internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry, CultivateType, uint>, IAppDbEntity
{
/// <summary>
/// 内部Id

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Abstraction;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -13,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("cultivate_projects")]
internal sealed class CultivateProject : ISelectable, IMappingFrom<CultivateProject, string, string>
internal sealed class CultivateProject : ISelectable, IMappingFrom<CultivateProject, string, string>, IAppDbEntity
{
/// <summary>
/// 内部Id

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Abstraction;
namespace Snap.Hutao.Service.Abstraction;
@@ -12,13 +11,25 @@ internal static class AppDbServiceAppDbEntityExtension
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class, IAppDbEntity
{
return service.Execute(dbset => dbset.ExecuteDeleteWhere(e => e.InnerId == entity.InnerId));
return service.DeleteByInnerId(entity.InnerId);
}
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, Guid innerId)
where TEntity : class, IAppDbEntity
{
return service.Delete(e => e.InnerId == innerId);
}
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class, IAppDbEntity
{
return service.ExecuteAsync((dbset, token) => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId, token), token);
return service.DeleteByInnerIdAsync(entity.InnerId, token);
}
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, Guid innerId, CancellationToken token = default)
where TEntity : class, IAppDbEntity
{
return service.DeleteAsync(e => e.InnerId == innerId, token);
}
public static List<TEntity> ListByArchiveId<TEntity>(this IAppDbService<TEntity> service, Guid archiveId)

View File

@@ -30,7 +30,13 @@ internal static class AppDbServiceCollectionExtension
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);
return service.ListAsync(query => query.Where(predicate), token);
}
public static ValueTask<List<TResult>> ListAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, IQueryable<TResult>> query, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query1, token) => query(query1).ToListAsync(token), token);
}
public static ObservableCollection<TEntity> ObservableCollection<TEntity>(this IAppDbService<TEntity> service)

View File

@@ -126,6 +126,18 @@ internal static class AppDbServiceExtension
return service.QueryAsync((query, token) => query.SingleAsync(predicate, token), token);
}
public static TEntity? SingleOrDefault<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Query(query => query.SingleOrDefault(predicate));
}
public static ValueTask<TEntity?> SingleOrDefaultAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query, token) => query.SingleOrDefaultAsync(predicate, token), token);
}
public static int Update<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class
{
@@ -144,9 +156,21 @@ internal static class AppDbServiceExtension
return service.Execute(dbset => dbset.RemoveAndSave(entity));
}
public static int Delete<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Execute(dbset => dbset.Where(predicate).ExecuteDelete());
}
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.RemoveAndSaveAsync(entity, token), token);
}
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.Where(predicate).ExecuteDeleteAsync(token), token);
}
}

View File

@@ -46,13 +46,12 @@ internal sealed partial class AchievementDbService : IAchievementDbService
[SuppressMessage("", "CA1305")]
public ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take, CancellationToken token = default)
{
return this.QueryAsync<EntityAchievement, List<EntityAchievement>>(
(query, token) => query
return this.ListAsync<EntityAchievement, EntityAchievement>(
query => query
.Where(a => a.ArchiveId == archiveId)
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
.OrderByDescending(a => a.Time.ToString())
.Take(take)
.ToListAsync(token),
.Take(take),
token);
}

View File

@@ -12,12 +12,5 @@ namespace Snap.Hutao.Service.Announcement;
[HighQuality]
internal interface IAnnouncementService
{
/// <summary>
/// 异步获取游戏公告与活动,通常会进行缓存
/// </summary>
/// <param name="languageCode">语言代码</param>
/// <param name="region">服务器</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>公告包装器</returns>
ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default);
ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken token = default);
}

View File

@@ -29,11 +29,11 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
public void RemoveAvatarInfoRangeByUid(string uid)
{
this.Execute(dbset => dbset.Where(i => i.Uid == uid).ExecuteDelete());
this.Delete(i => i.Uid == uid);
}
public async ValueTask RemoveAvatarInfoRangeByUidAsync(string uid, CancellationToken token = default)
{
await this.ExecuteAsync((dbset, token) => dbset.Where(i => i.Uid == uid).ExecuteDeleteAsync(token), token).ConfigureAwait(false);
await this.DeleteAsync(i => i.Uid == uid, token).ConfigureAwait(false);
}
}

View File

@@ -25,11 +25,11 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
private readonly IMetadataService metadataService;
private readonly ISummaryFactory summaryFactory;
public async ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default)
public async ValueTask<ValueResult<RefreshResultKind, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default)
{
if (!await metadataService.InitializeAsync().ConfigureAwait(false))
{
return new(RefreshResult.MetadataNotInitialized, null);
return new(RefreshResultKind.MetadataNotInitialized, null);
}
switch (refreshOption)
@@ -40,43 +40,43 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
if (resp is null)
{
return new(RefreshResult.APIUnavailable, default);
return new(RefreshResultKind.APIUnavailable, default);
}
if (!string.IsNullOrEmpty(resp.Message))
{
return new(RefreshResult.StatusCodeNotSucceed, new Summary { Message = resp.Message });
return new(RefreshResultKind.StatusCodeNotSucceed, new Summary { Message = resp.Message });
}
if (!resp.IsValid)
{
return new(RefreshResult.ShowcaseNotOpen, default);
return new(RefreshResultKind.ShowcaseNotOpen, default);
}
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcaseAsync(userAndUid.Uid.Value, resp.AvatarInfoList, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
return new(RefreshResultKind.Ok, summary);
}
case RefreshOption.RequestFromHoyolabGameRecord:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
return new(RefreshResultKind.Ok, summary);
}
case RefreshOption.RequestFromHoyolabCalculate:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
return new(RefreshResultKind.Ok, summary);
}
default:
{
List<EntityAvatarInfo> list = await avatarInfoDbService.GetAvatarInfoListByUidAsync(userAndUid.Uid.Value, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary);
return new(RefreshResultKind.Ok, summary.Avatars.Count == 0 ? null : summary);
}
}
}

View File

@@ -19,5 +19,5 @@ internal interface IAvatarInfoService
/// <param name="refreshOption">刷新选项</param>
/// <param name="token">取消令牌</param>
/// <returns>总览数据</returns>
ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default);
ValueTask<ValueResult<RefreshResultKind, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default);
}

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.AvatarInfo;
/// 刷新结果
/// </summary>
[HighQuality]
internal enum RefreshResult
internal enum RefreshResultKind
{
/// <summary>
/// 正常

View File

@@ -26,11 +26,11 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private HashSet<string> currentBackgroundPathSet;
private HashSet<string>? currentBackgroundPathSet;
public async ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous)
public async ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous, CancellationToken token = default)
{
HashSet<string> backgroundSet = await SkipOrInitBackgroundAsync().ConfigureAwait(false);
HashSet<string> backgroundSet = await SkipOrInitBackgroundAsync(token).ConfigureAwait(false);
if (backgroundSet.Count <= 0)
{
@@ -79,7 +79,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
}
}
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync()
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync(CancellationToken token = default)
{
switch (appOptions.BackgroundImageType)
{
@@ -90,7 +90,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
currentBackgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.EnumerateFiles(backgroundFolder, "*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
}
@@ -100,13 +100,13 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
}
case BackgroundImageType.HutaoBing:
await SetCurrentBackgroundPathSetAsync(client => client.GetBingWallpaperAsync()).ConfigureAwait(false);
await SetCurrentBackgroundPathSetAsync((client, token) => client.GetBingWallpaperAsync(token), token).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoDaily:
await SetCurrentBackgroundPathSetAsync(client => client.GetTodayWallpaperAsync()).ConfigureAwait(false);
await SetCurrentBackgroundPathSetAsync((client, token) => client.GetTodayWallpaperAsync(token), token).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoOfficialLauncher:
await SetCurrentBackgroundPathSetAsync(client => client.GetLauncherWallpaperAsync()).ConfigureAwait(false);
await SetCurrentBackgroundPathSetAsync((client, token) => client.GetLauncherWallpaperAsync(token), token).ConfigureAwait(false);
break;
default:
currentBackgroundPathSet = [];
@@ -116,10 +116,10 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
currentBackgroundPathSet ??= [];
return currentBackgroundPathSet;
async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, ValueTask<Response<Wallpaper>>> responseFactory)
async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, CancellationToken, ValueTask<Response<Wallpaper>>> responseFactory, CancellationToken token = default)
{
HutaoWallpaperClient wallpaperClient = serviceProvider.GetRequiredService<HutaoWallpaperClient>();
Response<Wallpaper> response = await responseFactory(wallpaperClient).ConfigureAwait(false);
Response<Wallpaper> response = await responseFactory(wallpaperClient, token).ConfigureAwait(false);
if (response is { Data: Wallpaper wallpaper })
{
await taskContext.SwitchToMainThreadAsync();

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.BackgroundImage;
internal interface IBackgroundImageService
{
ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous);
ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous, CancellationToken token = default);
}

View File

@@ -2,9 +2,8 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.Abstraction;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Cultivation;
@@ -15,182 +14,90 @@ internal sealed partial class CultivationDbService : ICultivationDbService
{
private readonly IServiceProvider serviceProvider;
public IServiceProvider ServiceProvider { get => serviceProvider; }
public List<InventoryItem> GetInventoryItemListByProjectId(Guid projectId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
IQueryable<InventoryItem> result = appDbContext.InventoryItems.AsNoTracking().Where(a => a.ProjectId == projectId);
return [.. result];
}
return this.List<InventoryItem>(i => i.ProjectId == projectId);
}
public async ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId)
public ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.InventoryItems
.AsNoTracking()
.Where(a => a.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
}
return this.ListAsync<InventoryItem>(i => i.ProjectId == projectId, token);
}
public async ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId)
public ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
}
return this.ListAsync<CultivateEntry>(e => e.ProjectId == projectId, token);
}
public async ValueTask<List<CultivateEntry>> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId)
public ValueTask<List<CultivateEntry>> GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(Guid projectId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.Include(e => e.LevelInformation)
.ToListAsync()
.ConfigureAwait(false);
}
return this.ListAsync<CultivateEntry, CultivateEntry>(query => query.Where(e => e.ProjectId == projectId).Include(e => e.LevelInformation), token);
}
public async ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId)
public ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateItems
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
}
return this.ListAsync<CultivateItem, CultivateItem>(query => query.Where(i => i.EntryId == entryId).OrderBy(i => i.ItemId), token);
}
public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId)
public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateEntries
.ExecuteDeleteWhereAsync(i => i.InnerId == entryId)
.ConfigureAwait(false);
}
await this.DeleteByInnerIdAsync<CultivateEntry>(entryId, token).ConfigureAwait(false);
}
public void UpdateCultivateItem(CultivateItem item)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.CultivateItems.UpdateAndSave(item);
}
this.Update(item);
}
public async ValueTask UpdateCultivateItemAsync(CultivateItem item)
public async ValueTask UpdateCultivateItemAsync(CultivateItem item, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems.UpdateAndSaveAsync(item).ConfigureAwait(false);
}
await this.UpdateAsync(item, token).ConfigureAwait(false);
}
public async ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId)
public async ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateEntries
.SingleOrDefaultAsync(e => e.ProjectId == projectId && e.Id == itemId)
.ConfigureAwait(false);
}
return await this.SingleOrDefaultAsync<CultivateEntry>(e => e.ProjectId == projectId && e.Id == itemId, token).ConfigureAwait(false);
}
public async ValueTask AddCultivateEntryAsync(CultivateEntry entry)
public async ValueTask AddCultivateEntryAsync(CultivateEntry entry, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false);
}
await this.AddAsync(entry, token).ConfigureAwait(false);
}
public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId)
public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems
.ExecuteDeleteWhereAsync(i => i.EntryId == entryId)
.ConfigureAwait(false);
}
await this.DeleteAsync<CultivateItem>(i => i.EntryId == entryId, token).ConfigureAwait(false);
}
public async ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd)
public async ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false);
}
await this.AddRangeAsync(toAdd, token).ConfigureAwait(false);
}
public async ValueTask AddCultivateProjectAsync(CultivateProject project)
public async ValueTask AddCultivateProjectAsync(CultivateProject project, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateProjects.AddAndSaveAsync(project).ConfigureAwait(false);
}
await this.AddAsync(project, token).ConfigureAwait(false);
}
public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId)
public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateProjects
.ExecuteDeleteWhereAsync(p => p.InnerId == projectId)
.ConfigureAwait(false);
}
await this.DeleteByInnerIdAsync<CultivateProject>(projectId, token).ConfigureAwait(false);
}
public ObservableCollection<CultivateProject> GetCultivateProjectCollection()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.CultivateProjects.AsNoTracking().ToObservableCollection();
}
return this.ObservableCollection<CultivateProject>();
}
public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId)
public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.LevelInformations.ExecuteDeleteWhereAsync(l => l.EntryId == entryId).ConfigureAwait(false);
}
await this.DeleteAsync<CultivateEntryLevelInformation>(l => l.EntryId == entryId, token).ConfigureAwait(false);
}
public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation)
public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.LevelInformations.AddAndSaveAsync(levelInformation).ConfigureAwait(false);
}
await this.AddAsync(levelInformation, token).ConfigureAwait(false);
}
}

View File

@@ -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.Cultivation;
/// <summary>
/// 集合部分
/// </summary>
internal sealed partial class CultivationService
{
private ObservableCollection<CultivateProject>? projects;
/// <inheritdoc/>
public CultivateProject? Current
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public ObservableCollection<CultivateProject> ProjectCollection
{
get
{
if (projects is null)
{
projects = cultivationDbService.GetCultivateProjectCollection();
Current ??= projects.SelectedOrDefault();
}
return projects;
}
}
/// <inheritdoc/>
public async ValueTask<ProjectAddResult> TryAddProjectAsync(CultivateProject project)
{
if (string.IsNullOrWhiteSpace(project.Name))
{
return ProjectAddResult.InvalidName;
}
ArgumentNullException.ThrowIfNull(projects);
if (projects.Any(a => a.Name == project.Name))
{
return ProjectAddResult.AlreadyExists;
}
// Sync cache
await taskContext.SwitchToMainThreadAsync();
projects.Add(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.AddCultivateProjectAsync(project).ConfigureAwait(false);
return ProjectAddResult.Added;
}
/// <inheritdoc/>
public async ValueTask RemoveProjectAsync(CultivateProject project)
{
ArgumentNullException.ThrowIfNull(projects);
// Sync cache
// Keep this on main thread.
await taskContext.SwitchToMainThreadAsync();
projects.Remove(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false);
}
}

View File

@@ -4,13 +4,13 @@
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Service.Inventory;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.Cultivation;
using System.Collections.ObjectModel;
using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
namespace Snap.Hutao.Service.Cultivation;
@@ -25,28 +25,46 @@ internal sealed partial class CultivationService : ICultivationService
private readonly ScopedDbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent;
private readonly ICultivationDbService cultivationDbService;
private readonly IInventoryDbService inventoryDbService;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private ObservableCollection<CultivateProject>? projects;
/// <inheritdoc/>
public CultivateProject? Current
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public ObservableCollection<CultivateProject> ProjectCollection
{
get
{
if (projects is null)
{
projects = cultivationDbService.GetCultivateProjectCollection();
Current ??= projects.SelectedOrDefault();
}
return projects;
}
}
/// <inheritdoc/>
public List<InventoryItemView> GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand)
{
using (IServiceScope scope = serviceProvider.CreateScope())
Guid projectId = cultivateProject.InnerId;
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventoryMaterial())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Guid projectId = cultivateProject.InnerId;
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventoryMaterial())
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand));
}
return results;
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand));
}
return results;
}
/// <inheritdoc/>
@@ -54,13 +72,15 @@ internal sealed partial class CultivationService : ICultivationService
{
await taskContext.SwitchToBackgroundAsync();
List<CultivateEntry> entries = await cultivationDbService
.GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(cultivateProject.InnerId)
.GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(cultivateProject.InnerId)
.ConfigureAwait(false);
List<CultivateEntryView> resultEntries = new(entries.Count);
foreach (CultivateEntry entry in entries)
{
List<CultivateItemView> entryItems = [];
// Async operation here, thus we can't use the Span trick.
foreach (CultivateItem cultivateItem in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
{
entryItems.Add(new(cultivateItem, context.GetMaterial(cultivateItem.ItemId)));
@@ -78,9 +98,7 @@ internal sealed partial class CultivationService : ICultivationService
resultEntries.Add(new(entry, item, entryItems));
}
return resultEntries
.OrderByDescending(e => e.IsToday)
.ToObservableCollection();
return resultEntries.SortByDescending(e => e.IsToday).ToObservableCollection();
}
/// <inheritdoc/>
@@ -92,11 +110,9 @@ internal sealed partial class CultivationService : ICultivationService
Guid projectId = cultivateProject.InnerId;
token.ThrowIfCancellationRequested();
foreach (CultivateEntry entry in await cultivationDbService.GetCultivateEntryListByProjectIdAsync(projectId).ConfigureAwait(false))
foreach (CultivateEntry entry in await cultivationDbService.GetCultivateEntryListByProjectIdAsync(projectId, token).ConfigureAwait(false))
{
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId, token).ConfigureAwait(false))
{
if (item.IsFinished)
{
@@ -114,9 +130,7 @@ internal sealed partial class CultivationService : ICultivationService
}
}
token.ThrowIfCancellationRequested();
foreach (InventoryItem inventoryItem in await cultivationDbService.GetInventoryItemListByProjectIdAsync(projectId).ConfigureAwait(false))
foreach (InventoryItem inventoryItem in await cultivationDbService.GetInventoryItemListByProjectIdAsync(projectId, token).ConfigureAwait(false))
{
if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is { } existedItem)
{
@@ -124,15 +138,12 @@ internal sealed partial class CultivationService : ICultivationService
}
}
token.ThrowIfCancellationRequested();
return resultItems.SortBy(item => item.Inner.Id, MaterialIdComparer.Shared).ToObservableCollection();
}
/// <inheritdoc/>
public async ValueTask RemoveCultivateEntryAsync(Guid entryId)
{
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateEntryByIdAsync(entryId).ConfigureAwait(false);
}
@@ -149,7 +160,7 @@ internal sealed partial class CultivationService : ICultivationService
}
/// <inheritdoc/>
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items, LevelInformation levelInformation)
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<CalculateItem> items, LevelInformation levelInformation)
{
if (items.Count == 0)
{
@@ -190,4 +201,45 @@ internal sealed partial class CultivationService : ICultivationService
return true;
}
/// <inheritdoc/>
public async ValueTask<ProjectAddResultKind> TryAddProjectAsync(CultivateProject project)
{
if (string.IsNullOrWhiteSpace(project.Name))
{
return ProjectAddResultKind.InvalidName;
}
ArgumentNullException.ThrowIfNull(projects);
if (projects.Any(a => a.Name == project.Name))
{
return ProjectAddResultKind.AlreadyExists;
}
// Sync cache
await taskContext.SwitchToMainThreadAsync();
projects.Add(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.AddCultivateProjectAsync(project).ConfigureAwait(false);
return ProjectAddResultKind.Added;
}
/// <inheritdoc/>
public async ValueTask RemoveProjectAsync(CultivateProject project)
{
ArgumentNullException.ThrowIfNull(projects);
// Sync cache
// Keep this on main thread.
await taskContext.SwitchToMainThreadAsync();
projects.Remove(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false);
}
}

View File

@@ -2,43 +2,48 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Cultivation;
internal interface ICultivationDbService
internal interface ICultivationDbService : IAppDbService<InventoryItem>,
IAppDbService<CultivateEntryLevelInformation>,
IAppDbService<CultivateProject>,
IAppDbService<CultivateEntry>,
IAppDbService<CultivateItem>
{
ValueTask AddCultivateProjectAsync(CultivateProject project);
ValueTask AddCultivateProjectAsync(CultivateProject project, CancellationToken token = default);
ValueTask RemoveCultivateEntryByIdAsync(Guid entryId);
ValueTask RemoveCultivateEntryByIdAsync(Guid entryId, CancellationToken token = default);
ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId);
ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId, CancellationToken token = default);
ValueTask RemoveCultivateProjectByIdAsync(Guid projectId);
ValueTask RemoveCultivateProjectByIdAsync(Guid projectId, CancellationToken token = default);
ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId);
ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId, CancellationToken token = default);
ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId);
ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId, CancellationToken token = default);
ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId);
ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId, CancellationToken token = default);
ObservableCollection<CultivateProject> GetCultivateProjectCollection();
List<InventoryItem> GetInventoryItemListByProjectId(Guid projectId);
ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId);
ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId, CancellationToken token = default);
ValueTask AddCultivateEntryAsync(CultivateEntry entry);
ValueTask AddCultivateEntryAsync(CultivateEntry entry, CancellationToken token = default);
ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd);
ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd, CancellationToken token = default);
void UpdateCultivateItem(CultivateItem item);
ValueTask UpdateCultivateItemAsync(CultivateItem item);
ValueTask UpdateCultivateItemAsync(CultivateItem item, CancellationToken token = default);
ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId);
ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId, CancellationToken token = default);
ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation);
ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation, CancellationToken token = default);
ValueTask<List<CultivateEntry>> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId);
ValueTask<List<CultivateEntry>> GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(Guid projectId, CancellationToken token = default);
}

View File

@@ -65,5 +65,5 @@ internal interface ICultivationService
/// </summary>
/// <param name="project">项目</param>
/// <returns>添加操作的结果</returns>
ValueTask<ProjectAddResult> TryAddProjectAsync(CultivateProject project);
ValueTask<ProjectAddResultKind> TryAddProjectAsync(CultivateProject project);
}

View File

@@ -8,9 +8,10 @@ namespace Snap.Hutao.Service.Cultivation;
internal sealed class MaterialIdComparer : IComparer<MaterialId>
{
private static readonly Lazy<MaterialIdComparer> LazyShared = new(() => new());
private static MaterialIdComparer? shared;
private static object? syncRoot;
public static MaterialIdComparer Shared { get => LazyShared.Value; }
public static MaterialIdComparer Shared { get => LazyInitializer.EnsureInitialized(ref shared, ref syncRoot, () => new()); }
public int Compare(MaterialId x, MaterialId y)
{

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Cultivation;
/// 项目添加结果
/// </summary>
[HighQuality]
internal enum ProjectAddResult
internal enum ProjectAddResultKind
{
/// <summary>
/// 添加成功

View File

@@ -25,7 +25,7 @@ internal sealed partial class DailyNoteNotificationOperation
private readonly ITaskContext taskContext;
private readonly IGameServiceFacade gameService;
private readonly BindingClient bindingClient;
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly DailyNoteOptions options;
private readonly RuntimeOptions runtimeOptions;
@@ -58,14 +58,19 @@ internal sealed partial class DailyNoteNotificationOperation
}
else
{
Response<ListWrapper<UserGameRole>> rolesResponse = await bindingClient
.GetUserGameRolesOverseaAwareAsync(entry.User)
.ConfigureAwait(false);
if (rolesResponse.IsOk())
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
List<UserGameRole> roles = rolesResponse.Data.List;
attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? ToastAttributionUnknown;
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
Response<ListWrapper<UserGameRole>> rolesResponse = await bindingClient
.GetUserGameRolesOverseaAwareAsync(entry.User)
.ConfigureAwait(false);
if (rolesResponse.IsOk())
{
List<UserGameRole> roles = rolesResponse.Data.List;
attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? ToastAttributionUnknown;
}
}
}

View File

@@ -120,7 +120,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
{
try
{
ValueResult<RefreshResult, Summary?> summaryResult;
ValueResult<RefreshResultKind, Summary?> summaryResult;
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ContentDialog dialog = await contentDialogFactory
@@ -135,8 +135,8 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
}
}
(RefreshResult result, Summary? summary) = summaryResult;
if (result == RefreshResult.Ok)
(RefreshResultKind result, Summary? summary) = summaryResult;
if (result == RefreshResultKind.Ok)
{
await taskContext.SwitchToMainThreadAsync();
Summary = summary;
@@ -146,16 +146,16 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
{
switch (result)
{
case RefreshResult.APIUnavailable:
case RefreshResultKind.APIUnavailable:
infoBarService.Warning(SH.ViewModelAvatarPropertyEnkaApiUnavailable);
break;
case RefreshResult.StatusCodeNotSucceed:
case RefreshResultKind.StatusCodeNotSucceed:
ArgumentNullException.ThrowIfNull(summary);
infoBarService.Warning(summary.Message);
break;
case RefreshResult.ShowcaseNotOpen:
case RefreshResultKind.ShowcaseNotOpen:
infoBarService.Warning(SH.ViewModelAvatarPropertyShowcaseNotOpen);
break;
}

View File

@@ -86,15 +86,15 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
switch (await cultivationService.TryAddProjectAsync(project).ConfigureAwait(false))
{
case ProjectAddResult.Added:
case ProjectAddResultKind.Added:
infoBarService.Success(SH.ViewModelCultivationProjectAdded);
await taskContext.SwitchToMainThreadAsync();
SelectedProject = project;
break;
case ProjectAddResult.InvalidName:
case ProjectAddResultKind.InvalidName:
infoBarService.Information(SH.ViewModelCultivationProjectInvalidName);
break;
case ProjectAddResult.AlreadyExists:
case ProjectAddResultKind.AlreadyExists:
infoBarService.Information(SH.ViewModelCultivationProjectAlreadyExists);
break;
default: