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 @@
-
+