Libation/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs
Mbucari 657a7bb6bc 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.
2025-07-21 09:49:25 -06:00

107 lines
3.5 KiB
C#

using DataLayer;
using Dinah.Core.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace LibationUiBase.GridView
{
/// <summary>The View Model for a LibraryBook that is ContentType.Parent</summary>
public class SeriesEntry<TStatus> : GridEntry<TStatus>, ISeriesEntry where TStatus : IEntryStatus
{
public List<ILibraryBookEntry> Children { get; }
public override DateTime DateAdded => Children.Max(c => c.DateAdded);
private bool suspendCounting = false;
public void ChildRemoveUpdate()
{
if (suspendCounting) return;
var removeCount = Children.Count(c => c.Remove == true);
remove = removeCount == 0 ? false : removeCount == Children.Count ? true : null;
RaisePropertyChanged(nameof(Remove));
}
public override bool? Remove
{
get => remove;
set
{
remove = value ?? false;
suspendCounting = true;
foreach (var item in Children)
item.Remove = value;
suspendCounting = false;
RaisePropertyChanged(nameof(Remove));
}
}
public SeriesEntry(LibraryBook parent, LibraryBook child) : this(parent, new[] { child }) { }
public SeriesEntry(LibraryBook parent, IEnumerable<LibraryBook> children)
{
LastDownload = new();
SeriesIndex = -1;
Children = children
.Select(c => new LibraryBookEntry<TStatus>(c, this))
.OrderByDescending(c => c.SeriesOrder)
.ToList<ILibraryBookEntry>();
UpdateLibraryBook(parent);
LoadCover();
}
/// <summary>
/// Creates <see cref="SeriesEntry{TStatus}"/> for all episodic series in an enumeration of <see cref="LibraryBook"/>.
/// </summary>
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
public static async Task<List<ISeriesEntry>> GetAllSeriesEntriesAsync(IEnumerable<LibraryBook> libraryBooks)
{
var seriesEntries = await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeParent(), lb => new SeriesEntry<TStatus>(lb, []) as ISeriesEntry);
var seriesDict = seriesEntries.ToDictionarySafe(s => s.AudibleProductId);
await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeChild(), CreateAndLinkEpisodeEntry);
//sort episodes by series order descending and update SeriesEntry
foreach (var series in seriesEntries)
{
series.Children.Sort((a, b) => -a.SeriesOrder.CompareTo(b.SeriesOrder));
series.UpdateLibraryBook(series.LibraryBook);
}
return seriesEntries.Where(s => s.Children.Count != 0).ToList();
//Create a LibraryBookEntry for an episode and link it to its series parent
LibraryBookEntry<TStatus> CreateAndLinkEpisodeEntry(LibraryBook episode)
{
foreach (var s in episode.Book.SeriesLink)
{
if (seriesDict.TryGetValue(s.Series.AudibleSeriesId, out var seriesParent))
{
var entry = new LibraryBookEntry<TStatus>(episode, seriesParent);
seriesParent.Children.Add(entry);
return entry;
}
}
return null;
}
}
public void RemoveChild(ILibraryBookEntry lbe)
{
Children.Remove(lbe);
PurchaseDate = GetPurchaseDateString();
Length = GetBookLengthString();
}
protected override string GetBookTags() => null;
protected override int GetLengthInMinutes() => Children.Count == 0 ? 0 : Children.Sum(c => c.LibraryBook.Book.LengthInMinutes);
protected override DateTime GetPurchaseDate() => Children.Count == 0 ? default : Children.Min(c => c.LibraryBook.DateAdded);
}
}