From 21955744229ed3b064ec49b9560f849932b3ac1b Mon Sep 17 00:00:00 2001 From: Alanoll Date: Mon, 26 Jun 2023 12:18:15 -0500 Subject: [PATCH] feat: add Book subtitle capturing so TitleShort reflects titles better --- Source/DataLayer/EfClasses/Book.cs | 6 +- ...20230626171442_AddBookSubtitle.Designer.cs | 416 ++++++++++++++++++ .../20230626171442_AddBookSubtitle.cs | 28 ++ .../LibationContextModelSnapshot.cs | 5 +- Source/DtoImporterService/BookImporter.cs | 7 +- Source/FileLiberator/UtilityExtensions.cs | 1 + Source/LibationFileManager/LibraryBookDto.cs | 17 +- .../LibationFileManager/TemplateEditor[T].cs | 3 +- Source/LibationFileManager/Templates.cs | 8 +- 9 files changed, 482 insertions(+), 9 deletions(-) create mode 100644 Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.Designer.cs create mode 100644 Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.cs diff --git a/Source/DataLayer/EfClasses/Book.cs b/Source/DataLayer/EfClasses/Book.cs index 8b253411..47c2442b 100644 --- a/Source/DataLayer/EfClasses/Book.cs +++ b/Source/DataLayer/EfClasses/Book.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Dinah.Core; using Microsoft.EntityFrameworkCore; @@ -33,7 +34,8 @@ namespace DataLayer // immutable public string AudibleProductId { get; private set; } - public string Title { get; private set; } + public string Title { get; set; } + public string Subtitle { get; set; } public string Description { get; private set; } public int LengthInMinutes { get; private set; } public ContentType ContentType { get; private set; } @@ -70,6 +72,7 @@ namespace DataLayer public Book( AudibleProductId audibleProductId, string title, + string subtitle, string description, int lengthInMinutes, ContentType contentType, @@ -99,6 +102,7 @@ namespace DataLayer // simple assigns Title = title.Trim() ?? ""; + Subtitle = subtitle?.Trim() ?? ""; Description = description?.Trim() ?? ""; LengthInMinutes = lengthInMinutes; ContentType = contentType; diff --git a/Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.Designer.cs b/Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.Designer.cs new file mode 100644 index 00000000..911c0b20 --- /dev/null +++ b/Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.Designer.cs @@ -0,0 +1,416 @@ +// +using System; +using DataLayer; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DataLayer.Migrations +{ + [DbContext(typeof(LibationContext))] + [Migration("20230626171442_AddBookSubtitle")] + partial class AddBookSubtitle + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.5"); + + modelBuilder.Entity("DataLayer.Book", b => + { + b.Property("BookId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudibleProductId") + .HasColumnType("TEXT"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("ContentType") + .HasColumnType("INTEGER"); + + b.Property("DatePublished") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsAbridged") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LengthInMinutes") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .HasColumnType("TEXT"); + + b.Property("PictureId") + .HasColumnType("TEXT"); + + b.Property("PictureLarge") + .HasColumnType("TEXT"); + + b.Property("Subtitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("_audioFormat") + .HasColumnType("INTEGER"); + + b.HasKey("BookId"); + + b.HasIndex("AudibleProductId"); + + b.HasIndex("CategoryId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("DataLayer.BookContributor", b => + { + b.Property("BookId") + .HasColumnType("INTEGER"); + + b.Property("ContributorId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.HasKey("BookId", "ContributorId", "Role"); + + b.HasIndex("BookId"); + + b.HasIndex("ContributorId"); + + b.ToTable("BookContributor"); + }); + + modelBuilder.Entity("DataLayer.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudibleCategoryId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ParentCategoryCategoryId") + .HasColumnType("INTEGER"); + + b.HasKey("CategoryId"); + + b.HasIndex("AudibleCategoryId"); + + b.HasIndex("ParentCategoryCategoryId"); + + b.ToTable("Categories"); + + b.HasData( + new + { + CategoryId = -1, + AudibleCategoryId = "", + Name = "" + }); + }); + + modelBuilder.Entity("DataLayer.Contributor", b => + { + b.Property("ContributorId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudibleContributorId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("ContributorId"); + + b.HasIndex("Name"); + + b.ToTable("Contributors"); + + b.HasData( + new + { + ContributorId = -1, + Name = "" + }); + }); + + modelBuilder.Entity("DataLayer.LibraryBook", b => + { + b.Property("BookId") + .HasColumnType("INTEGER"); + + b.Property("AbsentFromLastScan") + .HasColumnType("INTEGER"); + + b.Property("Account") + .HasColumnType("TEXT"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.HasKey("BookId"); + + b.ToTable("LibraryBooks"); + }); + + modelBuilder.Entity("DataLayer.Series", b => + { + b.Property("SeriesId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudibleSeriesId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("SeriesId"); + + b.HasIndex("AudibleSeriesId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("DataLayer.SeriesBook", b => + { + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("BookId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("TEXT"); + + b.HasKey("SeriesId", "BookId"); + + b.HasIndex("BookId"); + + b.HasIndex("SeriesId"); + + b.ToTable("SeriesBook"); + }); + + modelBuilder.Entity("DataLayer.Book", b => + { + b.HasOne("DataLayer.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("DataLayer.Rating", "Rating", b1 => + { + b1.Property("BookId") + .HasColumnType("INTEGER"); + + b1.Property("OverallRating") + .HasColumnType("REAL"); + + b1.Property("PerformanceRating") + .HasColumnType("REAL"); + + b1.Property("StoryRating") + .HasColumnType("REAL"); + + b1.HasKey("BookId"); + + b1.ToTable("Books"); + + b1.WithOwner() + .HasForeignKey("BookId"); + }); + + b.OwnsMany("DataLayer.Supplement", "Supplements", b1 => + { + b1.Property("SupplementId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("BookId") + .HasColumnType("INTEGER"); + + b1.Property("Url") + .HasColumnType("TEXT"); + + b1.HasKey("SupplementId"); + + b1.HasIndex("BookId"); + + b1.ToTable("Supplement"); + + b1.WithOwner("Book") + .HasForeignKey("BookId"); + + b1.Navigation("Book"); + }); + + b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 => + { + b1.Property("BookId") + .HasColumnType("INTEGER"); + + b1.Property("BookStatus") + .HasColumnType("INTEGER"); + + b1.Property("LastDownloaded") + .HasColumnType("TEXT"); + + b1.Property("LastDownloadedVersion") + .HasColumnType("TEXT"); + + b1.Property("PdfStatus") + .HasColumnType("INTEGER"); + + b1.Property("Tags") + .HasColumnType("TEXT"); + + b1.HasKey("BookId"); + + b1.ToTable("UserDefinedItem", (string)null); + + b1.WithOwner("Book") + .HasForeignKey("BookId"); + + b1.OwnsOne("DataLayer.Rating", "Rating", b2 => + { + b2.Property("UserDefinedItemBookId") + .HasColumnType("INTEGER"); + + b2.Property("OverallRating") + .HasColumnType("REAL"); + + b2.Property("PerformanceRating") + .HasColumnType("REAL"); + + b2.Property("StoryRating") + .HasColumnType("REAL"); + + b2.HasKey("UserDefinedItemBookId"); + + b2.ToTable("UserDefinedItem"); + + b2.WithOwner() + .HasForeignKey("UserDefinedItemBookId"); + }); + + b1.Navigation("Book"); + + b1.Navigation("Rating"); + }); + + b.Navigation("Category"); + + b.Navigation("Rating"); + + b.Navigation("Supplements"); + + b.Navigation("UserDefinedItem"); + }); + + modelBuilder.Entity("DataLayer.BookContributor", b => + { + b.HasOne("DataLayer.Book", "Book") + .WithMany("ContributorsLink") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DataLayer.Contributor", "Contributor") + .WithMany("BooksLink") + .HasForeignKey("ContributorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Book"); + + b.Navigation("Contributor"); + }); + + modelBuilder.Entity("DataLayer.Category", b => + { + b.HasOne("DataLayer.Category", "ParentCategory") + .WithMany() + .HasForeignKey("ParentCategoryCategoryId"); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("DataLayer.LibraryBook", b => + { + b.HasOne("DataLayer.Book", "Book") + .WithOne() + .HasForeignKey("DataLayer.LibraryBook", "BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Book"); + }); + + modelBuilder.Entity("DataLayer.SeriesBook", b => + { + b.HasOne("DataLayer.Book", "Book") + .WithMany("SeriesLink") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DataLayer.Series", "Series") + .WithMany("BooksLink") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Book"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("DataLayer.Book", b => + { + b.Navigation("ContributorsLink"); + + b.Navigation("SeriesLink"); + }); + + modelBuilder.Entity("DataLayer.Contributor", b => + { + b.Navigation("BooksLink"); + }); + + modelBuilder.Entity("DataLayer.Series", b => + { + b.Navigation("BooksLink"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.cs b/Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.cs new file mode 100644 index 00000000..bd815674 --- /dev/null +++ b/Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DataLayer.Migrations +{ + /// + public partial class AddBookSubtitle : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Subtitle", + table: "Books", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Subtitle", + table: "Books"); + } + } +} diff --git a/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs b/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs index 2a0a1869..65d792b1 100644 --- a/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs +++ b/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace DataLayer.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); + modelBuilder.HasAnnotation("ProductVersion", "7.0.5"); modelBuilder.Entity("DataLayer.Book", b => { @@ -56,6 +56,9 @@ namespace DataLayer.Migrations b.Property("PictureLarge") .HasColumnType("TEXT"); + b.Property("Subtitle") + .HasColumnType("TEXT"); + b.Property("Title") .HasColumnType("TEXT"); diff --git a/Source/DtoImporterService/BookImporter.cs b/Source/DtoImporterService/BookImporter.cs index 700ad7d4..2cb3b875 100644 --- a/Source/DtoImporterService/BookImporter.cs +++ b/Source/DtoImporterService/BookImporter.cs @@ -118,7 +118,8 @@ namespace DtoImporterService { book = DbContext.Books.Add(new Book( new AudibleProductId(item.ProductId), - item.TitleWithSubtitle, + item.Title, + item.Subtitle, item.Description, item.LengthInMinutes, contentType, @@ -164,6 +165,10 @@ namespace DtoImporterService { var item = importItem.DtoItem; + // Update the book titles, since formatting can change + book.Title = item.Title; + book.Subtitle = item.Subtitle; + var codec = item.AvailableCodecs?.Max(f => AudioFormat.FromString(f.EnhancedCodec)) ?? new AudioFormat(); book.AudioFormat = codec; diff --git a/Source/FileLiberator/UtilityExtensions.cs b/Source/FileLiberator/UtilityExtensions.cs index 358cdd60..14e818f9 100644 --- a/Source/FileLiberator/UtilityExtensions.cs +++ b/Source/FileLiberator/UtilityExtensions.cs @@ -41,6 +41,7 @@ namespace FileLiberator AudibleProductId = libraryBook.Book.AudibleProductId, Title = libraryBook.Book.Title ?? "", + Subtitle = libraryBook.Book.Subtitle ?? "", Locale = libraryBook.Book.Locale, YearPublished = libraryBook.Book.DatePublished?.Year, DatePublished = libraryBook.Book.DatePublished, diff --git a/Source/LibationFileManager/LibraryBookDto.cs b/Source/LibationFileManager/LibraryBookDto.cs index a4e7456e..cb3a022f 100644 --- a/Source/LibationFileManager/LibraryBookDto.cs +++ b/Source/LibationFileManager/LibraryBookDto.cs @@ -8,7 +8,22 @@ namespace LibationFileManager { public string AudibleProductId { get; set; } public string Title { get; set; } - public string Locale { get; set; } + public string Subtitle { get; set; } + public string TitleWithSubtitle + { + get + { + string text = Title?.Trim(); + string text2 = Subtitle?.Trim(); + if (string.IsNullOrWhiteSpace(text2)) + { + return text; + } + + return text + ": " + text2; + } + } + public string Locale { get; set; } public int? YearPublished { get; set; } public IEnumerable Authors { get; set; } diff --git a/Source/LibationFileManager/TemplateEditor[T].cs b/Source/LibationFileManager/TemplateEditor[T].cs index 5ee16095..e3d68dc1 100644 --- a/Source/LibationFileManager/TemplateEditor[T].cs +++ b/Source/LibationFileManager/TemplateEditor[T].cs @@ -57,7 +57,8 @@ namespace LibationFileManager DateAdded = new DateTime(2022, 6, 9, 0, 0, 0), DatePublished = new DateTime(2017, 2, 27, 0, 0, 0), AudibleProductId = "123456789", - Title = "A Study in Scarlet: A Sherlock Holmes Novel", + Title = "A Study in Scarlet", + Subtitle = "A Sherlock Holmes Novel", Locale = "us", YearPublished = 2017, Authors = new List { "Arthur Conan Doyle", "Stephen Fry - introductions" }, diff --git a/Source/LibationFileManager/Templates.cs b/Source/LibationFileManager/Templates.cs index 97146032..6b8be956 100644 --- a/Source/LibationFileManager/Templates.cs +++ b/Source/LibationFileManager/Templates.cs @@ -247,8 +247,8 @@ namespace LibationFileManager { //Don't allow formatting of Id { TemplateTags.Id, lb => lb.AudibleProductId, v => v }, - { TemplateTags.Title, lb => lb.Title }, - { TemplateTags.TitleShort, lb => getTitleShort(lb.Title) }, + { TemplateTags.Title, lb => lb.TitleWithSubtitle }, + { TemplateTags.TitleShort, lb => lb.Title }, { TemplateTags.Author, lb => lb.Authors, NameListFormat.Formatter }, { TemplateTags.FirstAuthor, lb => lb.FirstAuthor }, { TemplateTags.Narrator, lb => lb.Narrators, NameListFormat.Formatter }, @@ -274,8 +274,8 @@ namespace LibationFileManager { new PropertyTagCollection(caseSensative: true, StringFormatter) { - { TemplateTags.Title, lb => lb.Title }, - { TemplateTags.TitleShort, lb => getTitleShort(lb.Title) }, + { TemplateTags.Title, lb => lb.TitleWithSubtitle }, + { TemplateTags.TitleShort, lb => lb.Title }, { TemplateTags.Series, lb => lb.SeriesName }, }, new PropertyTagCollection(caseSensative: true, StringFormatter, IntegerFormatter, DateTimeFormatter)