user service refactor

This commit is contained in:
Lightczx
2023-12-12 17:07:11 +08:00
parent 57f7ac944c
commit 1c991aa120
12 changed files with 269 additions and 220 deletions

View File

@@ -10,17 +10,10 @@ public class CollectionsMarshalTest
[TestMethod] [TestMethod]
public void DictionaryMarshalGetValueRefOrNullRefIsNullRef() public void DictionaryMarshalGetValueRefOrNullRefIsNullRef()
{ {
#if NET8_0_OR_GREATER
Dictionary<uint, string> dictionaryValueKeyRefValue = []; Dictionary<uint, string> dictionaryValueKeyRefValue = [];
Dictionary<uint, uint> dictionaryValueKeyValueValue = []; Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
Dictionary<string, uint> dictionaryRefKeyValueValue = []; Dictionary<string, uint> dictionaryRefKeyValueValue = [];
Dictionary<string, string> dictionaryRefKeyRefValue = []; Dictionary<string, string> dictionaryRefKeyRefValue = [];
#else
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
Dictionary<string, string> dictionaryRefKeyRefValue = new();
#endif
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyRefValue, 1U))); Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyRefValue, 1U)));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyValueValue, 1U))); Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyValueValue, 1U)));
@@ -31,17 +24,10 @@ public class CollectionsMarshalTest
[TestMethod] [TestMethod]
public void DictionaryMarshalGetValueRefOrAddDefaultIsDefault() public void DictionaryMarshalGetValueRefOrAddDefaultIsDefault()
{ {
#if NET8_0_OR_GREATER
Dictionary<uint, string> dictionaryValueKeyRefValue = []; Dictionary<uint, string> dictionaryValueKeyRefValue = [];
Dictionary<uint, uint> dictionaryValueKeyValueValue = []; Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
Dictionary<string, uint> dictionaryRefKeyValueValue = []; Dictionary<string, uint> dictionaryRefKeyValueValue = [];
Dictionary<string, string> dictionaryRefKeyRefValue = []; Dictionary<string, string> dictionaryRefKeyRefValue = [];
#else
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
Dictionary<string, string> dictionaryRefKeyRefValue = new();
#endif
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyRefValue, 1U, out _) == default); Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyRefValue, 1U, out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyValueValue, 1U, out _) == default); Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyValueValue, 1U, out _) == default);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -7,8 +8,6 @@ namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass] [TestClass]
public sealed class JsonSerializeTest public sealed class JsonSerializeTest
{ {
public TestContext? TestContext { get; set; }
private readonly JsonSerializerOptions AlowStringNumberOptions = new() private readonly JsonSerializerOptions AlowStringNumberOptions = new()
{ {
NumberHandling = JsonNumberHandling.AllowReadingFromString, NumberHandling = JsonNumberHandling.AllowReadingFromString,
@@ -36,7 +35,7 @@ public sealed class JsonSerializeTest
[TestMethod] [TestMethod]
public void DelegatePropertyCanSerialize() public void DelegatePropertyCanSerialize()
{ {
Sample sample = JsonSerializer.Deserialize<Sample>(SmapleObjectJson)!; SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SmapleObjectJson)!;
Assert.AreEqual(sample.B, 1); Assert.AreEqual(sample.B, 1);
} }
@@ -44,7 +43,7 @@ public sealed class JsonSerializeTest
[ExpectedException(typeof(JsonException))] [ExpectedException(typeof(JsonException))]
public void EmptyStringCannotSerializeAsNumber() public void EmptyStringCannotSerializeAsNumber()
{ {
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleEmptyStringObjectJson)!; SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SmapleEmptyStringObjectJson)!;
Assert.AreEqual(sample.A, 0); Assert.AreEqual(sample.A, 0);
} }
@@ -58,37 +57,28 @@ public sealed class JsonSerializeTest
[TestMethod] [TestMethod]
public void ByteArraySerializeAsBase64() public void ByteArraySerializeAsBase64()
{ {
byte[] array = SampleByteArrayPropertyClass sample = new()
#if NET8_0_OR_GREATER
[1, 2, 3, 4, 5];
#else
{ 1, 2, 3, 4, 5 };
#endif
ByteArraySample sample = new()
{ {
Array = array, Array = [1, 2, 3, 4, 5],
}; };
string result = JsonSerializer.Serialize(sample); string result = JsonSerializer.Serialize(sample);
TestContext!.WriteLine($"ByteArray Serialize Result: {result}"); Assert.AreEqual(result, """{"Array":"AQIDBAU="}""");
Assert.AreEqual(result, """
{"Array":"AQIDBAU="}
""");
} }
private sealed class Sample private sealed class SampleDelegatePropertyClass
{ {
public int A { get => B; set => B = value; } public int A { get => B; set => B = value; }
public int B { get; set; } public int B { get; set; }
} }
private sealed class StringNumberSample private sealed class SampleStringReadWriteNumberPropertyClass
{ {
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int A { get; set; } public int A { get; set; }
} }
private sealed class ByteArraySample private sealed class SampleByteArrayPropertyClass
{ {
public byte[]? Array { get; set; } public byte[]? Array { get; set; }
} }

View File

@@ -9,11 +9,10 @@ public sealed class LinqTest
{ {
[TestMethod] [TestMethod]
[ExpectedException(typeof(InvalidOperationException))] [ExpectedException(typeof(InvalidOperationException))]
public void LinqOrderByWithWrapperStruct() public void LinqOrderByWithWrapperStructThrow()
{ {
List<MyUInt32> list = [1, 5, 2, 6, 3, 7, 4, 8]; List<MyUInt32> list = [1, 5, 2, 6, 3, 7, 4, 8];
string result = string.Join(", ", list.OrderBy(i => i).Select(i => i.Value)); string result = string.Join(", ", list.OrderBy(i => i).Select(i => i.Value));
Console.WriteLine(result); Console.WriteLine(result);
} }

View File

@@ -159,19 +159,11 @@ public sealed class GeniusInvokationDecoding
result.CopyTo(resultArray); result.CopyTo(resultArray);
ushort[] testKnownResult = ushort[] testKnownResult =
#if NET8_0_OR_GREATER
[ [
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153, 060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201, 181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270, 217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
]; ];
#else
{
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
};
#endif
CollectionAssert.AreEqual(resultArray, testKnownResult); CollectionAssert.AreEqual(resultArray, testKnownResult);
} }

View File

@@ -15,6 +15,7 @@ public class SpiralAbyssScheduleIdTest
DateTimeOffset dateTimeOffset = new(2020, 7, 1, 4, 0, 0, Utc8); DateTimeOffset dateTimeOffset = new(2020, 7, 1, 4, 0, 0, Utc8);
Console.WriteLine($"2020-07-01 04:00:00 为第 {GetForDateTimeOffset(dateTimeOffset)} 期"); Console.WriteLine($"2020-07-01 04:00:00 为第 {GetForDateTimeOffset(dateTimeOffset)} 期");
} }
public static int GetForDateTimeOffset(DateTimeOffset dateTimeOffset) public static int GetForDateTimeOffset(DateTimeOffset dateTimeOffset)
{ {
// Force time in UTC+08 // Force time in UTC+08

View File

@@ -167,7 +167,7 @@ internal static partial class EnumerableExtension
return results; return results;
} }
public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, CancellationToken, ValueTask<TResult>> selector, CancellationToken token) public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, CancellationToken, ValueTask<TResult>> selector, CancellationToken token = default)
{ {
List<TResult> results = new(list.Count); List<TResult> results = new(list.Count);

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using System.Collections.ObjectModel;
using BindingUser = Snap.Hutao.ViewModel.User.User;
namespace Snap.Hutao.Service.User;
internal interface IUserCollectionService
{
BindingUser? CurrentUser { get; set; }
ValueTask<ObservableCollection<UserAndUid>> GetUserAndUidCollectionAsync();
ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync();
UserGameRole? GetUserGameRoleByUid(string uid);
ValueTask RemoveUserAsync(BindingUser user);
ValueTask<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea);
bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user);
}

