fix user collection initialization race condition

This commit is contained in:
DismissedLight
2023-08-21 22:51:33 +08:00
parent e347694a0b
commit 23feb78f05
8 changed files with 68 additions and 52 deletions

View File

@@ -9,21 +9,6 @@ namespace Snap.Hutao.Control;
[HighQuality]
internal static class BoxedValues
{
/// <summary>
/// <see cref="double"/> 0
/// </summary>
public static readonly object DoubleZero = 0D;
/// <summary>
/// <see cref="double"/> 1
/// </summary>
public static readonly object DoubleOne = 1D;
/// <summary>
/// <see cref="int"/> 0
/// </summary>
public static readonly object Int32Zero = 0;
/// <summary>
/// <see cref="true"/>
/// </summary>

View File

@@ -131,8 +131,6 @@ internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
private static bool GetElevated()
{
return true;
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new(identity);

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading;
internal sealed class Throttler
{
private readonly ConcurrentDictionary<string, SemaphoreSlim> methodSemaphoreMap = new();
public ValueTask<SemaphoreSlimToken> ThrottleAsync(CancellationToken token = default, [CallerMemberName] string callerName = default!, [CallerLineNumber] int callerLine = 0)
{
string key = $"{callerName}L{callerLine}";
SemaphoreSlim semaphore = methodSemaphoreMap.GetOrAdd(key, name => new SemaphoreSlim(1));
return semaphore.EnterAsync(token);
}
}

View File

@@ -6,6 +6,7 @@ using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Message;
using Windows.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao;
@@ -22,6 +23,8 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi
private const int MinHeight = 524;
private readonly WindowOptions windowOptions;
private readonly ILogger<MainWindow> logger;
private readonly TypedEventHandler<object, WindowEventArgs> closedEventHander;
/// <summary>
/// 构造一个新的主窗体
@@ -33,9 +36,12 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
ExtendedWindow<MainWindow>.Initialize(this, serviceProvider);
serviceProvider.GetRequiredService<IMessenger>().Register(this);
logger = serviceProvider.GetRequiredService<ILogger<MainWindow>>();
// If not complete we should present the welcome view.
ContentSwitchPresenter.Value = StaticResource.IsAnyUnfulfilledContractPresent();
closedEventHander = OnClosed;
Closed += closedEventHander;
}
/// <inheritdoc/>
@@ -53,4 +59,9 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi
{
ContentSwitchPresenter.Value = false;
}
private void OnClosed(object sender, WindowEventArgs args)
{
logger.LogInformation("MainWindow Closed");
}
}

View File

