mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
metadata service refactor
This commit is contained in:
@@ -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<AvatarView> avatars = avatarInfos
|
||||
|
||||
@@ -31,7 +31,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
public async ValueTask<GachaStatistics> CreateAsync(List<Model.Entity.GachaItem> items, GachaLogServiceMetadataContext context)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
|
||||
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync().ConfigureAwait(false);
|
||||
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
|
||||
|
||||
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
|
||||
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
|
||||
{
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
|
||||
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false);
|
||||
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false);
|
||||
HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents);
|
||||
|
||||
GachaEventStatistics raw = response.Data;
|
||||
|
||||
@@ -12,13 +12,10 @@ using Snap.Hutao.Web.Hutao.SpiralAbyss;
|
||||
|
||||
namespace Snap.Hutao.Service.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 缓存
|
||||
/// </summary>
|
||||
[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<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
||||
public async ValueTask<bool> InitializeForSpiralAbyssViewAsync()
|
||||
{
|
||||
if (databaseViewModelTaskSource is not null)
|
||||
{
|
||||
@@ -82,7 +79,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
|
||||
public async ValueTask<bool> InitializeForWikiAvatarViewAsync()
|
||||
{
|
||||
if (wikiAvatarViewModelTaskSource is not null)
|
||||
{
|
||||
@@ -106,7 +103,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiWeaponViewModelAsync()
|
||||
public async ValueTask<bool> InitializeForWikiWeaponViewAsync()
|
||||
{
|
||||
if (wikiWeaponViewModelTaskSource is not null)
|
||||
{
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.Hutao;
|
||||
/// 胡桃 API 缓存
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IHutaoCache
|
||||
internal interface IHutaoSpiralAbyssStatisticsCache
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色使用率
|
||||
@@ -53,17 +53,17 @@ internal interface IHutaoCache
|
||||
/// 为数据库视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>是否初始化完成</returns>
|
||||
ValueTask<bool> InitializeForDatabaseViewModelAsync();
|
||||
ValueTask<bool> InitializeForSpiralAbyssViewAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 为Wiki角色视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>是否初始化完成</returns>
|
||||
ValueTask<bool> InitializeForWikiAvatarViewModelAsync();
|
||||
ValueTask<bool> InitializeForWikiAvatarViewAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 为Wiki武器视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>是否初始化完成</returns>
|
||||
ValueTask<bool> InitializeForWikiWeaponViewModelAsync();
|
||||
ValueTask<bool> InitializeForWikiWeaponViewAsync();
|
||||
}
|
||||
@@ -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;
|
||||
/// 元数据服务
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IMetadataService : ICastService,
|
||||
IMetadataServiceRawData,
|
||||
IMetadataServiceIdDataMap,
|
||||
IMetadataServiceNameDataMap,
|
||||
IMetadataServiceNameLevelCurveMap
|
||||
internal interface IMetadataService : ICastService
|
||||
{
|
||||
IMemoryCache MemoryCache { get; }
|
||||
|
||||
ValueTask<T> FromCacheOrFileAsync<T>(string fileName, CancellationToken token)
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化服务,尝试更新元数据
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取装备被动Id到圣遗物套装的映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>装备被动Id到圣遗物套装的映射</returns>
|
||||
ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取成就映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>成就映射</returns>
|
||||
ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取Id到角色的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到角色的字典</returns>
|
||||
ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取显示与材料映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>显示与材料映射</returns>
|
||||
ValueTask<Dictionary<MaterialId, DisplayItem>> GetIdToDisplayItemAndMaterialMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取Id到材料的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到材料的字典</returns>
|
||||
ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取Id到圣遗物权重的映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到圣遗物权重的字典</returns>
|
||||
ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取ID到圣遗物副词条的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>字典</returns>
|
||||
ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物主词条Id与属性的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>字典</returns>
|
||||
ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取ID到武器的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到武器的字典</returns>
|
||||
ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取名称到角色的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>名称到角色的字典</returns>
|
||||
ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取名称到武器的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>名称到武器的字典</returns>
|
||||
ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取等级角色曲线映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>等级角色曲线映射</returns>
|
||||
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取等级怪物曲线映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>等级怪物曲线映射</returns>
|
||||
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取等级武器曲线映射
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>等级武器曲线映射</returns>
|
||||
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取成就列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>成就列表</returns>
|
||||
ValueTask<List<Model.Metadata.Achievement.Achievement>> GetAchievementsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取成就分类列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>成就分类列表</returns>
|
||||
ValueTask<List<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色突破列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色突破列表</returns>
|
||||
ValueTask<List<Promote>> GetAvatarPromotesAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>角色列表</returns>
|
||||
ValueTask<List<Avatar>> GetAvatarsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取卡池配置列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>卡池配置列表</returns>
|
||||
ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取材料列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>材料列表</returns>
|
||||
ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取怪物列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>怪物列表</returns>
|
||||
ValueTask<List<Monster>> GetMonstersAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物列表</returns>
|
||||
ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物强化属性列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物强化属性列表</returns>
|
||||
ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixesAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物等级数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物等级数据</returns>
|
||||
ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物主属性强化属性列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物强化属性列表</returns>
|
||||
ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixesAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物套装
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物套装列表</returns>
|
||||
ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取武器突破列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>武器突破列表</returns>
|
||||
ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取武器列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>武器列表</returns>
|
||||
ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
/// <summary>
|
||||
/// 名称常量
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 接口实现部分
|
||||
/// </summary>
|
||||
internal sealed partial class MetadataService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<AchievementGoal>>(FileNameAchievementGoal, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Model.Metadata.Achievement.Achievement>> GetAchievementsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>(FileNameAchievement, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Avatar>> GetAvatarsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Avatar>>(FileNameAvatar, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Promote>> GetAvatarPromotesAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Promote>>(FileNameAvatarPromote, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<GachaEvent>>(FileNameGachaEvent, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Material>>(FileNameMaterial, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Monster>> GetMonstersAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Monster>>(FileNameMonster, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Reliquary>>(FileNameReliquary, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<ReliquaryMainAffixLevel>>(FileNameReliquaryMainAffixLevel, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixesAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<ReliquaryMainAffix>>(FileNameReliquaryMainAffix, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixesAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<ReliquarySubAffix>>(FileNameReliquarySubAffix, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Weapon>>(FileNameWeapon, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<Promote>>(FileNameWeaponPromote, token);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 索引部分
|
||||
/// </summary>
|
||||
internal sealed partial class MetadataService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>(FileNameReliquarySet, r => r.EquipAffixId, token);
|
||||
}
|
||||
|
||||
public async ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> 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<ExtendedEquipAffixId, ReliquarySet>)value;
|
||||
}
|
||||
|
||||
List<ReliquarySet> list = await FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token).ConfigureAwait(false);
|
||||
|
||||
Dictionary<ExtendedEquipAffixId, ReliquarySet> 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<Dictionary<TowerLevelGroupId, List<TowerLevel>>> 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<TowerLevelGroupId, List<TowerLevel>>)value;
|
||||
}
|
||||
|
||||
List<TowerLevel> list = await FromCacheOrFileAsync<List<TowerLevel>>(FileNameTowerLevel, token).ConfigureAwait(false);
|
||||
Dictionary<TowerLevelGroupId, List<TowerLevel>> dict = list.GroupBy(l => l.GroupId).ToDictionary(g => g.Key, g => g.ToList());
|
||||
return memoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>(FileNameAchievement, a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<AvatarId, Avatar>(FileNameAvatar, a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<Dictionary<MaterialId, DisplayItem>> 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<MaterialId, DisplayItem>)value;
|
||||
}
|
||||
|
||||
Dictionary<MaterialId, DisplayItem> displays = await FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false);
|
||||
Dictionary<MaterialId, Material> materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false);
|
||||
|
||||
Dictionary<MaterialId, DisplayItem> results = new(displays);
|
||||
|
||||
foreach ((MaterialId id, DisplayItem material) in materials)
|
||||
{
|
||||
results[id] = material;
|
||||
}
|
||||
|
||||
return memoryCache.Set(cacheKey, results);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<ReliquarySubAffixId, ReliquarySubAffix>(FileNameReliquarySubAffix, a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryMainAffix>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token);
|
||||
}
|
||||
|
||||
public ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<TowerFloorId, TowerFloor>(FileNameTowerFloor, t => t.Id, token);
|
||||
}
|
||||
|
||||
public ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<TowerScheduleId, TowerSchedule>(FileNameTowerSchedule, t => t.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<WeaponId, Weapon>(FileNameWeapon, w => w.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameAvatarCurve, a => a.Level, a => a.Map, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameMonsterCurve, m => m.Level, m => m.Map, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameWeaponCurve, w => w.Level, w => w.Map, token);
|
||||
}
|
||||
|
||||
public ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<MonsterRelationshipId, Monster>(FileNameMonster, m => m.RelationshipId, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<string, Avatar>(FileNameAvatar, a => a.Name, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<string, Weapon>(FileNameWeapon, w => w.Name, token);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IMemoryCache MemoryCache { get => memoryCache; }
|
||||
|
||||
public async ValueTask<bool> InitializeAsync()
|
||||
{
|
||||
await initializeCompletionSource.Task.ConfigureAwait(false);
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<bool> TryUpdateMetadataAsync(CancellationToken token)
|
||||
public async ValueTask<T> FromCacheOrFileAsync<T>(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<T>(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<bool> DownloadMetadataDescriptionFileAndCheckAsync(CancellationToken token)
|
||||
{
|
||||
if (LocalSetting.Get(SettingKeys.SuppressMetadataInitialization, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Dictionary<string, string>? 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<Dictionary<string, string>?> DownloadMetadataDescriptionFileAsync(CancellationToken token)
|
||||
{
|
||||
Dictionary<string, string>? metadataFileHashs;
|
||||
try
|
||||
{
|
||||
string metadataFile = metadataOptions.GetLocalizedRemoteFile(MetaFileName);
|
||||
|
||||
// download meta check file
|
||||
metaXXH64Map = await httpClient
|
||||
.GetFromJsonAsync<Dictionary<string, string>>(metadataFile, options, token)
|
||||
metadataFileHashs = await httpClient
|
||||
.GetFromJsonAsync<Dictionary<string, string>>(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<string, string> 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<T> FromCacheOrFileAsync<T>(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<T>(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<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue>(string fileName, Func<TValue, TKey> 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<TKey, TValue>)value;
|
||||
}
|
||||
|
||||
List<TValue> list = await FromCacheOrFileAsync<List<TValue>>(fileName, token).ConfigureAwait(false);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items
|
||||
return memoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue, TData>(string fileName, Func<TData, TKey> keySelector, Func<TData, TValue> 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<TKey, TValue>)value;
|
||||
}
|
||||
|
||||
List<TData> list = await FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
|
||||
return memoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
}
|
||||
@@ -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<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>(FileNameReliquarySet, r => r.EquipAffixId, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<ReliquarySet, (ExtendedEquipAffixId Id, ReliquarySet Set), ExtendedEquipAffixId, ReliquarySet>(
|
||||
FileNameReliquarySet,
|
||||
list => list.SelectMany(set => set.EquipAffixIds.Select(id => (Id: id, Set: set))),
|
||||
tuple => tuple.Id,
|
||||
tuple => tuple.Set,
|
||||
token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<TowerLevel, IGrouping<TowerLevelGroupId, TowerLevel>, TowerLevelGroupId, List<TowerLevel>>(
|
||||
FileNameTowerLevel,
|
||||
list => list.GroupBy(l => l.GroupId),
|
||||
g => g.Key,
|
||||
g => g.ToList(),
|
||||
token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>(FileNameAchievement, a => a.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<AvatarId, Avatar>(FileNameAvatar, a => a.Id, token);
|
||||
}
|
||||
|
||||
public static async ValueTask<Dictionary<MaterialId, DisplayItem>> 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<MaterialId, DisplayItem>)value;
|
||||
}
|
||||
|
||||
Dictionary<MaterialId, DisplayItem> displays = await metadataService.FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false);
|
||||
Dictionary<MaterialId, Material> materials = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
|
||||
|
||||
Dictionary<MaterialId, DisplayItem> results = new(displays);
|
||||
|
||||
foreach ((MaterialId id, DisplayItem material) in materials)
|
||||
{
|
||||
results[id] = material;
|
||||
}
|
||||
|
||||
return metadataService.MemoryCache.Set(cacheKey, results);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<ReliquaryMainAffix, ReliquaryMainAffixId, FightProperty>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<ReliquarySubAffixId, ReliquarySubAffix>(FileNameReliquarySubAffix, a => a.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<TowerFloorId, TowerFloor>(FileNameTowerFloor, t => t.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<TowerScheduleId, TowerSchedule>(FileNameTowerSchedule, t => t.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<WeaponId, Weapon>(FileNameWeapon, w => w.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<GrowCurve, Level, Dictionary<GrowCurveType, float>>(FileNameAvatarCurve, a => a.Level, a => a.Map, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<GrowCurve, Level, Dictionary<GrowCurveType, float>>(FileNameMonsterCurve, m => m.Level, m => m.Map, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<GrowCurve, Level, Dictionary<GrowCurveType, float>>(FileNameWeaponCurve, w => w.Level, w => w.Map, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<string, Avatar>(FileNameAvatar, a => a.Name, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<string, Weapon>(FileNameWeapon, w => w.Name, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<MonsterRelationshipId, Monster>(FileNameMonster, m => m.RelationshipId, token);
|
||||
}
|
||||
|
||||
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue>(this IMetadataService metadataService, string fileName, Func<TValue, TKey> 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<TKey, TValue>)value;
|
||||
}
|
||||
|
||||
List<TValue> list = await metadataService.FromCacheOrFileAsync<List<TValue>>(fileName, token).ConfigureAwait(false);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items
|
||||
return metadataService.MemoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<TData, TKey> keySelector, Func<TData, TValue> 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<TKey, TValue>)value;
|
||||
}
|
||||
|
||||
List<TData> list = await metadataService.FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
|
||||
return metadataService.MemoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TMiddle, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<TMiddle>> listSelector, Func<TMiddle, TKey> keySelector, Func<TMiddle, TValue> 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<TKey, TValue>)value;
|
||||
}
|
||||
|
||||
List<TData> list = await metadataService.FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
|
||||
Dictionary<TKey, TValue> dict = listSelector(list).ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
|
||||
return metadataService.MemoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
}
|
||||
@@ -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<List<Model.Metadata.Achievement.Achievement>> GetAchievementListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>(FileNameAchievement, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<AchievementGoal>> GetAchievementGoalListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<AchievementGoal>>(FileNameAchievementGoal, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Avatar>> GetAvatarListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Avatar>>(FileNameAvatar, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<GrowCurve>> GetAvatarCurveListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameAvatarCurve, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Promote>> GetAvatarPromoteListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Promote>>(FileNameAvatarPromote, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<DisplayItem>> GetDisplayItemListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<DisplayItem>>(FileNameDisplayItem, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<GachaEvent>> GetGachaEventListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<GachaEvent>>(FileNameGachaEvent, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Material>> GetMaterialListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Material>>(FileNameMaterial, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Monster>> GetMonsterListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Monster>>(FileNameMonster, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<GrowCurve>> GetMonsterCurveListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameMonsterCurve, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Reliquary>> GetReliquaryListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Reliquary>>(FileNameReliquary, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<ReliquaryAffixWeight>> GetReliquaryAffixWeightListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<ReliquaryAffixWeight>>(FileNameReliquaryAffixWeight, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<ReliquaryMainAffix>>(FileNameReliquaryMainAffix, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<ReliquaryMainAffixLevel>>(FileNameReliquaryMainAffixLevel, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<ReliquarySet>> GetReliquarySetListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<ReliquarySubAffix>>(FileNameReliquarySubAffix, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<TowerFloor>> GetTowerFloorListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<TowerFloor>>(FileNameTowerFloor, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<TowerLevel>> GetTowerLevelListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<TowerLevel>>(FileNameTowerLevel, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<TowerSchedule>> GetTowerScheduleListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<TowerSchedule>>(FileNameTowerSchedule, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Weapon>> GetWeaponListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Weapon>>(FileNameWeapon, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<GrowCurve>> GetWeaponCurveListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameWeaponCurve, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Promote>> GetWeaponPromoteListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Promote>>(FileNameWeaponPromote, token);
|
||||
}
|
||||
}
|
||||
@@ -321,7 +321,7 @@
|
||||
<cwcont:SwitchPresenter
|
||||
Grid.Row="2"
|
||||
Margin="12,0"
|
||||
ContentTransitions="{StaticResource ContentThemeTransitions}"
|
||||
ContentTransitions="{StaticResource EntranceThemeTransitions}"
|
||||
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
|
||||
<cwcont:Case Value="List">
|
||||
<ListView
|
||||
|
||||
@@ -228,6 +228,11 @@
|
||||
Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
DefaultLabelPosition="Right">
|
||||
<CommandBar.Resources>
|
||||
<StaticResource x:Key="CommandBarBackgroundOpen" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<Thickness x:Key="CommandBarBorderThicknessOpen">0</Thickness>
|
||||
</CommandBar.Resources>
|
||||
|
||||
<CommandBar.Content>
|
||||
<AutoSuggestBox
|
||||
Width="300"
|
||||
@@ -294,11 +299,10 @@
|
||||
</Grid>
|
||||
<cwc:SwitchPresenter
|
||||
Grid.Row="1"
|
||||
ContentTransitions="{StaticResource ContentThemeTransitions}"
|
||||
ContentTransitions="{StaticResource EntranceThemeTransitions}"
|
||||
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
|
||||
<cwc:Case Value="List">
|
||||
<SplitView
|
||||
Grid.Row="1"
|
||||
DisplayMode="Inline"
|
||||
IsPaneOpen="True"
|
||||
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength2}"
|
||||
|
||||
@@ -552,7 +552,7 @@
|
||||
|
||||
<cwcont:SwitchPresenter
|
||||
Grid.Row="1"
|
||||
ContentTransitions="{ThemeResource ContentThemeTransitions}"
|
||||
ContentTransitions="{ThemeResource EntranceThemeTransitions}"
|
||||
Value="{Binding ElementName=ItemsPanelSelector, Path=Current, Mode=OneWay}">
|
||||
<cwcont:Case Value="Grid">
|
||||
<ScrollViewer>
|
||||
|
||||
@@ -280,14 +280,12 @@
|
||||
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
|
||||
<cwc:Case Value="List">
|
||||
<SplitView
|
||||
Grid.Row="1"
|
||||
DisplayMode="Inline"
|
||||
IsPaneOpen="True"
|
||||
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength}"
|
||||
PaneBackground="{ThemeResource CardBackgroundFillColorSecondaryBrush}">
|
||||
<SplitView.Pane>
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
ItemTemplate="{StaticResource AvatarListTemplate}"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
SelectedItem="{Binding Selected, Mode=TwoWay}"
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
</CommandBar>
|
||||
<cwc:SwitchPresenter
|
||||
Grid.Row="1"
|
||||
ContentTransitions="{ThemeResource ContentThemeTransitions}"
|
||||
ContentTransitions="{ThemeResource EntranceThemeTransitions}"
|
||||
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
|
||||
<cwc:Case Value="List">
|
||||
<SplitView
|
||||
|
||||
@@ -168,7 +168,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
List<MetadataAchievementGoal> 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<MetadataAchievement> achievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<MetadataAchievement> achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
||||
{
|
||||
|
||||
@@ -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<AvatarRankView>? avatarUsageRanks;
|
||||
@@ -61,7 +61,7 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
if (await hutaoCache.InitializeForDatabaseViewModelAsync().ConfigureAwait(false))
|
||||
if (await hutaoCache.InitializeForSpiralAbyssViewAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
AvatarAppearanceRanks = hutaoCache.AvatarAppearanceRanks;
|
||||
|
||||
@@ -135,7 +135,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (project is not null)
|
||||
{
|
||||
List<Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
|
||||
List<Material> materials = await metadataService.GetMaterialListAsync().ConfigureAwait(false);
|
||||
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -194,7 +194,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
ObservableCollection<StatisticsCultivateItem> statistics;
|
||||
try
|
||||
{
|
||||
List<Material> materials = await metadataService.GetMaterialsAsync(token).ConfigureAwait(false);
|
||||
List<Material> materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false);
|
||||
statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, materials, token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -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<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
|
||||
List<Avatar> avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
|
||||
List<Avatar> avatars = await metadataService.GetAvatarListAsync().ConfigureAwait(false);
|
||||
IOrderedEnumerable<Avatar> 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<Avatar> avatars, Dictionary<MaterialId, Material> idMaterialMap)
|
||||
{
|
||||
if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
|
||||
if (!await hutaoCache.InitializeForWikiAvatarViewAsync().ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
|
||||
{
|
||||
levelMonsterCurveMap = await metadataService.GetLevelToMonsterCurveMapAsync().ConfigureAwait(false);
|
||||
|
||||
List<Monster> monsters = await metadataService.GetMonstersAsync().ConfigureAwait(false);
|
||||
List<Monster> monsters = await metadataService.GetMonsterListAsync().ConfigureAwait(false);
|
||||
Dictionary<MaterialId, DisplayItem> idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false);
|
||||
foreach (Monster monster in monsters)
|
||||
{
|
||||
|
||||
@@ -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<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
|
||||
|
||||
List<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
|
||||
List<Weapon> weapons = await metadataService.GetWeaponListAsync().ConfigureAwait(false);
|
||||
IEnumerable<Weapon> 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<Weapon> weapons, Dictionary<MaterialId, Material> idMaterialMap)
|
||||
{
|
||||
if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false))
|
||||
if (await hutaoCache.InitializeForWikiWeaponViewAsync().ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user