Create some extension members

Trying out .NET 10s extension members with some Book extension properties.
This commit is contained in:
Michael Bucari-Tovo
2025-11-21 12:26:27 -07:00
parent dfbc5ec9db
commit b0a40e12b7
16 changed files with 107 additions and 103 deletions

View File

@@ -577,7 +577,7 @@ namespace ApplicationServices
// must be here instead of in db layer due to AaxcExists
public static LiberatedStatus Liberated_Status(Book book)
=> book.Audio_Exists() ? book.UserDefinedItem.BookStatus
=> book.AudioExists ? book.UserDefinedItem.BookStatus
: AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
: LiberatedStatus.NotLiberated;
@@ -645,7 +645,7 @@ namespace ApplicationServices
var pdfResults = libraryBooks
.AsParallel()
.Where(lb => lb.Book.HasPdf())
.Where(lb => lb.Book.HasPdf)
.Select(lb => new { absent = lb.AbsentFromLastScan, status = Pdf_Status(lb.Book) })
.ToList();

View File

@@ -150,12 +150,12 @@ namespace ApplicationServices
Locale = a.Book.Locale,
Title = a.Book.Title,
Subtitle = a.Book.Subtitle,
AuthorNames = a.Book.AuthorNames(),
NarratorNames = a.Book.NarratorNames(),
AuthorNames = a.Book.AuthorNames,
NarratorNames = a.Book.NarratorNames,
LengthInMinutes = a.Book.LengthInMinutes,
Description = a.Book.Description,
Publisher = a.Book.Publisher,
HasPdf = a.Book.HasPdf(),
HasPdf = a.Book.HasPdf,
SeriesNames = a.Book.SeriesNames(),
SeriesOrder = a.Book.SeriesLink.Any() ? a.Book.SeriesLink?.Select(sl => $"{sl.Order} : {sl.Series.Name}").Aggregate((a, b) => $"{a}, {b}") : "",
CommunityRatingOverall = a.Book.Rating?.OverallRating.ZeroIsNull(),

View File