View File

@@ -50,13 +50,6 @@ internal interface IUserService
/// <returns>处理的结果</returns> /// <returns>处理的结果</returns>
ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea); ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea);
/// <summary>
/// 异步刷新 Cookie 的 CookieToken
/// </summary>
/// <param name="user">用户</param>
/// <returns>是否刷新成功</returns>
ValueTask<bool> RefreshCookieTokenAsync(BindingUser user);
ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user); ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user);
/// <summary> /// <summary>

View File

@@ -0,0 +1,185 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Message;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using System.Collections.ObjectModel;
using BindingUser = Snap.Hutao.ViewModel.User.User;
namespace Snap.Hutao.Service.User;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IUserCollectionService))]
internal sealed partial class UserCollectionService : IUserCollectionService
{
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
private readonly IUserInitializationService userInitializationService;
private readonly IUserDbService userDbService;
private readonly ITaskContext taskContext;
private readonly IMessenger messenger;
private readonly Throttler throttler = new();
private ObservableCollection<BindingUser>? userCollection;
private Dictionary<string, BindingUser>? midUserMap;
private ObservableCollection<UserAndUid>? userAndUidCollection;
private Dictionary<string, UserGameRole>? uidUserGameRoleMap;
public BindingUser? CurrentUser
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
{
using (await throttler.ThrottleAsync().ConfigureAwait(false))
{
if (userCollection is null)
{
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
List<BindingUser> users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync).ConfigureAwait(false);
midUserMap = [];
foreach (BindingUser user in users)
{
if (user.Entity.Mid is not null)
{
midUserMap[user.Entity.Mid] = user;
}
if (user.NeedDbUpdateAfterResume)
{
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
user.NeedDbUpdateAfterResume = false;
}
}
userCollection = users.ToObservableCollection();
try
{
CurrentUser = users.SelectedOrDefault();
}
catch (InvalidOperationException ex)
{
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
}
}
}
return userCollection;
}
public async ValueTask<ObservableCollection<UserAndUid>> GetUserAndUidCollectionAsync()
{
if (userAndUidCollection is null)
{
await taskContext.SwitchToBackgroundAsync();
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
List<UserAndUid> roles = [];
uidUserGameRoleMap = [];
foreach (BindingUser user in users)
{
foreach (UserGameRole role in user.UserGameRoles)
{
roles.Add(UserAndUid.From(user.Entity, role));
uidUserGameRoleMap[role.GameUid] = role;
}
}
userAndUidCollection = roles.ToObservableCollection();
}
return userAndUidCollection;
}
public async ValueTask RemoveUserAsync(BindingUser user)
{
// Sync cache
await taskContext.SwitchToMainThreadAsync();
ArgumentNullException.ThrowIfNull(userCollection);
userCollection.Remove(user);
userAndUidCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
if (user.Entity.Mid is not null)
{
midUserMap?.Remove(user.Entity.Mid);
}
// Sync database
await taskContext.SwitchToBackgroundAsync();
await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false);
messenger.Send(new UserRemovedMessage(user.Entity));
}
public UserGameRole? GetUserGameRoleByUid(string uid)
{
if (uidUserGameRoleMap is null)
{
return default;
}
try
{
return uidUserGameRoleMap[uid];
}
catch (InvalidOperationException)
{
// Sequence contains more than one matching element
// TODO: return a specialize UserGameRole to indicate error
}
return default;
}
public bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user)
{
ArgumentNullException.ThrowIfNull(midUserMap);
return midUserMap.TryGetValue(mid, out user);
}
public async ValueTask<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea)
{
await taskContext.SwitchToBackgroundAsync();
BindingUser? newUser = await userInitializationService.CreateUserFromCookieOrDefaultAsync(cookie, isOversea).ConfigureAwait(false);
if (newUser is null)
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed);
}
await GetUserCollectionAsync().ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(userCollection);
// Sync cache
await taskContext.SwitchToMainThreadAsync();
userCollection.Add(newUser);
if (newUser.Entity.Mid is not null)
{
midUserMap?.Add(newUser.Entity.Mid, newUser);
}
if (userAndUidCollection is not null)
{
foreach (UserGameRole role in newUser.UserGameRoles)
{
userAndUidCollection.Add(new(newUser.Entity, role));
uidUserGameRoleMap?.Add(role.GameUid, role);
}
}
// Sync database
await taskContext.SwitchToBackgroundAsync();
await userDbService.AddUserAsync(newUser.Entity).ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
}
}

View File

@@ -1,11 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Message;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Hoyolab.Passport;
@@ -23,39 +19,20 @@ 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 IUserCollectionService userCollectionService;
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
private readonly IUserInitializationService userInitializationService;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IUserDbService userDbService; private readonly IUserDbService userDbService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IMessenger messenger;
private ObservableCollection<BindingUser>? userCollection;
private ObservableCollection<UserAndUid>? userAndUidCollection;
/// <inheritdoc/>
public BindingUser? Current public BindingUser? Current
{ {
get => dbCurrent.Current; get => userCollectionService.CurrentUser;
set => dbCurrent.Current = value; set => userCollectionService.CurrentUser = value;
} }
/// <inheritdoc/> public ValueTask RemoveUserAsync(BindingUser user)
public async ValueTask RemoveUserAsync(BindingUser user)
{ {
// Sync cache return userCollectionService.RemoveUserAsync(user);
await taskContext.SwitchToMainThreadAsync();
ArgumentNullException.ThrowIfNull(userCollection);
userCollection.Remove(user);
userAndUidCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false);
messenger.Send(new UserRemovedMessage(user.Entity));
} }
public async ValueTask UnsafeRemoveUsersAsync() public async ValueTask UnsafeRemoveUsersAsync()
@@ -64,76 +41,21 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
await userDbService.RemoveUsersAsync().ConfigureAwait(false); await userDbService.RemoveUsersAsync().ConfigureAwait(false);
} }
/// <inheritdoc/> public ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
{ {
using (await throttler.ThrottleAsync().ConfigureAwait(false)) return userCollectionService.GetUserCollectionAsync();
{
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);
foreach (BindingUser user in users)
{
if (user.NeedDbUpdateAfterResume)
{
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
user.NeedDbUpdateAfterResume = false;
}
}
userCollection = users.ToObservableCollection();
try
{
Current = users.SelectedOrDefault();
}
catch (InvalidOperationException ex)
{
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
}
}
}
return userCollection;
} }
/// <inheritdoc/> public ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
public async ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
{ {
await taskContext.SwitchToBackgroundAsync(); return userCollectionService.GetUserAndUidCollectionAsync();
if (userAndUidCollection is null)
{
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
userAndUidCollection = users
.SelectMany(user => user.UserGameRoles.Select(role => UserAndUid.From(user.Entity, role)))
.ToObservableCollection();
}
return userAndUidCollection;
} }
/// <inheritdoc/>
public UserGameRole? GetUserGameRoleByUid(string uid) public UserGameRole? GetUserGameRoleByUid(string uid)
{ {
if (userCollection is not null) return userCollectionService.GetUserGameRoleByUid(uid);
{
try
{
return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid);
}
catch (InvalidOperationException)
{
// Sequence contains more than one matching element
// TODO: return a specialize UserGameRole to indicate error
}
}
return default;
} }
/// <inheritdoc/>
public async ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea) public async ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea)
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
@@ -145,33 +67,22 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
} }
// 检查 mid 对应用户是否存在 // 检查 mid 对应用户是否存在
ArgumentNullException.ThrowIfNull(userCollection); if (!userCollectionService.TryGetUserByMid(mid, out BindingUser? user))
if (TryGetUser(userCollection, mid, out BindingUser? user))
{ {
if (cookie.TryGetSToken(isOversea, out Cookie? stoken)) return await userCollectionService.TryCreateAndAddUserFromCookieAsync(cookie, isOversea).ConfigureAwait(false);
{
user.SToken = stoken;
user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken;
user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
return new(UserOptionResult.Updated, mid);
}
else
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken);
}
} }
else
if (!cookie.TryGetSToken(isOversea, out Cookie? stoken))
{ {
return await TryCreateUserAndAddAsync(cookie, isOversea).ConfigureAwait(false); return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken);
} }
}
/// <inheritdoc/> user.SToken = stoken;
public ValueTask<bool> RefreshCookieTokenAsync(BindingUser user) user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken;
{ user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
return RefreshCookieTokenAsync(user.Entity);
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
return new(UserOptionResult.Updated, mid);
} }
public async ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user) public async ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user)
@@ -183,65 +94,20 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
.GetCookieAccountInfoBySTokenAsync(user) .GetCookieAccountInfoBySTokenAsync(user)
.ConfigureAwait(false); .ConfigureAwait(false);
if (cookieTokenResponse.IsOk()) if (!cookieTokenResponse.IsOk())
{
string cookieToken = cookieTokenResponse.Data.CookieToken;
// Check null and create a new one to avoid System.NullReferenceException
user.CookieToken ??= new();
// Sync ui and database
user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken;
await userDbService.UpdateUserAsync(user).ConfigureAwait(false);
return true;
}
else
{ {
return false; return false;
} }
}
private static bool TryGetUser(ObservableCollection<BindingUser> users, string mid, [NotNullWhen(true)] out BindingUser? user) string cookieToken = cookieTokenResponse.Data.CookieToken;
{
// TODO: System.InvalidOperationException: Sequence contains more than one matching element
user = users.SingleOrDefault(u => u.Entity.Mid == mid);
return user is not null;
}
private async ValueTask<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea) // Check null and create a new one to avoid System.NullReferenceException
{ user.CookieToken ??= new();
await taskContext.SwitchToBackgroundAsync();
BindingUser? newUser = await userInitializationService.CreateUserFromCookieOrDefaultAsync(cookie, isOversea).ConfigureAwait(false);
if (newUser is not null) // Sync ui and database
{ user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken;
// Sync cache await userDbService.UpdateUserAsync(user).ConfigureAwait(false);
if (userCollection is not null)
{
await taskContext.SwitchToMainThreadAsync();
{
userCollection.Add(newUser);
if (userAndUidCollection is not null) return true;
{
foreach (UserGameRole role in newUser.UserGameRoles)
{
userAndUidCollection.Add(new(newUser.Entity, role));
}
}
}
}
// Sync database
await taskContext.SwitchToBackgroundAsync();
await userDbService.AddUserAsync(newUser.Entity).ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
}
else
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed);
}
} }
} }

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using BindingUser = Snap.Hutao.ViewModel.User.User;
namespace Snap.Hutao.Service.User;
internal static class UserServiceExtension
{
public static ValueTask<bool> RefreshCookieTokenAsync(this IUserService userService, BindingUser user)
{
return userService.RefreshCookieTokenAsync(user.Entity);
}
}

View File

@@ -52,14 +52,12 @@ internal sealed partial class UserViewModel : ObservableObject
get => selectedUser ??= userService.Current; get => selectedUser ??= userService.Current;
set set
{ {
// Pre select the chosen role to avoid multiple UserChangedMessage
value?.SetSelectedUserGameRole(value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen), false);
if (SetProperty(ref selectedUser, value)) if (SetProperty(ref selectedUser, value))
{ {
userService.Current = value; userService.Current = value;
if (value is not null)
{
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
}
} }
} }
} }