user service left

This commit is contained in:
Lightczx
2023-08-02 21:58:46 +08:00
parent 1c46412324
commit 0ee875d28d
22 changed files with 287 additions and 163 deletions

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.Entity.Database;
namespace Snap.Hutao.Core.Database; namespace Snap.Hutao.Core.Database;
@@ -77,3 +78,70 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
} }
} }
} }
[SuppressMessage("", "SA1402")]
internal sealed class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
where TEntityOnly : class, IEntityOnly<TEntity>
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TEntityOnly>, new()
{
private readonly IServiceProvider serviceProvider;
private readonly IMessenger messenger;
private TEntityOnly? current;
/// <summary>
/// 构造一个新的数据库当前项
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
public ScopedDbCurrent(IServiceProvider serviceProvider)
{
messenger = serviceProvider.GetRequiredService<IMessenger>();
this.serviceProvider = serviceProvider;
}
/// <summary>
/// 当前选中的项
/// </summary>
public TEntityOnly? Current
{
get => current;
set
{
// prevent useless sets
if (current == value)
{
return;
}
// TODO: Troubeshooting why the serviceProvider will NRE
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
// only update when not processing a deletion
if (value != null)
{
if (current != null)
{
current.Entity.IsSelected = false;
dbSet.UpdateAndSave(current.Entity);
}
}
TMessage message = new() { OldValue = current, NewValue = value };
current = value;
if (current != null)
{
current.Entity.IsSelected = true;
dbSet.UpdateAndSave(current.Entity);
}
messenger.Send(message);
}
}
}
}

View File

@@ -32,6 +32,7 @@ internal static class IocConfiguration
{ {
return services return services
.AddTransient(typeof(Database.ScopedDbCurrent<,>)) .AddTransient(typeof(Database.ScopedDbCurrent<,>))
.AddTransient(typeof(Database.ScopedDbCurrent<,,>))
.AddDbContext<AppDbContext>(AddDbContextCore); .AddDbContext<AppDbContext>(AddDbContextCore);
} }

View File

@@ -22,4 +22,10 @@ internal readonly struct Delay
{ {
return Task.Delay(TimeSpan.FromSeconds(seconds)).AsValueTask(); return Task.Delay(TimeSpan.FromSeconds(seconds)).AsValueTask();
} }
[SuppressMessage("", "VSTHRD200")]
public static ValueTask FromMilliSeconds(int seconds)
{
return Task.Delay(TimeSpan.FromMilliseconds(seconds)).AsValueTask();
}
} }

View File

@@ -9,15 +9,18 @@ namespace Snap.Hutao.Model;
/// <typeparam name="TEntity">实体</typeparam> /// <typeparam name="TEntity">实体</typeparam>
/// <typeparam name="TMetadata">元数据</typeparam> /// <typeparam name="TMetadata">元数据</typeparam>
[HighQuality] [HighQuality]
internal interface IEntityWithMetadata<out TEntity, out TMetadata> internal interface IEntityWithMetadata<out TEntity, out TMetadata> : IEntityOnly<TEntity>
{ {
/// <summary>
/// 实体
/// </summary>
TEntity Entity { get; }
/// <summary> /// <summary>
/// 元数据 /// 元数据
/// </summary> /// </summary>
TMetadata Inner { get; } TMetadata Inner { get; }
} }
internal interface IEntityOnly<out TEntity>
{
/// <summary>
/// 实体
/// </summary>
TEntity Entity { get; }
}

View File

@@ -29,7 +29,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task InitializeInternalAsync(CancellationToken token = default) public async ValueTask InitializeInternalAsync(CancellationToken token = default)
{ {
string userName = LocalSetting.Get(SettingKeys.PassportUserName, string.Empty); string userName = LocalSetting.Get(SettingKeys.PassportUserName, string.Empty);
string passport = LocalSetting.Get(SettingKeys.PassportPassword, string.Empty); string passport = LocalSetting.Get(SettingKeys.PassportPassword, string.Empty);

View File

@@ -13,5 +13,5 @@ internal interface IHutaoUserServiceInitialization
/// </summary> /// </summary>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>任务</returns> /// <returns>任务</returns>
Task InitializeInternalAsync(CancellationToken token = default); ValueTask InitializeInternalAsync(CancellationToken token = default);
} }

View File

@@ -14,5 +14,5 @@ internal interface IMetadataServiceInitialization
/// </summary> /// </summary>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>任务</returns> /// <returns>任务</returns>
Task InitializeInternalAsync(CancellationToken token = default); ValueTask InitializeInternalAsync(CancellationToken token = default);
} }

View File

@@ -44,7 +44,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task InitializeInternalAsync(CancellationToken token = default) public async ValueTask InitializeInternalAsync(CancellationToken token = default)
{ {
if (isInitialized) if (isInitialized)
{ {
@@ -58,19 +58,19 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
} }
} }
private async Task<bool> TryUpdateMetadataAsync(CancellationToken token) private async ValueTask<bool> TryUpdateMetadataAsync(CancellationToken token)
{ {
IDictionary<string, string>? metaMd5Map; Dictionary<string, string>? metaXXH64Map;
try try
{ {
string metadataFile = metadataOptions.GetLocalizedRemoteFile(MetaFileName); string metadataFile = metadataOptions.GetLocalizedRemoteFile(MetaFileName);
// download meta check file // download meta check file
metaMd5Map = await httpClient metaXXH64Map = await httpClient
.GetFromJsonAsync<IDictionary<string, string>>(metadataFile, options, token) .GetFromJsonAsync<Dictionary<string, string>>(metadataFile, options, token)
.ConfigureAwait(false); .ConfigureAwait(false);
if (metaMd5Map is null) if (metaXXH64Map is null)
{ {
infoBarService.Error(SH.ServiceMetadataParseFailed); infoBarService.Error(SH.ServiceMetadataParseFailed);
return false; return false;
@@ -90,27 +90,20 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
return false; return false;
} }
await CheckMetadataAsync(metaMd5Map, token).ConfigureAwait(false); await CheckMetadataSourceFilesAsync(metaXXH64Map, token).ConfigureAwait(false);
// save metadataFile // save metadataFile
using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName))) using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName)))
{ {
await JsonSerializer await JsonSerializer
.SerializeAsync(metaFileStream, metaMd5Map, options, token) .SerializeAsync(metaFileStream, metaXXH64Map, options, token)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
return true; return true;
} }
/// <summary> private ValueTask CheckMetadataSourceFilesAsync(Dictionary<string, string> metaMd5Map, CancellationToken token)
/// 检查元数据的Md5值是否匹配
/// 如果不匹配则尝试下载
/// </summary>
/// <param name="metaMd5Map">元数据校验表</param>
/// <param name="token">取消令牌</param>
/// <returns>令牌</returns>
private Task CheckMetadataAsync(IDictionary<string, string> metaMd5Map, CancellationToken token)
{ {
return Parallel.ForEachAsync(metaMd5Map, token, async (pair, token) => return Parallel.ForEachAsync(metaMd5Map, token, async (pair, token) =>
{ {
@@ -126,14 +119,14 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
if (!skip) if (!skip)
{ {
logger.LogInformation("{hash} of {file} not matched, begin downloading", nameof(XXH64), fileFullName); logger.LogInformation("{Hash} of {File} not matched, begin downloading", nameof(XXH64), fileFullName);
await DownloadMetadataAsync(fileFullName, token).ConfigureAwait(false); await DownloadMetadataSourceFilesAsync(fileFullName, token).ConfigureAwait(false);
} }
}); }).AsValueTask();
} }
private async Task DownloadMetadataAsync(string fileFullName, CancellationToken token) private async ValueTask DownloadMetadataSourceFilesAsync(string fileFullName, CancellationToken token)
{ {
Stream sourceStream = await httpClient Stream sourceStream = await httpClient
.GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token) .GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token)

View File

@@ -18,5 +18,5 @@ internal interface INavigationAwaiter
/// 等待导航完成,或直到抛出异常 /// 等待导航完成,或直到抛出异常
/// </summary> /// </summary>
/// <returns>导航完成的任务</returns> /// <returns>导航完成的任务</returns>
Task WaitForCompletionAsync(); ValueTask WaitForCompletionAsync();
} }

View File

@@ -16,5 +16,5 @@ internal interface INavigationRecipient
/// </summary> /// </summary>
/// <param name="data">导航数据</param> /// <param name="data">导航数据</param>
/// <returns>接收处理结果是否成功</returns> /// <returns>接收处理结果是否成功</returns>
Task<bool> ReceiveAsync(INavigationData data); ValueTask<bool> ReceiveAsync(INavigationData data);
} }

View File

@@ -40,7 +40,7 @@ internal interface INavigationService : ICastService
/// <param name="data">要传递的数据</param> /// <param name="data">要传递的数据</param>
/// <param name="syncNavigationViewItem">是否同步标签,当在代码中调用时应设为 true</param> /// <param name="syncNavigationViewItem">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns> /// <returns>是否导航成功</returns>
Task<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false) ValueTask<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page; where TPage : Page;
/// <summary> /// <summary>

View File

@@ -27,9 +27,9 @@ internal sealed class NavigationExtra : INavigationData, INavigationAwaiter
public object? Data { get; set; } public object? Data { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public Task WaitForCompletionAsync() public ValueTask WaitForCompletionAsync()
{ {
return navigationCompletedTaskCompletionSource.Task; return navigationCompletedTaskCompletionSource.Task.AsValueTask();
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Helper; using Snap.Hutao.View.Helper;
using Snap.Hutao.View.Page; using Snap.Hutao.View.Page;
using Windows.Foundation;
namespace Snap.Hutao.Service.Navigation; namespace Snap.Hutao.Service.Navigation;
@@ -14,18 +15,34 @@ namespace Snap.Hutao.Service.Navigation;
/// 导航服务 /// 导航服务
/// </summary> /// </summary>
[HighQuality] [HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(INavigationService))] [Injection(InjectAs.Singleton, typeof(INavigationService))]
internal sealed partial class NavigationService : INavigationService, INavigationInitialization internal sealed class NavigationService : INavigationService, INavigationInitialization
{ {
private readonly ILogger<INavigationService> logger; private readonly ILogger<INavigationService> logger;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly TypedEventHandler<NavigationView, NavigationViewItemInvokedEventArgs> itemInvokedEventHandler;
private readonly TypedEventHandler<NavigationView, NavigationViewBackRequestedEventArgs> backRequestedEventHandler;
private readonly TypedEventHandler<NavigationView, object> paneOpenedEventHandler;
private readonly TypedEventHandler<NavigationView, object> paneClosedEventHandler;
private Frame? frame; private Frame? frame;
private NavigationView? navigationView; private NavigationView? navigationView;
private NavigationViewItem? selected; private NavigationViewItem? selected;
public NavigationService(IServiceProvider serviceProvider)
{
logger = serviceProvider.GetRequiredService<ILogger<INavigationService>>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
itemInvokedEventHandler = OnItemInvoked;
backRequestedEventHandler = OnBackRequested;
paneOpenedEventHandler = OnPaneStateChanged;
paneClosedEventHandler = OnPaneStateChanged;
}
private NavigationView? NavigationView private NavigationView? NavigationView
{ {
get => navigationView; get => navigationView;
@@ -33,24 +50,24 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
set set
{ {
// remove old listener // remove old listener
if (navigationView != null) if (navigationView is not null)
{ {
navigationView.ItemInvoked -= OnItemInvoked; navigationView.ItemInvoked -= itemInvokedEventHandler;
navigationView.BackRequested -= OnBackRequested; navigationView.BackRequested -= backRequestedEventHandler;
navigationView.PaneClosed -= OnPaneStateChanged; navigationView.PaneClosed -= paneOpenedEventHandler;
navigationView.PaneOpened -= OnPaneStateChanged; navigationView.PaneOpened -= paneClosedEventHandler;
} }
ArgumentNullException.ThrowIfNull(value); ArgumentNullException.ThrowIfNull(value);
navigationView = value; navigationView = value;
// add new listener // add new listener
if (navigationView != null) if (navigationView is not null)
{ {
navigationView.ItemInvoked += OnItemInvoked; navigationView.ItemInvoked += itemInvokedEventHandler;
navigationView.BackRequested += OnBackRequested; navigationView.BackRequested += backRequestedEventHandler;
navigationView.PaneClosed += OnPaneStateChanged; navigationView.PaneClosed += paneOpenedEventHandler;
navigationView.PaneOpened += OnPaneStateChanged; navigationView.PaneOpened += paneClosedEventHandler;
} }
} }
} }
@@ -91,7 +108,7 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false) public async ValueTask<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page where TPage : Page
{ {
NavigationResult result = Navigate<TPage>(data, syncNavigationViewItem); NavigationResult result = Navigate<TPage>(data, syncNavigationViewItem);
@@ -180,12 +197,16 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
{ {
yield return item; yield return item;
// Suppress recursion method call if possible
if (item.MenuItems.Count > 0)
{
foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems)) foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems))
{ {
yield return subItem; yield return subItem;
} }
} }
} }
}
private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{ {

View File

@@ -4,20 +4,30 @@
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Animation;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Windows.Foundation;
namespace Snap.Hutao.Service.Notification; namespace Snap.Hutao.Service.Notification;
/// <inheritdoc/> /// <inheritdoc/>
[HighQuality] [HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IInfoBarService))] [Injection(InjectAs.Singleton, typeof(IInfoBarService))]
internal sealed partial class InfoBarService : IInfoBarService internal sealed class InfoBarService : IInfoBarService
{ {
private readonly ILogger<InfoBarService> logger; private readonly ILogger<InfoBarService> logger;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly TypedEventHandler<InfoBar, InfoBarClosedEventArgs> infobarClosedEventHandler;
private ObservableCollection<InfoBar>? collection; private ObservableCollection<InfoBar>? collection;
public InfoBarService(IServiceProvider serviceProvider)
{
logger = serviceProvider.GetRequiredService<ILogger<InfoBarService>>();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
infobarClosedEventHandler = OnInfoBarClosed;
}
/// <inheritdoc/> /// <inheritdoc/>
public ObservableCollection<InfoBar> Collection public ObservableCollection<InfoBar> Collection
{ {
@@ -101,7 +111,7 @@ internal sealed partial class InfoBarService : IInfoBarService
/// <param name="title">标题</param> /// <param name="title">标题</param>
/// <param name="message">消息</param> /// <param name="message">消息</param>
/// <param name="delay">关闭延迟</param> /// <param name="delay">关闭延迟</param>
private async Task PrepareInfoBarAndShowInternalAsync(InfoBarSeverity severity, string? title, string? message, int delay) private async ValueTask PrepareInfoBarAndShowInternalAsync(InfoBarSeverity severity, string? title, string? message, int delay)
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
@@ -114,12 +124,12 @@ internal sealed partial class InfoBarService : IInfoBarService
Transitions = new() { new AddDeleteThemeTransition() }, Transitions = new() { new AddDeleteThemeTransition() },
}; };
infoBar.Closed += OnInfoBarClosed; infoBar.Closed += infobarClosedEventHandler;
collection!.Add(infoBar); collection!.Add(infoBar);
if (delay > 0) if (delay > 0)
{ {
await Task.Delay(delay).ConfigureAwait(true); await Delay.FromMilliSeconds(delay).ConfigureAwait(true);
collection.Remove(infoBar); collection.Remove(infoBar);
infoBar.IsOpen = false; infoBar.IsOpen = false;
} }
@@ -128,6 +138,6 @@ internal sealed partial class InfoBarService : IInfoBarService
private void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args) private void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args)
{ {
taskContext.InvokeOnMainThread(() => collection!.Remove(sender)); taskContext.InvokeOnMainThread(() => collection!.Remove(sender));
sender.Closed -= OnInfoBarClosed; sender.Closed -= infobarClosedEventHandler;
} }
} }

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.SpiralAbyss;
internal interface ISpiralAbyssRecordDbService
{
ValueTask AddSpiralAbyssEntryAsync(SpiralAbyssEntry entry);
ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssEntryCollectionByUidAsync(string uid);
ValueTask UpdateSpiralAbyssEntryAsync(SpiralAbyssEntry entry);
}

View File

@@ -18,12 +18,12 @@ internal interface ISpiralAbyssRecordService
/// </summary> /// </summary>
/// <param name="userAndUid">当前角色</param> /// <param name="userAndUid">当前角色</param>
/// <returns>深渊记录集合</returns> /// <returns>深渊记录集合</returns>
Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid); ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid);
/// <summary> /// <summary>
/// 异步刷新深渊记录 /// 异步刷新深渊记录
/// </summary> /// </summary>
/// <param name="userAndUid">当前角色</param> /// <param name="userAndUid">当前角色</param>
/// <returns>任务</returns> /// <returns>任务</returns>
Task RefreshSpiralAbyssAsync(UserAndUid userAndUid); ValueTask RefreshSpiralAbyssAsync(UserAndUid userAndUid);
} }

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.SpiralAbyss;
[ConstructorGenerated]
[Injection(InjectAs.Scoped, typeof(ISpiralAbyssRecordDbService))]
internal sealed partial class SpiralAbyssRecordDbService : ISpiralAbyssRecordDbService
{
private readonly IServiceProvider serviceProvider;
public async ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssEntryCollectionByUidAsync(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<SpiralAbyssEntry> entries = await appDbContext.SpiralAbysses
.Where(s => s.Uid == uid)
.OrderByDescending(s => s.ScheduleId)
.ToListAsync()
.ConfigureAwait(false);
return entries.ToObservableCollection();
}
}
public async ValueTask UpdateSpiralAbyssEntryAsync(SpiralAbyssEntry entry)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.SpiralAbysses.UpdateAndSaveAsync(entry).ConfigureAwait(false);
}
}
public async ValueTask AddSpiralAbyssEntryAsync(SpiralAbyssEntry entry)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.SpiralAbysses.AddAndSaveAsync(entry).ConfigureAwait(false);
}
}
}