@@ -13,69 +13,74 @@ namespace DataLayer
.Where(a => a.Role == role)
.OrderBy(a => a.Order);
public static string TitleSortable(this Book book) => Formatters.GetSortName(book.Title + book.Subtitle);
public static string AuthorNames(this Book book) => string.Join(", ", book.Authors.Select(a => a.Name));
public static string NarratorNames(this Book book) => string.Join(", ", book.Narrators.Select(n => n.Name));
extension(Book book)
{
public string SeriesSortable() => Formatters.GetSortName(book.SeriesNames(true));
public string TitleSortable() => Formatters.GetSortName(book.Title + book.Subtitle);
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
public static bool Audio_Exists(this Book book) => book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
/// <summary>True if exists and IsLiberated. Else false</summary>
public static bool PDF_Exists(this Book book) => book.UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
public string AuthorNames => string.Join(", ", book.Authors.Select(a => a.Name));
public string NarratorNames => string.Join(", ", book.Narrators.Select(n => n.Name));
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
public bool AudioExists => book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated or LiberatedStatus.Error;
/// <summary>True if exists and IsLiberated. Else false</summary>
public bool PdfExists => book.UserDefinedItem.PdfStatus == LiberatedStatus.NotLiberated;
/// <summary> Whether the book has any supplements </summary>
public bool HasPdf => book.Supplements.Any();
public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames(true));
public static bool HasPdf(this Book book) => book.Supplements.Any();
public static string SeriesNames(this Book book, bool includeIndex = false)
{
if (book.SeriesLink is null)
return "";
public string SeriesNames(bool includeIndex = false)
{
if (book.SeriesLink is null)
return "";
// first: alphabetical by name
var withNames = book.SeriesLink
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
.Select(getSeriesNameString)
.OrderBy(a => a)
.ToList();
// then un-named are alpha by series id
var nullNames = book.SeriesLink
.Where(s => string.IsNullOrWhiteSpace(s.Series.Name))
.Select(s => s.Series.AudibleSeriesId)
.OrderBy(a => a)
.ToList();
// first: alphabetical by name
var withNames = book.SeriesLink
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
.Select(getSeriesNameString)
.OrderBy(a => a)
.ToList();
// then un-named are alpha by series id
var nullNames = book.SeriesLink
.Where(s => string.IsNullOrWhiteSpace(s.Series.Name))
.Select(s => s.Series.AudibleSeriesId)
.OrderBy(a => a)
.ToList();
var all = withNames.Union(nullNames).ToList();
return string.Join(", ", all);
var all = withNames.Union(nullNames).ToList();
return string.Join(", ", all);
string getSeriesNameString(SeriesBook sb)
=> includeIndex && !string.IsNullOrWhiteSpace(sb.Order) && sb.Order != "-1"
? $"{sb.Series.Name} (#{sb.Order})"
: sb.Series.Name;
string getSeriesNameString(SeriesBook sb)
=> includeIndex && !string.IsNullOrWhiteSpace(sb.Order) && sb.Order != "-1"
? $"{sb.Series.Name} (#{sb.Order})"
: sb.Series.Name;
}
public string[] LowestCategoryNames()
=> book.CategoriesLink?.Any() is not true ? Array.Empty<string>()
: book
.CategoriesLink
.Select(cl => cl.CategoryLadder.Categories.LastOrDefault()?.Name)
.Where(c => c is not null)
.Distinct()
.ToArray();
public string[] AllCategoryNames()
=> book.CategoriesLink?.Any() is not true ? Array.Empty<string>()
: book
.CategoriesLink
.SelectMany(cl => cl.CategoryLadder.Categories)
.Select(c => c.Name)
.ToArray();
public string[] AllCategoryIds()
=> book.CategoriesLink?.Any() is not true ? null
: book
.CategoriesLink
.SelectMany(cl => cl.CategoryLadder.Categories)
.Select(c => c.AudibleCategoryId)
.ToArray();
}
public static string[] LowestCategoryNames(this Book book)
=> book.CategoriesLink?.Any() is not true ? Array.Empty<string>()
: book
.CategoriesLink
.Select(cl => cl.CategoryLadder.Categories.LastOrDefault()?.Name)
.Where(c => c is not null)
.Distinct()
.ToArray();
public static string[] AllCategoryNames(this Book book)
=> book.CategoriesLink?.Any() is not true ? Array.Empty<string>()
: book
.CategoriesLink
.SelectMany(cl => cl.CategoryLadder.Categories)
.Select(c => c.Name)
.ToArray();
public static string[] AllCategoryIds(this Book book)
=> book.CategoriesLink?.Any() is not true ? null
: book
.CategoriesLink
.SelectMany(cl => cl.CategoryLadder.Categories)
.Select(c => c.AudibleCategoryId)
.ToArray();
public static string AggregateTitles(this IEnumerable<LibraryBook> libraryBooks, int max = 5)
{
@@ -93,7 +98,7 @@ namespace DataLayer
return titlesAgg;
}
public static float FirstScore(this Rating rating)
public static float FirstScore(this Rating rating)
=> rating.OverallRating > 0 ? rating.OverallRating
: rating.PerformanceRating > 0 ? rating.PerformanceRating
: rating.StoryRating;

View File

@@ -28,7 +28,7 @@ namespace FileLiberator
/// </summary>
public DownloadOptions.LicenseInfo? LicenseInfo { get; set; }
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists();
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.AudioExists;
public override async Task CancelAsync()
{
if (abDownloader is not null) await abDownloader.CancelAsync();
@@ -43,7 +43,7 @@ namespace FileLiberator
try
{
if (libraryBook.Book.Audio_Exists())
if (libraryBook.Book.AudioExists)
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
DownloadValidation(libraryBook);

View File

@@ -18,7 +18,7 @@ namespace FileLiberator
public override string Name => "Download Pdf";
public override bool Validate(LibraryBook libraryBook)
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
&& !libraryBook.Book.PDF_Exists();
&& !libraryBook.Book.PdfExists;
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{

View File

@@ -115,16 +115,16 @@ namespace LibationAvalonia.Dialogs
var title = string.IsNullOrEmpty(Book.Subtitle) ? Book.Title : $"{Book.Title}\r\n {Book.Subtitle}";
//init book details
DetailsText = @$"
Title: {title}
Author(s): {Book.AuthorNames()}
Narrator(s): {Book.NarratorNames()}
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
Category: {string.Join(", ", Book.LowestCategoryNames())}
Purchase Date: {libraryBook.DateAdded:d}
Language: {Book.Language}
Audible ID: {Book.AudibleProductId}
".Trim();
DetailsText = $"""
Title: {title}
Author(s): {Book.AuthorNames}
Narrator(s): {Book.NarratorNames}
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
Category: {string.Join(", ", Book.LowestCategoryNames())}
Purchase Date: {libraryBook.DateAdded:d}
Language: {Book.Language}
Audible ID: {Book.AudibleProductId}
""";
var seriesNames = libraryBook.Book.SeriesNames();
if (!string.IsNullOrWhiteSpace(seriesNames))

View File

@@ -15,7 +15,6 @@ namespace LibationAvalonia.ViewModels
public partial class MainVM
{
private int _numAccountsScanning = 2;
private int _accountsCount = 0;
public string LocateAudiobooksTip => Configuration.GetHelpText("LocateAudiobooks");
/// <summary> Auto scanning accounts is enables </summary>

View File

@@ -38,7 +38,7 @@ namespace LibationAvalonia.ViewModels
{
if (ProcessQueue.QueueDownloadDecrypt(libraryBooks))
setQueueCollapseState(false);
else if (libraryBooks.Length == 1 && libraryBooks[0].Book.Audio_Exists())
else if (libraryBooks.Length == 1 && libraryBooks[0].Book.AudioExists)
{
// liberated: open explorer to file
var filePath = AudibleFileStorage.Audio.GetPath(libraryBooks[0].Book.AudibleProductId);

View File

@@ -327,7 +327,7 @@ namespace LibationAvalonia.Views
{
//No need to persist these changes. They only needs to last long for the files to start downloading
entry4.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
if (entry4.Book.HasPdf())
if (entry4.Book.HasPdf)
entry4.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
LiberateClicked?.Invoke(this, [entry4.LibraryBook]);
})

View File

@@ -37,8 +37,8 @@ namespace LibationSearchEngine
{ FieldType.ID, lb => lb.Book.AudibleProductId.ToLowerInvariant(), nameof(Book.AudibleProductId), "ProductId", "Id", "ASIN" },
{ FieldType.Raw, lb => lb.Book.AudibleProductId, _ID_ },
{ FieldType.String, lb => lb.Book.TitleWithSubtitle, "Title", "ProductId", "Id", "ASIN" },
{ FieldType.String, lb => lb.Book.AuthorNames(), "AuthorNames", "Author", "Authors" },
{ FieldType.String, lb => lb.Book.NarratorNames(), "NarratorNames", "Narrator", "Narrators" },
{ FieldType.String, lb => lb.Book.AuthorNames, "AuthorNames", "Author", "Authors" },
{ FieldType.String, lb => lb.Book.NarratorNames, "NarratorNames", "Narrator", "Narrators" },
{ FieldType.String, lb => lb.Book.Publisher, nameof(Book.Publisher) },
{ FieldType.String, lb => lb.Book.SeriesNames(), "SeriesNames", "Narrator", "Series" },
{ FieldType.String, lb => string.Join(", ", lb.Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)), "SeriesId" },
@@ -48,7 +48,7 @@ namespace LibationSearchEngine
{ FieldType.String, lb => lb.Book.Locale, "Locale", "Region" },
{ FieldType.String, lb => lb.Account, "Account", "Email" },
{ FieldType.String, lb => lb.Book.UserDefinedItem.LastDownloadedFormat?.CodecString, "Codec", "DownloadedCodec" },
{ FieldType.Bool, lb => lb.Book.HasPdf().ToString(), "HasDownloads", "HasDownload", "Downloads" , "Download", "HasPDFs", "HasPDF" , "PDFs", "PDF" },
{ FieldType.Bool, lb => lb.Book.HasPdf.ToString(), "HasDownloads", "HasDownload", "Downloads" , "Download", "HasPDFs", "HasPDF" , "PDFs", "PDF" },
{ FieldType.Bool, lb => (lb.Book.UserDefinedItem.Rating.OverallRating > 0f).ToString(), "IsRated", "Rated" },
{ FieldType.Bool, lb => isAuthorNarrated(lb.Book).ToString(), "IsAuthorNarrated", "AuthorNarrated" },
{ FieldType.Bool, lb => lb.Book.IsAbridged.ToString(), nameof(Book.IsAbridged), "Abridged" },

View File

@@ -60,7 +60,7 @@ public class GridContextMenu
.UpdateUserDefinedItemAsync(udi =>
{
udi.BookStatus = LiberatedStatus.Liberated;
if (udi.Book.HasPdf())
if (udi.Book.HasPdf)
udi.SetPdfStatus(LiberatedStatus.Liberated);
});
}
@@ -71,7 +71,7 @@ public class GridContextMenu
.UpdateUserDefinedItemAsync(udi =>
{
udi.BookStatus = LiberatedStatus.NotLiberated;
if (udi.Book.HasPdf())
if (udi.Book.HasPdf)
udi.SetPdfStatus(LiberatedStatus.NotLiberated);
});
}

View File

@@ -98,8 +98,8 @@ namespace LibationUiBase.GridView
RaiseAndSetIfChanged(ref _myRating, Book.UserDefinedItem.Rating, nameof(MyRating));
PurchaseDate = GetPurchaseDateString();
ProductRating = Book.Rating ?? new Rating(0, 0, 0);
Authors = Book.AuthorNames();
Narrators = Book.NarratorNames();
Authors = Book.AuthorNames;
Narrators = Book.NarratorNames;
Category = string.Join(", ", Book.LowestCategoryNames());
Misc = GetMiscDisplay(libraryBook);
LastDownload = new(Book.UserDefinedItem);
@@ -306,7 +306,7 @@ namespace LibationUiBase.GridView
details.Add($"Account: {locale} - {acct}");
if (libraryBook.Book.HasPdf())
if (libraryBook.Book.HasPdf)
details.Add("Has PDF");
if (libraryBook.Book.IsAbridged)
details.Add("Abridged");

View File

@@ -100,8 +100,8 @@ public class ProcessBookViewModel : ReactiveObject
LibraryBook = libraryBook;
Title = LibraryBook.Book.TitleWithSubtitle;
Author = LibraryBook.Book.AuthorNames();
Narrator = LibraryBook.Book.NarratorNames();
Author = LibraryBook.Book.AuthorNames;
Narrator = LibraryBook.Book.NarratorNames;
(bool isDefault, byte[] picture) = PictureStorage.GetPicture(new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._80x80));
@@ -296,8 +296,8 @@ public class ProcessBookViewModel : ReactiveObject
LogInfo($"{Environment.NewLine}{processable.Name} Step, Begin: {libraryBook.Book}");
Title = libraryBook.Book.TitleWithSubtitle;
Author = libraryBook.Book.AuthorNames();
Narrator = libraryBook.Book.NarratorNames();
Author = libraryBook.Book.AuthorNames;
Narrator = libraryBook.Book.NarratorNames;
}
private async void Processable_Completed(object? sender, LibraryBook libraryBook)
@@ -375,8 +375,8 @@ public class ProcessBookViewModel : ReactiveObject
details = $"""
Title: {libraryBook.Book.TitleWithSubtitle}
ID: {libraryBook.Book.AudibleProductId}
Author: {trunc(libraryBook.Book.AuthorNames())}
Narr: {trunc(libraryBook.Book.NarratorNames())}
Author: {trunc(libraryBook.Book.AuthorNames)}
Narr: {trunc(libraryBook.Book.NarratorNames)}
""";
}
catch

