mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix user collection initialization race condition
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
19
src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user