diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs index 50af6fa2..ee3b1565 100644 --- a/ApplicationServices/LibraryCommands.cs +++ b/ApplicationServices/LibraryCommands.cs @@ -41,7 +41,8 @@ namespace ApplicationServices // However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement: // https://github.com/RehanSaeed/Serilog.Exceptions // work-around: use 3rd param. don't just put exception object in 3rd param -- info overload: stack trace, etc - Log.Logger.Error(lfEx, "Error importing library. Login failed. {@DebugInfo}", new { + Log.Logger.Error(lfEx, "Error importing library. Login failed. {@DebugInfo}", new + { lfEx.RequestUrl, ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, ResponseStatusCodeDesc = lfEx.ResponseStatusCode, @@ -100,10 +101,12 @@ namespace ApplicationServices return newCount; } - public static int UpdateTags(this LibationContext context, Book book, string newTags) + public static int UpdateTags(Book book, string newTags) { try { + using var context = DbContexts.GetContext(); + var udi = book.UserDefinedItem; // Attach() NoTracking entities before SaveChanges() @@ -122,5 +125,52 @@ namespace ApplicationServices throw; } } + + public static int UpdateBook(LibraryBook libraryBook, LiberatedStatus liberatedStatus, string finalAudioPath) + { + try + { + using var context = DbContexts.GetContext(); + + var udi = libraryBook.Book.UserDefinedItem; + + // Attach() NoTracking entities before SaveChanges() + udi.BookStatus = liberatedStatus; + udi.BookLocation = finalAudioPath; + context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified; + var qtyChanges = context.SaveChanges(); + if (qtyChanges > 0) + SearchEngineCommands.UpdateLiberatedStatus(libraryBook.Book); + + return qtyChanges; + } + catch (Exception ex) + { + Log.Logger.Error(ex, "Error updating tags"); + throw; + } + } + + public static int UpdatePdf(LibraryBook libraryBook, LiberatedStatus liberatedStatus) + { + try + { + using var context = DbContexts.GetContext(); + + var udi = libraryBook.Book.UserDefinedItem; + + // Attach() NoTracking entities before SaveChanges() + udi.PdfStatus = liberatedStatus; + context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified; + var qtyChanges = context.SaveChanges(); + + return qtyChanges; + } + catch (Exception ex) + { + Log.Logger.Error(ex, "Error updating tags"); + throw; + } + } } } diff --git a/DataLayer/EfClasses/Book.cs b/DataLayer/EfClasses/Book.cs index e1784e82..3a566870 100644 --- a/DataLayer/EfClasses/Book.cs +++ b/DataLayer/EfClasses/Book.cs @@ -220,8 +220,11 @@ namespace DataLayer ArgumentValidator.EnsureNotNullOrWhiteSpace(url, nameof(url)); - if (!_supplements.Any(s => url.EqualsInsensitive(url))) - _supplements.Add(new Supplement(this, url)); + if (_supplements.Any(s => url.EqualsInsensitive(url))) + return; + + _supplements.Add(new Supplement(this, url)); + UserDefinedItem.PdfStatus ??= LiberatedStatus.NotLiberated; } #endregion diff --git a/FileLiberator/BackupBook.cs b/FileLiberator/BackupBook.cs index 5a06ce73..8c42fb19 100644 --- a/FileLiberator/BackupBook.cs +++ b/FileLiberator/BackupBook.cs @@ -20,7 +20,7 @@ namespace FileLiberator public event EventHandler StatusUpdate; public event EventHandler Completed; - public DownloadDecryptBook DecryptBook { get; } = new DownloadDecryptBook(); + public DownloadDecryptBook DownloadDecryptBook { get; } = new DownloadDecryptBook(); public DownloadPdf DownloadPdf { get; } = new DownloadPdf(); public bool Validate(LibraryBook libraryBook) @@ -35,7 +35,7 @@ namespace FileLiberator try { { - var statusHandler = await DecryptBook.TryProcessAsync(libraryBook); + var statusHandler = await DownloadDecryptBook.TryProcessAsync(libraryBook); if (statusHandler.HasErrors) return statusHandler; } diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 57473342..e49ffbf5 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -1,15 +1,14 @@ -using DataLayer; -using Dinah.Core; -using Dinah.Core.ErrorHandling; -using FileManager; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using AaxDecrypter; using AudibleApi; +using DataLayer; +using Dinah.Core; +using Dinah.Core.ErrorHandling; +using FileManager; namespace FileLiberator { @@ -51,6 +50,11 @@ namespace FileLiberator if (!finalAudioExists) return new StatusHandler { "Cannot find final audio file after decryption" }; + // GetPath() is very cheap when file exists + var finalAudioPath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); + // only need to update if success. if failure, it will remain at 0 == NotLiberated + ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Liberated, finalAudioPath); + return new StatusHandler(); } finally diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs index d16c86d5..6e3e9558 100644 --- a/FileLiberator/DownloadPdf.cs +++ b/FileLiberator/DownloadPdf.cs @@ -1,4 +1,6 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -19,7 +21,12 @@ namespace FileLiberator { var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook); await downloadPdfAsync(libraryBook, proposedDownloadFilePath); - return verifyDownload(libraryBook); + var result = verifyDownload(libraryBook); + + var liberatedStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated; + ApplicationServices.LibraryCommands.UpdatePdf(libraryBook, liberatedStatus); + + return result; } private static string getProposedDownloadFilePath(LibraryBook libraryBook) diff --git a/FileLiberator/IProcessableExt.cs b/FileLiberator/IProcessableExt.cs index d5e46596..af4e33cb 100644 --- a/FileLiberator/IProcessableExt.cs +++ b/FileLiberator/IProcessableExt.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using ApplicationServices; diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 3c7026a1..0023cc5a 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -86,7 +86,6 @@ namespace LibationWinForms.BookLiberation var convertBook = new ConvertToMp3(); convertBook.Begin += (_, l) => wireUpEvents(convertBook, l, "Converting"); - convertBook.Completed += updateLiberatedStatus; var automatedBackupsForm = new AutomatedBackupsForm(); @@ -104,36 +103,24 @@ namespace LibationWinForms.BookLiberation convertBook.Begin -= convertBookBegin; convertBook.StatusUpdate -= statusUpdate; convertBook.Completed -= convertBookCompleted; - convertBook.Completed -= updateLiberatedStatus; } private static BackupBook getWiredUpBackupBook(EventHandler completedAction) { var backupBook = new BackupBook(); - backupBook.DecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DecryptBook, l); + backupBook.DownloadDecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DownloadDecryptBook, l); backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf); - // must occur before completedAction. A common use case is: - // - filter by -liberated - // - liberate only that book - // 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 += updateLiberatedStatus; - backupBook.DownloadPdf.Completed += updateLiberatedStatus; - if (completedAction != null) { - backupBook.DecryptBook.Completed += completedAction; + backupBook.DownloadDecryptBook.Completed += completedAction; backupBook.DownloadPdf.Completed += completedAction; } return backupBook; } - private static void updateLiberatedStatus(object sender, LibraryBook e) => ApplicationServices.SearchEngineCommands.UpdateLiberatedStatus(e.Book); - private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) { #region create logger @@ -151,9 +138,9 @@ namespace LibationWinForms.BookLiberation #endregion #region subscribe new form to model's events - backupBook.DecryptBook.Begin += decryptBookBegin; - backupBook.DecryptBook.StatusUpdate += statusUpdate; - backupBook.DecryptBook.Completed += decryptBookCompleted; + backupBook.DownloadDecryptBook.Begin += decryptBookBegin; + backupBook.DownloadDecryptBook.StatusUpdate += statusUpdate; + backupBook.DownloadDecryptBook.Completed += decryptBookCompleted; backupBook.DownloadPdf.Begin += downloadPdfBegin; backupBook.DownloadPdf.StatusUpdate += statusUpdate; backupBook.DownloadPdf.Completed += downloadPdfCompleted; @@ -163,9 +150,9 @@ namespace LibationWinForms.BookLiberation // unsubscribe so disposed forms aren't still trying to receive notifications Action unsubscribe = () => { - backupBook.DecryptBook.Begin -= decryptBookBegin; - backupBook.DecryptBook.StatusUpdate -= statusUpdate; - backupBook.DecryptBook.Completed -= decryptBookCompleted; + backupBook.DownloadDecryptBook.Begin -= decryptBookBegin; + backupBook.DownloadDecryptBook.StatusUpdate -= statusUpdate; + backupBook.DownloadDecryptBook.Completed -= decryptBookCompleted; backupBook.DownloadPdf.Begin -= downloadPdfBegin; backupBook.DownloadPdf.StatusUpdate -= statusUpdate; backupBook.DownloadPdf.Completed -= downloadPdfCompleted; @@ -497,6 +484,7 @@ $@" Title: {libraryBook.Book.Title} if (dialogResult == CreateSkipFileResult) { + ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error, null); var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage); LogMe.Info($@" Created new 'skip' file diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 89e27cbe..ad264170 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -36,8 +36,6 @@ namespace LibationWinForms // alias private DataGridView dataGridView => gridEntryDataGridView; - private LibationContext context; - public ProductsGrid() { InitializeComponent(); @@ -45,7 +43,6 @@ namespace LibationWinForms addLiberateButtons(); addEditTagsButtons(); formatColumns(); - Disposed += (_, __) => context?.Dispose(); manageLiveImageUpdateSubscriptions(); } @@ -250,7 +247,7 @@ namespace LibationWinForms if (editTagsForm.ShowDialog() != DialogResult.OK) return; - var qtyChanges = context.UpdateTags(liveGridEntry.GetBook(), editTagsForm.NewTags); + var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.GetBook(), editTagsForm.NewTags); if (qtyChanges == 0) return; @@ -337,7 +334,7 @@ namespace LibationWinForms // // transform into sorted GridEntry.s BEFORE binding // - context = DbContexts.GetContext(); + using var context = DbContexts.GetContext(); var lib = context.GetLibrary_Flat_NoTracking(); // if no data. hide all columns. return