View File

@@ -45,16 +45,16 @@ namespace LibationWinForms.Dialogs
this.coverPb.Image = WinFormsUtil.TryLoadImageOrDefault(picture, PictureSize._80x80);
var title = string.IsNullOrEmpty(Book.Subtitle) ? Book.Title : $"{Book.Title}\r\n {Book.Subtitle}";
var t = @$"
Title: {title}
Author(s): {Book.AuthorNames()}
Narrator(s): {Book.NarratorNames()}
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
Category: {string.Join(", ", Book.LowestCategoryNames())}
Purchase Date: {_libraryBook.DateAdded:d}
Language: {Book.Language}
Audible ID: {Book.AudibleProductId}
".Trim();
var t = $"""
Title: {title}
Author(s): {Book.AuthorNames}
Narrator(s): {Book.NarratorNames}
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
Category: {string.Join(", ", Book.LowestCategoryNames())}
Purchase Date: {_libraryBook.DateAdded:d}
Language: {Book.Language}
Audible ID: {Book.AudibleProductId}
""";
var seriesNames = Book.SeriesNames();
if (!string.IsNullOrWhiteSpace(seriesNames))

View File

@@ -31,7 +31,7 @@ namespace LibationWinForms
{
if (processBookQueue1.ViewModel.QueueDownloadDecrypt(libraryBooks))
SetQueueCollapseState(false);
else if (libraryBooks.Length == 1 && libraryBooks[0].Book.Audio_Exists())
else if (libraryBooks.Length == 1 && libraryBooks[0].Book.AudioExists)
{
// liberated: open explorer to file
var filePath = AudibleFileStorage.Audio.GetPath(libraryBooks[0].Book.AudibleProductId);

View File

@@ -219,7 +219,7 @@ namespace LibationWinForms.GridView
{
//No need to persist these changes. They only needs to last long for the files to start downloading
entry4.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
if (entry4.Book.HasPdf())
if (entry4.Book.HasPdf)
entry4.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
LiberateClicked?.Invoke(s, [entry4.LibraryBook]);