diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs index bb0b1378..afa03b4c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs @@ -27,8 +27,8 @@ internal sealed partial class SummaryFactory : ISummaryFactory IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false), IdReliquaryMainAffixMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false), IdReliquarySubAffixMap = await metadataService.GetIdToReliquarySubAffixMapAsync(token).ConfigureAwait(false), - ReliquaryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false), - Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false), + ReliquaryLevels = await metadataService.GetReliquaryLevelListAsync(token).ConfigureAwait(false), + Reliquaries = await metadataService.GetReliquaryListAsync(token).ConfigureAwait(false), }; IOrderedEnumerable avatars = avatarInfos diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs index 48bda3db..56506999 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -31,7 +31,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory public async ValueTask CreateAsync(List items, GachaLogServiceMetadataContext context) { await taskContext.SwitchToBackgroundAsync(); - List gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false); + List gachaEvents = await metadataService.GetGachaEventListAsync().ConfigureAwait(false); List historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context)); return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs index 5b50ca21..c5d95cc4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogHutaoCloudService.cs @@ -96,7 +96,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer { Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); - List gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false); + List gachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false); HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents); GachaEventStatistics raw = response.Data; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoSpiralAbyssStatisticsCache.cs similarity index 96% rename from src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoSpiralAbyssStatisticsCache.cs index c5c049da..401efa83 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoSpiralAbyssStatisticsCache.cs @@ -12,13 +12,10 @@ using Snap.Hutao.Web.Hutao.SpiralAbyss; namespace Snap.Hutao.Service.Hutao; -/// -/// 胡桃 API 缓存 -/// [HighQuality] [ConstructorGenerated] -[Injection(InjectAs.Singleton, typeof(IHutaoCache))] -internal sealed partial class HutaoCache : IHutaoCache +[Injection(InjectAs.Singleton, typeof(IHutaoSpiralAbyssStatisticsCache))] +internal sealed partial class HutaoSpiralAbyssStatisticsCache : IHutaoSpiralAbyssStatisticsCache { private readonly IMetadataService metadataService; private readonly IServiceProvider serviceProvider; @@ -51,7 +48,7 @@ internal sealed partial class HutaoCache : IHutaoCache public Dictionary? WeaponCollocations { get; set; } /// - public async ValueTask InitializeForDatabaseViewModelAsync() + public async ValueTask InitializeForSpiralAbyssViewAsync() { if (databaseViewModelTaskSource is not null) { @@ -82,7 +79,7 @@ internal sealed partial class HutaoCache : IHutaoCache } /// - public async ValueTask InitializeForWikiAvatarViewModelAsync() + public async ValueTask InitializeForWikiAvatarViewAsync() { if (wikiAvatarViewModelTaskSource is not null) { @@ -106,7 +103,7 @@ internal sealed partial class HutaoCache : IHutaoCache } /// - public async ValueTask InitializeForWikiWeaponViewModelAsync() + public async ValueTask InitializeForWikiWeaponViewAsync() { if (wikiWeaponViewModelTaskSource is not null) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoSpiralAbyssStatisticsCache.cs similarity index 87% rename from src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoSpiralAbyssStatisticsCache.cs index 13f191cc..2c1b76e8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoSpiralAbyssStatisticsCache.cs @@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.Hutao; /// 胡桃 API 缓存 /// [HighQuality] -internal interface IHutaoCache +internal interface IHutaoSpiralAbyssStatisticsCache { /// /// 角色使用率 @@ -53,17 +53,17 @@ internal interface IHutaoCache /// 为数据库视图模型初始化 /// /// 是否初始化完成 - ValueTask InitializeForDatabaseViewModelAsync(); + ValueTask InitializeForSpiralAbyssViewAsync(); /// /// 为Wiki角色视图模型初始化 /// /// 是否初始化完成 - ValueTask InitializeForWikiAvatarViewModelAsync(); + ValueTask InitializeForWikiAvatarViewAsync(); /// /// 为Wiki武器视图模型初始化 /// /// 是否初始化完成 - ValueTask InitializeForWikiWeaponViewModelAsync(); + ValueTask InitializeForWikiWeaponViewAsync(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs index cb40f184..40c038dd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.Extensions.Caching.Memory; using Snap.Hutao.Core.DependencyInjection.Abstraction; namespace Snap.Hutao.Service.Metadata; @@ -9,12 +10,13 @@ namespace Snap.Hutao.Service.Metadata; /// 元数据服务 /// [HighQuality] -internal interface IMetadataService : ICastService, - IMetadataServiceRawData, - IMetadataServiceIdDataMap, - IMetadataServiceNameDataMap, - IMetadataServiceNameLevelCurveMap +internal interface IMetadataService : ICastService { + IMemoryCache MemoryCache { get; } + + ValueTask FromCacheOrFileAsync(string fileName, CancellationToken token) + where T : class; + /// /// 异步初始化服务,尝试更新元数据 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceIdDataMap.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceIdDataMap.cs deleted file mode 100644 index b7dcd248..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceIdDataMap.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Metadata.Monster; -using Snap.Hutao.Model.Metadata.Reliquary; -using Snap.Hutao.Model.Metadata.Tower; -using Snap.Hutao.Model.Metadata.Weapon; -using Snap.Hutao.Model.Primitive; - -namespace Snap.Hutao.Service.Metadata; - -internal interface IMetadataServiceIdDataMap -{ - /// - /// 异步获取装备被动Id到圣遗物套装的映射 - /// - /// 取消令牌 - /// 装备被动Id到圣遗物套装的映射 - ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default); - - ValueTask> GetExtendedEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default); - - /// - /// 异步获取成就映射 - /// - /// 取消令牌 - /// 成就映射 - ValueTask> GetIdToAchievementMapAsync(CancellationToken token = default); - - /// - /// 异步获取Id到角色的字典 - /// - /// 取消令牌 - /// Id到角色的字典 - ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default); - - /// - /// 异步获取显示与材料映射 - /// - /// 取消令牌 - /// 显示与材料映射 - ValueTask> GetIdToDisplayItemAndMaterialMapAsync(CancellationToken token = default); - - /// - /// 异步获取Id到材料的字典 - /// - /// 取消令牌 - /// Id到材料的字典 - ValueTask> GetIdToMaterialMapAsync(CancellationToken token = default(CancellationToken)); - - /// - /// 异步获取Id到圣遗物权重的映射 - /// - /// 取消令牌 - /// Id到圣遗物权重的字典 - ValueTask> GetIdToReliquaryAffixWeightMapAsync(CancellationToken token = default); - - /// - /// 异步获取ID到圣遗物副词条的字典 - /// - /// 取消令牌 - /// 字典 - ValueTask> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default); - - /// - /// 异步获取圣遗物主词条Id与属性的字典 - /// - /// 取消令牌 - /// 字典 - ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default); - - ValueTask> GetIdToTowerScheduleMapAsync(CancellationToken token = default); - - /// - /// 异步获取ID到武器的字典 - /// - /// 取消令牌 - /// Id到武器的字典 - ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default); - - ValueTask>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default); - - ValueTask> GetIdToTowerFloorMapAsync(CancellationToken token = default); - - ValueTask> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default); -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceNameDataMap.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceNameDataMap.cs deleted file mode 100644 index 7577d4ed..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceNameDataMap.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Weapon; - -namespace Snap.Hutao.Service.Metadata; - -internal interface IMetadataServiceNameDataMap -{ - /// - /// 异步获取名称到角色的字典 - /// - /// 取消令牌 - /// 名称到角色的字典 - ValueTask> GetNameToAvatarMapAsync(CancellationToken token = default); - - /// - /// 异步获取名称到武器的字典 - /// - /// 取消令牌 - /// 名称到武器的字典 - ValueTask> GetNameToWeaponMapAsync(CancellationToken token = default); -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceNameLevelCurveMap.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceNameLevelCurveMap.cs deleted file mode 100644 index c715e79a..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceNameLevelCurveMap.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Primitive; - -namespace Snap.Hutao.Service.Metadata; - -internal interface IMetadataServiceNameLevelCurveMap -{ - /// - /// 异步获取等级角色曲线映射 - /// - /// 取消令牌 - /// 等级角色曲线映射 - ValueTask>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default); - - /// - /// 异步获取等级怪物曲线映射 - /// - /// 取消令牌 - /// 等级怪物曲线映射 - ValueTask>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default); - - /// - /// 异步获取等级武器曲线映射 - /// - /// 取消令牌 - /// 等级武器曲线映射 - ValueTask>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default); -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceRawData.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceRawData.cs deleted file mode 100644 index 864f8b6a..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataServiceRawData.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Metadata; -using Snap.Hutao.Model.Metadata.Achievement; -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Metadata.Monster; -using Snap.Hutao.Model.Metadata.Reliquary; -using Snap.Hutao.Model.Metadata.Weapon; - -namespace Snap.Hutao.Service.Metadata; - -internal interface IMetadataServiceRawData -{ - /// - /// 异步获取成就列表 - /// - /// 取消令牌 - /// 成就列表 - ValueTask> GetAchievementsAsync(CancellationToken token = default); - - /// - /// 异步获取成就分类列表 - /// - /// 取消令牌 - /// 成就分类列表 - ValueTask> GetAchievementGoalsAsync(CancellationToken token = default); - - /// - /// 异步获取角色突破列表 - /// - /// 取消令牌 - /// 角色突破列表 - ValueTask> GetAvatarPromotesAsync(CancellationToken token = default); - - /// - /// 异步获取角色列表 - /// - /// 取消令牌 - /// 角色列表 - ValueTask> GetAvatarsAsync(CancellationToken token = default); - - /// - /// 异步获取卡池配置列表 - /// - /// 取消令牌 - /// 卡池配置列表 - ValueTask> GetGachaEventsAsync(CancellationToken token = default); - - /// - /// 异步获取材料列表 - /// - /// 取消令牌 - /// 材料列表 - ValueTask> GetMaterialsAsync(CancellationToken token = default); - - /// - /// 异步获取怪物列表 - /// - /// 取消令牌 - /// 怪物列表 - ValueTask> GetMonstersAsync(CancellationToken token = default); - - /// - /// 异步获取圣遗物列表 - /// - /// 取消令牌 - /// 圣遗物列表 - ValueTask> GetReliquariesAsync(CancellationToken token = default); - - /// - /// 异步获取圣遗物强化属性列表 - /// - /// 取消令牌 - /// 圣遗物强化属性列表 - ValueTask> GetReliquarySubAffixesAsync(CancellationToken token = default); - - /// - /// 异步获取圣遗物等级数据 - /// - /// 取消令牌 - /// 圣遗物等级数据 - ValueTask> GetReliquaryLevelsAsync(CancellationToken token = default); - - /// - /// 异步获取圣遗物主属性强化属性列表 - /// - /// 取消令牌 - /// 圣遗物强化属性列表 - ValueTask> GetReliquaryMainAffixesAsync(CancellationToken token = default); - - /// - /// 异步获取圣遗物套装 - /// - /// 取消令牌 - /// 圣遗物套装列表 - ValueTask> GetReliquarySetsAsync(CancellationToken token = default); - - /// - /// 异步获取武器突破列表 - /// - /// 取消令牌 - /// 武器突破列表 - ValueTask> GetWeaponPromotesAsync(CancellationToken token = default); - - /// - /// 异步获取武器列表 - /// - /// 取消令牌 - /// 武器列表 - ValueTask> GetWeaponsAsync(CancellationToken token = default); -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs new file mode 100644 index 00000000..f668c965 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataFileNames.cs @@ -0,0 +1,30 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Metadata; + +internal static class MetadataFileNames +{ + public const string FileNameAchievement = "Achievement"; + public const string FileNameAchievementGoal = "AchievementGoal"; + public const string FileNameAvatar = "Avatar"; + public const string FileNameAvatarCurve = "AvatarCurve"; + public const string FileNameAvatarPromote = "AvatarPromote"; + public const string FileNameDisplayItem = "DisplayItem"; + public const string FileNameGachaEvent = "GachaEvent"; + public const string FileNameMaterial = "Material"; + public const string FileNameMonster = "Monster"; + public const string FileNameMonsterCurve = "MonsterCurve"; + public const string FileNameReliquary = "Reliquary"; + public const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight"; + public const string FileNameReliquaryMainAffix = "ReliquaryMainAffix"; + public const string FileNameReliquaryMainAffixLevel = "ReliquaryMainAffixLevel"; + public const string FileNameReliquarySet = "ReliquarySet"; + public const string FileNameReliquarySubAffix = "ReliquarySubAffix"; + public const string FileNameTowerFloor = "TowerFloor"; + public const string FileNameTowerLevel = "TowerLevel"; + public const string FileNameTowerSchedule = "TowerSchedule"; + public const string FileNameWeapon = "Weapon"; + public const string FileNameWeaponCurve = "WeaponCurve"; + public const string FileNameWeaponPromote = "WeaponPromote"; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Constants.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Constants.cs deleted file mode 100644 index 662ddc54..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Constants.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Service.Metadata; - -/// -/// 名称常量 -/// -internal partial class MetadataService -{ - private const string FileNameAchievement = "Achievement"; - private const string FileNameAchievementGoal = "AchievementGoal"; - private const string FileNameAvatar = "Avatar"; - private const string FileNameAvatarCurve = "AvatarCurve"; - private const string FileNameAvatarPromote = "AvatarPromote"; - private const string FileNameDisplayItem = "DisplayItem"; - private const string FileNameGachaEvent = "GachaEvent"; - private const string FileNameMaterial = "Material"; - private const string FileNameMonster = "Monster"; - private const string FileNameMonsterCurve = "MonsterCurve"; - private const string FileNameReliquary = "Reliquary"; - private const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight"; - private const string FileNameReliquaryMainAffix = "ReliquaryMainAffix"; - private const string FileNameReliquaryMainAffixLevel = "ReliquaryMainAffixLevel"; - private const string FileNameReliquarySet = "ReliquarySet"; - private const string FileNameReliquarySubAffix = "ReliquarySubAffix"; - private const string FileNameTowerFloor = "TowerFloor"; - private const string FileNameTowerLevel = "TowerLevel"; - private const string FileNameTowerSchedule = "TowerSchedule"; - private const string FileNameWeapon = "Weapon"; - private const string FileNameWeaponCurve = "WeaponCurve"; - private const string FileNameWeaponPromote = "WeaponPromote"; -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs deleted file mode 100644 index c8423955..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Model.Metadata; -using Snap.Hutao.Model.Metadata.Achievement; -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Metadata.Monster; -using Snap.Hutao.Model.Metadata.Reliquary; -using Snap.Hutao.Model.Metadata.Weapon; - -namespace Snap.Hutao.Service.Metadata; - -/// -/// 接口实现部分 -/// -internal sealed partial class MetadataService -{ - /// - public ValueTask> GetAchievementGoalsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameAchievementGoal, token); - } - - /// - public ValueTask> GetAchievementsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameAchievement, token); - } - - /// - public ValueTask> GetAvatarsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameAvatar, token); - } - - /// - public ValueTask> GetAvatarPromotesAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameAvatarPromote, token); - } - - /// - public ValueTask> GetGachaEventsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameGachaEvent, token); - } - - /// - public ValueTask> GetMaterialsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameMaterial, token); - } - - /// - public ValueTask> GetMonstersAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameMonster, token); - } - - /// - public ValueTask> GetReliquariesAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameReliquary, token); - } - - /// - public ValueTask> GetReliquaryLevelsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameReliquaryMainAffixLevel, token); - } - - /// - public ValueTask> GetReliquaryMainAffixesAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameReliquaryMainAffix, token); - } - - /// - public ValueTask> GetReliquarySetsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameReliquarySet, token); - } - - /// - public ValueTask> GetReliquarySubAffixesAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameReliquarySubAffix, token); - } - - /// - public ValueTask> GetWeaponsAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameWeapon, token); - } - - /// - public ValueTask> GetWeaponPromotesAsync(CancellationToken token = default) - { - return FromCacheOrFileAsync>(FileNameWeaponPromote, token); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs deleted file mode 100644 index 64dddd00..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Extensions.Caching.Memory; -using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Metadata; -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Metadata.Monster; -using Snap.Hutao.Model.Metadata.Reliquary; -using Snap.Hutao.Model.Metadata.Tower; -using Snap.Hutao.Model.Metadata.Weapon; -using Snap.Hutao.Model.Primitive; - -namespace Snap.Hutao.Service.Metadata; - -/// -/// 索引部分 -/// -internal sealed partial class MetadataService -{ - /// - public ValueTask> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameReliquarySet, r => r.EquipAffixId, token); - } - - public async ValueTask> GetExtendedEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default) - { - string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameReliquarySet}.Map.{nameof(ExtendedEquipAffixId)}"; - - if (memoryCache.TryGetValue(cacheKey, out object? value)) - { - ArgumentNullException.ThrowIfNull(value); - return (Dictionary)value; - } - - List list = await FromCacheOrFileAsync>(FileNameReliquarySet, token).ConfigureAwait(false); - - Dictionary dict = list - .SelectMany(set => set.EquipAffixIds.Select(id => (id, set))) - .ToDictionary(tuple => tuple.id, tuple => tuple.set); - return memoryCache.Set(cacheKey, dict); - } - - public async ValueTask>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default) - { - string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameTowerLevel}.Map.Group.{nameof(TowerLevelGroupId)}"; - - if (memoryCache.TryGetValue(cacheKey, out object? value)) - { - ArgumentNullException.ThrowIfNull(value); - return (Dictionary>)value; - } - - List list = await FromCacheOrFileAsync>(FileNameTowerLevel, token).ConfigureAwait(false); - Dictionary> dict = list.GroupBy(l => l.GroupId).ToDictionary(g => g.Key, g => g.ToList()); - return memoryCache.Set(cacheKey, dict); - } - - /// - public ValueTask> GetIdToAchievementMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameAchievement, a => a.Id, token); - } - - /// - public ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameAvatar, a => a.Id, token); - } - - /// - public async ValueTask> GetIdToDisplayItemAndMaterialMapAsync(CancellationToken token = default) - { - string cacheKey = $"{nameof(MetadataService)}.Cache.{nameof(DisplayItem)}.Map.{typeof(MaterialId).Name}"; - - if (memoryCache.TryGetValue(cacheKey, out object? value)) - { - ArgumentNullException.ThrowIfNull(value); - return (Dictionary)value; - } - - Dictionary displays = await FromCacheAsDictionaryAsync(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false); - Dictionary materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false); - - Dictionary results = new(displays); - - foreach ((MaterialId id, DisplayItem material) in materials) - { - results[id] = material; - } - - return memoryCache.Set(cacheKey, results); - } - - /// - public ValueTask> GetIdToMaterialMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameMaterial, a => a.Id, token); - } - - /// - public ValueTask> GetIdToReliquaryAffixWeightMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameReliquaryAffixWeight, r => r.AvatarId, token); - } - - /// - public ValueTask> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameReliquarySubAffix, a => a.Id, token); - } - - /// - public ValueTask> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token); - } - - public ValueTask> GetIdToTowerFloorMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameTowerFloor, t => t.Id, token); - } - - public ValueTask> GetIdToTowerScheduleMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameTowerSchedule, t => t.Id, token); - } - - /// - public ValueTask> GetIdToWeaponMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameWeapon, w => w.Id, token); - } - - /// - public ValueTask>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync, GrowCurve>(FileNameAvatarCurve, a => a.Level, a => a.Map, token); - } - - /// - public ValueTask>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync, GrowCurve>(FileNameMonsterCurve, m => m.Level, m => m.Map, token); - } - - /// - public ValueTask>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync, GrowCurve>(FileNameWeaponCurve, w => w.Level, w => w.Map, token); - } - - public ValueTask> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameMonster, m => m.RelationshipId, token); - } - - /// - public ValueTask> GetNameToAvatarMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameAvatar, a => a.Name, token); - } - - /// - public ValueTask> GetNameToWeaponMapAsync(CancellationToken token = default) - { - return FromCacheAsDictionaryAsync(FileNameWeapon, w => w.Name, token); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs index ef716b30..9f0e03a3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs @@ -13,6 +13,7 @@ using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Json; +using Windows.ApplicationModel.Appointments; namespace Snap.Hutao.Service.Metadata; @@ -38,14 +39,14 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi private bool isInitialized; - /// + public IMemoryCache MemoryCache { get => memoryCache; } + public async ValueTask InitializeAsync() { await initializeCompletionSource.Task.ConfigureAwait(false); return isInitialized; } - /// public async ValueTask InitializeInternalAsync(CancellationToken token = default) { if (isInitialized) @@ -55,38 +56,87 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi using (ValueStopwatch.MeasureExecution(logger)) { - isInitialized = await TryUpdateMetadataAsync(token).ConfigureAwait(false); + isInitialized = await DownloadMetadataDescriptionFileAndCheckAsync(token).ConfigureAwait(false); initializeCompletionSource.TrySetResult(); } } - private async ValueTask TryUpdateMetadataAsync(CancellationToken token) + public async ValueTask FromCacheOrFileAsync(string fileName, CancellationToken token) + where T : class + { + Verify.Operation(isInitialized, SH.ServiceMetadataNotInitialized); + string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}"; + + if (memoryCache.TryGetValue(cacheKey, out object? value)) + { + ArgumentNullException.ThrowIfNull(value); + return (T)value; + } + + string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json"); + if (File.Exists(path)) + { + using (Stream fileStream = File.OpenRead(path)) + { + T? result = await JsonSerializer.DeserializeAsync(fileStream, options, token).ConfigureAwait(false); + ArgumentNullException.ThrowIfNull(result); + return memoryCache.Set(cacheKey, result); + } + } + else + { + FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName); + throw ThrowHelper.UserdataCorrupted(SH.ServiceMetadataFileNotFound, exception); + } + } + + private async ValueTask DownloadMetadataDescriptionFileAndCheckAsync(CancellationToken token) { if (LocalSetting.Get(SettingKeys.SuppressMetadataInitialization, false)) { return true; } - Dictionary? metaXXH64Map; + if (await DownloadMetadataDescriptionFileAsync(token).ConfigureAwait(false) is not { } metadataFileHashs) + { + return false; + } + + await CheckMetadataSourceFilesAsync(metadataFileHashs, token).ConfigureAwait(false); + + // save metadataFile + using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName))) + { + await JsonSerializer + .SerializeAsync(metaFileStream, metadataFileHashs, options, token) + .ConfigureAwait(false); + } + + return true; + } + + private async ValueTask?> DownloadMetadataDescriptionFileAsync(CancellationToken token) + { + Dictionary? metadataFileHashs; try { - string metadataFile = metadataOptions.GetLocalizedRemoteFile(MetaFileName); - // download meta check file - metaXXH64Map = await httpClient - .GetFromJsonAsync>(metadataFile, options, token) + metadataFileHashs = await httpClient + .GetFromJsonAsync>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token) .ConfigureAwait(false); - if (metaXXH64Map is null) + if (metadataFileHashs is null) { infoBarService.Error(SH.ServiceMetadataParseFailed); - return false; + return default; } + + return metadataFileHashs; } catch (JsonException ex) { infoBarService.Error(ex, SH.ServiceMetadataRequestFailed); - return false; + return default; } catch (HttpRequestException ex) { @@ -99,20 +149,8 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi infoBarService.Error(SH.FormatServiceMetadataHttpRequestFailed(ex.StatusCode, ex.HttpRequestError)); } - return false; + return default; } - - await CheckMetadataSourceFilesAsync(metaXXH64Map, token).ConfigureAwait(false); - - // save metadataFile - using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName))) - { - await JsonSerializer - .SerializeAsync(metaFileStream, metaXXH64Map, options, token) - .ConfigureAwait(false); - } - - return true; } private ValueTask CheckMetadataSourceFilesAsync(Dictionary metaMd5Map, CancellationToken token) @@ -121,9 +159,9 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi { (string fileName, string md5) = pair; string fileFullName = $"{fileName}.json"; + string fileFullPath = metadataOptions.GetLocalizedLocalFile(fileFullName); bool skip = false; - string fileFullPath = metadataOptions.GetLocalizedLocalFile(fileFullName); if (File.Exists(fileFullPath)) { skip = md5 == await XXH64.HashFileAsync(fileFullPath, token).ConfigureAwait(false); @@ -132,7 +170,6 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi if (!skip) { logger.LogInformation("{Hash} of {File} not matched, begin downloading", nameof(XXH64), fileFullName); - await DownloadMetadataSourceFilesAsync(fileFullName, token).ConfigureAwait(false); } }).AsValueTask(); @@ -163,65 +200,4 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi logger.LogInformation("Download {file} completed", fileFullName); } - - private async ValueTask FromCacheOrFileAsync(string fileName, CancellationToken token) - where T : class - { - Verify.Operation(isInitialized, SH.ServiceMetadataNotInitialized); - string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}"; - - if (memoryCache.TryGetValue(cacheKey, out object? value)) - { - ArgumentNullException.ThrowIfNull(value); - return (T)value; - } - - string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json"); - if (File.Exists(path)) - { - using (Stream fileStream = File.OpenRead(path)) - { - T? result = await JsonSerializer.DeserializeAsync(fileStream, options, token).ConfigureAwait(false); - ArgumentNullException.ThrowIfNull(result); - return memoryCache.Set(cacheKey, result); - } - } - else - { - FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName); - throw ThrowHelper.UserdataCorrupted(SH.ServiceMetadataFileNotFound, exception); - } - } - - private async ValueTask> FromCacheAsDictionaryAsync(string fileName, Func keySelector, CancellationToken token) - where TKey : notnull - { - string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{typeof(TKey).Name}"; - - if (memoryCache.TryGetValue(cacheKey, out object? value)) - { - ArgumentNullException.ThrowIfNull(value); - return (Dictionary)value; - } - - List list = await FromCacheOrFileAsync>(fileName, token).ConfigureAwait(false); - Dictionary dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items - return memoryCache.Set(cacheKey, dict); - } - - private async ValueTask> FromCacheAsDictionaryAsync(string fileName, Func keySelector, Func valueSelector, CancellationToken token) - where TKey : notnull - { - string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{typeof(TKey).Name}.{typeof(TValue).Name}"; - - if (memoryCache.TryGetValue(cacheKey, out object? value)) - { - ArgumentNullException.ThrowIfNull(value); - return (Dictionary)value; - } - - List list = await FromCacheOrFileAsync>(fileName, token).ConfigureAwait(false); - Dictionary dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items - return memoryCache.Set(cacheKey, dict); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs new file mode 100644 index 00000000..b7720d1e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs @@ -0,0 +1,198 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Extensions.Caching.Memory; +using Snap.Hutao.Core; +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Item; +using Snap.Hutao.Model.Metadata.Monster; +using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Metadata.Tower; +using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; +using static Snap.Hutao.Service.Metadata.MetadataFileNames; + +namespace Snap.Hutao.Service.Metadata; + +internal static class MetadataServiceDictionaryExtension +{ + public static ValueTask> GetEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameReliquarySet, r => r.EquipAffixId, token); + } + + public static ValueTask> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync( + FileNameReliquarySet, + list => list.SelectMany(set => set.EquipAffixIds.Select(id => (Id: id, Set: set))), + tuple => tuple.Id, + tuple => tuple.Set, + token); + } + + public static ValueTask>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync, TowerLevelGroupId, List>( + FileNameTowerLevel, + list => list.GroupBy(l => l.GroupId), + g => g.Key, + g => g.ToList(), + token); + } + + public static ValueTask> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameAchievement, a => a.Id, token); + } + + public static ValueTask> GetIdToAvatarMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameAvatar, a => a.Id, token); + } + + public static async ValueTask> GetIdToDisplayItemAndMaterialMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameDisplayItem}+{FileNameMaterial}.Map.{typeof(MaterialId).Name}.{nameof(DisplayItem)}+{nameof(Material)}"; + + if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value)) + { + ArgumentNullException.ThrowIfNull(value); + return (Dictionary)value; + } + + Dictionary displays = await metadataService.FromCacheAsDictionaryAsync(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false); + Dictionary materials = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false); + + Dictionary results = new(displays); + + foreach ((MaterialId id, DisplayItem material) in materials) + { + results[id] = material; + } + + return metadataService.MemoryCache.Set(cacheKey, results); + } + + public static ValueTask> GetIdToMaterialMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameMaterial, a => a.Id, token); + } + + public static ValueTask> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameReliquaryAffixWeight, r => r.AvatarId, token); + } + + public static ValueTask> GetIdToReliquaryMainPropertyMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token); + } + + public static ValueTask> GetIdToReliquarySubAffixMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameReliquarySubAffix, a => a.Id, token); + } + + public static ValueTask> GetIdToTowerFloorMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameTowerFloor, t => t.Id, token); + } + + public static ValueTask> GetIdToTowerScheduleMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameTowerSchedule, t => t.Id, token); + } + + public static ValueTask> GetIdToWeaponMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameWeapon, w => w.Id, token); + } + + public static ValueTask>> GetLevelToAvatarCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync>(FileNameAvatarCurve, a => a.Level, a => a.Map, token); + } + + public static ValueTask>> GetLevelToMonsterCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync>(FileNameMonsterCurve, m => m.Level, m => m.Map, token); + } + + public static ValueTask>> GetLevelToWeaponCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync>(FileNameWeaponCurve, w => w.Level, w => w.Map, token); + } + + public static ValueTask> GetNameToAvatarMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameAvatar, a => a.Name, token); + } + + public static ValueTask> GetNameToWeaponMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameWeapon, w => w.Name, token); + } + + public static ValueTask> GetRelationshipIdToMonsterMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameMonster, m => m.RelationshipId, token); + } + + private static async ValueTask> FromCacheAsDictionaryAsync(this IMetadataService metadataService, string fileName, Func keySelector, CancellationToken token) + where TKey : notnull + { + string keyName = TypeNameHelper.GetTypeDisplayName(typeof(TKey)); + string valueName = TypeNameHelper.GetTypeDisplayName(typeof(TValue)); + string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{keyName}.{valueName}"; + + if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value)) + { + ArgumentNullException.ThrowIfNull(value); + return (Dictionary)value; + } + + List list = await metadataService.FromCacheOrFileAsync>(fileName, token).ConfigureAwait(false); + Dictionary dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items + return metadataService.MemoryCache.Set(cacheKey, dict); + } + + private static async ValueTask> FromCacheAsDictionaryAsync(this IMetadataService metadataService, string fileName, Func keySelector, Func valueSelector, CancellationToken token) + where TKey : notnull + { + string keyName = TypeNameHelper.GetTypeDisplayName(typeof(TKey)); + string valueName = TypeNameHelper.GetTypeDisplayName(typeof(TValue)); + string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{keyName}.{valueName}"; + + if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value)) + { + ArgumentNullException.ThrowIfNull(value); + return (Dictionary)value; + } + + List list = await metadataService.FromCacheOrFileAsync>(fileName, token).ConfigureAwait(false); + Dictionary dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items + return metadataService.MemoryCache.Set(cacheKey, dict); + } + + private static async ValueTask> FromCacheAsDictionaryAsync(this IMetadataService metadataService, string fileName, Func, IEnumerable> listSelector, Func keySelector, Func valueSelector, CancellationToken token) + where TKey : notnull + { + string keyName = TypeNameHelper.GetTypeDisplayName(typeof(TKey)); + string middleName = TypeNameHelper.GetTypeDisplayName(typeof(TMiddle)); + string valueName = TypeNameHelper.GetTypeDisplayName(typeof(TValue)); + string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{keyName}.{middleName}.{valueName}"; + + if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value)) + { + ArgumentNullException.ThrowIfNull(value); + return (Dictionary)value; + } + + List list = await metadataService.FromCacheOrFileAsync>(fileName, token).ConfigureAwait(false); + Dictionary dict = listSelector(list).ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items + return metadataService.MemoryCache.Set(cacheKey, dict); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs new file mode 100644 index 00000000..f6387425 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs @@ -0,0 +1,127 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata; +using Snap.Hutao.Model.Metadata.Achievement; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Item; +using Snap.Hutao.Model.Metadata.Monster; +using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Metadata.Tower; +using Snap.Hutao.Model.Metadata.Weapon; +using static Snap.Hutao.Service.Metadata.MetadataFileNames; + +namespace Snap.Hutao.Service.Metadata; + +internal static class MetadataServiceListExtension +{ + public static ValueTask> GetAchievementListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameAchievement, token); + } + + public static ValueTask> GetAchievementGoalListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameAchievementGoal, token); + } + + public static ValueTask> GetAvatarListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameAvatar, token); + } + + public static ValueTask> GetAvatarCurveListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameAvatarCurve, token); + } + + public static ValueTask> GetAvatarPromoteListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameAvatarPromote, token); + } + + public static ValueTask> GetDisplayItemListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameDisplayItem, token); + } + + public static ValueTask> GetGachaEventListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameGachaEvent, token); + } + + public static ValueTask> GetMaterialListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameMaterial, token); + } + + public static ValueTask> GetMonsterListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameMonster, token); + } + + public static ValueTask> GetMonsterCurveListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameMonsterCurve, token); + } + + public static ValueTask> GetReliquaryListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameReliquary, token); + } + + public static ValueTask> GetReliquaryAffixWeightListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameReliquaryAffixWeight, token); + } + + public static ValueTask> GetReliquaryMainAffixListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameReliquaryMainAffix, token); + } + + public static ValueTask> GetReliquaryLevelListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameReliquaryMainAffixLevel, token); + } + + public static ValueTask> GetReliquarySetListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameReliquarySet, token); + } + + public static ValueTask> GetReliquarySubAffixListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameReliquarySubAffix, token); + } + + public static ValueTask> GetTowerFloorListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameTowerFloor, token); + } + + public static ValueTask> GetTowerLevelListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameTowerLevel, token); + } + + public static ValueTask> GetTowerScheduleListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameTowerSchedule, token); + } + + public static ValueTask> GetWeaponListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameWeapon, token); + } + + public static ValueTask> GetWeaponCurveListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameWeaponCurve, token); + } + + public static ValueTask> GetWeaponPromoteListAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheOrFileAsync>(FileNameWeaponPromote, token); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml index 30b344c8..17aaaa0a 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml @@ -321,7 +321,7 @@ + + + 0 + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 45257449..1ab5437b 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -280,14 +280,12 @@ Value="{Binding ElementName=ItemsPanelSelector, Path=Current}"> goals = await metadataService - .GetAchievementGoalsAsync(CancellationToken) + .GetAchievementGoalListAsync(CancellationToken) .ConfigureAwait(false); sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From); @@ -309,7 +309,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav return; } - List achievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false); + List achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false); if (TryGetAchievements(archive, achievements, out List? combined)) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs index 207df391..7226e6d6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs @@ -15,7 +15,7 @@ namespace Snap.Hutao.ViewModel.Complex; [Injection(InjectAs.Scoped)] internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel { - private readonly IHutaoCache hutaoCache; + private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache; private readonly ITaskContext taskContext; private List? avatarUsageRanks; @@ -61,7 +61,7 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel /// protected override async Task OpenUIAsync() { - if (await hutaoCache.InitializeForDatabaseViewModelAsync().ConfigureAwait(false)) + if (await hutaoCache.InitializeForSpiralAbyssViewAsync().ConfigureAwait(false)) { await taskContext.SwitchToMainThreadAsync(); AvatarAppearanceRanks = hutaoCache.AvatarAppearanceRanks; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs index 90024897..19b51d44 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs @@ -135,7 +135,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel { if (project is not null) { - List materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false); + List materials = await metadataService.GetMaterialListAsync().ConfigureAwait(false); Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); @@ -194,7 +194,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel ObservableCollection statistics; try { - List materials = await metadataService.GetMaterialsAsync(token).ConfigureAwait(false); + List materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, materials, token).ConfigureAwait(false); } catch (OperationCanceledException) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index cde3c7c4..68d0116d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -38,7 +38,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private readonly ICultivationService cultivationService; private readonly IMetadataService metadataService; private readonly ITaskContext taskContext; - private readonly IHutaoCache hutaoCache; + private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache; private readonly IInfoBarService infoBarService; private readonly CalculateClient calculateClient; private readonly IUserService userService; @@ -87,10 +87,10 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel } levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false); - promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false); + promotes = await metadataService.GetAvatarPromoteListAsync().ConfigureAwait(false); Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false); - List avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false); + List avatars = await metadataService.GetAvatarListAsync().ConfigureAwait(false); IOrderedEnumerable sorted = avatars .OrderByDescending(avatar => avatar.BeginTime) .ThenByDescending(avatar => avatar.Sort); @@ -106,7 +106,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private async ValueTask CombineComplexDataAsync(List avatars, Dictionary idMaterialMap) { - if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false)) + if (!await hutaoCache.InitializeForWikiAvatarViewAsync().ConfigureAwait(false)) { return; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs index d65bde83..cd42acc1 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs @@ -55,7 +55,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel { levelMonsterCurveMap = await metadataService.GetLevelToMonsterCurveMapAsync().ConfigureAwait(false); - List monsters = await metadataService.GetMonstersAsync().ConfigureAwait(false); + List monsters = await metadataService.GetMonsterListAsync().ConfigureAwait(false); Dictionary idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false); foreach (Monster monster in monsters) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 9c096448..17544deb 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -36,7 +36,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel private readonly ICultivationService cultivationService; private readonly ITaskContext taskContext; private readonly IMetadataService metadataService; - private readonly IHutaoCache hutaoCache; + private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache; private readonly IInfoBarService infoBarService; private readonly IUserService userService; @@ -82,10 +82,10 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel if (await metadataService.InitializeAsync().ConfigureAwait(false)) { levelWeaponCurveMap = await metadataService.GetLevelToWeaponCurveMapAsync().ConfigureAwait(false); - promotes = await metadataService.GetWeaponPromotesAsync().ConfigureAwait(false); + promotes = await metadataService.GetWeaponPromoteListAsync().ConfigureAwait(false); Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false); - List weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false); + List weapons = await metadataService.GetWeaponListAsync().ConfigureAwait(false); IEnumerable sorted = weapons .OrderByDescending(weapon => weapon.RankLevel) .ThenBy(weapon => weapon.WeaponType) @@ -103,7 +103,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel private async ValueTask CombineComplexDataAsync(List weapons, Dictionary idMaterialMap) { - if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false)) + if (await hutaoCache.InitializeForWikiWeaponViewAsync().ConfigureAwait(false)) { ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations);