From 5d05c31af591333631b05ac203e93e6d152ca6ec Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Fri, 29 Dec 2023 10:05:03 +0800 Subject: [PATCH] fix startup crash --- .../CodeAnalysis/AlsoAsyncSetsAttribute.cs | 23 ++++ .../CodeAnalysis/AlsoSetsAttribute.cs | 23 ++++ .../Core/ExceptionService/HutaoException.cs | 26 ++++ .../Snap.Hutao/View/Page/LaunchGamePage.xaml | 2 +- .../ViewModel/Game/LaunchGameViewModel.cs | 124 +++++++++--------- .../ViewModel/Game/LaunchGameViewModelSlim.cs | 4 +- .../ViewModel/Setting/SettingViewModel.cs | 1 - 7 files changed, 134 insertions(+), 69 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoAsyncSetsAttribute.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoSetsAttribute.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoAsyncSetsAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoAsyncSetsAttribute.cs new file mode 100644 index 00000000..d31233df --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoAsyncSetsAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis; + +/// +/// 指示此特性附加的属性会在属性改变后会异步地设置的其他属性 +/// +[AttributeUsage(AttributeTargets.Property, Inherited = false)] +internal sealed class AlsoAsyncSetsAttribute : Attribute +{ + public AlsoAsyncSetsAttribute(string propertyName) + { + } + + public AlsoAsyncSetsAttribute(string propertyName1, string propertyName2) + { + } + + public AlsoAsyncSetsAttribute(params string[] propertyNames) + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoSetsAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoSetsAttribute.cs new file mode 100644 index 00000000..fd07a2a9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/CodeAnalysis/AlsoSetsAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis; + +/// +/// 指示此特性附加的属性会在属性改变后会设置的其他属性 +/// +[AttributeUsage(AttributeTargets.Property, Inherited = false)] +internal sealed class AlsoSetsAttribute : Attribute +{ + public AlsoSetsAttribute(string propertyName) + { + } + + public AlsoSetsAttribute(string propertyName1, string propertyName2) + { + } + + public AlsoSetsAttribute(params string[] propertyNames) + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs new file mode 100644 index 00000000..90c02aef --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs @@ -0,0 +1,26 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.ExceptionService; + +internal sealed class HutaoException : Exception +{ + public HutaoException(HutaoExceptionKind kind, string message, Exception? innerException) + : this(message, innerException) + { + Kind = kind; + } + + public HutaoException(string message, Exception? innerException) + : base($"{message}\n{innerException?.Message}", innerException) + { + } + + public HutaoExceptionKind Kind { get; private set; } +} + +internal enum HutaoExceptionKind +{ + None, + +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml index a23d2af8..372c0b72 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml @@ -198,7 +198,7 @@ diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index bee15112..3c64dd3b 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -2,10 +2,10 @@ // Licensed under the MIT license. using CommunityToolkit.WinUI.Collections; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.Caching.Memory; using Snap.Hutao.Control.Extension; using Snap.Hutao.Core; +using Snap.Hutao.Core.Diagnostics.CodeAnalysis; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Factory.Progress; @@ -64,8 +64,17 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel private GamePathEntry? selectedGamePathEntry; private GameAccountFilter? gameAccountFilter; + public LaunchOptions LaunchOptions { get => launchOptions; } + + public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } + + public RuntimeOptions RuntimeOptions { get => runtimeOptions; } + + public AppOptions AppOptions { get => appOptions; } + public List KnownSchemes { get; } = KnownLaunchSchemes.Get(); + [AlsoAsyncSets(nameof(GameResource), nameof(GameAccountsView))] public LaunchScheme? SelectedScheme { get => selectedScheme; @@ -76,16 +85,9 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); } - public LaunchOptions LaunchOptions { get => launchOptions; } - - public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } - - public RuntimeOptions RuntimeOptions { get => runtimeOptions; } - - public AppOptions AppOptions { get => appOptions; } - public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); } + [AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))] public bool GamePathSelectedAndValid { get => gamePathSelectedAndValid; @@ -106,24 +108,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel await taskContext.SwitchToMainThreadAsync(); await SetSelectedSchemeAsync(scheme).ConfigureAwait(true); - - // Sync uid, almost never hit, so we are not so care about performance - if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid) - { - ArgumentNullException.ThrowIfNull(GameAccountsView); - - // Exists in the source collection - if (GameAccountsView.SourceCollection.Cast().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount) - { - SelectedGameAccount = GameAccountsView.Cast().FirstOrDefault(g => g.AttachUid == uid); - - // But not exists in the view for current scheme - if (SelectedGameAccount is null) - { - infoBarService.Warning(SH.FormatViewModelLaunchGameUnableToSwitchUidAttachedGameAccount(uid, sourceAccount.Name)); - } - } - } + TrySetGameAccountByDesiredUid(); // Try set to the current account. if (SelectedScheme is not null) @@ -142,59 +127,68 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel infoBarService.Error(ex); } } + + void TrySetGameAccountByDesiredUid() + { + // Sync uid, almost never hit, so we are not so care about performance + if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid) + { + ArgumentNullException.ThrowIfNull(GameAccountsView); + + // Exists in the source collection + if (GameAccountsView.SourceCollection.Cast().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount) + { + SelectedGameAccount = GameAccountsView.Cast().FirstOrDefault(g => g.AttachUid == uid); + + // But not exists in the view for current scheme + if (SelectedGameAccount is null) + { + infoBarService.Warning(SH.FormatViewModelLaunchGameUnableToSwitchUidAttachedGameAccount(uid, sourceAccount.Name)); + } + } + } + } } } public ImmutableList GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); } + [AlsoSets(nameof(GamePathSelectedAndValid))] public GamePathEntry? SelectedGamePathEntry { get => selectedGamePathEntry; - set => UpdateSelectedGamePathEntry(value, true); + set + { + if (SetProperty(ref selectedGamePathEntry, value, nameof(SelectedGamePathEntry))) + { + if (IsViewDisposed) + { + return; + } + + launchOptions.GamePath = value?.Path ?? string.Empty; + GamePathSelectedAndValid = File.Exists(launchOptions.GamePath); + } + } } protected override ValueTask InitializeUIAsync() { - GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry); - SelectedGamePathEntry = entry; + SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions(); return ValueTask.FromResult(true); } - private void UpdateSelectedGamePathEntry(GamePathEntry? value, bool setBack) - { - if (SetProperty(ref selectedGamePathEntry, value, nameof(SelectedGamePathEntry)) && setBack) - { - if (IsViewDisposed) - { - return; - } - - launchOptions.GamePath = value?.Path ?? string.Empty; - GamePathSelectedAndValid = File.Exists(launchOptions.GamePath); - } - } - [Command("SetGamePathCommand")] private async Task SetGamePathAsync() { - IGameLocator locator = gameLocatorFactory.Create(GameLocationSource.Manual); - - (bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false); + (bool isOk, string path) = await gameLocatorFactory.LocateAsync(GameLocationSource.Manual).ConfigureAwait(false); if (!isOk) { return; } await taskContext.SwitchToMainThreadAsync(); - try - { - GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path); - } - catch (SqliteException ex) - { - // 文件夹权限不足,无法写入数据库 - infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle); - } + GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path); } [Command("ResetGamePathCommand")] @@ -237,18 +231,14 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel else { await taskContext.SwitchToMainThreadAsync(); - GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry); - UpdateSelectedGamePathEntry(entry, true); + SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions(); } } - if (SelectedGameAccount is not null) + if (SelectedGameAccount is not null && !gameService.SetGameAccount(SelectedGameAccount)) { - if (!gameService.SetGameAccount(SelectedGameAccount)) - { - infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail); - return; - } + infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail); + return; } IProgress launchProgress = progressFactory.CreateForMainThread(status => launchStatusOptions.LaunchStatus = status); @@ -377,4 +367,10 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel }; } } + + private void SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions() + { + GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry); + SelectedGamePathEntry = entry; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs index b1743441..d61fbf5e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs @@ -5,9 +5,7 @@ using CommunityToolkit.WinUI.Collections; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Factory.Progress; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Service.Game; -using Snap.Hutao.Service.Game.Configuration; using Snap.Hutao.Service.Game.Scheme; using Snap.Hutao.Service.Notification; using System.Collections.ObjectModel; @@ -43,6 +41,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli protected override async Task OpenUIAsync() { LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService); + ObservableCollection accounts = gameService.GameAccountCollection; try { @@ -58,7 +57,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli } gameAccountFilter = new(scheme?.GetSchemeType()); - ObservableCollection accounts = gameService.GameAccountCollection; await taskContext.SwitchToMainThreadAsync(); GameAccountsView = new(accounts, true) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index 9ff8767f..c6a183af 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -15,7 +15,6 @@ using Snap.Hutao.Model; using Snap.Hutao.Service; using Snap.Hutao.Service.GachaLog.QueryProvider; using Snap.Hutao.Service.Game; -using Snap.Hutao.Service.Game.Locator; using Snap.Hutao.Service.Hutao; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification;