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 Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity.Database;
namespace Snap.Hutao.Core.Database;
@@ -72,6 +73,73 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
dbSet.UpdateAndSave(current);
}
messenger.Send(message);
}
}
}
}
[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
.AddTransient(typeof(Database.ScopedDbCurrent<,>))
.AddTransient(typeof(Database.ScopedDbCurrent<,,>))
.AddDbContext<AppDbContext>(AddDbContextCore);
}

View File

@@ -22,4 +22,10 @@ internal readonly struct Delay
{
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="TMetadata">元数据</typeparam>
[HighQuality]
internal interface IEntityWithMetadata<out TEntity, out TMetadata>
internal interface IEntityWithMetadata<out TEntity, out TMetadata> : IEntityOnly<TEntity>
{
/// <summary>
/// 元数据
/// </summary>
TMetadata Inner { get; }
}
internal interface IEntityOnly<out TEntity>
{
/// <summary>
/// 实体
/// </summary>
TEntity Entity { get; }
/// <summary>
/// 元数据
/// </summary>
TMetadata Inner { get; }
}

View File

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

View File

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

View File

@@ -14,5 +14,5 @@ internal interface IMetadataServiceInitialization
/// </summary>
/// <param name="token">取消令牌</param>
/// <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/>
public async Task InitializeInternalAsync(CancellationToken token = default)
public async ValueTask InitializeInternalAsync(CancellationToken token = default)
{
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
{
string metadataFile = metadataOptions.GetLocalizedRemoteFile(MetaFileName);
// download meta check file
metaMd5Map = await httpClient
.GetFromJsonAsync<IDictionary<string, string>>(metadataFile, options, token)
metaXXH64Map = await httpClient
.GetFromJsonAsync<Dictionary<string, string>>(metadataFile, options, token)
.ConfigureAwait(false);
if (metaMd5Map is null)
if (metaXXH64Map is null)
{
infoBarService.Error(SH.ServiceMetadataParseFailed);
return false;
@@ -90,27 +90,20 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
return false;
}
await CheckMetadataAsync(metaMd5Map, token).ConfigureAwait(false);
await CheckMetadataSourceFilesAsync(metaXXH64Map, token).ConfigureAwait(false);
// save metadataFile
using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName)))
{
await JsonSerializer
.SerializeAsync(metaFileStream, metaMd5Map, options, token)
.SerializeAsync(metaFileStream, metaXXH64Map, options, token)
.ConfigureAwait(false);
}
return true;
}
/// <summary>
/// 检查元数据的Md5值是否匹配
/// 如果不匹配则尝试下载
/// </summary>
/// <param name="metaMd5Map">元数据校验表</param>
/// <param name="token">取消令牌</param>
/// <returns>令牌</returns>
private Task CheckMetadataAsync(IDictionary<string, string> metaMd5Map, CancellationToken token)
private ValueTask CheckMetadataSourceFilesAsync(Dictionary<string, string> metaMd5Map, CancellationToken token)
{
return Parallel.ForEachAsync(metaMd5Map, token, async (pair, token) =>
{
@@ -126,14 +119,14 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
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
.GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token)

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Helper;
using Snap.Hutao.View.Page;
using Windows.Foundation;
namespace Snap.Hutao.Service.Navigation;
@@ -14,18 +15,34 @@ namespace Snap.Hutao.Service.Navigation;
/// 导航服务
/// </summary>
[HighQuality]
[ConstructorGenerated]
[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 IInfoBarService infoBarService;
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 NavigationView? navigationView;
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
{
get => navigationView;
@@ -33,24 +50,24 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
set
{
// remove old listener
if (navigationView != null)
if (navigationView is not null)
{
navigationView.ItemInvoked -= OnItemInvoked;
navigationView.BackRequested -= OnBackRequested;
navigationView.PaneClosed -= OnPaneStateChanged;
navigationView.PaneOpened -= OnPaneStateChanged;
navigationView.ItemInvoked -= itemInvokedEventHandler;
navigationView.BackRequested -= backRequestedEventHandler;
navigationView.PaneClosed -= paneOpenedEventHandler;
navigationView.PaneOpened -= paneClosedEventHandler;
}
ArgumentNullException.ThrowIfNull(value);
navigationView = value;
// add new listener
if (navigationView != null)
if (navigationView is not null)
{
navigationView.ItemInvoked += OnItemInvoked;
navigationView.BackRequested += OnBackRequested;
navigationView.PaneClosed += OnPaneStateChanged;
navigationView.PaneOpened += OnPaneStateChanged;
navigationView.ItemInvoked += itemInvokedEventHandler;
navigationView.BackRequested += backRequestedEventHandler;
navigationView.PaneClosed += paneOpenedEventHandler;
navigationView.PaneOpened += paneClosedEventHandler;
}
}
}
@@ -91,7 +108,7 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
}
/// <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
{
NavigationResult result = Navigate<TPage>(data, syncNavigationViewItem);
@@ -180,9 +197,13 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
{
yield return item;
foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems))
// Suppress recursion method call if possible
if (item.MenuItems.Count > 0)
{
yield return subItem;
foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems))
{
yield return subItem;
}
}
}
}

View File

@@ -4,20 +4,30 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using System.Collections.ObjectModel;
using Windows.Foundation;
namespace Snap.Hutao.Service.Notification;
/// <inheritdoc/>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IInfoBarService))]
internal sealed partial class InfoBarService : IInfoBarService
internal sealed class InfoBarService : IInfoBarService
{
private readonly ILogger<InfoBarService> logger;
private readonly ITaskContext taskContext;
private readonly TypedEventHandler<InfoBar, InfoBarClosedEventArgs> infobarClosedEventHandler;
private ObservableCollection<InfoBar>? collection;
public InfoBarService(IServiceProvider serviceProvider)
{
logger = serviceProvider.GetRequiredService<ILogger<InfoBarService>>();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
infobarClosedEventHandler = OnInfoBarClosed;
}
/// <inheritdoc/>
public ObservableCollection<InfoBar> Collection
{
@@ -101,7 +111,7 @@ internal sealed partial class InfoBarService : IInfoBarService
/// <param name="title">标题</param>
/// <param name="message">消息</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();
@@ -114,12 +124,12 @@ internal sealed partial class InfoBarService : IInfoBarService
Transitions = new() { new AddDeleteThemeTransition() },
};
infoBar.Closed += OnInfoBarClosed;
infoBar.Closed += infobarClosedEventHandler;
collection!.Add(infoBar);
if (delay > 0)
{
await Task.Delay(delay).ConfigureAwait(true);
await Delay.FromMilliSeconds(delay).ConfigureAwait(true);
collection.Remove(infoBar);
infoBar.IsOpen = false;
}
@@ -128,6 +138,6 @@ internal sealed partial class InfoBarService : IInfoBarService
private void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args)
{
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>
/// <param name="userAndUid">当前角色</param>
/// <returns>深渊记录集合</returns>
Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid);
ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid);
/// <summary>
/// 异步刷新深渊记录
/// </summary>
/// <param name="userAndUid">当前角色</param>
/// <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.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Response;
@@ -22,12 +19,14 @@ namespace Snap.Hutao.Service.SpiralAbyss;
internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService
{
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly ISpiralAbyssRecordDbService spiralAbyssRecordDbService;
private string? uid;
private ObservableCollection<SpiralAbyssEntry>? spiralAbysses;
/// <inheritdoc/>
public async Task<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid)
public async ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid)
{
if (uid != userAndUid.Uid.Value)
{
@@ -35,33 +34,21 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
}
uid = userAndUid.Uid.Value;
if (spiralAbysses == null)
{
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);
spiralAbysses = entries.ToObservableCollection();
}
}
spiralAbysses ??= await spiralAbyssRecordDbService
.GetSpiralAbyssEntryCollectionByUidAsync(userAndUid.Uid.Value)
.ConfigureAwait(false);
return spiralAbysses;
}
/// <inheritdoc/>
public async Task RefreshSpiralAbyssAsync(UserAndUid userAndUid)
public async ValueTask RefreshSpiralAbyssAsync(UserAndUid userAndUid)
{
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).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
.GetRequiredService<IOverseaSupportFactory<IGameRecordClient>>()
@@ -73,31 +60,23 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
{
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
SpiralAbyssEntry? existEntry = spiralAbysses!.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId);
using (IServiceScope scope = serviceProvider.CreateScope())
if (spiralAbysses!.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId) is SpiralAbyssEntry existEntry)
{
ITaskContext taskContext = scope.ServiceProvider.GetRequiredService<ITaskContext>();
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await taskContext.SwitchToMainThreadAsync();
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
if (existEntry != null)
{
await taskContext.SwitchToMainThreadAsync();
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
await taskContext.SwitchToBackgroundAsync();
await spiralAbyssRecordDbService.UpdateSpiralAbyssEntryAsync(existEntry).ConfigureAwait(false);
}
else
{
SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss);
await taskContext.SwitchToBackgroundAsync();
await appDbContext.SpiralAbysses.UpdateAndSaveAsync(existEntry).ConfigureAwait(false);
}
else
{
SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss);
await taskContext.SwitchToMainThreadAsync();
spiralAbysses!.Insert(0, newEntry);
await taskContext.SwitchToMainThreadAsync();
spiralAbysses!.Insert(0, newEntry);
await taskContext.SwitchToBackgroundAsync();
await appDbContext.SpiralAbysses.AddAndSaveAsync(newEntry).ConfigureAwait(false);
}
await taskContext.SwitchToBackgroundAsync();
await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false);
}
}
}

View File

@@ -33,7 +33,7 @@ internal interface IUserService
/// 此操作不能取消
/// </summary>
/// <returns>准备完成的用户信息集合</returns>
Task<ObservableCollection<BindingUser>> GetUserCollectionAsync();
ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync();
/// <summary>
/// 获取角色信息
@@ -62,5 +62,5 @@ internal interface IUserService
/// </summary>
/// <param name="user">待移除的用户</param>
/// <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
{
private readonly ITaskContext taskContext;
private readonly IUserDbService userDbService;
private readonly IServiceProvider serviceProvider;
private readonly IMessenger messenger;
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
private BindingUser? currentUser;
private ObservableCollection<BindingUser>? userCollection;
private ObservableCollection<UserAndUid>? roleCollection;
private ObservableCollection<UserAndUid>? userAndUidCollection;
/// <inheritdoc/>
public BindingUser? Current
{
get => currentUser;
set
{
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);
}
}
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public async Task RemoveUserAsync(BindingUser user)
public async ValueTask RemoveUserAsync(BindingUser user)
{
// Sync cache
await taskContext.SwitchToMainThreadAsync();
userCollection!.Remove(user);
roleCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
userAndUidCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
// Sync database
await taskContext.SwitchToBackgroundAsync();
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users
.ExecuteDeleteWhereAsync(u => u.InnerId == user.Entity.InnerId)
.ConfigureAwait(false);
}
await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false);
messenger.Send(new UserRemovedMessage(user.Entity));
}
/// <inheritdoc/>
public async Task<ObservableCollection<BindingUser>> GetUserCollectionAsync()
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
{
await taskContext.SwitchToBackgroundAsync();
if (userCollection == null)
@@ -138,7 +92,7 @@ internal sealed partial class UserService : IUserService
public async Task<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
{
await taskContext.SwitchToBackgroundAsync();
if (roleCollection == null)
if (userAndUidCollection == null)
{
List<UserAndUid> userAndUids = new();
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/>
@@ -269,11 +223,11 @@ internal sealed partial class UserService : IUserService
{
userCollection!.Add(newUser);
if (roleCollection != null)
if (userAndUidCollection != null)
{
foreach (UserGameRole role in newUser.UserGameRoles)
{
roleCollection.Add(new(newUser.Entity, role));
userAndUidCollection.Add(new(newUser.Entity, role));
}
}
}
@@ -290,4 +244,25 @@ 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/>
public async Task<bool> ReceiveAsync(INavigationData data)
public async ValueTask<bool> ReceiveAsync(INavigationData data)
{
if (await openUITaskCompletionSource.Task.ConfigureAwait(false))
{

View File

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