View File

@@ -1,11 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
@@ -22,12 +19,14 @@ namespace Snap.Hutao.Service.SpiralAbyss;
internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly ISpiralAbyssRecordDbService spiralAbyssRecordDbService;
private string? uid; private string? uid;
private ObservableCollection<SpiralAbyssEntry>? spiralAbysses; private ObservableCollection<SpiralAbyssEntry>? spiralAbysses;
/// <inheritdoc/> /// <inheritdoc/>
public async Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid) public async ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid)
{ {
if (uid != userAndUid.Uid.Value) if (uid != userAndUid.Uid.Value)
{ {
@@ -35,33 +34,21 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
} }
uid = userAndUid.Uid.Value; uid = userAndUid.Uid.Value;
if (spiralAbysses == null) spiralAbysses ??= await spiralAbyssRecordDbService
{ .GetSpiralAbyssEntryCollectionByUidAsync(userAndUid.Uid.Value)
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<SpiralAbyssEntry> entries = await appDbContext.SpiralAbysses
.Where(s => s.Uid == userAndUid.Uid.Value)
.OrderByDescending(s => s.ScheduleId)
.ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
spiralAbysses = entries.ToObservableCollection();
}
}
return spiralAbysses; return spiralAbysses;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task RefreshSpiralAbyssAsync(UserAndUid userAndUid) public async ValueTask RefreshSpiralAbyssAsync(UserAndUid userAndUid)
{ {
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).ConfigureAwait(false); await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).ConfigureAwait(false);
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Current).ConfigureAwait(false); await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Current).ConfigureAwait(false);
} }
private async Task RefreshSpiralAbyssCoreAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule) private async ValueTask RefreshSpiralAbyssCoreAsync(UserAndUid userAndUid, SpiralAbyssSchedule schedule)
{ {
Response<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss> response = await serviceProvider Response<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss> response = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>() .GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
@@ -73,20 +60,13 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
{ {
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data; Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
SpiralAbyssEntry? existEntry = spiralAbysses!.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId); if (spiralAbysses!.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId) is SpiralAbyssEntry existEntry)
using (IServiceScope scope = serviceProvider.CreateScope())
{
ITaskContext taskContext = scope.ServiceProvider.GetRequiredService<ITaskContext>();
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
if (existEntry != null)
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
existEntry.UpdateSpiralAbyss(webSpiralAbyss); existEntry.UpdateSpiralAbyss(webSpiralAbyss);
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await appDbContext.SpiralAbysses.UpdateAndSaveAsync(existEntry).ConfigureAwait(false); await spiralAbyssRecordDbService.UpdateSpiralAbyssEntryAsync(existEntry).ConfigureAwait(false);
} }
else else
{ {
@@ -96,8 +76,7 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
spiralAbysses!.Insert(0, newEntry); spiralAbysses!.Insert(0, newEntry);
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await appDbContext.SpiralAbysses.AddAndSaveAsync(newEntry).ConfigureAwait(false); await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false);
}
} }
} }
} }

View File

@@ -33,7 +33,7 @@ internal interface IUserService
/// 此操作不能取消 /// 此操作不能取消
/// </summary> /// </summary>
/// <returns>准备完成的用户信息集合</returns> /// <returns>准备完成的用户信息集合</returns>
Task<ObservableCollection<BindingUser>> GetUserCollectionAsync(); ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync();
/// <summary> /// <summary>
/// 获取角色信息 /// 获取角色信息
@@ -62,5 +62,5 @@ internal interface IUserService
/// </summary> /// </summary>
/// <param name="user">待移除的用户</param> /// <param name="user">待移除的用户</param>
/// <returns>任务</returns> /// <returns>任务</returns>
Task RemoveUserAsync(BindingUser user); ValueTask RemoveUserAsync(BindingUser user);
} }

View File

