mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
user service left
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ internal static class IocConfiguration
|
||||
{
|
||||
return services
|
||||
.AddTransient(typeof(Database.ScopedDbCurrent<,>))
|
||||
.AddTransient(typeof(Database.ScopedDbCurrent<,,>))
|
||||
.AddDbContext<AppDbContext>(AddDbContextCore);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -13,5 +13,5 @@ internal interface IHutaoUserServiceInitialization
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>任务</returns>
|
||||
Task InitializeInternalAsync(CancellationToken token = default);
|
||||
ValueTask InitializeInternalAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -14,5 +14,5 @@ internal interface IMetadataServiceInitialization
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>任务</returns>
|
||||
Task InitializeInternalAsync(CancellationToken token = default);
|
||||
ValueTask InitializeInternalAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -18,5 +18,5 @@ internal interface INavigationAwaiter
|
||||
/// 等待导航完成,或直到抛出异常
|
||||
/// </summary>
|
||||
/// <returns>导航完成的任务</returns>
|
||||
Task WaitForCompletionAsync();
|
||||
ValueTask WaitForCompletionAsync();
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ internal interface INavigationRecipient
|
||||
/// </summary>
|
||||
/// <param name="data">导航数据</param>
|
||||
/// <returns>接收处理结果是否成功</returns>
|
||||
Task<bool> ReceiveAsync(INavigationData data);
|
||||
ValueTask<bool> ReceiveAsync(INavigationData data);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user