From 657a7bb6bcd5028002bed3021e81085a9986e593 Mon Sep 17 00:00:00 2001 From: Mbucari <37587114+Mbucari@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:22:05 -0600 Subject: [PATCH 1/7] Improve podcast episode GridEntry creation performance. Tested on a library with ~5000 podcast episodes on an AMD Ryzen 7700X. Startup time decreases by ~400 ms in Release mode. --- .../GridView/GridEntry[TStatus].cs | 33 ++++++++++- .../GridView/LibraryBookEntry[TStatus].cs | 26 +------- .../GridView/SeriesEntry[TStatus].cs | 59 ++++++------------- 3 files changed, 51 insertions(+), 67 deletions(-) diff --git a/Source/LibationUiBase/GridView/GridEntry[TStatus].cs b/Source/LibationUiBase/GridView/GridEntry[TStatus].cs index a57b52db..e00991cd 100644 --- a/Source/LibationUiBase/GridView/GridEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/GridEntry[TStatus].cs @@ -1,7 +1,6 @@ using ApplicationServices; using DataLayer; using Dinah.Core; -using Dinah.Core.Threading; using FileLiberator; using LibationFileManager; using System; @@ -9,7 +8,7 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; namespace LibationUiBase.GridView @@ -311,6 +310,36 @@ namespace LibationUiBase.GridView #endregion + + /// + /// Creates for all non-episode books in an enumeration of . + /// + /// Can be called from any thread, but requires the calling thread's to be valid. + public static async Task> GetAllProductsAsync(IEnumerable libraryBooks, Func includeIf, Func factory) + where TEntry : IGridEntry + { + var products = libraryBooks.Where(includeIf).ToArray(); + if (products.Length == 0) + return []; + + int parallelism = int.Max(1, Environment.ProcessorCount - 1); + + (int batchSize, int rem) = int.DivRem(products.Length, parallelism); + if (rem != 0) batchSize++; + + var syncContext = SynchronizationContext.Current; + + //Asynchronously create a GridEntry for every book in the library + var tasks = products.Chunk(batchSize).Select(batch => Task.Run(() => + { + SynchronizationContext.SetSynchronizationContext(syncContext); + return batch.Select(factory).OfType().ToArray(); + })); + + return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList(); + } + + ~GridEntry() { PictureStorage.PictureCached -= PictureStorage_PictureCached; diff --git a/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs b/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs index 2d118e36..0dbff282 100644 --- a/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; -using System.Threading; -using System.Linq; namespace LibationUiBase.GridView { @@ -36,29 +34,9 @@ namespace LibationUiBase.GridView /// /// Creates for all non-episode books in an enumeration of . /// - /// Can be called from any thread, but requires the calling thread's to be valid. + /// Can be called from any thread, but requires the calling thread's to be valid. public static async Task> GetAllProductsAsync(IEnumerable libraryBooks) - { - var products = libraryBooks.Where(lb => lb.Book.IsProduct()).ToArray(); - if (products.Length == 0) - return []; - - int parallelism = int.Max(1, Environment.ProcessorCount - 1); - - (int batchSize, int rem) = int.DivRem(products.Length, parallelism); - if (rem != 0) batchSize++; - - var syncContext = SynchronizationContext.Current; - - //Asynchronously create an ILibraryBookEntry for every book in the library - var tasks = products.Chunk(batchSize).Select(batch => Task.Run(() => - { - SynchronizationContext.SetSynchronizationContext(syncContext); - return batch.Select(lb => new LibraryBookEntry(lb) as IGridEntry); - })); - - return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList(); - } + => await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsProduct(), lb => new LibraryBookEntry(lb) as IGridEntry); protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); } diff --git a/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs b/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs index 17eeff93..80627eac 100644 --- a/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs @@ -1,4 +1,5 @@ using DataLayer; +using Dinah.Core.Collections.Generic; using System; using System.Collections.Generic; using System.Linq; @@ -62,56 +63,32 @@ namespace LibationUiBase.GridView /// Can be called from any thread, but requires the calling thread's to be valid. public static async Task> GetAllSeriesEntriesAsync(IEnumerable libraryBooks) { - var seriesBooks = libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).ToArray(); - var allEpisodes = libraryBooks.Where(lb => lb.Book.IsEpisodeChild()).ToArray(); + var seriesEntries = await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeParent(), lb => new SeriesEntry(lb, []) as ISeriesEntry); + var seriesDict = seriesEntries.ToDictionarySafe(s => s.AudibleProductId); + await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeChild(), CreateAndLinkEpisodeEntry); - var seriesEntries = new ISeriesEntry[seriesBooks.Length]; - var seriesEpisodes = new ILibraryBookEntry[seriesBooks.Length][]; - - var syncContext = SynchronizationContext.Current; - var options = new ParallelOptions { MaxDegreeOfParallelism = int.Max(1, Environment.ProcessorCount - 1) }; - - //Asynchronously create an ILibraryBookEntry for every episode in the library - await Parallel.ForEachAsync(getAllEpisodes(), options, createEpisodeEntry); - - //Match all episode entries to their corresponding parents - for (int i = seriesEntries.Length - 1; i >= 0; i--) + //sort episodes by series order descending and update SeriesEntry + foreach (var series in seriesEntries) { - var series = seriesEntries[i]; - - //Sort episodes by series order descending, then add them to their parent's entry - Array.Sort(seriesEpisodes[i], (a, b) => -a.SeriesOrder.CompareTo(b.SeriesOrder)); - series.Children.AddRange(seriesEpisodes[i]); + series.Children.Sort((a, b) => -a.SeriesOrder.CompareTo(b.SeriesOrder)); series.UpdateLibraryBook(series.LibraryBook); } - return seriesEntries.Where(s => s.Children.Count != 0).Cast().ToList(); + return seriesEntries.Where(s => s.Children.Count != 0).ToList(); - //Create a LibraryBookEntry for a single episode - ValueTask createEpisodeEntry((int seriesIndex, int episodeIndex, LibraryBook episode) data, CancellationToken cancellationToken) + //Create a LibraryBookEntry for an episode and link it to its series parent + LibraryBookEntry CreateAndLinkEpisodeEntry(LibraryBook episode) { - SynchronizationContext.SetSynchronizationContext(syncContext); - var parent = seriesEntries[data.seriesIndex]; - seriesEpisodes[data.seriesIndex][data.episodeIndex] = new LibraryBookEntry(data.episode, parent); - return ValueTask.CompletedTask; - } - - //Enumeration all series episodes, along with the index to its seriesEntries entry - //and an index to its seriesEpisodes entry - IEnumerable<(int seriesIndex, int episodeIndex, LibraryBook episode)> getAllEpisodes() - { - for (int i = 0; i < seriesBooks.Length; i++) + foreach (var s in episode.Book.SeriesLink) { - var series = seriesBooks[i]; - var childEpisodes = allEpisodes.FindChildren(series); - - SynchronizationContext.SetSynchronizationContext(syncContext); - seriesEntries[i] = new SeriesEntry(series, []); - seriesEpisodes[i] = new ILibraryBookEntry[childEpisodes.Count]; - - for (int j = 0; j < childEpisodes.Count; j++) - yield return (i, j, childEpisodes[j]); + if (seriesDict.TryGetValue(s.Series.AudibleSeriesId, out var seriesParent)) + { + var entry = new LibraryBookEntry(episode, seriesParent); + seriesParent.Children.Add(entry); + return entry; + } } + return null; } } From bff9b67b7270d037b3c00452221749bbe28c5337 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 21 Jul 2025 10:47:10 -0600 Subject: [PATCH 2/7] Remove GridEntry derrived types and interfaces Use existing BaseUtil.LoadImage delegate, obviating need for derrived classes to load images Since GridEntry types are no longer generic, interfaces are unnecessary and deleted. --- .../Controls/DataGridCheckBoxColumnExt.cs | 2 +- .../Controls/DataGridContextMenus.cs | 8 ++-- .../ViewModels/AvaloniaEntryStatus.cs | 20 ---------- .../ViewModels/MainVM.ProcessQueue.cs | 2 +- .../ViewModels/MainVM._NoUI.cs | 15 +++++++ .../ViewModels/ProductsDisplayViewModel.cs | 40 +++++++++---------- .../ViewModels/RowComparer.cs | 2 +- .../Views/LiberateStatusButton.axaml.cs | 4 +- .../Views/MainWindow.axaml.cs | 2 +- .../Views/ProductsDisplay.axaml | 34 ++++++++-------- .../Views/ProductsDisplay.axaml.cs | 34 ++++++++-------- Source/LibationUiBase/BaseUtil.cs | 28 +++++++++++-- Source/LibationUiBase/GridView/EntryStatus.cs | 16 ++------ .../GridView/GridContextMenu.cs | 12 +++--- .../GridView/GridEntry[TStatus].cs | 13 +++--- Source/LibationUiBase/GridView/IGridEntry.cs | 34 ---------------- .../GridView/ILibraryBookEntry.cs | 7 ---- .../LibationUiBase/GridView/ISeriesEntry.cs | 11 ----- ...kEntry[TStatus].cs => LibraryBookEntry.cs} | 10 ++--- .../GridView/QueryExtensions.cs | 20 +++++----- .../GridView/RowComparerBase.cs | 18 ++++----- ...SeriesEntry[TStatus].cs => SeriesEntry.cs} | 18 ++++----- Source/LibationWinForms/Form1.Designer.cs | 2 +- Source/LibationWinForms/Form1.ProcessQueue.cs | 2 +- Source/LibationWinForms/Form1._NonUI.cs | 1 + .../EditTagsDataGridViewImageButtonColumn.cs | 2 +- .../GridView/GridEntryBindingList.cs | 30 +++++++------- .../LiberateDataGridViewImageButtonColumn.cs | 12 ++++-- .../GridView/ProductsDisplay.cs | 24 +++++------ .../GridView/ProductsGrid.Designer.cs | 2 +- .../LibationWinForms/GridView/ProductsGrid.cs | 40 +++++++++---------- .../LibationWinForms/GridView/RowComparer.cs | 4 +- .../GridView/WinFormsEntryStatus.cs | 24 ----------- 33 files changed, 214 insertions(+), 279 deletions(-) delete mode 100644 Source/LibationAvalonia/ViewModels/AvaloniaEntryStatus.cs delete mode 100644 Source/LibationUiBase/GridView/IGridEntry.cs delete mode 100644 Source/LibationUiBase/GridView/ILibraryBookEntry.cs delete mode 100644 Source/LibationUiBase/GridView/ISeriesEntry.cs rename Source/LibationUiBase/GridView/{LibraryBookEntry[TStatus].cs => LibraryBookEntry.cs} (72%) rename Source/LibationUiBase/GridView/{SeriesEntry[TStatus].cs => SeriesEntry.cs} (81%) delete mode 100644 Source/LibationWinForms/GridView/WinFormsEntryStatus.cs diff --git a/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.cs b/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.cs index f99d9bda..d4caadfc 100644 --- a/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.cs +++ b/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.cs @@ -9,7 +9,7 @@ namespace LibationAvalonia.Controls { //Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary. var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox; - ele.IsThreeState = dataItem is ISeriesEntry; + ele.IsThreeState = dataItem is SeriesEntry; return ele; } } diff --git a/Source/LibationAvalonia/Controls/DataGridContextMenus.cs b/Source/LibationAvalonia/Controls/DataGridContextMenus.cs index d4db742b..10b6c099 100644 --- a/Source/LibationAvalonia/Controls/DataGridContextMenus.cs +++ b/Source/LibationAvalonia/Controls/DataGridContextMenus.cs @@ -34,11 +34,11 @@ namespace LibationAvalonia.Controls private static void Cell_ContextRequested(object sender, ContextRequestedEventArgs e) { if (sender is DataGridCell cell && - cell.DataContext is IGridEntry clickedEntry && + cell.DataContext is GridEntry clickedEntry && OwningColumnProperty.GetValue(cell) is DataGridColumn column && OwningGridProperty.GetValue(column) is DataGrid grid) { - var allSelected = grid.SelectedItems.OfType().ToArray(); + var allSelected = grid.SelectedItems.OfType().ToArray(); var clickedIndex = Array.IndexOf(allSelected, clickedEntry); if (clickedIndex == -1) { @@ -101,7 +101,7 @@ namespace LibationAvalonia.Controls private static string RemoveLineBreaks(string text) => text.Replace("\r\n", "").Replace('\r', ' ').Replace('\n', ' '); - private string GetRowClipboardContents(IGridEntry gridEntry) + private string GetRowClipboardContents(GridEntry gridEntry) { var contents = Grid.Columns.Where(c => c.IsVisible).OrderBy(c => c.DisplayIndex).Select(c => RemoveLineBreaks(GetCellValue(c, gridEntry))).ToArray(); return string.Join("\t", contents); @@ -109,7 +109,7 @@ namespace LibationAvalonia.Controls public required DataGrid Grid { get; init; } public required DataGridColumn Column { get; init; } - public required IGridEntry[] GridEntries { get; init; } + public required GridEntry[] GridEntries { get; init; } public required ContextMenu ContextMenu { get; init; } public AvaloniaList ContextMenuItems => ContextMenu.ItemsSource as AvaloniaList; diff --git a/Source/LibationAvalonia/ViewModels/AvaloniaEntryStatus.cs b/Source/LibationAvalonia/ViewModels/AvaloniaEntryStatus.cs deleted file mode 100644 index 605f2e17..00000000 --- a/Source/LibationAvalonia/ViewModels/AvaloniaEntryStatus.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia.Media.Imaging; -using DataLayer; -using LibationUiBase.GridView; -using System; - -#nullable enable -namespace LibationAvalonia.ViewModels -{ - public class AvaloniaEntryStatus : EntryStatus, IEntryStatus, IComparable - { - private AvaloniaEntryStatus(LibraryBook libraryBook) : base(libraryBook) { } - public static EntryStatus Create(LibraryBook libraryBook) => new AvaloniaEntryStatus(libraryBook); - - protected override Bitmap LoadImage(byte[] picture) - => AvaloniaUtils.TryLoadImageOrDefault(picture, LibationFileManager.PictureSize._80x80); - - //Button icons are handled by LiberateStatusButton - protected override Bitmap? GetResourceImage(string rescName) => null; - } -} diff --git a/Source/LibationAvalonia/ViewModels/MainVM.ProcessQueue.cs b/Source/LibationAvalonia/ViewModels/MainVM.ProcessQueue.cs index a35f1036..21d1b509 100644 --- a/Source/LibationAvalonia/ViewModels/MainVM.ProcessQueue.cs +++ b/Source/LibationAvalonia/ViewModels/MainVM.ProcessQueue.cs @@ -57,7 +57,7 @@ namespace LibationAvalonia.ViewModels } } - public void LiberateSeriesClicked(ISeriesEntry series) + public void LiberateSeriesClicked(SeriesEntry series) { try { diff --git a/Source/LibationAvalonia/ViewModels/MainVM._NoUI.cs b/Source/LibationAvalonia/ViewModels/MainVM._NoUI.cs index d57594e3..0d361117 100644 --- a/Source/LibationAvalonia/ViewModels/MainVM._NoUI.cs +++ b/Source/LibationAvalonia/ViewModels/MainVM._NoUI.cs @@ -1,5 +1,6 @@ using LibationFileManager; using LibationUiBase; +using System; using System.IO; #nullable enable @@ -23,6 +24,20 @@ namespace LibationAvalonia.ViewModels PictureStorage.SetDefaultImage(PictureSize.Native, ms3.ToArray()); BaseUtil.SetLoadImageDelegate(AvaloniaUtils.TryLoadImageOrDefault); + BaseUtil.SetLoadResourceImageDelegate(LoadResourceImage); + } + private static Avalonia.Media.Imaging.Bitmap? LoadResourceImage(string resourceName) + { + try + { + using var stream = App.OpenAsset(resourceName); + return new Avalonia.Media.Imaging.Bitmap(stream); + } + catch (Exception ex) + { + Serilog.Log.Error(ex, "Failed to load resource image: {ResourceName}", resourceName); + return null; + } } } } diff --git a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs index e7416e14..304d8d21 100644 --- a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs @@ -26,9 +26,9 @@ namespace LibationAvalonia.ViewModels public event EventHandler? RemovableCountChanged; /// Backing list of all grid entries - private readonly AvaloniaList SOURCE = new(); + private readonly AvaloniaList SOURCE = new(); /// Grid entries included in the filter set. If null, all grid entries are shown - private HashSet? FilteredInGridEntries; + private HashSet? FilteredInGridEntries; public string? FilterString { get; private set; } private DataGridCollectionView? _gridEntries; @@ -43,15 +43,15 @@ namespace LibationAvalonia.ViewModels public List GetVisibleBookEntries() => FilteredInGridEntries? - .OfType() + .OfType() .Select(lbe => lbe.LibraryBook) .ToList() ?? SOURCE - .OfType() + .OfType() .Select(lbe => lbe.LibraryBook) .ToList(); - private IEnumerable GetAllBookEntries() + private IEnumerable GetAllBookEntries() => SOURCE .BookEntries(); @@ -112,8 +112,8 @@ namespace LibationAvalonia.ViewModels var sc = await Dispatcher.UIThread.InvokeAsync(() => AvaloniaSynchronizationContext.Current); AvaloniaSynchronizationContext.SetSynchronizationContext(sc); - var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks); - var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks); + var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks); + var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks); //Add all IGridEntries to the SOURCE list. Note that SOURCE has not yet been linked to the UI via //the GridEntries property, so adding items to SOURCE will not trigger any refreshes or UI action. @@ -147,8 +147,8 @@ namespace LibationAvalonia.ViewModels private void GridEntries_CollectionChanged(object? sender = null, EventArgs? e = null) { var count - = FilteredInGridEntries?.OfType().Count() - ?? SOURCE.OfType().Count(); + = FilteredInGridEntries?.OfType().Count() + ?? SOURCE.OfType().Count(); VisibleCountChanged?.Invoke(this, count); } @@ -223,9 +223,9 @@ namespace LibationAvalonia.ViewModels GridEntries_CollectionChanged(); } - private void RemoveBooks(IEnumerable removedBooks, IEnumerable removedSeries) + private void RemoveBooks(IEnumerable removedBooks, IEnumerable removedSeries) { - foreach (var removed in removedBooks.Cast().Concat(removedSeries).Where(b => b is not null).ToList()) + foreach (var removed in removedBooks.Cast().Concat(removedSeries).Where(b => b is not null).ToList()) { if (GridEntries?.PassesFilter(removed) ?? false) GridEntries.Remove(removed); @@ -238,21 +238,21 @@ namespace LibationAvalonia.ViewModels } } - private void UpsertBook(LibraryBook book, ILibraryBookEntry? existingBookEntry) + private void UpsertBook(LibraryBook book, LibraryBookEntry? existingBookEntry) { if (existingBookEntry is null) // Add the new product to top - SOURCE.Insert(0, new LibraryBookEntry(book)); + SOURCE.Insert(0, new LibraryBookEntry(book)); else // update existing existingBookEntry.UpdateLibraryBook(book); } - private void UpsertEpisode(LibraryBook episodeBook, ILibraryBookEntry? existingEpisodeEntry, List seriesEntries, IEnumerable dbBooks) + private void UpsertEpisode(LibraryBook episodeBook, LibraryBookEntry? existingEpisodeEntry, List seriesEntries, IEnumerable dbBooks) { if (existingEpisodeEntry is null) { - ILibraryBookEntry episodeEntry; + LibraryBookEntry episodeEntry; var seriesEntry = seriesEntries.FindSeriesParent(episodeBook); @@ -270,7 +270,7 @@ namespace LibationAvalonia.ViewModels return; } - seriesEntry = new SeriesEntry(seriesBook, episodeBook); + seriesEntry = new SeriesEntry(seriesBook, episodeBook); seriesEntries.Add(seriesEntry); episodeEntry = seriesEntry.Children[0]; @@ -280,7 +280,7 @@ namespace LibationAvalonia.ViewModels else { //Series exists. Create and add episode child then update the SeriesEntry - episodeEntry = new LibraryBookEntry(episodeBook, seriesEntry); + episodeEntry = new LibraryBookEntry(episodeBook, seriesEntry); seriesEntry.Children.Add(episodeEntry); seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex)); var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId); @@ -307,7 +307,7 @@ namespace LibationAvalonia.ViewModels } } - public async Task ToggleSeriesExpanded(ISeriesEntry seriesEntry) + public async Task ToggleSeriesExpanded(SeriesEntry seriesEntry) { seriesEntry.Liberate.Expanded = !seriesEntry.Liberate.Expanded; @@ -332,7 +332,7 @@ namespace LibationAvalonia.ViewModels private bool CollectionFilter(object item) { - if (item is ILibraryBookEntry lbe + if (item is LibraryBookEntry lbe && lbe.Liberate.IsEpisode && lbe.Parent?.Liberate?.Expanded != true) return false; @@ -454,7 +454,7 @@ namespace LibationAvalonia.ViewModels private void GridEntry_PropertyChanged(object? sender, PropertyChangedEventArgs? e) { - if (e?.PropertyName == nameof(IGridEntry.Remove) && sender is ILibraryBookEntry) + if (e?.PropertyName == nameof(GridEntry.Remove) && sender is LibraryBookEntry) { int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true); RemovableCountChanged?.Invoke(this, removeCount); diff --git a/Source/LibationAvalonia/ViewModels/RowComparer.cs b/Source/LibationAvalonia/ViewModels/RowComparer.cs index 071bedc6..bcdf50b0 100644 --- a/Source/LibationAvalonia/ViewModels/RowComparer.cs +++ b/Source/LibationAvalonia/ViewModels/RowComparer.cs @@ -17,7 +17,7 @@ namespace LibationAvalonia.ViewModels public RowComparer(DataGridColumn? column) { Column = column; - PropertyName = Column?.SortMemberPath ?? nameof(IGridEntry.DateAdded); + PropertyName = Column?.SortMemberPath ?? nameof(GridEntry.DateAdded); } //Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection diff --git a/Source/LibationAvalonia/Views/LiberateStatusButton.axaml.cs b/Source/LibationAvalonia/Views/LiberateStatusButton.axaml.cs index f1217ca2..072137ce 100644 --- a/Source/LibationAvalonia/Views/LiberateStatusButton.axaml.cs +++ b/Source/LibationAvalonia/Views/LiberateStatusButton.axaml.cs @@ -53,8 +53,8 @@ namespace LibationAvalonia.Views private void LiberateStatusButton_DataContextChanged(object sender, EventArgs e) { //Force book status recheck when an entry is scrolled into view. - //This will force a recheck for a paprtially downloaded file. - var status = DataContext as ILibraryBookEntry; + //This will force a recheck for a partially downloaded file. + var status = DataContext as LibraryBookEntry; status?.Liberate.Invalidate(nameof(status.Liberate.BookStatus)); } diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml.cs b/Source/LibationAvalonia/Views/MainWindow.axaml.cs index 783e69e1..e98d71db 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow.axaml.cs @@ -137,7 +137,7 @@ namespace LibationAvalonia.Views } public void ProductsDisplay_LiberateClicked(object _, LibraryBook[] libraryBook) => ViewModel.LiberateClicked(libraryBook); - public void ProductsDisplay_LiberateSeriesClicked(object _, ISeriesEntry series) => ViewModel.LiberateSeriesClicked(series); + public void ProductsDisplay_LiberateSeriesClicked(object _, SeriesEntry series) => ViewModel.LiberateSeriesClicked(series); public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook[] libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook); BookDetailsDialog bookDetailsForm; diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index 90517420..739c15d7 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -59,7 +59,7 @@ Width="75"> - + - + - + @@ -93,7 +93,7 @@ - + @@ -103,7 +103,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -123,7 +123,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -143,7 +143,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -192,7 +192,7 @@ - + @@ -213,7 +213,7 @@ - + @@ -223,7 +223,7 @@ - +