diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs index 4fde20be..bb05fa6d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.EntityFrameworkCore; +using Snap.Hutao.Extension; namespace Snap.Hutao.Core.Database; @@ -14,7 +15,7 @@ namespace Snap.Hutao.Core.Database; /// 消息的类型 internal class DbCurrent where TEntity : class, ISelectable - where TMessage : Message.ValueChangedMessage + where TMessage : Message.ValueChangedMessage, new() { private readonly DbContext dbContext; private readonly DbSet dbSet; @@ -25,12 +26,12 @@ internal class DbCurrent /// /// 构造一个新的数据库当前项 /// - /// 数据库上下文 /// 数据集 /// 消息器 - public DbCurrent(DbContext dbContext, DbSet dbSet, IMessenger messenger) + /// + public DbCurrent(DbSet dbSet, IMessenger messenger) { - this.dbContext = dbContext; + this.dbContext = dbSet.Context(); this.dbSet = dbSet; this.messenger = messenger; } @@ -55,96 +56,18 @@ internal class DbCurrent if (current != null) { current.IsSelected = false; - dbSet.Update(current); - dbContext.SaveChanges(); + dbSet.UpdateAndSave(current); } } - TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!; + TMessage message = new() { OldValue = current, NewValue = value }; + current = value; if (current != null) { current.IsSelected = true; - dbSet.Update(current); - dbContext.SaveChanges(); - } - - messenger.Send(message); - } - } -} - -/// -/// 数据库当前项 -/// 简化对数据库中选中项的管理 -/// -/// 绑定类型 -/// 实体的类型 -/// 消息的类型 -[SuppressMessage("", "SA1402")] -internal class DbCurrent - where TObservable : class - where TEntity : class, ISelectable - where TMessage : Message.ValueChangedMessage -{ - private readonly DbContext dbContext; - private readonly DbSet dbSet; - private readonly IMessenger messenger; - private readonly Func selector; - - private TObservable? current; - - /// - /// 构造一个新的数据库当前项 - /// - /// 数据库上下文 - /// 数据集 - /// 选择器 - /// 消息器 - public DbCurrent(DbContext dbContext, DbSet dbSet, Func selector, IMessenger messenger) - { - this.dbContext = dbContext; - this.dbSet = dbSet; - this.selector = selector; - this.messenger = messenger; - } - - /// - /// 当前选中的项 - /// - public TObservable? Current - { - get => current; - set - { - // prevent useless sets - if (current == value) - { - return; - } - - // only update when not processing a deletion - if (value != null) - { - if (current != null) - { - TEntity entity = selector(current); - entity.IsSelected = false; - dbSet.Update(entity); - dbContext.SaveChanges(); - } - } - - TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!; - current = value; - - if (current != null) - { - TEntity entity = selector(current); - entity.IsSelected = true; - dbSet.Update(entity); - dbContext.SaveChanges(); + dbSet.UpdateAndSave(current); } messenger.Send(message); diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs new file mode 100644 index 00000000..aac80a60 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs @@ -0,0 +1,66 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Snap.Hutao.Extension; + +/// +/// 数据库集合上下文 +/// +public static class DbSetExtension +{ + /// + /// 获取对应的数据库上下文 + /// + /// 实体类型 + /// 数据库集 + /// 对应的数据库上下文 + public static DbContext Context(this DbSet dbSet) + where TEntity : class + { + return dbSet.GetService().Context; + } + + /// + /// 获取或添加一个对应的实体 + /// + /// 实体类型 + /// 数据库集 + /// 谓词 + /// 实体工厂 + /// 是否添加 + /// 实体 + public static TEntity SingleOrAdd(this DbSet dbSet, Func predicate, Func entityFactory, out bool added) + where TEntity : class + { + added = false; + TEntity? entry = dbSet.SingleOrDefault(predicate); + + if (entry == null) + { + entry = entityFactory(); + dbSet.Add(entry); + dbSet.Context().SaveChanges(); + + added = true; + } + + return entry; + } + + /// + /// 更新并保存 + /// + /// 实体类型 + /// 数据库集 + /// 实体 + /// 影响条数 + public static int UpdateAndSave(this DbSet dbSet, TEntity entity) + where TEntity : class + { + dbSet.Update(entity); + return dbSet.Context().SaveChanges(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs index f263ca0d..09b442b6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs @@ -8,16 +8,7 @@ namespace Snap.Hutao.Message; /// /// 成就存档切换消息 /// -[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] internal class AchievementArchiveChangedMessage : ValueChangedMessage { - /// - /// 构造一个新的用户切换消息 - /// - /// 老用户 - /// 新用户 - public AchievementArchiveChangedMessage(AchievementArchive? oldArchive, AchievementArchive? newArchive) - : base(oldArchive, newArchive) - { - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs index 8d94d630..edd62093 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs @@ -8,16 +8,7 @@ namespace Snap.Hutao.Message; /// /// 祈愿记录存档切换消息 /// -[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] internal class GachaArchiveChangedMessage : ValueChangedMessage { - /// - /// 构造一个新的用户切换消息 - /// - /// 老用户 - /// 新用户 - public GachaArchiveChangedMessage(GachaArchive? oldArchive, GachaArchive? newArchive) - : base(oldArchive, newArchive) - { - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs index 19ffd27d..3bf2443a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs @@ -8,15 +8,7 @@ namespace Snap.Hutao.Message; /// /// 用户切换消息 /// +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] internal class UserChangedMessage : ValueChangedMessage { - /// - /// 构造一个新的用户切换消息 - /// - /// 老用户 - /// 新用户 - public UserChangedMessage(User? oldUser, User? newUser) - : base(oldUser, newUser) - { - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs index 4af90ee3..4bd83669 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs @@ -7,9 +7,17 @@ namespace Snap.Hutao.Message; /// 值变化消息 /// /// 值的类型 +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] internal abstract class ValueChangedMessage where TValue : class { + /// + /// 动态访问 + /// + public ValueChangedMessage() + { + } + /// /// 构造一个新的值变化消息 /// @@ -24,10 +32,10 @@ internal abstract class ValueChangedMessage /// /// 旧的值 /// - public TValue? OldValue { get; private set; } + public TValue? OldValue { get; set; } /// /// 新的值 /// - public TValue? NewValue { get; private set; } + public TValue? NewValue { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs index 9f513d57..aafe0979 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs @@ -24,7 +24,7 @@ public class GachaStatistics public TypedWishSummary PermanentWish { get; set; } = default!; /// - /// 历史 + /// 历史卡池 /// public List HistoryWishes { get; set; } = default!; diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs index 3fb45900..74d06f99 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs @@ -12,6 +12,16 @@ namespace Snap.Hutao.Model.Entity; [Table("settings")] public class SettingEntry { + /// + /// 游戏路径 + /// + public const string GamePath = "GamePath"; + + /// + /// 空的历史记录卡池是否可见 + /// + public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible"; + /// /// 构造一个新的设置入口 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs index 7dc477bd..d12b3321 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs @@ -13,6 +13,8 @@ internal class SkillIconConverter : ValueConverterBase private const string SkillUrl = "https://static.snapgenshin.com/Skill/{0}.png"; private const string TalentUrl = "https://static.snapgenshin.com/Talent/{0}.png"; + private static readonly Uri UIIconNone = new("https://static.snapgenshin.com/Bg/UI_Icon_None.png"); + /// /// 名称转Uri /// @@ -20,6 +22,11 @@ internal class SkillIconConverter : ValueConverterBase /// 链接 public static Uri IconNameToUri(string name) { + if (string.IsNullOrWhiteSpace(name)) + { + return UIIconNone; + } + if (name.StartsWith("UI_Talent_")) { return new Uri(string.Format(TalentUrl, name)); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index 518e4230..2a21e0bc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -40,7 +40,7 @@ internal class AchievementService : IAchievementService this.appDbContext = appDbContext; this.logger = logger; - dbCurrent = new(appDbContext, appDbContext.AchievementArchives, messenger); + dbCurrent = new(appDbContext.AchievementArchives, messenger); achievementDbOperation = new(appDbContext); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs index c91412a4..441cf087 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using System.Diagnostics; + namespace Snap.Hutao.Service.AppCenter.Model.Log; [SuppressMessage("", "SA1600")] @@ -21,7 +23,7 @@ public static class LogHelper { Type = exception.GetType().ToString(), Message = exception.Message, - StackTrace = exception.ToString(), + StackTrace = exception.StackTrace, }; if (exception is AggregateException aggregateException) @@ -35,12 +37,21 @@ public static class LogHelper } } } - else if (exception.InnerException != null) + + if (exception.InnerException != null) { current.InnerExceptions ??= new(); current.InnerExceptions.Add(Create(exception.InnerException)); } + StackTrace stackTrace = new(exception, true); + StackFrame[] frames = stackTrace.GetFrames(); + + if (frames.Length > 0 && frames[0].HasNativeImage()) + { + + } + return current; } } \ No newline at end of file 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 90c5f8d7..61ee8144 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.EntityFrameworkCore; +using Snap.Hutao.Context.Database; using Snap.Hutao.Extension; using Snap.Hutao.Model.Binding.Gacha; using Snap.Hutao.Model.Entity; @@ -19,14 +21,17 @@ namespace Snap.Hutao.Service.GachaLog.Factory; internal class GachaStatisticsFactory : IGachaStatisticsFactory { private readonly IMetadataService metadataService; + private readonly AppDbContext appDbContext; /// /// 构造一个新的祈愿统计工厂 /// /// 元数据服务 - public GachaStatisticsFactory(IMetadataService metadataService) + /// 数据库上下文 + public GachaStatisticsFactory(IMetadataService metadataService, AppDbContext appDbContext) { this.metadataService = metadataService; + this.appDbContext = appDbContext; } /// @@ -41,15 +46,29 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory List gachaevents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false); List historyWishBuilders = gachaevents.Select(g => new HistoryWishBuilder(g, nameAvatarMap, nameWeaponMap)).ToList(); + SettingEntry? entry = await appDbContext.Settings + .SingleOrDefaultAsync(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible) + .ConfigureAwait(false); + + if (entry == null) + { + entry = new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString()); + appDbContext.Settings.Add(entry); + await appDbContext.SaveChangesAsync().ConfigureAwait(false); + } + + bool isEmptyHistoryWishVisible = bool.Parse(entry.Value!); + IOrderedEnumerable orderedItems = items.OrderBy(i => i.Id); - return await Task.Run(() => CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap)).ConfigureAwait(false); + return await Task.Run(() => CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap, isEmptyHistoryWishVisible)).ConfigureAwait(false); } private static GachaStatistics CreateCore( IOrderedEnumerable items, List historyWishBuilders, Dictionary avatarMap, - Dictionary weaponMap) + Dictionary weaponMap, + bool isEmptyHistoryWishVisible) { TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.PermanentWish, 90, 10); TypedWishSummaryBuilder avatarWishBuilder = new("角色活动", TypedWishSummaryBuilder.AvatarEventWish, 90, 10); @@ -131,6 +150,7 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory { // history HistoryWishes = historyWishBuilders + .Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty)) .OrderByDescending(builder => builder.From) .ThenBy(builder => builder.ConfigType, new GachaConfigTypeComparar()) .Select(builder => builder.ToHistoryWish()).ToList(), diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs index c8e53cfe..acddb2e9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs @@ -67,6 +67,14 @@ internal class HistoryWishBuilder get => gachaEvent.To; } + /// + /// 卡池是否为空 + /// + public bool IsEmpty + { + get => totalCountTracker <= 0; + } + /// /// 计数五星角色 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index f44cc6c6..fef5135c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -88,7 +88,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization this.logger = logger; this.gachaStatisticsFactory = gachaStatisticsFactory; - dbCurrent = new(appDbContext, appDbContext.GachaArchives, messenger); + dbCurrent = new(appDbContext.GachaArchives, messenger); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 35af6758..8b29b3c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Snap.Hutao.Context.Database; using Snap.Hutao.Core.Threading; +using Snap.Hutao.Extension; using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Game.Locator; @@ -16,8 +17,6 @@ namespace Snap.Hutao.Service.Game; [Injection(InjectAs.Transient, typeof(IGameService))] internal class GameService : IGameService { - private const string GamePath = "GamePath"; - private readonly AppDbContext appDbContext; private readonly IMemoryCache memoryCache; private readonly IEnumerable gameLocators; @@ -38,7 +37,7 @@ internal class GameService : IGameService /// public async ValueTask> GetGamePathAsync() { - string key = $"{nameof(GameService)}.Cache.{GamePath}"; + string key = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}"; if (memoryCache.TryGetValue(key, out object? value)) { @@ -46,16 +45,11 @@ internal class GameService : IGameService } else { - SettingEntry? entry = await appDbContext.Settings - .SingleOrDefaultAsync(e => e.Key == GamePath) - .ConfigureAwait(false); + SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added); // Cannot find in setting - if (entry == null) + if (added) { - // Create new setting - entry = new(GamePath, null); - // Try locate by registry IGameLocator locator = gameLocators.Single(l => l.Name == nameof(RegistryLauncherLocator)); ValueResult result = await locator.LocateGamePathAsync().ConfigureAwait(false); @@ -71,7 +65,7 @@ internal class GameService : IGameService { // Save result. entry.Value = result.Value; - await appDbContext.Settings.AddAsync(entry).ConfigureAwait(false); + appDbContext.Settings.Update(entry); await appDbContext.SaveChangesAsync().ConfigureAwait(false); } else diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs index 616b462f..7d8086c0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs @@ -29,8 +29,8 @@ internal class RegistryLauncherLocator : IGameLocator } else { - string path = result.Value; - string configPath = Path.Combine(path, "config.ini"); + string? path = Path.GetDirectoryName(result.Value); + string configPath = Path.Combine(path!, "config.ini"); string? escapedPath = null; using (FileStream stream = File.OpenRead(configPath)) { @@ -40,7 +40,8 @@ internal class RegistryLauncherLocator : IGameLocator if (escapedPath != null) { - return Task.FromResult>(new(true, Unescape(escapedPath))); + string gamePath = Path.Combine(Unescape(escapedPath), "YuanShen.exe"); + return Task.FromResult>(new(true, gamePath)); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs new file mode 100644 index 00000000..5304e4ab --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs @@ -0,0 +1,160 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Binding.Hutao; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Web.Hutao.Model; + +namespace Snap.Hutao.Service.Hutao; + +/// +/// 胡桃 API 缓存 +/// +[Injection(InjectAs.Singleton, typeof(IHtaoCache))] +internal class HutaoCache : IHtaoCache +{ + private readonly IHutaoService hutaoService; + private readonly IMetadataService metadataService; + + private Dictionary? idAvatarExtendedMap; + + /// + /// 构造一个新的胡桃 API 缓存 + /// + /// 胡桃服务 + /// 元数据服务 + public HutaoCache(IHutaoService hutaoService, IMetadataService metadataService) + { + this.hutaoService = hutaoService; + this.metadataService = metadataService; + } + + /// + public List? AvatarUsageRanks { get; set; } + + /// + public List? AvatarAppearanceRanks { get; set; } + + /// + public List? AvatarConstellationInfos { get; set; } + + /// + public List? TeamAppearances { get; set; } + + /// + public Overview? Overview { get; set; } + + /// + public List? AvatarCollocations { get; set; } + + /// + public async ValueTask InitializeForDatabaseViewModelAsync() + { + if (await metadataService.InitializeAsync().ConfigureAwait(false)) + { + Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); + + Task avatarAppearanceRankTask = AvatarAppearanceRankAsync(idAvatarMap); + Task avatarUsageRank = AvatarUsageRanksAsync(idAvatarMap); + Task avatarConstellationInfoTask = AvatarConstellationInfosAsync(idAvatarMap); + Task teamAppearanceTask = TeamAppearancesAsync(idAvatarMap); + Task ovewviewTask = OverviewAsync(); + + await Task.WhenAll( + avatarAppearanceRankTask, + avatarUsageRank, + avatarConstellationInfoTask, + teamAppearanceTask, + ovewviewTask) + .ConfigureAwait(false); + + return true; + } + + return false; + } + + /// + public async ValueTask InitializeForWikiAvatarViewModelAsync() + { + if (await metadataService.InitializeAsync().ConfigureAwait(false)) + { + Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); + Dictionary idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false); + + // AvatarCollocation + List avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false); + AvatarCollocations = avatarCollocationsRaw.Select(co => + { + return new ComplexAvatarCollocation(idAvatarMap[co.AvatarId]) + { + Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(), + Weapons = co.Weapons.Select(w => new ComplexWeapon(idWeaponMap[w.Item], w.Rate)).ToList(), + ReliquarySets = co.Reliquaries.Select(r => new ComplexReliquarySet(r, idReliquarySetMap)).ToList(), + }; + }).ToList(); + + return true; + } + + return false; + } + + private async ValueTask> GetIdAvatarMapExtendedAsync() + { + if (idAvatarExtendedMap == null) + { + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); + idAvatarExtendedMap = new(idAvatarMap) + { + [10000005] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE }, + [10000007] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerGirl", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE }, + }; + } + + return idAvatarExtendedMap; + } + + private async Task AvatarAppearanceRankAsync(Dictionary idAvatarMap) + { + List avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false); + AvatarAppearanceRanks = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank + { + Floor = $"第 {rank.Floor} 层", + Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(), + }).ToList(); + } + + private async Task AvatarUsageRanksAsync(Dictionary idAvatarMap) + { + List avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false); + AvatarUsageRanks = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank + { + Floor = $"第 {rank.Floor} 层", + Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(), + }).ToList(); + } + + private async Task AvatarConstellationInfosAsync(Dictionary idAvatarMap) + { + List avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false); + AvatarConstellationInfos = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info => + { + return new ComplexAvatarConstellationInfo(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.Select(x => x.Rate)); + }).ToList(); + } + + private async Task TeamAppearancesAsync(Dictionary idAvatarMap) + { + List teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false); + TeamAppearances = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList(); + } + + private async Task OverviewAsync() + { + Overview = await hutaoService.GetOverviewAsync().ConfigureAwait(false); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs similarity index 97% rename from src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs index 120c2654..0b2b6578 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs @@ -2,11 +2,10 @@ // Licensed under the MIT license. using Microsoft.Extensions.Caching.Memory; -using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Web.Hutao; using Snap.Hutao.Web.Hutao.Model; -namespace Snap.Hutao.Service; +namespace Snap.Hutao.Service.Hutao; /// /// 胡桃 API 服务 @@ -75,4 +74,4 @@ internal class HutaoService : IHutaoService T web = await taskFunc(default).ConfigureAwait(false); return memoryCache.Set(key, web, TimeSpan.FromMinutes(30)); } -} \ No newline at end of file +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs new file mode 100644 index 00000000..d2443b33 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs @@ -0,0 +1,55 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Binding.Hutao; +using Snap.Hutao.Web.Hutao.Model; + +namespace Snap.Hutao.Service.Hutao; + +/// +/// 胡桃 API 缓存 +/// +internal interface IHtaoCache +{ + /// + /// 角色使用率 + /// + List? AvatarUsageRanks { get; set; } + + /// + /// 角色上场率 + /// + List? AvatarAppearanceRanks { get; set; } + + /// + /// 角色命座信息 + /// + List? AvatarConstellationInfos { get; set; } + + /// + /// 队伍出场 + /// + List? TeamAppearances { get; set; } + + /// + /// 总览数据 + /// + Overview? Overview { get; set; } + + /// + /// 角色搭配 + /// + List? AvatarCollocations { get; set; } + + /// + /// 为数据库视图模型初始化 + /// + /// 任务 + ValueTask InitializeForDatabaseViewModelAsync(); + + /// + /// 为Wiki角色视图模型初始化 + /// + /// 任务 + ValueTask InitializeForWikiAvatarViewModelAsync(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IHutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs similarity index 96% rename from src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IHutaoService.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs index e55c86c9..1618541e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IHutaoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs @@ -3,7 +3,7 @@ using Snap.Hutao.Web.Hutao.Model; -namespace Snap.Hutao.Service.Abstraction; +namespace Snap.Hutao.Service.Hutao; /// /// 胡桃 API 服务 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index eed4d38e..87b5511c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -73,7 +73,7 @@ internal class UserService : IUserService } } - Message.UserChangedMessage message = new(currentUser, value); + Message.UserChangedMessage message = new() { OldValue = currentUser, NewValue = value }; // 当删除到无用户时也能正常反应状态 currentUser = value; diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 575d5367..7de19a0f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -28,11 +28,7 @@ Snap.Hutao.Program $(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT True - - - embedded - - + true embedded diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml index c2848949..707e49cb 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml @@ -25,6 +25,17 @@ + + + + + + + + + + + @@ -38,7 +49,7 @@ + Margin="12,12,0,-12">