diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs
index 96012f68..ed36595e 100644
--- a/ApplicationServices/LibraryCommands.cs
+++ b/ApplicationServices/LibraryCommands.cs
@@ -11,13 +11,6 @@ using Serilog;
namespace ApplicationServices
{
- // subtly different from DataLayer.LiberatedStatus
- // - DataLayer.LiberatedStatus: has no concept of partially downloaded
- // - ApplicationServices.LiberatedState: has no concept of Error/skipped
- public enum LiberatedState { NotDownloaded, PartialDownload, Liberated }
-
- public enum PdfState { NoPdf, Downloaded, NotDownloaded }
-
public static class LibraryCommands
{
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
@@ -162,94 +155,24 @@ namespace ApplicationServices
#endregion
#region Update book details
- public static int UpdateUserDefinedItem(Book book, string newTags, LiberatedStatus bookStatus, LiberatedStatus? pdfStatus)
+
+ public static int UpdateUserDefinedItem(Book book)
{
try
{
using var context = DbContexts.GetContext();
- var udi = book.UserDefinedItem;
-
- var tagsChanged = udi.Tags != newTags;
- var bookStatusChanged = udi.BookStatus != bookStatus;
- var pdfStatusChanged = udi.PdfStatus != pdfStatus;
-
- if (!tagsChanged && !bookStatusChanged && !pdfStatusChanged)
- return 0;
-
- udi.Tags = newTags;
- udi.BookStatus = bookStatus;
- udi.PdfStatus = pdfStatus;
-
// Attach() NoTracking entities before SaveChanges()
- context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
+ context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
var qtyChanges = context.SaveChanges();
-
- if (qtyChanges == 0)
- return 0;
-
- if (tagsChanged)
- SearchEngineCommands.UpdateBookTags(book);
- if (bookStatusChanged || pdfStatusChanged)
+ if (qtyChanges > 0)
SearchEngineCommands.UpdateLiberatedStatus(book);
return qtyChanges;
}
catch (Exception ex)
{
- Log.Logger.Error(ex, "Error updating tags");
- throw;
- }
- }
-
- public static int UpdateBook(LibraryBook libraryBook, LiberatedStatus liberatedStatus)
- {
- try
- {
- using var context = DbContexts.GetContext();
-
- var udi = libraryBook.Book.UserDefinedItem;
-
- if (udi.BookStatus == liberatedStatus)
- return 0;
-
- // Attach() NoTracking entities before SaveChanges()
- udi.BookStatus = liberatedStatus;
- 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;
-
- if (udi.PdfStatus == liberatedStatus)
- return 0;
-
- // 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");
+ Log.Logger.Error(ex, $"Error updating {nameof(book.UserDefinedItem)}");
throw;
}
}
@@ -257,15 +180,15 @@ namespace ApplicationServices
// below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those...
- public static LiberatedState Liberated_Status(Book book)
- => book.Audio_Exists ? LiberatedState.Liberated
- : FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedState.PartialDownload
- : LiberatedState.NotDownloaded;
+ public static LiberatedStatus Liberated_Status(Book book)
+ => book.Audio_Exists ? LiberatedStatus.Liberated
+ : FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
+ : LiberatedStatus.NotLiberated;
- public static PdfState Pdf_Status(Book book)
- => !book.Supplements.Any() ? PdfState.NoPdf
- : book.PDF_Exists ? PdfState.Downloaded
- : PdfState.NotDownloaded;
+ public static LiberatedStatus? Pdf_Status(Book book)
+ => !book.Supplements.Any() ? null
+ : book.PDF_Exists ? LiberatedStatus.Liberated
+ : LiberatedStatus.NotLiberated;
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int pdfsDownloaded, int pdfsNotDownloaded) { }
public static LibraryStats GetCounts()
@@ -276,9 +199,9 @@ namespace ApplicationServices
.AsParallel()
.Select(lb => Liberated_Status(lb.Book))
.ToList();
- var booksFullyBackedUp = results.Count(r => r == LiberatedState.Liberated);
- var booksDownloadedOnly = results.Count(r => r == LiberatedState.PartialDownload);
- var booksNoProgress = results.Count(r => r == LiberatedState.NotDownloaded);
+ var booksFullyBackedUp = results.Count(r => r == LiberatedStatus.Liberated);
+ var booksDownloadedOnly = results.Count(r => r == LiberatedStatus.PartialDownload);
+ var booksNoProgress = results.Count(r => r == LiberatedStatus.NotLiberated);
Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress });
@@ -287,8 +210,8 @@ namespace ApplicationServices
.Where(lb => lb.Book.Supplements.Any())
.Select(lb => Pdf_Status(lb.Book))
.ToList();
- var pdfsDownloaded = boolResults.Count(r => r == PdfState.Downloaded);
- var pdfsNotDownloaded = boolResults.Count(r => r == PdfState.NotDownloaded);
+ var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated);
+ var pdfsNotDownloaded = boolResults.Count(r => r == LiberatedStatus.NotLiberated);
Log.Logger.Information("PDF counts. {@DebugInfo}", new { total = boolResults.Count, pdfsDownloaded, pdfsNotDownloaded });
diff --git a/DataLayer/EfClasses/UserDefinedItem.cs b/DataLayer/EfClasses/UserDefinedItem.cs
index 4178a20e..5bd52beb 100644
--- a/DataLayer/EfClasses/UserDefinedItem.cs
+++ b/DataLayer/EfClasses/UserDefinedItem.cs
@@ -14,7 +14,11 @@ namespace DataLayer
NotLiberated = 0,
Liberated = 1,
/// Error occurred during liberation. Don't retry
- Error = 2
+ Error = 2,
+
+ /// Application-state only. Not a valid persistence state.
+ PartialDownload = 0x1000
+
}
public class UserDefinedItem
@@ -38,7 +42,15 @@ namespace DataLayer
public string Tags
{
get => _tags;
- set => _tags = sanitize(value);
+ set
+ {
+ var newTags = sanitize(value);
+ if (_tags != newTags)
+ {
+ _tags = newTags;
+ ItemChanged?.Invoke(this, nameof(Tags));
+ }
+ }
}
public IEnumerable TagsEnumerated => Tags == "" ? new string[0] : Tags.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
@@ -95,10 +107,38 @@ namespace DataLayer
#endregion
#region LiberatedStatuses
- public LiberatedStatus BookStatus { get; set; }
- public LiberatedStatus? PdfStatus { get; set; }
- #endregion
+ private LiberatedStatus _bookStatus;
+ private LiberatedStatus? _pdfStatus;
+ public LiberatedStatus BookStatus
+ {
+ get => _bookStatus;
+ set
+ {
+ if (_bookStatus != value)
+ {
+ _bookStatus = value;
+ ItemChanged?.Invoke(this, nameof(BookStatus));
+ }
+ }
+ }
+ public LiberatedStatus? PdfStatus
+ {
+ get => _pdfStatus;
+ set
+ {
+ if (_pdfStatus != value)
+ {
+ _pdfStatus = value;
+ ItemChanged?.Invoke(this, nameof(PdfStatus));
+ }
+ }
+ }
+ #endregion
+ ///
+ /// Occurs when , , or values change.
+ ///
+ public static event EventHandler ItemChanged;
public override string ToString() => $"{Book} {Rating} {Tags}";
}
}
diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs
index 7b9ec687..db7fc144 100644
--- a/FileLiberator/DownloadDecryptBook.cs
+++ b/FileLiberator/DownloadDecryptBook.cs
@@ -16,22 +16,22 @@ namespace FileLiberator
public class DownloadDecryptBook : IAudioDecodable
{
- private AaxcDownloadConverter aaxcDownloader;
+ private AaxcDownloadConverter aaxcDownloader;
- public event EventHandler StreamingTimeRemaining;
- public event EventHandler> RequestCoverArt;
- public event EventHandler TitleDiscovered;
- public event EventHandler AuthorsDiscovered;
- public event EventHandler NarratorsDiscovered;
- public event EventHandler CoverImageDiscovered;
- public event EventHandler StreamingBegin;
- public event EventHandler StreamingProgressChanged;
- public event EventHandler StreamingCompleted;
- public event EventHandler Begin;
- public event EventHandler StatusUpdate;
- public event EventHandler Completed;
+ public event EventHandler StreamingTimeRemaining;
+ public event EventHandler> RequestCoverArt;
+ public event EventHandler TitleDiscovered;
+ public event EventHandler AuthorsDiscovered;
+ public event EventHandler NarratorsDiscovered;
+ public event EventHandler CoverImageDiscovered;
+ public event EventHandler StreamingBegin;
+ public event EventHandler StreamingProgressChanged;
+ public event EventHandler StreamingCompleted;
+ public event EventHandler Begin;
+ public event EventHandler StatusUpdate;
+ public event EventHandler Completed;
- public async Task ProcessAsync(LibraryBook libraryBook)
+ public async Task ProcessAsync(LibraryBook libraryBook)
{
Begin?.Invoke(this, libraryBook);
@@ -47,10 +47,12 @@ namespace FileLiberator
return new StatusHandler { "Decrypt failed" };
// moves files and returns dest dir
- _ = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
+ var moveResults = MoveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
- // only need to update if success. if failure, it will remain at 0 == NotLiberated
- ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Liberated);
+ if (!moveResults.movedAudioFile)
+ return new StatusHandler { "Cannot find final audio file after decryption" };
+
+ libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
return new StatusHandler();
}
@@ -123,7 +125,7 @@ namespace FileLiberator
}
- private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
+ private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
{
if (e is null && Configuration.Instance.AllowLibationFixup)
{
@@ -140,10 +142,10 @@ namespace FileLiberator
{
TitleDiscovered?.Invoke(this, e.TitleSansUnabridged);
AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]");
- NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
+ NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
}
- private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
+ private static (string destinationDir, bool movedAudioFile) MoveFilesToBooksDir(Book product, string outputAudioFilename)
{
// create final directory. move each file into it. MOVE AUDIO FILE LAST
// new dir: safetitle_limit50char + " [" + productId + "]"
@@ -158,6 +160,7 @@ namespace FileLiberator
// audio filename: safetitle_limit50char + " [" + productId + "]." + audio_ext
var audioFileName = FileUtility.GetValidFilename(destinationDir, product.Title, musicFileExt, product.AudibleProductId);
+ bool movedAudioFile = false;
foreach (var f in sortedFiles)
{
var dest
@@ -170,11 +173,14 @@ namespace FileLiberator
Cue.UpdateFileName(f, audioFileName);
File.Move(f.FullName, dest);
+
+ movedAudioFile |= AudibleFileStorage.Audio.IsFileTypeMatch(f);
+
}
AudibleFileStorage.Audio.Refresh();
- return destinationDir;
+ return (destinationDir, movedAudioFile);
}
private static List getProductFilesSorted(Book product, string outputAudioFilename)
@@ -197,26 +203,26 @@ namespace FileLiberator
}
private static void validate(LibraryBook libraryBook)
- {
- string errorString(string field)
- => $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
+ {
+ string errorString(string field)
+ => $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
- string errorTitle()
- {
- var title
- = (libraryBook.Book.Title.Length > 53)
- ? $"{libraryBook.Book.Title.Truncate(50)}..."
- : libraryBook.Book.Title;
- var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
- return errorBookTitle;
- };
+ string errorTitle()
+ {
+ var title
+ = (libraryBook.Book.Title.Length > 53)
+ ? $"{libraryBook.Book.Title.Truncate(50)}..."
+ : libraryBook.Book.Title;
+ var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
+ return errorBookTitle;
+ };
- if (string.IsNullOrWhiteSpace(libraryBook.Account))
- throw new Exception(errorString("Account"));
+ if (string.IsNullOrWhiteSpace(libraryBook.Account))
+ throw new Exception(errorString("Account"));
- if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
- throw new Exception(errorString("Locale"));
- }
+ if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
+ throw new Exception(errorString("Locale"));
+ }
public bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists;
diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs
index 356cb13a..a9b8815c 100644
--- a/FileLiberator/DownloadPdf.cs
+++ b/FileLiberator/DownloadPdf.cs
@@ -23,8 +23,7 @@ namespace FileLiberator
await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
var result = verifyDownload(libraryBook);
- var liberatedStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
- ApplicationServices.LibraryCommands.UpdatePdf(libraryBook, liberatedStatus);
+ libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
return result;
}
diff --git a/FileLiberator/FileLiberator.csproj b/FileLiberator/FileLiberator.csproj
index 08a7d736..776a70fa 100644
--- a/FileLiberator/FileLiberator.csproj
+++ b/FileLiberator/FileLiberator.csproj
@@ -7,7 +7,6 @@
-
diff --git a/FileLiberator/IProcessableExt.cs b/FileLiberator/IProcessableExt.cs
index 3a4339e3..0ba57965 100644
--- a/FileLiberator/IProcessableExt.cs
+++ b/FileLiberator/IProcessableExt.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using ApplicationServices;
using DataLayer;
using Dinah.Core;
using Dinah.Core.ErrorHandling;
@@ -18,10 +17,8 @@ namespace FileLiberator
// when used in foreach: stateful. deferred execution
- public static IEnumerable GetValidLibraryBooks(this IProcessable processable)
- => DbContexts.GetContext()
- .GetLibrary_Flat_NoTracking()
- .Where(libraryBook => processable.Validate(libraryBook));
+ public static IEnumerable GetValidLibraryBooks(this IProcessable processable, IEnumerable library)
+ => library.Where(libraryBook => processable.Validate(libraryBook));
public static async Task ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate)
{
diff --git a/Libation.sln b/Libation.sln
index 8d11ecc4..1dcd66ee 100644
--- a/Libation.sln
+++ b/Libation.sln
@@ -86,7 +86,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore.T
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -222,7 +222,7 @@ Global
{8BD8E012-F44F-4EE2-A234-D66C14D5FE4B} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{1AE65B61-9C05-4C80-ABFF-48F16E22FDF1} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{59A10DF3-63EC-43F1-A3BF-4000CFA118D2} = {751093DD-5DBA-463E-ADBE-E05FAFB6983E}
- {393B5B27-D15C-4F77-9457-FA14BA8F3C73} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
+ {393B5B27-D15C-4F77-9457-FA14BA8F3C73} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{06882742-27A6-4347-97D9-56162CEC9C11} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
{2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{9F1AA3DE-962F-469B-82B2-46F93491389B} = {F61184E7-2426-4A13-ACEF-5689928E2CE2}
diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs
index 034be1af..6d604184 100644
--- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs
+++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs
@@ -50,24 +50,24 @@ namespace LibationWinForms.BookLiberation
public static class ProcessorAutomationController
{
- public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler completedAction = null)
+ public static async Task BackupSingleBookAsync(LibraryBook libraryBook)
{
Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId });
var logMe = LogMe.RegisterForm();
- var backupBook = CreateBackupBook(completedAction, logMe);
+ var backupBook = CreateBackupBook(logMe);
// continue even if libraryBook is null. we'll display even that in the processing box
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
}
- public static async Task BackupAllBooksAsync(EventHandler completedAction = null)
+ public static async Task BackupAllBooksAsync()
{
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
var automatedBackupsForm = new AutomatedBackupsForm();
var logMe = LogMe.RegisterForm(automatedBackupsForm);
- var backupBook = CreateBackupBook(completedAction, logMe);
+ var backupBook = CreateBackupBook(logMe);
await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync();
}
@@ -84,19 +84,19 @@ namespace LibationWinForms.BookLiberation
await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync();
}
- public static async Task BackupAllPdfsAsync(EventHandler completedAction = null)
+ public static async Task BackupAllPdfsAsync()
{
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync));
var automatedBackupsForm = new AutomatedBackupsForm();
var logMe = LogMe.RegisterForm(automatedBackupsForm);
- var downloadPdf = CreateProcessable(logMe, completedAction);
+ var downloadPdf = CreateProcessable(logMe);
await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync();
}
- private static IProcessable CreateBackupBook(EventHandler completedAction, LogMe logMe)
+ private static IProcessable CreateBackupBook(LogMe logMe)
{
var downloadPdf = CreateProcessable(logMe);
@@ -104,7 +104,6 @@ namespace LibationWinForms.BookLiberation
async void onDownloadDecryptBookCompleted(object sender, LibraryBook e)
{
await downloadPdf.TryProcessAsync(e);
- completedAction(sender, e);
}
var downloadDecryptBook = CreateProcessable(logMe, onDownloadDecryptBookCompleted);
@@ -246,7 +245,7 @@ $@" Title: {libraryBook.Book.Title}
if (dialogResult == SkipResult)
{
- ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error);
+ libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
}
@@ -305,7 +304,7 @@ An error occurred while trying to process this book.
protected override async Task RunAsync()
{
// support for 'skip this time only' requires state. iterators provide this state for free. therefore: use foreach/iterator here
- foreach (var libraryBook in Processable.GetValidLibraryBooks())
+ foreach (var libraryBook in Processable.GetValidLibraryBooks(ApplicationServices.DbContexts.GetContext().GetLibrary_Flat_NoTracking()))
{
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
if (!keepGoing)
diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs
index a114b094..134a2123 100644
--- a/LibationWinForms/Form1.cs
+++ b/LibationWinForms/Form1.cs
@@ -384,15 +384,14 @@ namespace LibationWinForms
#region liberate menu
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
- => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow);
+ => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync();
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
- => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow);
+ => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync();
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
- private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
#endregion
#region Export menu
diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs
index 7a90c437..fb716e31 100644
--- a/LibationWinForms/GridEntry.cs
+++ b/LibationWinForms/GridEntry.cs
@@ -12,6 +12,9 @@ using Dinah.Core.Drawing;
namespace LibationWinForms
{
+ ///
+ /// The View Model for a LibraryBook
+ ///
internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
{
#region implementation properties
@@ -26,12 +29,15 @@ namespace LibationWinForms
private Book Book => LibraryBook.Book;
private Image _cover;
+ private Action Refilter { get; }
- public GridEntry(LibraryBook libraryBook)
+ public GridEntry(LibraryBook libraryBook, Action refilterOnChanged = null)
{
LibraryBook = libraryBook;
+ Refilter = refilterOnChanged;
_memberValues = CreateMemberValueDictionary();
+
//Get cover art. If it's default, subscribe to PictureCached
{
(bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80));
@@ -58,7 +64,7 @@ namespace LibationWinForms
Description = GetDescriptionDisplay(Book);
}
- //DisplayTags and Liberate properties are live.
+ UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
}
private void PictureStorage_PictureCached(object sender, FileManager.PictureCachedEventArgs e)
@@ -70,8 +76,77 @@ namespace LibationWinForms
}
}
- #region Data Source properties
+ #region detect changes to the model, update the view, and save to database.
+ ///
+ /// This event handler receives notifications from the model that it has changed.
+ /// Save to the database and notify the view that it's changed.
+ ///
+ private void UserDefinedItem_ItemChanged(object sender, string itemName)
+ {
+ var udi = sender as UserDefinedItem;
+
+ if (udi.Book.AudibleProductId != Book.AudibleProductId)
+ return;
+
+ switch (itemName)
+ {
+ case nameof(udi.Tags):
+ {
+ Book.UserDefinedItem.Tags = udi.Tags;
+ NotifyPropertyChanged(nameof(DisplayTags));
+ }
+ break;
+ case nameof(udi.BookStatus):
+ {
+ Book.UserDefinedItem.BookStatus = udi.BookStatus;
+ NotifyPropertyChanged(nameof(Liberate));
+ }
+ break;
+ case nameof(udi.PdfStatus):
+ {
+ Book.UserDefinedItem.PdfStatus = udi.PdfStatus;
+ NotifyPropertyChanged(nameof(Liberate));
+ }
+ break;
+ }
+
+ if (!suspendCommit)
+ Commit();
+ }
+ private bool suspendCommit = false;
+
+ ///
+ /// Begin editing the model, suspending commits until is called.
+ ///
+ public void BeginEdit() => suspendCommit = true;
+
+ ///
+ /// Save all edits to the database.
+ ///
+ public void EndEdit()
+ {
+ Commit();
+ suspendCommit = false;
+ }
+
+ private void Commit()
+ {
+ //We don't want LiberatedStatus.PartialDownload to be a persistent status.
+ var bookStatus = Book.UserDefinedItem.BookStatus;
+ var saveStatus = bookStatus == LiberatedStatus.PartialDownload ? LiberatedStatus.NotLiberated : bookStatus;
+ Book.UserDefinedItem.BookStatus = saveStatus;
+
+ LibraryCommands.UpdateUserDefinedItem(Book);
+
+ Book.UserDefinedItem.BookStatus = bookStatus;
+
+ Refilter?.Invoke();
+ }
+
+ #endregion
+
+ #region Model properties exposed to the view
public Image Cover
{
get
@@ -96,8 +171,22 @@ namespace LibationWinForms
public string Category { get; }
public string Misc { get; }
public string Description { get; }
- public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
- public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book));
+ public string DisplayTags
+ {
+ get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
+ set => Book.UserDefinedItem.Tags = value;
+ }
+ public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
+ {
+ get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book));
+
+ set
+ {
+ LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus;
+ LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus;
+ }
+ }
+
#endregion
#region Data Sorting
@@ -121,7 +210,7 @@ namespace LibationWinForms
{ nameof(Category), () => Category },
{ nameof(Misc), () => Misc },
{ nameof(DisplayTags), () => DisplayTags },
- { nameof(Liberate), () => Liberate.Item1 }
+ { nameof(Liberate), () => Liberate.BookStatus }
};
// Instantiate comparers for every exposed member object type.
@@ -131,7 +220,7 @@ namespace LibationWinForms
{ typeof(int), new ObjectComparer() },
{ typeof(float), new ObjectComparer() },
{ typeof(DateTime), new ObjectComparer() },
- { typeof(LiberatedState), new ObjectComparer() },
+ { typeof(LiberatedStatus), new ObjectComparer() },
};
public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
@@ -200,5 +289,11 @@ namespace LibationWinForms
}
#endregion
+
+ ~GridEntry()
+ {
+ UserDefinedItem.ItemChanged -= UserDefinedItem_ItemChanged;
+ FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached;
+ }
}
}
diff --git a/LibationWinForms/LibationWinForms.csproj b/LibationWinForms/LibationWinForms.csproj
index 343425c6..7eee10cc 100644
--- a/LibationWinForms/LibationWinForms.csproj
+++ b/LibationWinForms/LibationWinForms.csproj
@@ -14,6 +14,7 @@
+
diff --git a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs
index b8509e5d..f1769f36 100644
--- a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs
+++ b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs
@@ -3,6 +3,7 @@ using System;
using System.Drawing;
using System.Windows.Forms;
using System.Linq;
+using DataLayer;
namespace LibationWinForms
{
@@ -20,9 +21,11 @@ namespace LibationWinForms
{
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
- if (value is (LiberatedState liberatedState, PdfState pdfState))
+ if (value is (LiberatedStatus, LiberatedStatus) or (LiberatedStatus, null))
{
- (string mouseoverText, Bitmap buttonImage) = GetLiberateDisplay(liberatedState, pdfState);
+ var (bookState, pdfState) = ((LiberatedStatus bookState, LiberatedStatus? pdfState))value;
+
+ (string mouseoverText, Bitmap buttonImage) = GetLiberateDisplay(bookState, pdfState);
DrawButtonImage(graphics, buttonImage, cellBounds);
@@ -30,29 +33,31 @@ namespace LibationWinForms
}
}
- private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus)
+ private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedStatus liberatedStatus, LiberatedStatus? pdfStatus)
{
(string libState, string image_lib) = liberatedStatus switch
{
- LiberatedState.Liberated => ("Liberated", "green"),
- LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
- LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"),
+ LiberatedStatus.Liberated => ("Liberated", "green"),
+ LiberatedStatus.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
+ LiberatedStatus.NotLiberated => ("Book NOT downloaded", "red"),
+ LiberatedStatus.Error => ("Book downloaded ERROR", "red"),
_ => throw new Exception("Unexpected liberation state")
};
(string pdfState, string image_pdf) = pdfStatus switch
{
- PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"),
- PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"),
- PdfState.NoPdf => ("", ""),
+ LiberatedStatus.Liberated => ("\r\nPDF downloaded", "_pdf_yes"),
+ LiberatedStatus.NotLiberated => ("\r\nPDF NOT downloaded", "_pdf_no"),
+ LiberatedStatus.Error => ("\r\nPDF downloaded ERROR", "_pdf_no"),
+ null => ("", ""),
_ => throw new Exception("Unexpected PDF state")
};
var mouseoverText = libState + pdfState;
- if (liberatedStatus == LiberatedState.NotDownloaded ||
- liberatedStatus == LiberatedState.PartialDownload ||
- pdfStatus == PdfState.NotDownloaded)
+ if (liberatedStatus == LiberatedStatus.NotLiberated ||
+ liberatedStatus == LiberatedStatus.PartialDownload ||
+ pdfStatus == LiberatedStatus.NotLiberated)
mouseoverText += "\r\nClick to complete";
var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");
diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs
index 46b721a4..b6c6b5b0 100644
--- a/LibationWinForms/ProductsGrid.cs
+++ b/LibationWinForms/ProductsGrid.cs
@@ -86,7 +86,7 @@ namespace LibationWinForms
}
// else: liberate
- await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId));
+ await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook);
}
private void Details_Click(GridEntry liveGridEntry)
@@ -95,15 +95,14 @@ namespace LibationWinForms
if (bookDetailsForm.ShowDialog() != DialogResult.OK)
return;
- var qtyChanges = LibraryCommands.UpdateUserDefinedItem(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
- if (qtyChanges == 0)
- return;
+ liveGridEntry.BeginEdit();
- //Re-apply filters
- Filter();
+ liveGridEntry.DisplayTags = bookDetailsForm.NewTags;
+ liveGridEntry.Liberate = (bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
- //Update whole GridEntry row
- liveGridEntry.NotifyPropertyChanged();
+ liveGridEntry.EndEdit();
+
+ BackupCountsChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
@@ -132,7 +131,7 @@ namespace LibationWinForms
}
var orderedGridEntries = lib
- .Select(lb => new GridEntry(lb)).ToList()
+ .Select(lb => new GridEntry(lb, Filter)).ToList()
// default load order
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
//// more advanced example: sort by author, then series, then title
@@ -150,19 +149,6 @@ namespace LibationWinForms
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
}
- public void RefreshRow(string productId)
- {
- var liveGridEntry = getGridEntry((ge) => ge.AudibleProductId == productId);
-
- // update GridEntry Liberate cell
- liveGridEntry?.NotifyPropertyChanged(nameof(liveGridEntry.Liberate));
-
- // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change
- Filter();
-
- BackupCountsChanged?.Invoke(this, EventArgs.Empty);
- }
-
#endregion
#region Filter
@@ -195,12 +181,7 @@ namespace LibationWinForms
#endregion
#region DataGridView Macro
-
- private GridEntry getGridEntry(Func predicate)
- => ((SortableBindingList)gridEntryBindingSource.DataSource).FirstOrDefault(predicate);
-
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex);
-
#endregion
}
}