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]
|
[HighQuality]
|
||||||
internal static class BoxedValues
|
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>
|
/// <summary>
|
||||||
/// <see cref="true"/>
|
/// <see cref="true"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -131,8 +131,6 @@ internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
|
|||||||
|
|
||||||
private static bool GetElevated()
|
private static bool GetElevated()
|
||||||
{
|
{
|
||||||
return true;
|
|
||||||
|
|
||||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||||
{
|
{
|
||||||
WindowsPrincipal principal = new(identity);
|
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.Setting;
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.Core.Windowing;
|
||||||
using Snap.Hutao.Message;
|
using Snap.Hutao.Message;
|
||||||
|
using Windows.Foundation;
|
||||||
using Windows.Win32.UI.WindowsAndMessaging;
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
@@ -22,6 +23,8 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi
|
|||||||
private const int MinHeight = 524;
|
private const int MinHeight = 524;
|
||||||
|
|
||||||
private readonly WindowOptions windowOptions;
|
private readonly WindowOptions windowOptions;
|
||||||
|
private readonly ILogger<MainWindow> logger;
|
||||||
|
private readonly TypedEventHandler<object, WindowEventArgs> closedEventHander;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的主窗体
|
/// 构造一个新的主窗体
|
||||||
@@ -33,9 +36,12 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi
|
|||||||
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
|
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
|
||||||
ExtendedWindow<MainWindow>.Initialize(this, serviceProvider);
|
ExtendedWindow<MainWindow>.Initialize(this, serviceProvider);
|
||||||
serviceProvider.GetRequiredService<IMessenger>().Register(this);
|
serviceProvider.GetRequiredService<IMessenger>().Register(this);
|
||||||
|
logger = serviceProvider.GetRequiredService<ILogger<MainWindow>>();
|
||||||
|
|
||||||
// If not complete we should present the welcome view.
|
// If not complete we should present the welcome view.
|
||||||
ContentSwitchPresenter.Value = StaticResource.IsAnyUnfulfilledContractPresent();
|
ContentSwitchPresenter.Value = StaticResource.IsAnyUnfulfilledContractPresent();
|
||||||
|
closedEventHander = OnClosed;
|
||||||
|
Closed += closedEventHander;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -53,4 +59,9 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi
|
|||||||
{
|
{
|
||||||
ContentSwitchPresenter.Value = false;
|
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))]
|
[Injection(InjectAs.Singleton, typeof(IUserService))]
|
||||||
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||||
{
|
{
|
||||||
|
private readonly Throttler throttler = new();
|
||||||
|
|
||||||
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||||
private readonly IUserInitializationService userInitializationService;
|
private readonly IUserInitializationService userInitializationService;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
@@ -67,7 +69,8 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
using (await throttler.ThrottleAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
if (userCollection is null)
|
if (userCollection is null)
|
||||||
{
|
{
|
||||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
||||||
@@ -83,6 +86,7 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
|||||||
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
|
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return userCollection;
|
return userCollection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
<ContentDialog
|
||||||
<!-- Licensed under the MIT License. -->
|
|
||||||
|
|
||||||
<ContentDialog
|
|
||||||
x:Class="Snap.Hutao.View.Dialog.LaunchGamePackageConvertDialog"
|
x:Class="Snap.Hutao.View.Dialog.LaunchGamePackageConvertDialog"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
|||||||
@@ -12,28 +12,14 @@ namespace Snap.Hutao.Web.Hutao;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 胡桃祈愿记录API客户端
|
/// 胡桃祈愿记录API客户端
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ConstructorGenerated(ResolveHttpClient = true)]
|
||||||
[HttpClient(HttpClientConfiguration.Default)]
|
[HttpClient(HttpClientConfiguration.Default)]
|
||||||
internal sealed class HomaGachaLogClient
|
internal sealed partial class HomaGachaLogClient
|
||||||
{
|
{
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
private readonly JsonSerializerOptions options;
|
private readonly JsonSerializerOptions options;
|
||||||
private readonly ILogger<HomaGachaLogClient> logger;
|
private readonly ILogger<HomaGachaLogClient> logger;
|
||||||
|
private readonly HutaoUserOptions hutaoUserOptions;
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步获取祈愿统计信息
|
/// 异步获取祈愿统计信息
|
||||||
@@ -42,6 +28,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>祈愿统计信息</returns>
|
/// <returns>祈愿统计信息</returns>
|
||||||
public async ValueTask<Response<GachaEventStatistics>> GetGachaEventStatisticsAsync(CancellationToken token = default)
|
public async ValueTask<Response<GachaEventStatistics>> GetGachaEventStatisticsAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
|
||||||
|
|
||||||
Response<GachaEventStatistics>? resp = await httpClient
|
Response<GachaEventStatistics>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<GachaEventStatistics>>(HutaoEndpoints.GachaLogStatisticsCurrentEvents, options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<GachaEventStatistics>>(HutaoEndpoints.GachaLogStatisticsCurrentEvents, options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@@ -57,6 +45,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>祈愿分布</returns>
|
/// <returns>祈愿分布</returns>
|
||||||
public async ValueTask<Response<GachaDistribution>> GetGachaDistributionAsync(GachaDistributionType distributionType, CancellationToken token = default)
|
public async ValueTask<Response<GachaDistribution>> GetGachaDistributionAsync(GachaDistributionType distributionType, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
|
||||||
|
|
||||||
Response<GachaDistribution>? resp = await httpClient
|
Response<GachaDistribution>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<GachaDistribution>>(HutaoEndpoints.GachaLogStatisticsDistribution(distributionType), options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<GachaDistribution>>(HutaoEndpoints.GachaLogStatisticsDistribution(distributionType), options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@@ -72,6 +62,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
[Obsolete("Use GetGachaEntriesAsync instead")]
|
[Obsolete("Use GetGachaEntriesAsync instead")]
|
||||||
public async ValueTask<Response<List<string>>> GetUidsAsync(CancellationToken token = default)
|
public async ValueTask<Response<List<string>>> GetUidsAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
|
||||||
|
|
||||||
Response<List<string>>? resp = await httpClient
|
Response<List<string>>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<List<string>>>(HutaoEndpoints.GachaLogUids, options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<List<string>>>(HutaoEndpoints.GachaLogUids, options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@@ -86,6 +78,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>Uid 列表</returns>
|
/// <returns>Uid 列表</returns>
|
||||||
public async ValueTask<Response<List<GachaEntry>>> GetGachaEntriesAsync(CancellationToken token = default)
|
public async ValueTask<Response<List<GachaEntry>>> GetGachaEntriesAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
|
||||||
|
|
||||||
Response<List<GachaEntry>>? resp = await httpClient
|
Response<List<GachaEntry>>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<List<GachaEntry>>>(HutaoEndpoints.GachaLogEntries, options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<List<GachaEntry>>>(HutaoEndpoints.GachaLogEntries, options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@@ -101,6 +95,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>末尾Id</returns>
|
/// <returns>末尾Id</returns>
|
||||||
public async ValueTask<Response<EndIds>> GetEndIdsAsync(string uid, CancellationToken token = default)
|
public async ValueTask<Response<EndIds>> GetEndIdsAsync(string uid, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
|
||||||
|
|
||||||
Response<EndIds>? resp = await httpClient
|
Response<EndIds>? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<EndIds>>(HutaoEndpoints.GachaLogEndIds(uid), options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<EndIds>>(HutaoEndpoints.GachaLogEndIds(uid), options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@@ -117,6 +113,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>云端祈愿记录</returns>
|
/// <returns>云端祈愿记录</returns>
|
||||||
public async ValueTask<Response<List<GachaItem>>> RetrieveGachaItemsAsync(string uid, EndIds endIds, CancellationToken token = default)
|
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);
|
UidAndEndIds uidAndEndIds = new(uid, endIds);
|
||||||
|
|
||||||
Response<List<GachaItem>>? resp = await httpClient
|
Response<List<GachaItem>>? resp = await httpClient
|
||||||
@@ -135,6 +133,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>响应</returns>
|
/// <returns>响应</returns>
|
||||||
public async ValueTask<Response.Response> UploadGachaItemsAsync(string uid, List<GachaItem> gachaItems, CancellationToken token = default)
|
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);
|
UidAndItems uidAndItems = new(uid, gachaItems);
|
||||||
|
|
||||||
Response.Response? resp = await httpClient
|
Response.Response? resp = await httpClient
|
||||||
@@ -152,6 +152,8 @@ internal sealed class HomaGachaLogClient
|
|||||||
/// <returns>响应</returns>
|
/// <returns>响应</returns>
|
||||||
public async ValueTask<Response.Response> DeleteGachaItemsAsync(string uid, CancellationToken token = default)
|
public async ValueTask<Response.Response> DeleteGachaItemsAsync(string uid, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token);
|
||||||
|
|
||||||
Response.Response? resp = await httpClient
|
Response.Response? resp = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<List<GachaItem>>>(HutaoEndpoints.GachaLogDelete(uid), options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<List<GachaItem>>>(HutaoEndpoints.GachaLogDelete(uid), options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|||||||
@@ -151,10 +151,10 @@ internal sealed partial class HomaPassportClient
|
|||||||
private static string Encrypt(string text)
|
private static string Encrypt(string text)
|
||||||
{
|
{
|
||||||
byte[] plaintextBytes = Encoding.UTF8.GetBytes(text);
|
byte[] plaintextBytes = Encoding.UTF8.GetBytes(text);
|
||||||
using (RSACryptoServiceProvider rsa = new(2048))
|
using (RSA rsa = RSA.Create(2048))
|
||||||
{
|
{
|
||||||
rsa.ImportFromPem(PublicKey);
|
rsa.ImportFromPem(PublicKey);
|
||||||
byte[] encryptedBytes = rsa.Encrypt(plaintextBytes, true);
|
byte[] encryptedBytes = rsa.Encrypt(plaintextBytes, RSAEncryptionPadding.OaepSHA1);
|
||||||
return Convert.ToBase64String(encryptedBytes);
|
return Convert.ToBase64String(encryptedBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user