diff --git a/ApplicationServices/SearchEngineCommands.cs b/ApplicationServices/SearchEngineCommands.cs index deb99bec..183dd266 100644 --- a/ApplicationServices/SearchEngineCommands.cs +++ b/ApplicationServices/SearchEngineCommands.cs @@ -21,8 +21,8 @@ namespace ApplicationServices e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags) ); - public static void UpdateIsLiberated(Book book) => performSearchEngineAction_safe(e => - e.UpdateIsLiberated(book.AudibleProductId) + public static void UpdateLiberatedStatus(Book book) => performSearchEngineAction_safe(e => + e.UpdateLiberatedStatus(book) ); private static void performSearchEngineAction_safe(Action action) diff --git a/DataLayer/EfClasses/BookContributor.cs b/DataLayer/EfClasses/BookContributor.cs index 1c9c359a..0d3a2764 100644 --- a/DataLayer/EfClasses/BookContributor.cs +++ b/DataLayer/EfClasses/BookContributor.cs @@ -2,6 +2,8 @@ namespace DataLayer { + public enum Role { Author = 1, Narrator = 2, Publisher = 3 } + public class BookContributor { internal int BookId { get; private set; } diff --git a/DataLayer/EfClasses/Role.cs b/DataLayer/EfClasses/Role.cs deleted file mode 100644 index 3dd8f4a0..00000000 --- a/DataLayer/EfClasses/Role.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace DataLayer -{ - public enum Role { Author = 1, Narrator = 2, Publisher = 3 } -} diff --git a/DataLayer/EfClasses/UserDefinedItem.cs b/DataLayer/EfClasses/UserDefinedItem.cs index 5d7234ef..97292fe5 100644 --- a/DataLayer/EfClasses/UserDefinedItem.cs +++ b/DataLayer/EfClasses/UserDefinedItem.cs @@ -6,6 +6,17 @@ using Dinah.Core; namespace DataLayer { + /// + /// Do not track in-process state. In-process state is determined by the presence of temp file. + /// + public enum LiberatedStatus + { + NotLiberated = 0, + Liberated = 1, + /// Error occurred during liberation. Don't retry + Error = 2 + } + public class UserDefinedItem { internal int BookId { get; private set; } @@ -22,6 +33,7 @@ namespace DataLayer Tags = FileManager.TagsPersistence.GetTags(book.AudibleProductId); } + #region Tags private string _tags = ""; public string Tags { @@ -71,14 +83,23 @@ namespace DataLayer return string.Join(" ", unique); } #endregion + #endregion + #region Rating // owned: not an optional one-to-one /// The user's individual book rating public Rating Rating { get; private set; } = new Rating(0, 0, 0); public void UpdateRating(float overallRating, float performanceRating, float storyRating) => Rating.Update(overallRating, performanceRating, storyRating); + #endregion - public override string ToString() => $"{Book} {Rating} {Tags}"; + #region LiberatedStatuses and book file location + public LiberatedStatus BookStatus { get; set; } + public string BookLocation { get; set; } + public LiberatedStatus? PdfStatus { get; set; } + #endregion + + public override string ToString() => $"{Book} {Rating} {Tags}"; } } diff --git a/DataLayer/Migrations/20210727180408_AddLiberatedStatus.Designer.cs b/DataLayer/Migrations/20210727180408_AddLiberatedStatus.Designer.cs new file mode 100644 index 00000000..c4f0a10c --- /dev/null +++ b/DataLayer/Migrations/20210727180408_AddLiberatedStatus.Designer.cs @@ -0,0 +1,390 @@ +// +using System; +using DataLayer; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace DataLayer.Migrations +{ + [DbContext(typeof(LibationContext))] + [Migration("20210727180408_AddLiberatedStatus")] + partial class AddLiberatedStatus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.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("DatePublished") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsAbridged") + .HasColumnType("INTEGER"); + + b.Property("LengthInMinutes") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .HasColumnType("TEXT"); + + b.Property("PictureId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + 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("Account") + .HasColumnType("TEXT"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.HasKey("BookId"); + + b.ToTable("Library"); + }); + + 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("Index") + .HasColumnType("REAL"); + + 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("BookLocation") + .HasColumnType("TEXT"); + + b1.Property("BookStatus") + .HasColumnType("INTEGER"); + + b1.Property("PdfStatus") + .HasColumnType("INTEGER"); + + b1.Property("Tags") + .HasColumnType("TEXT"); + + b1.HasKey("BookId"); + + b1.ToTable("UserDefinedItem"); + + 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/DataLayer/Migrations/20210727180408_AddLiberatedStatus.cs b/DataLayer/Migrations/20210727180408_AddLiberatedStatus.cs new file mode 100644 index 00000000..68005c1f --- /dev/null +++ b/DataLayer/Migrations/20210727180408_AddLiberatedStatus.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace DataLayer.Migrations +{ + public partial class AddLiberatedStatus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BookLocation", + table: "UserDefinedItem", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "BookStatus", + table: "UserDefinedItem", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "PdfStatus", + table: "UserDefinedItem", + type: "INTEGER", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BookLocation", + table: "UserDefinedItem"); + + migrationBuilder.DropColumn( + name: "BookStatus", + table: "UserDefinedItem"); + + migrationBuilder.DropColumn( + name: "PdfStatus", + table: "UserDefinedItem"); + } + } +} diff --git a/DataLayer/Migrations/LibationContextModelSnapshot.cs b/DataLayer/Migrations/LibationContextModelSnapshot.cs index aa084c32..6ecd7a8a 100644 --- a/DataLayer/Migrations/LibationContextModelSnapshot.cs +++ b/DataLayer/Migrations/LibationContextModelSnapshot.cs @@ -253,6 +253,15 @@ namespace DataLayer.Migrations b1.Property("BookId") .HasColumnType("INTEGER"); + b1.Property("BookLocation") + .HasColumnType("TEXT"); + + b1.Property("BookStatus") + .HasColumnType("INTEGER"); + + b1.Property("PdfStatus") + .HasColumnType("INTEGER"); + b1.Property("Tags") .HasColumnType("TEXT"); diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 3fddfbc3..e7b3cdf9 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.0.1 + 5.4.0.5 diff --git a/LibationSearchEngine/SearchEngine.cs b/LibationSearchEngine/SearchEngine.cs index c2220b9f..a205b9e6 100644 --- a/LibationSearchEngine/SearchEngine.cs +++ b/LibationSearchEngine/SearchEngine.cs @@ -130,8 +130,9 @@ namespace LibationSearchEngine ["Abridged"] = lb => lb.Book.IsAbridged, // this will only be evaluated at time of re-index. ie: state of files moved later will be out of sync until next re-index - ["IsLiberated"] = lb => isLiberated(lb.Book.AudibleProductId), - ["Liberated"] = lb => isLiberated(lb.Book.AudibleProductId), + ["IsLiberated"] = lb => isLiberated(lb.Book), + ["Liberated"] = lb => isLiberated(lb.Book), + ["LiberatedError"] = lb => liberatedError(lb.Book), } ); @@ -142,7 +143,8 @@ namespace LibationSearchEngine return authors.Intersect(narrators).Any(); } - private static bool isLiberated(string id) => AudibleFileStorage.Audio.Exists(id); + private static bool isLiberated(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated || AudibleFileStorage.Audio.Exists(book.AudibleProductId); + private static bool liberatedError(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Error; // use these common fields in the "all" default search field private static IEnumerable> allFieldIndexRules { get; } @@ -300,18 +302,22 @@ namespace LibationSearchEngine }); // update single document entry - public void UpdateIsLiberated(string productId) + public void UpdateLiberatedStatus(Book book) => updateDocument( - productId, + book.AudibleProductId, d => { // fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY. // ie: must remove old before adding new else will create unwanted duplicates. - var v = isLiberated(productId); + var v1 = isLiberated(book); d.RemoveField("IsLiberated"); - d.AddBool("IsLiberated", v); + d.AddBool("IsLiberated", v1); d.RemoveField("Liberated"); - d.AddBool("Liberated", v); + d.AddBool("Liberated", v1); + + var v2 = liberatedError(book); + d.RemoveField("LiberatedError"); + d.AddBool("LiberatedError", v2); }); private static void updateDocument(string productId, Action action) diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 221f68fd..14e22bb9 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -93,8 +93,8 @@ namespace LibationWinForms.BookLiberation // completedAction is to refresh grid // - want to see that book disappear from grid // also for this to work, updateIsLiberated can NOT be async - backupBook.DecryptBook.Completed += updateIsLiberated; - backupBook.DownloadPdf.Completed += updateIsLiberated; + backupBook.DecryptBook.Completed += updateLiberatedStatus; + backupBook.DownloadPdf.Completed += updateLiberatedStatus; if (completedAction != null) { @@ -105,7 +105,7 @@ namespace LibationWinForms.BookLiberation return backupBook; } - private static void updateIsLiberated(object sender, LibraryBook e) => ApplicationServices.SearchEngineCommands.UpdateIsLiberated(e.Book); + private static void updateLiberatedStatus(object sender, LibraryBook e) => ApplicationServices.SearchEngineCommands.UpdateLiberatedStatus(e.Book); private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) {