@@ -25,6 +25,8 @@ namespace Snap.Hutao.Service.User;
[Injection(InjectAs.Singleton, typeof(IUserService))]
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
{
private readonly Throttler throttler = new();
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
private readonly IUserInitializationService userInitializationService;
private readonly IServiceProvider serviceProvider;
@@ -67,20 +69,22 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
/// <inheritdoc/>
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
{
await taskContext.SwitchToBackgroundAsync();
if (userCollection is null)
using (await throttler.ThrottleAsync().ConfigureAwait(false))
{
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
List<BindingUser> users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false);
userCollection = users.ToObservableCollection();
if (userCollection is null)
{
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
List<BindingUser> users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false);
userCollection = users.ToObservableCollection();
try
{
Current = users.SelectedOrDefault();
}
catch (InvalidOperationException ex)
{
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
try
{
Current = users.SelectedOrDefault();
}
catch (InvalidOperationException ex)
{
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
}
}
}

View File

@@ -1,7 +1,4 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<ContentDialog
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.LaunchGamePackageConvertDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

View File

@@ -12,28 +12,14 @@ namespace Snap.Hutao.Web.Hutao;
/// <summary>
/// 胡桃祈愿记录API客户端
/// </summary>
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.Default)]
internal sealed class HomaGachaLogClient
internal sealed partial class HomaGachaLogClient
{
private readonly HttpClient httpClient;
private readonly JsonSerializerOptions options;
private readonly ILogger<HomaGachaLogClient> logger;
/// <summary>
/// 构造一个新的胡桃祈愿记录API客户端
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="serviceProvider">服务提供器</param>
public HomaGachaLogClient(HttpClient httpClient, IServiceProvider serviceProvider)
{
options = serviceProvider.GetRequiredService<JsonSerializerOptions>();
logger = serviceProvider.GetRequiredService<ILogger<HomaGachaLogClient>>();
this.httpClient = httpClient;
HutaoUserOptions hutaoUserOptions = serviceProvider.GetRequiredService<HutaoUserOptions>();
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
}
private readonly HutaoUserOptions hutaoUserOptions;
/// <summary>
/// 异步获取祈愿统计信息
@@ -42,6 +28,8 @@ internal sealed class HomaGachaLogClient
/// <returns>祈愿统计信息</returns>
public async ValueTask<Response<GachaEventStatistics>> GetGachaEventStatisticsAsync(CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
Response<GachaEventStatistics>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<GachaEventStatistics>>(HutaoEndpoints.GachaLogStatisticsCurrentEvents, options, logger, token)
.ConfigureAwait(false);
@@ -57,6 +45,8 @@ internal sealed class HomaGachaLogClient
/// <returns>祈愿分布</returns>
public async ValueTask<Response<GachaDistribution>> GetGachaDistributionAsync(GachaDistributionType distributionType, CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
Response<GachaDistribution>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<GachaDistribution>>(HutaoEndpoints.GachaLogStatisticsDistribution(distributionType), options, logger, token)
.ConfigureAwait(false);
@@ -72,6 +62,8 @@ internal sealed class HomaGachaLogClient
[Obsolete("Use GetGachaEntriesAsync instead")]
public async ValueTask<Response<List<string>>> GetUidsAsync(CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
Response<List<string>>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<List<string>>>(HutaoEndpoints.GachaLogUids, options, logger, token)
.ConfigureAwait(false);
@@ -86,6 +78,8 @@ internal sealed class HomaGachaLogClient
/// <returns>Uid 列表</returns>
public async ValueTask<Response<List<GachaEntry>>> GetGachaEntriesAsync(CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
Response<List<GachaEntry>>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<List<GachaEntry>>>(HutaoEndpoints.GachaLogEntries, options, logger, token)
.ConfigureAwait(false);
@@ -101,6 +95,8 @@ internal sealed class HomaGachaLogClient
/// <returns>末尾Id</returns>
public async ValueTask<Response<EndIds>> GetEndIdsAsync(string uid, CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
Response<EndIds>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<EndIds>>(HutaoEndpoints.GachaLogEndIds(uid), options, logger, token)
.ConfigureAwait(false);
@@ -117,6 +113,8 @@ internal sealed class HomaGachaLogClient
/// <returns>云端祈愿记录</returns>
public async ValueTask<Response<List<GachaItem>>> RetrieveGachaItemsAsync(string uid, EndIds endIds, CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
UidAndEndIds uidAndEndIds = new(uid, endIds);
Response<List<GachaItem>>? resp = await httpClient
@@ -135,6 +133,8 @@ internal sealed class HomaGachaLogClient
/// <returns>响应</returns>
public async ValueTask<Response.Response> UploadGachaItemsAsync(string uid, List<GachaItem> gachaItems, CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
UidAndItems uidAndItems = new(uid, gachaItems);
Response.Response? resp = await httpClient
@@ -152,6 +152,8 @@ internal sealed class HomaGachaLogClient
/// <returns>响应</returns>
public async ValueTask<Response.Response> DeleteGachaItemsAsync(string uid, CancellationToken token = default)
{
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
Response.Response? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<List<GachaItem>>>(HutaoEndpoints.GachaLogDelete(uid), options, logger, token)
.ConfigureAwait(false);

View File

@@ -151,10 +151,10 @@ internal sealed partial class HomaPassportClient
private static string Encrypt(string text)
{
byte[] plaintextBytes = Encoding.UTF8.GetBytes(text);
using (RSACryptoServiceProvider rsa = new(2048))
using (RSA rsa = RSA.Create(2048))
{
rsa.ImportFromPem(PublicKey);
byte[] encryptedBytes = rsa.Encrypt(plaintextBytes, true);
byte[] encryptedBytes = rsa.Encrypt(plaintextBytes, RSAEncryptionPadding.OaepSHA1);
return Convert.ToBase64String(encryptedBytes);
}
}