@@ -25,84 +25,38 @@ namespace Snap.Hutao.Service.User;
internal sealed partial class UserService : IUserService internal sealed partial class UserService : IUserService
{ {
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IUserDbService userDbService;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IMessenger messenger; private readonly IMessenger messenger;
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
private BindingUser? currentUser;
private ObservableCollection<BindingUser>? userCollection; private ObservableCollection<BindingUser>? userCollection;
private ObservableCollection<UserAndUid>? roleCollection; private ObservableCollection<UserAndUid>? userAndUidCollection;
/// <inheritdoc/> /// <inheritdoc/>
public BindingUser? Current public BindingUser? Current
{ {
get => currentUser; get => dbCurrent.Current;
set set => dbCurrent.Current = value;
{
if (currentUser?.Entity.InnerId == value?.Entity.InnerId)
{
return;
}
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// only update when not processing a deletion
if (value != null)
{
if (currentUser != null)
{
currentUser.IsSelected = false;
appDbContext.Users.UpdateAndSave(currentUser.Entity);
}
}
UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
// 当删除到无用户时也能正常反应状态
currentUser = value;
if (currentUser != null)
{
currentUser.IsSelected = true;
try
{
appDbContext.Users.UpdateAndSave(currentUser.Entity);
}
catch (InvalidOperationException ex)
{
ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceUserCurrentUpdateAndSaveFailed, currentUser.UserInfo?.Uid), ex);
}
}
messenger.Send(message);
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task RemoveUserAsync(BindingUser user) public async ValueTask RemoveUserAsync(BindingUser user)
{ {
// Sync cache // Sync cache
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
userCollection!.Remove(user); userCollection!.Remove(user);
roleCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid); userAndUidCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
// Sync database // Sync database
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
using (IServiceScope scope = serviceProvider.CreateScope()) await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false);
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users
.ExecuteDeleteWhereAsync(u => u.InnerId == user.Entity.InnerId)
.ConfigureAwait(false);
}
messenger.Send(new UserRemovedMessage(user.Entity)); messenger.Send(new UserRemovedMessage(user.Entity));
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<ObservableCollection<BindingUser>> GetUserCollectionAsync() public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
if (userCollection == null) if (userCollection == null)
@@ -138,7 +92,7 @@ internal sealed partial class UserService : IUserService
public async Task<ObservableCollection<UserAndUid>> GetRoleCollectionAsync() public async Task<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
if (roleCollection == null) if (userAndUidCollection == null)
{ {
List<UserAndUid> userAndUids = new(); List<UserAndUid> userAndUids = new();
ObservableCollection<BindingUser> observableUsers = await GetUserCollectionAsync().ConfigureAwait(false); ObservableCollection<BindingUser> observableUsers = await GetUserCollectionAsync().ConfigureAwait(false);
@@ -150,10 +104,10 @@ internal sealed partial class UserService : IUserService
} }
} }
roleCollection = userAndUids.ToObservableCollection(); userAndUidCollection = userAndUids.ToObservableCollection();
} }
return roleCollection; return userAndUidCollection;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -269,11 +223,11 @@ internal sealed partial class UserService : IUserService
{ {
userCollection!.Add(newUser); userCollection!.Add(newUser);
if (roleCollection != null) if (userAndUidCollection != null)
{ {
foreach (UserGameRole role in newUser.UserGameRoles) foreach (UserGameRole role in newUser.UserGameRoles)
{ {
roleCollection.Add(new(newUser.Entity, role)); userAndUidCollection.Add(new(newUser.Entity, role));
} }
} }
} }
@@ -291,3 +245,24 @@ internal sealed partial class UserService : IUserService
} }
} }
} }
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IUserDbService))]
internal sealed partial class UserDbService : IUserDbService
{
private readonly IServiceProvider serviceProvider;
public async ValueTask DeleteUserByIdAsync(Guid id)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.ExecuteDeleteWhereAsync(u => u.InnerId == id).ConfigureAwait(false);
}
}
}
internal interface IUserDbService
{
ValueTask DeleteUserByIdAsync(Guid id);
}

View File

@@ -135,7 +135,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> ReceiveAsync(INavigationData data) public async ValueTask<bool> ReceiveAsync(INavigationData data)
{ {
if (await openUITaskCompletionSource.Task.ConfigureAwait(false)) if (await openUITaskCompletionSource.Task.ConfigureAwait(false))
{ {

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model;
using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Hoyolab.Passport;
@@ -17,7 +18,7 @@ namespace Snap.Hutao.ViewModel.User;
/// 用于视图绑定的用户 /// 用于视图绑定的用户
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class User : ObservableObject internal sealed class User : ObservableObject, IEntityOnly<EntityUser>
{ {
private readonly EntityUser inner; private readonly EntityUser inner;