From df531de255eac704858a47cee750065aa5f5b55a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 4 May 2026 14:38:24 -0600 Subject: [PATCH 1/3] Update Avalonia to v12 Avalonia 12 uses compiled bindings by default. Converted all remaining reflection bindings into compiled bindings, Fixed binding errors --- .../AudibleUtilities/AudibleUtilities.csproj | 2 +- .../DataLayer.Postgres.csproj | 4 +- .../DataLayer.Sqlite/DataLayer.Sqlite.csproj | 4 +- Source/DataLayer/DataLayer.csproj | 8 +- Source/FileManager/FileManager.csproj | 2 +- .../Controls/CheckedListBox.axaml | 6 +- .../HangoverAvalonia/HangoverAvalonia.csproj | 9 +- Source/HangoverAvalonia/Program.cs | 2 +- .../HangoverAvalonia/Views/MainWindow.axaml | 2 + Source/LibationAvalonia/App.axaml | 2 +- Source/LibationAvalonia/App.axaml.cs | 16 -- Source/LibationAvalonia/AvaloniaUtils.cs | 2 +- .../Controls/CheckedListBox.axaml | 5 +- .../Controls/DataGridMyRatingColumn.cs | 4 +- .../DirectoryOrCustomSelectControl.axaml | 5 +- .../DirectoryOrCustomSelectControl.axaml.cs | 89 ++++---- .../Controls/DirectorySelectControl.axaml | 8 +- .../Controls/Settings/Audio.axaml.cs | 33 ++- .../Controls/Settings/DownloadDecrypt.axaml | 48 ++--- .../Controls/Settings/Import.axaml | 26 +-- .../Controls/Settings/Important.axaml | 56 ++--- .../Controls/ThemePreviewControl.axaml | 1 + .../Dialogs/AboutDialog.axaml | 2 + .../Dialogs/AccountsDialog.axaml | 2 + .../Dialogs/BookRecordsDialog.axaml | 11 +- .../Dialogs/BookRecordsDialog.axaml.cs | 2 +- .../Dialogs/DescriptionDisplayDialog.axaml | 4 +- .../Dialogs/DescriptionDisplayDialog.axaml.cs | 2 +- .../LibationAvalonia/Dialogs/DialogWindow.cs | 13 +- .../Dialogs/EditQuickFilters.axaml | 16 +- .../Dialogs/EditQuickFilters.axaml.cs | 2 +- .../Dialogs/EditTemplateDialog.axaml | 18 +- .../FindBetterQualityBooksDialog.axaml | 20 +- .../Dialogs/ImageDisplayDialog.axaml | 2 + .../Dialogs/LibationFilesDialog.axaml | 2 + .../Dialogs/LibationFilesDialog.axaml.cs | 2 +- .../LiberatedStatusBatchAutoDialog.axaml | 2 + .../LiberatedStatusBatchAutoDialog.axaml.cs | 4 + .../LiberatedStatusBatchManualDialog.axaml | 2 + .../LiberatedStatusBatchManualDialog.axaml.cs | 8 +- .../Dialogs/Login/LoginExternalDialog.axaml | 2 + .../Login/LoginExternalDialog.axaml.cs | 1 + .../Dialogs/MessageBoxAlertAdminDialog.axaml | 2 + .../Dialogs/MessageBoxWindow.axaml | 32 +-- .../Dialogs/SearchSyntaxDialog.axaml | 16 +- .../Dialogs/SettingsDialog.axaml | 8 +- .../Dialogs/TagsBatchDialog.axaml | 2 + .../Dialogs/TagsBatchDialog.axaml.cs | 3 + .../Dialogs/ThemePickerDialog.axaml | 2 + .../Dialogs/ThemePickerDialog.axaml.cs | 18 +- .../Dialogs/TrashBinDialog.axaml | 2 +- .../Dialogs/UpgradeNotificationDialog.axaml | 2 + Source/LibationAvalonia/FormSaveExtension.cs | 58 ------ .../LibationAvalonia/LibationAvalonia.csproj | 16 +- Source/LibationAvalonia/MessageBox.cs | 2 +- Source/LibationAvalonia/Program.cs | 2 +- .../ViewModels/MainVM.Filters.cs | 2 +- .../Views/LiberateStatusButton.axaml | 24 +-- .../LibationAvalonia/Views/MainWindow.axaml | 146 ++++++------- .../Views/MainWindow.axaml.cs | 2 +- .../Views/ProcessBookControl.axaml | 20 +- .../Views/ProcessQueueControl.axaml | 2 + .../Views/ProcessQueueControl.axaml.cs | 1 + .../Views/ProductsDisplay.axaml | 193 ++++++++++++------ .../Views/ProductsDisplay.axaml.cs | 8 +- .../Views/SeriesViewDialog.axaml | 4 +- .../Views/SeriesViewGrid.axaml | 18 +- .../Views/SeriesViewGrid.axaml.cs | 2 +- .../LibationFileManager.csproj | 2 +- .../LibationWinForms/LibationWinForms.csproj | 2 +- .../AssertionHelper/AssertionHelper.csproj | 2 +- .../AudibleUtilities.Tests.csproj | 2 +- .../FileLiberator.Tests.csproj | 2 +- .../FileManager.Tests.csproj | 2 +- .../LibationFileManager.Tests.csproj | 2 +- .../LibationSearchEngine.Tests.csproj | 2 +- .../LibationUiBase.Tests.csproj | 2 +- 77 files changed, 543 insertions(+), 513 deletions(-) diff --git a/Source/AudibleUtilities/AudibleUtilities.csproj b/Source/AudibleUtilities/AudibleUtilities.csproj index 195877d1..09a933ce 100644 --- a/Source/AudibleUtilities/AudibleUtilities.csproj +++ b/Source/AudibleUtilities/AudibleUtilities.csproj @@ -7,7 +7,7 @@ - + diff --git a/Source/DataLayer.Postgres/DataLayer.Postgres.csproj b/Source/DataLayer.Postgres/DataLayer.Postgres.csproj index cdc2c8d0..1248a584 100644 --- a/Source/DataLayer.Postgres/DataLayer.Postgres.csproj +++ b/Source/DataLayer.Postgres/DataLayer.Postgres.csproj @@ -9,11 +9,11 @@ Library - + all runtime; build; native; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/DataLayer.Sqlite/DataLayer.Sqlite.csproj b/Source/DataLayer.Sqlite/DataLayer.Sqlite.csproj index f640ce8a..277f265b 100644 --- a/Source/DataLayer.Sqlite/DataLayer.Sqlite.csproj +++ b/Source/DataLayer.Sqlite/DataLayer.Sqlite.csproj @@ -9,11 +9,11 @@ Library - + all runtime; build; native; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/DataLayer/DataLayer.csproj b/Source/DataLayer/DataLayer.csproj index 2399147c..1af76dfd 100644 --- a/Source/DataLayer/DataLayer.csproj +++ b/Source/DataLayer/DataLayer.csproj @@ -13,13 +13,13 @@ - - - + + + all runtime; build; native; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/FileManager/FileManager.csproj b/Source/FileManager/FileManager.csproj index afe486c7..873caa35 100644 --- a/Source/FileManager/FileManager.csproj +++ b/Source/FileManager/FileManager.csproj @@ -7,7 +7,7 @@ - + diff --git a/Source/HangoverAvalonia/Controls/CheckedListBox.axaml b/Source/HangoverAvalonia/Controls/CheckedListBox.axaml index a1750735..20de74a8 100644 --- a/Source/HangoverAvalonia/Controls/CheckedListBox.axaml +++ b/Source/HangoverAvalonia/Controls/CheckedListBox.axaml @@ -3,14 +3,16 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + xmlns:controls="clr-namespace:HangoverAvalonia.Controls" + xmlns:vms="clr-namespace:HangoverAvalonia.ViewModels" x:Class="HangoverAvalonia.Controls.CheckedListBox"> - + - + diff --git a/Source/HangoverAvalonia/HangoverAvalonia.csproj b/Source/HangoverAvalonia/HangoverAvalonia.csproj index 4be1ca78..ea9de50d 100644 --- a/Source/HangoverAvalonia/HangoverAvalonia.csproj +++ b/Source/HangoverAvalonia/HangoverAvalonia.csproj @@ -70,12 +70,11 @@ - + - - - - + + + diff --git a/Source/HangoverAvalonia/Program.cs b/Source/HangoverAvalonia/Program.cs index 65a084c7..54fccc69 100644 --- a/Source/HangoverAvalonia/Program.cs +++ b/Source/HangoverAvalonia/Program.cs @@ -18,5 +18,5 @@ internal class Program => AppBuilder.Configure() .UsePlatformDetect() .LogToTrace() - .UseReactiveUI(); + .UseReactiveUI(_ => { }); } diff --git a/Source/HangoverAvalonia/Views/MainWindow.axaml b/Source/HangoverAvalonia/Views/MainWindow.axaml index f960db00..05114add 100644 --- a/Source/HangoverAvalonia/Views/MainWindow.axaml +++ b/Source/HangoverAvalonia/Views/MainWindow.axaml @@ -4,6 +4,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:HangoverAvalonia.Controls" + xmlns:vms="clr-namespace:HangoverAvalonia.ViewModels" + x:DataType="vms:MainVM" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="500" Width="800" Height="500" x:Class="HangoverAvalonia.Views.MainWindow" diff --git a/Source/LibationAvalonia/App.axaml b/Source/LibationAvalonia/App.axaml index f8dcd23d..e2105c99 100644 --- a/Source/LibationAvalonia/App.axaml +++ b/Source/LibationAvalonia/App.axaml @@ -89,7 +89,7 @@ + + SelectedItem="{Binding $parent[controls:DirectorySelectControl].SelectedDirectory, Mode=TwoWay}" + ItemsSource="{Binding $parent[controls:DirectorySelectControl].KnownDirectories}"> @@ -36,7 +36,7 @@ - + diff --git a/Source/LibationAvalonia/Controls/Settings/Audio.axaml.cs b/Source/LibationAvalonia/Controls/Settings/Audio.axaml.cs index 11106794..9b604a14 100644 --- a/Source/LibationAvalonia/Controls/Settings/Audio.axaml.cs +++ b/Source/LibationAvalonia/Controls/Settings/Audio.axaml.cs @@ -30,29 +30,26 @@ public partial class Audio : UserControl if (!accounts.AccountsSettings.Accounts.All(a => a.IdentityTokens?.DeviceType == AudibleApi.Resources.DeviceType)) { - if (VisualRoot is Window parent) - { - var choice = await MessageBox.Show(parent, - "In order to enable widevine content, Libation will need to log into your accounts again.\r\n\r\n" + - "Do you want Libation to clear your current account settings and prompt you to login before the next download?", - "Widevine Content Unavailable", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question, - MessageBoxDefaultButton.Button2); + var choice = await MessageBox.Show(this.GetParentWindow(), + "In order to enable widevine content, Libation will need to log into your accounts again.\r\n\r\n" + + "Do you want Libation to clear your current account settings and prompt you to login before the next download?", + "Widevine Content Unavailable", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button2); - if (choice == DialogResult.Yes) + if (choice == DialogResult.Yes) + { + foreach (var account in accounts.AccountsSettings.Accounts.ToArray()) { - foreach (var account in accounts.AccountsSettings.Accounts.ToArray()) + if (account.Locale is not null && account.IdentityTokens?.DeviceType != AudibleApi.Resources.DeviceType) { - if (account.Locale is not null && account.IdentityTokens?.DeviceType != AudibleApi.Resources.DeviceType) - { - accounts.AccountsSettings.Delete(account); - var acc = accounts.AccountsSettings.Upsert(account.AccountId, account.Locale.Name); - acc.AccountName = account.AccountName; - } + accounts.AccountsSettings.Delete(account); + var acc = accounts.AccountsSettings.Upsert(account.AccountId, account.Locale.Name); + acc.AccountName = account.AccountName; } - return; } + return; } _viewModel?.UseWidevine = false; diff --git a/Source/LibationAvalonia/Controls/Settings/DownloadDecrypt.axaml b/Source/LibationAvalonia/Controls/Settings/DownloadDecrypt.axaml index a075857d..15fa1d9c 100644 --- a/Source/LibationAvalonia/Controls/Settings/DownloadDecrypt.axaml +++ b/Source/LibationAvalonia/Controls/Settings/DownloadDecrypt.axaml @@ -12,7 +12,7 @@ + Label="{Binding BadBookGroupboxText}"> + IsChecked="{Binding BadBookAsk, Mode=TwoWay}"> - + + IsChecked="{Binding BadBookAbort, Mode=TwoWay}"> - + + IsChecked="{Binding BadBookRetry, Mode=TwoWay}"> - + + IsChecked="{Binding BadBookIgnore, Mode=TwoWay}"> - + @@ -90,12 +90,12 @@ Grid.Row="0" Grid.Column="0" Margin="0,5,0,0" - Text="{CompiledBinding FolderTemplateText}" /> + Text="{Binding FolderTemplateText}" /> + Text="{Binding FolderTemplate}" /> @@ -82,8 +82,8 @@ Click="MoveDownButton_Clicked"> - - + + diff --git a/Source/LibationAvalonia/Dialogs/EditQuickFilters.axaml.cs b/Source/LibationAvalonia/Dialogs/EditQuickFilters.axaml.cs index 2c81653c..c05dce7b 100644 --- a/Source/LibationAvalonia/Dialogs/EditQuickFilters.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/EditQuickFilters.axaml.cs @@ -86,7 +86,7 @@ public partial class EditQuickFilters : DialogWindow ReIndexFilters(); } - protected override void SaveAndClose() + public new void SaveAndClose() { QuickFilters.ReplaceAll(Filters.Select(x => x.AsNamedFilter()).OfType()); base.SaveAndClose(); diff --git a/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml b/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml index b535fba4..b0442baa 100644 --- a/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/EditTemplateDialog.axaml @@ -21,13 +21,13 @@ Grid.Column="0" Grid.Row="0" Margin="0,0,0,10" - Text="{CompiledBinding Description}" /> + Text="{Binding Description}" /> + Text="{Binding UserTemplateText, Mode=TwoWay}" /> - - diff --git a/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml b/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml index e6df63ba..eaa9b514 100644 --- a/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml @@ -49,26 +49,26 @@ - - + + - - + + - - + + - - + + diff --git a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml index cba6c958..e3302465 100644 --- a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml @@ -49,7 +49,7 @@ - + @@ -58,7 +58,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -77,7 +77,7 @@ - + diff --git a/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml b/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml index 925fdc9a..4df16d54 100644 --- a/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml @@ -4,6 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="630" d:DesignHeight="90" x:Class="LibationAvalonia.Dialogs.TagsBatchDialog" + xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs" + x:DataType="dialogs:TagsBatchDialog" MinWidth="630" MinHeight="90" MaxWidth="630" MaxHeight="90" Width="630" Height="110" diff --git a/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml.cs index 5177878b..7892c6b8 100644 --- a/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml.cs @@ -12,4 +12,7 @@ public partial class TagsBatchDialog : DialogWindow DataContext = this; } + + // For compiled bindings + public new void SaveAndClose() => base.SaveAndClose(); } diff --git a/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml b/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml index e86418f9..ac87390b 100644 --- a/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml @@ -6,6 +6,8 @@ Width="965" Height="850" x:Class="LibationAvalonia.Dialogs.ThemePickerDialog" xmlns:controls="clr-namespace:LibationAvalonia.Controls" + xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs" + x:DataType="dialogs:ThemePickerDialog" Title="Theme Editor"> diff --git a/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml.cs index 62195813..5bd471ee 100644 --- a/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/ThemePickerDialog.axaml.cs @@ -14,7 +14,7 @@ namespace LibationAvalonia.Dialogs; public partial class ThemePickerDialog : DialogWindow { - protected DataGridCollectionView ThemeColors { get; } + public AvaloniaList ThemeColors { get; } private ChardonnayTheme ExistingTheme { get; } = ChardonnayTheme.GetLiveTheme(); private ChardonnayTheme WorkingTheme { get; set; } @@ -38,7 +38,7 @@ public partial class ThemePickerDialog : DialogWindow } } - protected async Task ImportTheme() + public async Task ImportTheme() { try { @@ -74,7 +74,7 @@ public partial class ThemePickerDialog : DialogWindow } } - protected async Task ExportTheme() + public async Task ExportTheme() { try { @@ -110,22 +110,24 @@ public partial class ThemePickerDialog : DialogWindow } } - protected override void CancelAndClose() + public new void CancelAndClose() { ExistingTheme.ApplyTheme(ActualThemeVariant); base.CancelAndClose(); } - protected void ResetColors() + + + public void ResetColors() => ResetTheme(ExistingTheme); - protected void LoadDefaultColors() + public void LoadDefaultColors() { if (App.DefaultThemeColors is ChardonnayTheme defaults) ResetTheme(defaults); } - protected override async Task SaveAndCloseAsync() + public new async Task SaveAndCloseAsync() { using (var themePersister = ChardonnayThemePersister.Create()) { @@ -174,7 +176,7 @@ public partial class ThemePickerDialog : DialogWindow WorkingTheme.ApplyTheme(ActualThemeVariant); } - private class ThemeItemColor : ViewModels.ViewModelBase + public class ThemeItemColor : ViewModels.ViewModelBase { public required string ThemeItemName { get; init; } public required Action? ColorSetter { get; set; } diff --git a/Source/LibationAvalonia/Dialogs/TrashBinDialog.axaml b/Source/LibationAvalonia/Dialogs/TrashBinDialog.axaml index 891504a5..36b9b2f6 100644 --- a/Source/LibationAvalonia/Dialogs/TrashBinDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/TrashBinDialog.axaml @@ -34,7 +34,7 @@ DisableContextMenu="True" DisableColumnCustomization="True" IsEnabled="{Binding $parent.((dialogs:TrashBinViewModel)DataContext).ControlsEnabled}" - DataContext="{Binding ProductsDisplay}" /> + DataContext="{Binding ProductsDisplay, Mode=OneTime}" /> (WINDOW_STYLE)GetWindowLong(hWnd, GWL_STYLE); - static void SetWindowStyle(IntPtr hWnd, WINDOW_STYLE style) => SetWindowLong(hWnd, GWL_STYLE, (long)style); - - - [Flags] - enum WINDOW_STYLE : long - { - WS_OVERLAPPED = 0x0, - WS_TILED = 0x0, - WS_ACTIVECAPTION = 0x1, - WS_MAXIMIZEBOX = 0x10000, - WS_TABSTOP = 0x10000, - WS_MINIMIZEBOX = 0x20000, - WS_GROUP = 0x20000, - WS_THICKFRAME = 0x40000, - WS_SIZEBOX = 0x40000, - WS_SYSMENU = 0x80000, - WS_HSCROLL = 0x100000, - WS_VSCROLL = 0x200000, - WS_DLGFRAME = 0x400000, - WS_BORDER = 0x800000, - WS_CAPTION = 0xc00000, - WS_OVERLAPPEDWINDOW = 0xcf0000, - WS_TILEDWINDOW = 0xcf0000, - WS_MAXIMIZE = 0x1000000, - WS_CLIPCHILDREN = 0x2000000, - WS_CLIPSIBLINGS = 0x4000000, - WS_DISABLED = 0x8000000, - WS_VISIBLE = 0x10000000, - WS_ICONIC = 0x20000000, - WS_MINIMIZE = 0x20000000, - WS_CHILD = 0x40000000, - WS_CHILDWINDOW = 0x40000000, - WS_POPUP = 0x80000000, - WS_POPUPWINDOW = 0x80880000 - } } diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj index f5f4173e..95e5eb33 100644 --- a/Source/LibationAvalonia/LibationAvalonia.csproj +++ b/Source/LibationAvalonia/LibationAvalonia.csproj @@ -12,7 +12,6 @@ false false enable - @@ -73,14 +72,13 @@ - - - - - - - - + + + + + + + diff --git a/Source/LibationAvalonia/MessageBox.cs b/Source/LibationAvalonia/MessageBox.cs index 6f83bc91..67b361ce 100644 --- a/Source/LibationAvalonia/MessageBox.cs +++ b/Source/LibationAvalonia/MessageBox.cs @@ -189,7 +189,7 @@ public class MessageBox IsVisible = false, Height = 1, Width = 1, - SystemDecorations = SystemDecorations.None, + WindowDecorations = WindowDecorations.None, ShowInTaskbar = false }; diff --git a/Source/LibationAvalonia/Program.cs b/Source/LibationAvalonia/Program.cs index 9e3491d0..a0762347 100644 --- a/Source/LibationAvalonia/Program.cs +++ b/Source/LibationAvalonia/Program.cs @@ -89,7 +89,7 @@ static class Program return AppBuilder.Configure() .UsePlatformDetect() .LogToTrace() - .UseReactiveUI() + .UseReactiveUI(_ => { }) .AfterSetup(_ => SetupLock.Exit()); } } diff --git a/Source/LibationAvalonia/ViewModels/MainVM.Filters.cs b/Source/LibationAvalonia/ViewModels/MainVM.Filters.cs index 33a69d28..825e7cc4 100644 --- a/Source/LibationAvalonia/ViewModels/MainVM.Filters.cs +++ b/Source/LibationAvalonia/ViewModels/MainVM.Filters.cs @@ -26,7 +26,7 @@ partial class MainVM private void Configure_Filters() { FirstFilterIsDefault = QuickFilters.UseDefault; - MainWindow.Initialized += updateFiltersMenu; + MainWindow.Loaded += updateFiltersMenu; QuickFilters.Updated += updateFiltersMenu; //We need to be able to dynamically add and remove menu items from the Quick Filters menu. diff --git a/Source/LibationAvalonia/Views/LiberateStatusButton.axaml b/Source/LibationAvalonia/Views/LiberateStatusButton.axaml index f9fdaacd..701e87ff 100644 --- a/Source/LibationAvalonia/Views/LiberateStatusButton.axaml +++ b/Source/LibationAvalonia/Views/LiberateStatusButton.axaml @@ -36,7 +36,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="0" - IsEnabled="{CompiledBinding IsButtonEnabled}" Click="Button_Click" > + IsEnabled="{Binding IsButtonEnabled}" Click="Button_Click" > - + - - - + + + - - - + + + - - + + diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml b/Source/LibationAvalonia/Views/MainWindow.axaml index 8447b0e8..cab4de86 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml +++ b/Source/LibationAvalonia/Views/MainWindow.axaml @@ -19,56 +19,56 @@ - - - - + + + + - + - + - - - - - - + + + + + + - - + + - - + + - - + + @@ -78,7 +78,7 @@ - + @@ -90,80 +90,80 @@ - + - + - + - - - + + + - + - - - + + + - + - - - - + + + + - + - + - - - - - - - + + + + + + + - - + + - - + + - + - - + + - + - + @@ -182,21 +182,21 @@ - - @@ -231,19 +231,19 @@ - + - + - - - - + + + + diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml.cs b/Source/LibationAvalonia/Views/MainWindow.axaml.cs index 60622557..50332f6c 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow.axaml.cs @@ -25,7 +25,6 @@ public partial class MainWindow : ReactiveWindow if (Design.IsDesignMode) Configuration.CreateMockInstance(); - DataContext = new MainVM(this); ApiExtended.LoginChoiceFactory = account => Dispatcher.UIThread.Invoke(() => new Dialogs.Login.AvaloniaLoginChoiceEager(account)); AudibleApiStorage.LoadError += AudibleApiStorage_LoadError; @@ -46,6 +45,7 @@ public partial class MainWindow : ReactiveWindow Configuration.Instance.PropertyChanged += Settings_PropertyChanged; Settings_PropertyChanged(this, null); + DataContext = new MainVM(this); } [Dinah.Core.PropertyChangeFilter(nameof(Configuration.Books))] diff --git a/Source/LibationAvalonia/Views/ProcessBookControl.axaml b/Source/LibationAvalonia/Views/ProcessBookControl.axaml index f5b9d15e..cc7cbf61 100644 --- a/Source/LibationAvalonia/Views/ProcessBookControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessBookControl.axaml @@ -16,7 +16,7 @@ @@ -34,13 +34,13 @@ - + - - - + + + @@ -48,8 +48,8 @@ - - + + @@ -64,7 +64,7 @@ - + - + - + diff --git a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml index e6fa8f89..54a474d2 100644 --- a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml @@ -5,6 +5,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:LibationAvalonia.Views" xmlns:viewModels="clr-namespace:LibationAvalonia.ViewModels" + xmlns:vmbase="clr-namespace:LibationUiBase.ProcessQueue;assembly=LibationUiBase" + x:DataType="vmbase:ProcessQueueViewModel" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="650" Background="{DynamicResource SystemRegionColor}" diff --git a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs index d1c96e49..56ca826c 100644 --- a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Data.Converters; +using Avalonia.Input.Platform; using Avalonia.Threading; using DataLayer; using LibationUiBase; diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index 5e44b362..23894d48 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -17,7 +17,7 @@ ClipboardCopyMode="IncludeHeader" GridLinesVisibility="All" AutoGenerateColumns="False" - ItemsSource="{CompiledBinding GridEntries}" + ItemsSource="{Binding GridEntries}" CanUserSortColumns="True" BorderThickness="3" CanUserResizeColumns="True" LoadingRow="ProductsDisplay_LoadingRow" @@ -85,7 +85,7 @@ + IsChecked="{Binding Remove, Mode=TwoWay}" /> - + + + + - + - + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + + OpacityBinding="{Binding Liberate.Opacity}" + ClipboardContentBinding="{Binding ProductRating}" + Binding="{Binding ProductRating}"> + + + + - + - - + + + + + + OpacityBinding="{Binding Liberate.Opacity}" + ClipboardContentBinding="{Binding MyRating}" + Binding="{Binding MyRating, Mode=TwoWay}"> + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + - - + + + + + - + + + + diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index a80a2097..8614306b 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -33,10 +33,10 @@ public partial class ProductsDisplay : UserControl public static readonly StyledProperty DisableContextMenuProperty = - AvaloniaProperty.Register(nameof(DisableContextMenu)); + AvaloniaProperty.Register(nameof(DisableContextMenu)); public static readonly StyledProperty DisableColumnCustomizationProperty = - AvaloniaProperty.Register(nameof(DisableColumnCustomization)); + AvaloniaProperty.Register(nameof(DisableColumnCustomization)); public bool DisableContextMenu { @@ -495,7 +495,7 @@ public partial class ProductsDisplay : UserControl #endregion #region View Bookmarks/Clips (Single book only) - if (entries.Length == 1 && entries[0] is LibraryBookEntry entry3 && VisualRoot is Window window) + if (entries.Length == 1 && entries[0] is LibraryBookEntry entry3 && this.GetParentWindow() is Window window) { args.ContextMenuItems.Add(new MenuItem { @@ -658,7 +658,7 @@ public partial class ProductsDisplay : UserControl var pictureId = gEntry.LibraryBook.Book.PictureLarge ?? gEntry.LibraryBook.Book.PictureId; if (string.IsNullOrEmpty(pictureId)) { - await MessageBox.Show(VisualRoot as Window, "No cover art is available for this book.", "No Cover Art", MessageBoxButtons.OK, MessageBoxIcon.Information); + await MessageBox.Show(this.GetParentWindow(), "No cover art is available for this book.", "No Cover Art", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } diff --git a/Source/LibationAvalonia/Views/SeriesViewDialog.axaml b/Source/LibationAvalonia/Views/SeriesViewDialog.axaml index e55a6f12..e535bab6 100644 --- a/Source/LibationAvalonia/Views/SeriesViewDialog.axaml +++ b/Source/LibationAvalonia/Views/SeriesViewDialog.axaml @@ -3,11 +3,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + xmlns:local="clr-namespace:LibationAvalonia.Views" x:Class="LibationAvalonia.Views.SeriesViewDialog" + x:DataType="local:SeriesViewDialog" Title="View All Items in Series"> diff --git a/Source/LibationAvalonia/Views/SeriesViewGrid.axaml b/Source/LibationAvalonia/Views/SeriesViewGrid.axaml index 69e3326a..32acac98 100644 --- a/Source/LibationAvalonia/Views/SeriesViewGrid.axaml +++ b/Source/LibationAvalonia/Views/SeriesViewGrid.axaml @@ -4,6 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:controls="clr-namespace:LibationAvalonia.Controls" + xmlns:local="clr-namespace:LibationAvalonia.Views" + x:DataType="local:SeriesViewGrid" xmlns:uibase="clr-namespace:LibationUiBase.SeriesView;assembly=LibationUiBase" x:Class="LibationAvalonia.Views.SeriesViewGrid"> @@ -39,7 +41,7 @@ @@ -50,7 +52,7 @@ @@ -72,17 +74,17 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="Availability_Click" - IsVisible="{CompiledBinding Button.HasButtonAction}" - IsEnabled="{CompiledBinding Button.Enabled}"> + IsVisible="{Binding Button.HasButtonAction}" + IsEnabled="{Binding Button.Enabled}"> + IsVisible="{Binding !Button.HasButtonAction}" + Text="{Binding Button.DisplayText}" /> @@ -94,7 +96,7 @@ diff --git a/Source/LibationAvalonia/Views/SeriesViewGrid.axaml.cs b/Source/LibationAvalonia/Views/SeriesViewGrid.axaml.cs index 1043b02b..3cfea588 100644 --- a/Source/LibationAvalonia/Views/SeriesViewGrid.axaml.cs +++ b/Source/LibationAvalonia/Views/SeriesViewGrid.axaml.cs @@ -60,7 +60,7 @@ public partial class SeriesViewGrid : UserControl var pictureId = libraryBook.PictureLarge ?? libraryBook.PictureId; if (string.IsNullOrEmpty(pictureId)) { - await MessageBox.Show(VisualRoot as Window, "No cover art is available for this book.", "No Cover Art", MessageBoxButtons.OK, MessageBoxIcon.Information); + await MessageBox.Show(this.GetParentWindow(), "No cover art is available for this book.", "No Cover Art", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } diff --git a/Source/LibationFileManager/LibationFileManager.csproj b/Source/LibationFileManager/LibationFileManager.csproj index e7fd0a9d..86f6c0db 100644 --- a/Source/LibationFileManager/LibationFileManager.csproj +++ b/Source/LibationFileManager/LibationFileManager.csproj @@ -7,7 +7,7 @@ - + diff --git a/Source/LibationWinForms/LibationWinForms.csproj b/Source/LibationWinForms/LibationWinForms.csproj index 19897d85..4af0014f 100644 --- a/Source/LibationWinForms/LibationWinForms.csproj +++ b/Source/LibationWinForms/LibationWinForms.csproj @@ -42,7 +42,7 @@ - + diff --git a/Source/_Tests/AssertionHelper/AssertionHelper.csproj b/Source/_Tests/AssertionHelper/AssertionHelper.csproj index 9b340644..50369654 100644 --- a/Source/_Tests/AssertionHelper/AssertionHelper.csproj +++ b/Source/_Tests/AssertionHelper/AssertionHelper.csproj @@ -7,7 +7,7 @@ - + diff --git a/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj b/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj index 0b650963..b75a1356 100644 --- a/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj +++ b/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj b/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj index 626251f1..20c0dd27 100644 --- a/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj +++ b/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Source/_Tests/FileManager.Tests/FileManager.Tests.csproj b/Source/_Tests/FileManager.Tests/FileManager.Tests.csproj index 04056018..e84a06f9 100644 --- a/Source/_Tests/FileManager.Tests/FileManager.Tests.csproj +++ b/Source/_Tests/FileManager.Tests/FileManager.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Source/_Tests/LibationFileManager.Tests/LibationFileManager.Tests.csproj b/Source/_Tests/LibationFileManager.Tests/LibationFileManager.Tests.csproj index 3f308465..dcc8d9b3 100644 --- a/Source/_Tests/LibationFileManager.Tests/LibationFileManager.Tests.csproj +++ b/Source/_Tests/LibationFileManager.Tests/LibationFileManager.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Source/_Tests/LibationSearchEngine.Tests/LibationSearchEngine.Tests.csproj b/Source/_Tests/LibationSearchEngine.Tests/LibationSearchEngine.Tests.csproj index 90257f6f..28f9e3bd 100644 --- a/Source/_Tests/LibationSearchEngine.Tests/LibationSearchEngine.Tests.csproj +++ b/Source/_Tests/LibationSearchEngine.Tests/LibationSearchEngine.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Source/_Tests/LibationUiBase.Tests/LibationUiBase.Tests.csproj b/Source/_Tests/LibationUiBase.Tests/LibationUiBase.Tests.csproj index 3e011fd9..d8dea8ee 100644 --- a/Source/_Tests/LibationUiBase.Tests/LibationUiBase.Tests.csproj +++ b/Source/_Tests/LibationUiBase.Tests/LibationUiBase.Tests.csproj @@ -9,7 +9,7 @@ - + From 505c614210449930ba01b78e2dd86f45927b1090 Mon Sep 17 00:00:00 2001 From: MBucari Date: Mon, 4 May 2026 21:10:28 -0600 Subject: [PATCH 2/3] Code Cleanup Remove unused parameters Remove unnecessary casts Make fields readonly Order modifiers Format document Sort usings Remove unnecessary nullable directive Apply namespace preferences (file-level) --- Source/AppScaffolding/LibationScaffolding.cs | 88 +++---- Source/AppScaffolding/UpgradeProperties.cs | 6 +- Source/AudibleUtilities/Mkb79AuthImporter.cs | 2 +- .../20260427201825_ReAddCategoryName2.cs | 43 ++-- .../20260427201829_ReAddCategoryName2.cs | 43 ++-- .../NamingTemplate/CommonFormatters.cs | 6 +- .../ConditionalTagCollection[TClass].cs | 5 +- .../NamingTemplate/NamingTemplate.cs | 2 +- .../PropertyTagCollection[TClass].cs | 8 +- .../NamingTemplate/RegExpExtensions.cs | 2 +- .../LiberatedStatusBatchManualDialog.axaml.cs | 2 +- .../Templates/ContributorDto.cs | 2 +- .../Templates/CultureInfoDto.cs | 6 +- .../Templates/Templates.cs | 2 +- .../LibationFileManager/WindowsDirectory.cs | 14 +- Source/LibationSearchEngine/SearchEngine.cs | 18 +- .../CommonFormattersTests.cs | 2 +- .../SearchEngineTests.cs | 2 +- .../ExceptionDisplayTests.cs | 240 +++++++++--------- 19 files changed, 244 insertions(+), 249 deletions(-) diff --git a/Source/AppScaffolding/LibationScaffolding.cs b/Source/AppScaffolding/LibationScaffolding.cs index 395d14d8..3cc084bb 100644 --- a/Source/AppScaffolding/LibationScaffolding.cs +++ b/Source/AppScaffolding/LibationScaffolding.cs @@ -288,64 +288,64 @@ public static class LibationScaffolding } private static void logStartupState(Configuration config) - { + { #if DEBUG - var mode = "Debug"; + var mode = "Debug"; #else var mode = "Release"; #endif - if (Debugger.IsAttached) - mode += " (Debugger attached)"; + if (Debugger.IsAttached) + mode += " (Debugger attached)"; - // begin logging session with a form feed - Log.Logger.Information("\r\n\f"); + // begin logging session with a form feed + Log.Logger.Information("\r\n\f"); - static int fileCount(FileManager.LongPath? longPath) - { - if (longPath is null) - return -1; - try { return FileManager.FileUtility.SaferEnumerateFiles(longPath).Count(); } - catch { return -1; } - } + static int fileCount(FileManager.LongPath? longPath) + { + if (longPath is null) + return -1; + try { return FileManager.FileUtility.SaferEnumerateFiles(longPath).Count(); } + catch { return -1; } + } - Log.Logger.Information("Begin. {@DebugInfo}", new - { - AppName = EntryAssembly?.GetName().Name, - Version = BuildVersion?.ToString(), - ReleaseIdentifier, - Configuration.OS, - Environment.OSVersion, - InteropFactory.InteropFunctionsType, - Mode = mode, - LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(), - LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(), - LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(), - LogLevel_Warning_Enabled = Log.Logger.IsWarningEnabled(), - LogLevel_Error_Enabled = Log.Logger.IsErrorEnabled(), - LogLevel_Fatal_Enabled = Log.Logger.IsFatalEnabled(), + Log.Logger.Information("Begin. {@DebugInfo}", new + { + AppName = EntryAssembly?.GetName().Name, + Version = BuildVersion?.ToString(), + ReleaseIdentifier, + Configuration.OS, + Environment.OSVersion, + InteropFactory.InteropFunctionsType, + Mode = mode, + LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(), + LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(), + LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(), + LogLevel_Warning_Enabled = Log.Logger.IsWarningEnabled(), + LogLevel_Error_Enabled = Log.Logger.IsErrorEnabled(), + LogLevel_Fatal_Enabled = Log.Logger.IsFatalEnabled(), - config.AutoScan, - config.BetaOptIn, - config.UseCoverAsFolderIcon, - config.LibationFiles, - AudibleFileStorage.BooksDirectory, + config.AutoScan, + config.BetaOptIn, + config.UseCoverAsFolderIcon, + config.LibationFiles, + AudibleFileStorage.BooksDirectory, - config.InProgress, + config.InProgress, - AudibleFileStorage.DownloadsInProgressDirectory, - DownloadsInProgressFiles = fileCount(AudibleFileStorage.DownloadsInProgressDirectory), + AudibleFileStorage.DownloadsInProgressDirectory, + DownloadsInProgressFiles = fileCount(AudibleFileStorage.DownloadsInProgressDirectory), - AudibleFileStorage.DecryptInProgressDirectory, - DecryptInProgressFiles = fileCount(AudibleFileStorage.DecryptInProgressDirectory), + AudibleFileStorage.DecryptInProgressDirectory, + DecryptInProgressFiles = fileCount(AudibleFileStorage.DecryptInProgressDirectory), - disableIPv6 = AppContext.TryGetSwitch("System.Net.DisableIPv6", out bool disableIPv6Value), - }); + disableIPv6 = AppContext.TryGetSwitch("System.Net.DisableIPv6", out bool disableIPv6Value), + }); - if (InteropFactory.InteropFunctionsType is null) - Serilog.Log.Logger.Warning("WARNING: OSInteropProxy.InteropFunctionsType is null"); - } + if (InteropFactory.InteropFunctionsType is null) + Serilog.Log.Logger.Warning("WARNING: OSInteropProxy.InteropFunctionsType is null"); + } - private static void wireUpSystemEvents(Configuration configuration) + private static void wireUpSystemEvents(Configuration configuration) { LibraryCommands.LibrarySizeChanged += (object? _, List libraryBooks) => SearchEngineCommands.FullReIndex(libraryBooks); diff --git a/Source/AppScaffolding/UpgradeProperties.cs b/Source/AppScaffolding/UpgradeProperties.cs index e3c55ac9..ed8299eb 100644 --- a/Source/AppScaffolding/UpgradeProperties.cs +++ b/Source/AppScaffolding/UpgradeProperties.cs @@ -29,7 +29,7 @@ public partial record UpgradeProperties ZipUrl = zipUrl; LatestRelease = latestRelease; - var text = NoAppBlockRegex().Replace(notes, ""); + var text = NoAppBlockRegex().Replace(notes, ""); text = LinkStripRegex().Replace(text, "$1"); Notes = text.Trim(); } @@ -37,6 +37,6 @@ public partial record UpgradeProperties [GeneratedRegex(@"\[(.*)\]\(.*\)")] private static partial Regex LinkStripRegex(); - [GeneratedRegex(@".*?", RegexOptions.Singleline)] - private static partial Regex NoAppBlockRegex(); + [GeneratedRegex(@".*?", RegexOptions.Singleline)] + private static partial Regex NoAppBlockRegex(); } diff --git a/Source/AudibleUtilities/Mkb79AuthImporter.cs b/Source/AudibleUtilities/Mkb79AuthImporter.cs index 3cb1bd4b..82b21ccb 100644 --- a/Source/AudibleUtilities/Mkb79AuthImporter.cs +++ b/Source/AudibleUtilities/Mkb79AuthImporter.cs @@ -33,7 +33,7 @@ public static class Mkb79AuthImporter using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); if (persister.AccountsSettings.Accounts.Any(a => - a.AccountId == account.AccountId && a.IdentityTokens?.Locale.Name == account.Locale?.Name)) + a.AccountId == account.AccountId && a.IdentityTokens?.Locale.Name == account.Locale?.Name)) { return new Mkb79ImportResult(Mkb79ImportOutcome.DuplicateAccount, account); } diff --git a/Source/DataLayer.Postgres/Migrations/20260427201825_ReAddCategoryName2.cs b/Source/DataLayer.Postgres/Migrations/20260427201825_ReAddCategoryName2.cs index 634e0bbb..bb624335 100644 --- a/Source/DataLayer.Postgres/Migrations/20260427201825_ReAddCategoryName2.cs +++ b/Source/DataLayer.Postgres/Migrations/20260427201825_ReAddCategoryName2.cs @@ -1,29 +1,26 @@ using Microsoft.EntityFrameworkCore.Migrations; -#nullable disable +namespace DataLayer.Postgres.Migrations; -namespace DataLayer.Postgres.Migrations +/// +public partial class ReAddCategoryName2 : Migration { - /// - public partial class ReAddCategoryName2 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Name", - table: "Categories", - type: "text", - nullable: false, - defaultValue: ""); - } + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Name", + table: "Categories", + type: "text", + nullable: false, + defaultValue: ""); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Name", - table: "Categories"); - } - } + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Name", + table: "Categories"); + } } diff --git a/Source/DataLayer.Sqlite/Migrations/20260427201829_ReAddCategoryName2.cs b/Source/DataLayer.Sqlite/Migrations/20260427201829_ReAddCategoryName2.cs index 144665de..78d49188 100644 --- a/Source/DataLayer.Sqlite/Migrations/20260427201829_ReAddCategoryName2.cs +++ b/Source/DataLayer.Sqlite/Migrations/20260427201829_ReAddCategoryName2.cs @@ -1,29 +1,26 @@ using Microsoft.EntityFrameworkCore.Migrations; -#nullable disable +namespace DataLayer.Migrations; -namespace DataLayer.Migrations +/// +public partial class ReAddCategoryName2 : Migration { - /// - public partial class ReAddCategoryName2 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Name", - table: "Categories", - type: "TEXT", - nullable: false, - defaultValue: ""); - } + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Name", + table: "Categories", + type: "TEXT", + nullable: false, + defaultValue: ""); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Name", - table: "Categories"); - } - } + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Name", + table: "Categories"); + } } diff --git a/Source/FileManager/NamingTemplate/CommonFormatters.cs b/Source/FileManager/NamingTemplate/CommonFormatters.cs index 151d0101..66b13634 100644 --- a/Source/FileManager/NamingTemplate/CommonFormatters.cs +++ b/Source/FileManager/NamingTemplate/CommonFormatters.cs @@ -216,7 +216,7 @@ public static partial class CommonFormatters public static string Unescape(ReadOnlySpan valueSpan, ReadOnlySpan quoteChars, bool unquoteBackslash = true, bool unescapeDoubleQuotesInsideQuotes = true) { if (valueSpan.IsEmpty) return ""; - + Span search = stackalloc char[quoteChars.Length + 1]; search[0] = '\\'; quoteChars.CopyTo(search[1..]); @@ -245,8 +245,8 @@ public static partial class CommonFormatters { i++; // skip if (!unescapeDoubleQuotesInsideQuotes || - i >= valueSpan.Length || - valueSpan[i] != c) + i >= valueSpan.Length || + valueSpan[i] != c) // end block if no 2nd quote follows or doubled quotes don't have special meaning break; } diff --git a/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs b/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs index 428435ad..b184e6ed 100644 --- a/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs +++ b/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs @@ -307,7 +307,8 @@ public partial class ConditionalTagCollection(bool caseSensitive = true) { var cmp = GetStringComparer(culture); return e1.OrderBy(e => e, cmp).SequenceEqual(e2.OrderBy(e => e, cmp), cmp); - }, + } + , _ => throw new ArgumentOutOfRangeException() // this should never happen because the regex only allows these values }; return (v1, v2, culture) => v1 is not null && v2 is not null && checklist(ToEnumerable(v1), ToEnumerable(v2), culture); @@ -386,7 +387,7 @@ public partial class ConditionalTagCollection(bool caseSensitive = true) { return StringComparer.Create(culture ?? CultureInfo.CurrentCulture, ignoreCase: true); } - + /// /// Build a regular expression check. Uses culture-invariant matching for thread-safety and consistency. /// Applies a timeout to prevent regex patterns from causing excessive backtracking and blocking. diff --git a/Source/FileManager/NamingTemplate/NamingTemplate.cs b/Source/FileManager/NamingTemplate/NamingTemplate.cs index 59a61e9f..dddc86df 100644 --- a/Source/FileManager/NamingTemplate/NamingTemplate.cs +++ b/Source/FileManager/NamingTemplate/NamingTemplate.cs @@ -63,7 +63,7 @@ public class NamingTemplate return (_templateToString.DynamicInvoke(delegateArgs) as TemplatePart)!.FirstPart; } - + /// Parse a template string to a /// The template string to parse /// A collection of with diff --git a/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs b/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs index 5438d63a..1713c210 100644 --- a/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs +++ b/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs @@ -20,10 +20,10 @@ public class PropertyTagCollection : TagCollection var parameters = formatter.Method.GetParameters(); if (formatter.Method.ReturnType != typeof(string) - || parameters.Length != 4 - || parameters[0].ParameterType != typeof(ITemplateTag) - || parameters[2].ParameterType != typeof(string) - || !typeof(CultureInfo).IsAssignableFrom(parameters[3].ParameterType)) + || parameters.Length != 4 + || parameters[0].ParameterType != typeof(ITemplateTag) + || parameters[2].ParameterType != typeof(string) + || !typeof(CultureInfo).IsAssignableFrom(parameters[3].ParameterType)) throw new ArgumentException( $"{nameof(defaultFormatters)} must have a signature of [{nameof(String)} PropertyFormatter({nameof(ITemplateTag)}, T, {nameof(String)}, {nameof(CultureInfo)})]"); diff --git a/Source/FileManager/NamingTemplate/RegExpExtensions.cs b/Source/FileManager/NamingTemplate/RegExpExtensions.cs index 9830d3ad..6b1aaba9 100644 --- a/Source/FileManager/NamingTemplate/RegExpExtensions.cs +++ b/Source/FileManager/NamingTemplate/RegExpExtensions.cs @@ -60,7 +60,7 @@ public static class RegExpExtensions { // part before match if (m.Index > pos) - sb.Append(gapEvaluator(input[pos .. m.Index])); + sb.Append(gapEvaluator(input[pos..m.Index])); // the match itself sb.Append(matchEvaluator(m)); diff --git a/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs index 075476d4..bbeade9b 100644 --- a/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs @@ -42,7 +42,7 @@ public partial class LiberatedStatusBatchManualDialog : DialogWindow public LiberatedStatusBatchManualDialog() { InitializeComponent(); - SelectedItem = BookStatuses[0] as liberatedComboBoxItem; + SelectedItem = BookStatuses[0]; DataContext = this; ControlToFocusOnShow = SaveButton; } diff --git a/Source/LibationFileManager/Templates/ContributorDto.cs b/Source/LibationFileManager/Templates/ContributorDto.cs index abac37dd..0fcdc361 100644 --- a/Source/LibationFileManager/Templates/ContributorDto.cs +++ b/Source/LibationFileManager/Templates/ContributorDto.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using FileManager.NamingTemplate; -namespace LibationFileManager.Templates; +namespace LibationFileManager.Templates; public class ContributorDto(string name, string? audibleContributorId) : IFormattable { diff --git a/Source/LibationFileManager/Templates/CultureInfoDto.cs b/Source/LibationFileManager/Templates/CultureInfoDto.cs index 27d7d56c..277f6b9e 100644 --- a/Source/LibationFileManager/Templates/CultureInfoDto.cs +++ b/Source/LibationFileManager/Templates/CultureInfoDto.cs @@ -48,9 +48,9 @@ public record CultureInfoDto : IFormattable { var cultures = CultureInfo.GetCultures(types); return Match(cultures, input, c => c.Name) ?? - Match(cultures, input, c => c.TwoLetterISOLanguageName) ?? - Match(cultures, input, c => c.ThreeLetterISOLanguageName) ?? - Match(cultures, input, c => c.EnglishName); + Match(cultures, input, c => c.TwoLetterISOLanguageName) ?? + Match(cultures, input, c => c.ThreeLetterISOLanguageName) ?? + Match(cultures, input, c => c.EnglishName); } private static CultureInfo? Match(IEnumerable cultures, string input, Func selector, StringComparison cmp = StringComparison.OrdinalIgnoreCase) diff --git a/Source/LibationFileManager/Templates/Templates.cs b/Source/LibationFileManager/Templates/Templates.cs index 4da6ca34..c5be97b4 100644 --- a/Source/LibationFileManager/Templates/Templates.cs +++ b/Source/LibationFileManager/Templates/Templates.cs @@ -368,7 +368,7 @@ public abstract partial class Templates { return intVal; } - + // then check for property tags and retrieve their value foreach (var c in allPropertyTags.OfType>()) { diff --git a/Source/LibationFileManager/WindowsDirectory.cs b/Source/LibationFileManager/WindowsDirectory.cs index 344899b3..746590b3 100644 --- a/Source/LibationFileManager/WindowsDirectory.cs +++ b/Source/LibationFileManager/WindowsDirectory.cs @@ -8,20 +8,20 @@ public static class WindowsDirectory const int FolderIconMaxAttempts = 5; public static void SetCoverAsFolderIcon(string? pictureId, string directory, CancellationToken cancellationToken) - { - //Currently only works for Windows and macOS - if (!Configuration.Instance.UseCoverAsFolderIcon) + { + //Currently only works for Windows and macOS + if (!Configuration.Instance.UseCoverAsFolderIcon) return; if (string.IsNullOrEmpty(pictureId)) { Serilog.Log.Logger.Warning("No picture ID provided to set cover art as folder icon. {@DebugInfo}", new { directory }); return; - } + } - // Load JPEG bytes from Images cache (or download). Prefer bytes → ICO so we never depend on a - // path that might not exist when Amazon omits Content-Length or another downloader left a stale cache entry. + // Load JPEG bytes from Images cache (or download). Prefer bytes → ICO so we never depend on a + // path that might not exist when Amazon omits Content-Length or another downloader left a stale cache entry. - for (var attempt = 1; attempt <= FolderIconMaxAttempts; attempt++) + for (var attempt = 1; attempt <= FolderIconMaxAttempts; attempt++) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Source/LibationSearchEngine/SearchEngine.cs b/Source/LibationSearchEngine/SearchEngine.cs index a32f9ea0..6cbe8a34 100644 --- a/Source/LibationSearchEngine/SearchEngine.cs +++ b/Source/LibationSearchEngine/SearchEngine.cs @@ -86,9 +86,9 @@ public class SearchEngine // Corruption (e.g. checksum mismatch in segments) is not fixed by waiting; clear and rebuild immediately. var corruptRebuildAttemptsRemaining = 2; - // Exponential backoff retry: 400 ms, 800 ms, 1600 ms, etc + // Exponential backoff retry: 400 ms, 800 ms, 1600 ms, etc // Total wait time before giving up: 12.4 sec - for (var attempt = 0; attempt < maxRetries; attempt++) + for (var attempt = 0; attempt < maxRetries; attempt++) { try { @@ -118,13 +118,13 @@ public class SearchEngine } } - /// - /// Lucene 3 parses segments_* filenames in the index directory. Cloud sync (e.g. OneDrive) can leave debris - /// or conflict copies whose names break that parser, throwing with this message shape. - /// Actual error is likely to be something like: Invalid or unsupported character in number, hence this string check. - /// (e.g. checksum mismatch in segments) is also recoverable by deleting the index and rebuilding. - /// - public static bool IsRecoverableCorruptIndexException(Exception ex) + /// + /// Lucene 3 parses segments_* filenames in the index directory. Cloud sync (e.g. OneDrive) can leave debris + /// or conflict copies whose names break that parser, throwing with this message shape. + /// Actual error is likely to be something like: Invalid or unsupported character in number, hence this string check. + /// (e.g. checksum mismatch in segments) is also recoverable by deleting the index and rebuilding. + /// + public static bool IsRecoverableCorruptIndexException(Exception ex) => ex is CorruptIndexException || (ex is ArgumentException aex && aex.Message.Contains("character in number", StringComparison.OrdinalIgnoreCase)); diff --git a/Source/_Tests/FileManager.Tests/CommonFormattersTests.cs b/Source/_Tests/FileManager.Tests/CommonFormattersTests.cs index acf88cf5..bbfa9642 100644 --- a/Source/_Tests/FileManager.Tests/CommonFormattersTests.cs +++ b/Source/_Tests/FileManager.Tests/CommonFormattersTests.cs @@ -321,7 +321,7 @@ public class CommonFormattersTests // THEN Assert.AreEqual(expected, unescaped); } - + private class TestClass { diff --git a/Source/_Tests/LibationSearchEngine.Tests/SearchEngineTests.cs b/Source/_Tests/LibationSearchEngine.Tests/SearchEngineTests.cs index 183422d1..d2ab0a1c 100644 --- a/Source/_Tests/LibationSearchEngine.Tests/SearchEngineTests.cs +++ b/Source/_Tests/LibationSearchEngine.Tests/SearchEngineTests.cs @@ -74,7 +74,7 @@ public class FormatSearchQuery public void FormattingTest(string input, string output) { CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - + using var analyzer = new StandardAnalyzer(SearchEngine.Version); QuerySanitizer.Sanitize(input, analyzer).Should().Be(output); diff --git a/Source/_Tests/LibationUiBase.Tests/ExceptionDisplayTests.cs b/Source/_Tests/LibationUiBase.Tests/ExceptionDisplayTests.cs index f8ac1915..063d57be 100644 --- a/Source/_Tests/LibationUiBase.Tests/ExceptionDisplayTests.cs +++ b/Source/_Tests/LibationUiBase.Tests/ExceptionDisplayTests.cs @@ -3,28 +3,28 @@ [TestClass] public class ExceptionDisplayTests { - /// Outer exception with a fixed so golden-string tests are stable. - private sealed class ExceptionWithFixedStack : Exception - { - private string _fixedStack { get; } - public ExceptionWithFixedStack(string message, Exception? innerException, string fixedStack) - : base(message, innerException) => _fixedStack = fixedStack; - public override string? StackTrace => _fixedStack; - } + /// Outer exception with a fixed so golden-string tests are stable. + private sealed class ExceptionWithFixedStack : Exception + { + private string _fixedStack { get; } + public ExceptionWithFixedStack(string message, Exception? innerException, string fixedStack) + : base(message, innerException) => _fixedStack = fixedStack; + public override string? StackTrace => _fixedStack; + } - /// - /// Golden output for when there are fourteen - /// links (fifteen messages: outer L0 … deepest L14): first ten inners, - /// two omitted (L11–L12), then the two deepest (L13–L14), then the outer stack trace. - /// - [TestMethod] - public void _FullText_14Levels() - { - const string stack = "<>"; - var messages = Enumerable.Range(0, 15).Select(i => $"L{i}").ToArray(); - var ex = CreateChainWithFixedStack(stack, messages); + /// + /// Golden output for when there are fourteen + /// links (fifteen messages: outer L0 … deepest L14): first ten inners, + /// two omitted (L11–L12), then the two deepest (L13–L14), then the outer stack trace. + /// + [TestMethod] + public void _FullText_14Levels() + { + const string stack = "<>"; + var messages = Enumerable.Range(0, 15).Select(i => $"L{i}").ToArray(); + var ex = CreateChainWithFixedStack(stack, messages); - const string expected = """ + const string expected = """ L0 Inner exception: L1 @@ -55,129 +55,129 @@ Inner exception: L14 <> """; - Assert.AreEqual(expected.ReplaceLineEndings("\n"), Format(ex)); - } + Assert.AreEqual(expected.ReplaceLineEndings("\n"), Format(ex)); + } - /// - /// Same chain shape as , but the outer exception reports from . - private static Exception CreateChainWithFixedStack(string stackTrace, params string[] messages) - { - if (messages.Length == 0) - throw new ArgumentException("At least one message is required.", nameof(messages)); + /// + /// Same chain shape as , but the outer exception reports from . + private static Exception CreateChainWithFixedStack(string stackTrace, params string[] messages) + { + if (messages.Length == 0) + throw new ArgumentException("At least one message is required.", nameof(messages)); - var innermost = new Exception(messages[^1]); - for (var i = messages.Length - 2; i >= 1; i--) - innermost = new Exception(messages[i], innermost); - return new ExceptionWithFixedStack(messages[0], innermost, stackTrace); - } + var innermost = new Exception(messages[^1]); + for (var i = messages.Length - 2; i >= 1; i--) + innermost = new Exception(messages[i], innermost); + return new ExceptionWithFixedStack(messages[0], innermost, stackTrace); + } - /// messages[0] = outer; each following string is the next message. - private static Exception CreateChain(params string[] messages) - { - if (messages.Length == 0) - throw new ArgumentException("At least one message is required.", nameof(messages)); + /// messages[0] = outer; each following string is the next message. + private static Exception CreateChain(params string[] messages) + { + if (messages.Length == 0) + throw new ArgumentException("At least one message is required.", nameof(messages)); - var innermost = new Exception(messages[^1]); - for (var i = messages.Length - 2; i >= 0; i--) - innermost = new Exception(messages[i], innermost); - return innermost; - } + var innermost = new Exception(messages[^1]); + for (var i = messages.Length - 2; i >= 0; i--) + innermost = new Exception(messages[i], innermost); + return innermost; + } - private static string Format(Exception ex) => ExceptionDisplay.FormatMessageAndStackTrace(ex).ReplaceLineEndings("\n"); + private static string Format(Exception ex) => ExceptionDisplay.FormatMessageAndStackTrace(ex).ReplaceLineEndings("\n"); - [TestMethod] - public void NoInnerException_ContainsOuterMessageAndStack() - { - var ex = new Exception("outer only"); - var text = Format(ex); + [TestMethod] + public void NoInnerException_ContainsOuterMessageAndStack() + { + var ex = new Exception("outer only"); + var text = Format(ex); - Assert.StartsWith("outer only\n", text); - Assert.IsFalse(text.Contains("Inner exception:", StringComparison.Ordinal)); - if (ex.StackTrace is not null) - Assert.IsTrue(text.Contains(ex.StackTrace, StringComparison.Ordinal)); - } + Assert.StartsWith("outer only\n", text); + Assert.IsFalse(text.Contains("Inner exception:", StringComparison.Ordinal)); + if (ex.StackTrace is not null) + Assert.IsTrue(text.Contains(ex.StackTrace, StringComparison.Ordinal)); + } - [TestMethod] - public void OneInner_ShowsInnerLine() - { - var ex = CreateChain("outer", "inner-a"); - var text = Format(ex); + [TestMethod] + public void OneInner_ShowsInnerLine() + { + var ex = CreateChain("outer", "inner-a"); + var text = Format(ex); - var expectedHead = """ + var expectedHead = """ outer Inner exception: inner-a """.ReplaceLineEndings("\n"); - Assert.AreEqual(expectedHead + ex.StackTrace, text); - } + Assert.AreEqual(expectedHead + ex.StackTrace, text); + } - [TestMethod] - public void TenInners_ShowsAllTen() - { - var messages = new[] { "m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", "m10" }; - var ex = CreateChain(messages); - var text = Format(ex); + [TestMethod] + public void TenInners_ShowsAllTen() + { + var messages = new[] { "m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", "m10" }; + var ex = CreateChain(messages); + var text = Format(ex); - for (var i = 1; i <= 10; i++) - Assert.IsTrue(text.Contains($"Inner exception: m{i}\n", StringComparison.Ordinal), $"missing inner m{i}"); - Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase)); - } + for (var i = 1; i <= 10; i++) + Assert.IsTrue(text.Contains($"Inner exception: m{i}\n", StringComparison.Ordinal), $"missing inner m{i}"); + Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase)); + } - [TestMethod] - public void ElevenInners_FirstTenThenDeepestOnly_NoOmitLine() - { - var messages = Enumerable.Range(0, 12).Select(i => $"n{i}").ToArray(); - var ex = CreateChain(messages); - var text = Format(ex); + [TestMethod] + public void ElevenInners_FirstTenThenDeepestOnly_NoOmitLine() + { + var messages = Enumerable.Range(0, 12).Select(i => $"n{i}").ToArray(); + var ex = CreateChain(messages); + var text = Format(ex); - for (var i = 1; i <= 10; i++) - Assert.IsTrue(text.Contains($"Inner exception: n{i}\n", StringComparison.Ordinal)); - Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase)); - Assert.IsTrue(text.Contains("Inner exception: n11\n", StringComparison.Ordinal)); - } + for (var i = 1; i <= 10; i++) + Assert.IsTrue(text.Contains($"Inner exception: n{i}\n", StringComparison.Ordinal)); + Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(text.Contains("Inner exception: n11\n", StringComparison.Ordinal)); + } - [TestMethod] - public void TwelveInners_FirstTenThenLastTwo_NoOmitLine() - { - var messages = Enumerable.Range(0, 13).Select(i => $"p{i}").ToArray(); - var ex = CreateChain(messages); - var text = Format(ex); + [TestMethod] + public void TwelveInners_FirstTenThenLastTwo_NoOmitLine() + { + var messages = Enumerable.Range(0, 13).Select(i => $"p{i}").ToArray(); + var ex = CreateChain(messages); + var text = Format(ex); - for (var i = 1; i <= 10; i++) - Assert.IsTrue(text.Contains($"Inner exception: p{i}\n", StringComparison.Ordinal)); - Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase)); - Assert.IsTrue(text.Contains("Inner exception: p11\n", StringComparison.Ordinal)); - Assert.IsTrue(text.Contains("Inner exception: p12\n", StringComparison.Ordinal)); - } + for (var i = 1; i <= 10; i++) + Assert.IsTrue(text.Contains($"Inner exception: p{i}\n", StringComparison.Ordinal)); + Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(text.Contains("Inner exception: p11\n", StringComparison.Ordinal)); + Assert.IsTrue(text.Contains("Inner exception: p12\n", StringComparison.Ordinal)); + } - [TestMethod] - public void ThirteenInners_FirstTen_OmitOne_ThenLastTwo() - { - var messages = Enumerable.Range(0, 14).Select(i => $"q{i}").ToArray(); - var ex = CreateChain(messages); - var text = Format(ex); + [TestMethod] + public void ThirteenInners_FirstTen_OmitOne_ThenLastTwo() + { + var messages = Enumerable.Range(0, 14).Select(i => $"q{i}").ToArray(); + var ex = CreateChain(messages); + var text = Format(ex); - for (var i = 1; i <= 10; i++) - Assert.IsTrue(text.Contains($"Inner exception: q{i}\n", StringComparison.Ordinal)); - Assert.IsTrue(text.Contains("1 inner exception omitted.\n", StringComparison.Ordinal)); - Assert.IsFalse(text.Contains("Inner exception: q11\n", StringComparison.Ordinal), "omitted inner should not appear"); - Assert.IsTrue(text.Contains("Inner exception: q12\n", StringComparison.Ordinal)); - Assert.IsTrue(text.Contains("Inner exception: q13\n", StringComparison.Ordinal)); - } + for (var i = 1; i <= 10; i++) + Assert.IsTrue(text.Contains($"Inner exception: q{i}\n", StringComparison.Ordinal)); + Assert.IsTrue(text.Contains("1 inner exception omitted.\n", StringComparison.Ordinal)); + Assert.IsFalse(text.Contains("Inner exception: q11\n", StringComparison.Ordinal), "omitted inner should not appear"); + Assert.IsTrue(text.Contains("Inner exception: q12\n", StringComparison.Ordinal)); + Assert.IsTrue(text.Contains("Inner exception: q13\n", StringComparison.Ordinal)); + } - [TestMethod] - public void ManyInners_OmitCountPlural() - { - var depth = 20; - var messages = Enumerable.Range(0, depth + 1).Select(i => $"x{i}").ToArray(); - var ex = CreateChain(messages); - var text = Format(ex); + [TestMethod] + public void ManyInners_OmitCountPlural() + { + var depth = 20; + var messages = Enumerable.Range(0, depth + 1).Select(i => $"x{i}").ToArray(); + var ex = CreateChain(messages); + var text = Format(ex); - var omitted = depth - 10 - 2; - Assert.IsTrue(text.Contains($"{omitted} inner exceptions omitted.\n", StringComparison.Ordinal)); - Assert.IsTrue(text.Contains($"Inner exception: x{depth - 1}\n", StringComparison.Ordinal)); - Assert.IsTrue(text.Contains($"Inner exception: x{depth}\n", StringComparison.Ordinal)); - } + var omitted = depth - 10 - 2; + Assert.IsTrue(text.Contains($"{omitted} inner exceptions omitted.\n", StringComparison.Ordinal)); + Assert.IsTrue(text.Contains($"Inner exception: x{depth - 1}\n", StringComparison.Ordinal)); + Assert.IsTrue(text.Contains($"Inner exception: x{depth}\n", StringComparison.Ordinal)); + } } From e4daa5b8e84a83e4b48388b0fac086d2809c9eac Mon Sep 17 00:00:00 2001 From: MBucari Date: Mon, 4 May 2026 21:20:42 -0600 Subject: [PATCH 3/3] Fix macos bundle build failure --- Scripts/Bundle_MacOS.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/Bundle_MacOS.sh b/Scripts/Bundle_MacOS.sh index 8fe596cb..ed86ea4a 100644 --- a/Scripts/Bundle_MacOS.sh +++ b/Scripts/Bundle_MacOS.sh @@ -118,6 +118,7 @@ mkdir Libation/.background mv background.png Libation/.background/ ln -s /Applications "./Libation/ " mkdir ./bundle +sync hdiutil create -srcFolder ./Libation -o "./bundle/$DMG_FILE" # Create a .DS_Store by: # - mounting an existing image in shadow mode (hdiutil attach Libation.dmg -shadow junk.dmg)