From 83fb2cd1d061576fda9cd4cb5d0f4bbd50cf080c Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Thu, 29 Dec 2022 09:33:32 -0500 Subject: [PATCH] New feature #430 : bulk set pdf-downloaded status --- Source/ApplicationServices/LibraryCommands.cs | 651 +++++++++--------- Source/DataLayer/EfClasses/UserDefinedItem.cs | 11 +- .../LiberatedStatusBatchManualDialog.axaml.cs | 8 +- .../ViewModels/LibraryBookEntry.cs | 2 +- .../Views/MainWindow.VisibleBooks.cs | 28 +- .../LibationAvalonia/Views/MainWindow.axaml | 3 +- .../LiberatedStatusBatchManualDialog.cs | 10 +- Source/LibationWinForms/Form1.Designer.cs | 72 +- Source/LibationWinForms/Form1.VisibleBooks.cs | 25 +- .../GridView/LibraryBookEntry.cs | 2 +- 10 files changed, 441 insertions(+), 371 deletions(-) diff --git a/Source/ApplicationServices/LibraryCommands.cs b/Source/ApplicationServices/LibraryCommands.cs index 463e5cf2..d49c15aa 100644 --- a/Source/ApplicationServices/LibraryCommands.cs +++ b/Source/ApplicationServices/LibraryCommands.cs @@ -13,221 +13,221 @@ using static DtoImporterService.PerfLogger; namespace ApplicationServices { - public static class LibraryCommands - { - public static event EventHandler ScanBegin; - public static event EventHandler ScanEnd; + public static class LibraryCommands + { + public static event EventHandler ScanBegin; + public static event EventHandler ScanEnd; - public static bool Scanning { get; private set; } - private static object _lock { get; } = new(); + public static bool Scanning { get; private set; } + private static object _lock { get; } = new(); - static LibraryCommands() - { - ScanBegin += (_, __) => Scanning = true; - ScanEnd += (_, __) => Scanning = false; - } + static LibraryCommands() + { + ScanBegin += (_, __) => Scanning = true; + ScanEnd += (_, __) => Scanning = false; + } public static async Task> FindInactiveBooks(Func> apiExtendedfunc, IEnumerable existingLibrary, params Account[] accounts) - { - logRestart(); + { + logRestart(); - lock (_lock) - { - if (Scanning) - return new(); - } - ScanBegin?.Invoke(null, accounts.Length); - - //These are the minimum response groups required for the - //library scanner to pass all validation and filtering. - var libraryOptions = new LibraryOptions + lock (_lock) { - ResponseGroups - = LibraryOptions.ResponseGroupOptions.ProductAttrs - | LibraryOptions.ResponseGroupOptions.ProductDesc - | LibraryOptions.ResponseGroupOptions.Relationships - }; + if (Scanning) + return new(); + } + ScanBegin?.Invoke(null, accounts.Length); + + //These are the minimum response groups required for the + //library scanner to pass all validation and filtering. + var libraryOptions = new LibraryOptions + { + ResponseGroups + = LibraryOptions.ResponseGroupOptions.ProductAttrs + | LibraryOptions.ResponseGroupOptions.ProductDesc + | LibraryOptions.ResponseGroupOptions.Relationships + }; if (accounts is null || accounts.Length == 0) - return new List(); + return new List(); - try - { - logTime($"pre {nameof(scanAccountsAsync)} all"); - var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions); - logTime($"post {nameof(scanAccountsAsync)} all"); + try + { + logTime($"pre {nameof(scanAccountsAsync)} all"); + var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions); + logTime($"post {nameof(scanAccountsAsync)} all"); - var totalCount = libraryItems.Count; - Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}"); + var totalCount = libraryItems.Count; + Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}"); - var missingBookList = existingLibrary.Where(b => !libraryItems.Any(i => i.DtoItem.Asin == b.Book.AudibleProductId)).ToList(); + var missingBookList = existingLibrary.Where(b => !libraryItems.Any(i => i.DtoItem.Asin == b.Book.AudibleProductId)).ToList(); - return missingBookList; - } - catch (AudibleApi.Authentication.LoginFailedException lfEx) - { - lfEx.SaveFiles(Configuration.Instance.LibationFiles); + return missingBookList; + } + catch (AudibleApi.Authentication.LoginFailedException lfEx) + { + lfEx.SaveFiles(Configuration.Instance.LibationFiles); - // nuget Serilog.Exceptions would automatically log custom properties - // 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 scanning library. Login failed. {@DebugInfo}", new - { - lfEx.RequestUrl, - ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, - ResponseStatusCodeDesc = lfEx.ResponseStatusCode, - lfEx.ResponseInputFields, - lfEx.ResponseBodyFilePaths - }); - throw; - } - catch (Exception ex) - { - Log.Logger.Error(ex, "Error scanning library"); - throw; - } - finally - { - stop(); - var putBreakPointHere = logOutput; - ScanEnd?.Invoke(null, null); - } - } - - #region FULL LIBRARY scan and import - public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func> apiExtendedfunc, params Account[] accounts) - { - logRestart(); - - if (accounts is null || accounts.Length == 0) - return (0, 0); - - try - { - lock (_lock) + // nuget Serilog.Exceptions would automatically log custom properties + // 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 scanning library. Login failed. {@DebugInfo}", new { - if (Scanning) - return (0, 0); - } - ScanBegin?.Invoke(null, accounts.Length); + lfEx.RequestUrl, + ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, + ResponseStatusCodeDesc = lfEx.ResponseStatusCode, + lfEx.ResponseInputFields, + lfEx.ResponseBodyFilePaths + }); + throw; + } + catch (Exception ex) + { + Log.Logger.Error(ex, "Error scanning library"); + throw; + } + finally + { + stop(); + var putBreakPointHere = logOutput; + ScanEnd?.Invoke(null, null); + } + } - logTime($"pre {nameof(scanAccountsAsync)} all"); - var libraryOptions = new LibraryOptions - { - ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS, - ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215 - }; + #region FULL LIBRARY scan and import + public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func> apiExtendedfunc, params Account[] accounts) + { + logRestart(); + + if (accounts is null || accounts.Length == 0) + return (0, 0); + + try + { + lock (_lock) + { + if (Scanning) + return (0, 0); + } + ScanBegin?.Invoke(null, accounts.Length); + + logTime($"pre {nameof(scanAccountsAsync)} all"); + var libraryOptions = new LibraryOptions + { + ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS, + ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215 + }; var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions); - logTime($"post {nameof(scanAccountsAsync)} all"); + logTime($"post {nameof(scanAccountsAsync)} all"); - var totalCount = importItems.Count; - Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}"); + var totalCount = importItems.Count; + Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}"); - if (totalCount == 0) - return default; + if (totalCount == 0) + return default; - Log.Logger.Information("Begin scan for orphaned episode parents"); - var newParents = await findAndAddMissingParents(accounts); - Log.Logger.Information($"Orphan episode scan complete. New parents count {newParents}"); + Log.Logger.Information("Begin scan for orphaned episode parents"); + var newParents = await findAndAddMissingParents(accounts); + Log.Logger.Information($"Orphan episode scan complete. New parents count {newParents}"); - if (newParents >= 0) - { - //If any episodes are still orphaned, their series have been - //removed from the catalog and we'll never be able to find them. + if (newParents >= 0) + { + //If any episodes are still orphaned, their series have been + //removed from the catalog and we'll never be able to find them. - //only do this if findAndAddMissingParents returned >= 0. If it - //returned < 0, an error happened and there's still a chance that - //a future successful run will find missing parents. - removedOrphanedEpisodes(); - } + //only do this if findAndAddMissingParents returned >= 0. If it + //returned < 0, an error happened and there's still a chance that + //a future successful run will find missing parents. + removedOrphanedEpisodes(); + } - Log.Logger.Information("Begin long-running import"); - logTime($"pre {nameof(importIntoDbAsync)}"); - var newCount = await importIntoDbAsync(importItems); - logTime($"post {nameof(importIntoDbAsync)}"); - Log.Logger.Information($"Import complete. New count {newCount}"); + Log.Logger.Information("Begin long-running import"); + logTime($"pre {nameof(importIntoDbAsync)}"); + var newCount = await importIntoDbAsync(importItems); + logTime($"post {nameof(importIntoDbAsync)}"); + Log.Logger.Information($"Import complete. New count {newCount}"); - return (totalCount, newCount); - } - catch (AudibleApi.Authentication.LoginFailedException lfEx) - { - lfEx.SaveFiles(Configuration.Instance.LibationFiles); + return (totalCount, newCount); + } + catch (AudibleApi.Authentication.LoginFailedException lfEx) + { + lfEx.SaveFiles(Configuration.Instance.LibationFiles); - // nuget Serilog.Exceptions would automatically log custom properties - // 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 - { - lfEx.RequestUrl, - ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, - ResponseStatusCodeDesc = lfEx.ResponseStatusCode, - lfEx.ResponseInputFields, - lfEx.ResponseBodyFilePaths - }); - throw; - } - catch (Exception ex) - { - Log.Logger.Error(ex, "Error importing library"); - throw; - } - finally - { - stop(); - var putBreakPointHere = logOutput; - ScanEnd?.Invoke(null, null); - } - } + // nuget Serilog.Exceptions would automatically log custom properties + // 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 + { + lfEx.RequestUrl, + ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, + ResponseStatusCodeDesc = lfEx.ResponseStatusCode, + lfEx.ResponseInputFields, + lfEx.ResponseBodyFilePaths + }); + throw; + } + catch (Exception ex) + { + Log.Logger.Error(ex, "Error importing library"); + throw; + } + finally + { + stop(); + var putBreakPointHere = logOutput; + ScanEnd?.Invoke(null, null); + } + } - private static async Task> scanAccountsAsync(Func> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions) - { - var tasks = new List>>(); - foreach (var account in accounts) - { - // get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll) - var apiExtended = await apiExtendedfunc(account); + private static async Task> scanAccountsAsync(Func> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions) + { + var tasks = new List>>(); + foreach (var account in accounts) + { + // get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll) + var apiExtended = await apiExtendedfunc(account); - // add scanAccountAsync as a TASK: do not await - tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions)); - } + // add scanAccountAsync as a TASK: do not await + tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions)); + } - // import library in parallel - var arrayOfLists = await Task.WhenAll(tasks); - var importItems = arrayOfLists.SelectMany(a => a).ToList(); - return importItems; - } + // import library in parallel + var arrayOfLists = await Task.WhenAll(tasks); + var importItems = arrayOfLists.SelectMany(a => a).ToList(); + return importItems; + } - private static async Task> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions libraryOptions) - { - ArgumentValidator.EnsureNotNull(account, nameof(account)); + private static async Task> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions libraryOptions) + { + ArgumentValidator.EnsureNotNull(account, nameof(account)); - Log.Logger.Information("ImportLibraryAsync. {@DebugInfo}", new - { - Account = account?.MaskedLogEntry ?? "[null]" - }); + Log.Logger.Information("ImportLibraryAsync. {@DebugInfo}", new + { + Account = account?.MaskedLogEntry ?? "[null]" + }); - logTime($"pre scanAccountAsync {account.AccountName}"); + logTime($"pre scanAccountAsync {account.AccountName}"); - var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes); + var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes); - logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}"); + logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}"); - return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList(); - } + return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList(); + } - private static async Task importIntoDbAsync(List importItems) + private static async Task importIntoDbAsync(List importItems) { logTime("importIntoDbAsync -- pre db"); using var context = DbContexts.GetContext(); var libraryBookImporter = new LibraryBookImporter(context); var newCount = await Task.Run(() => libraryBookImporter.Import(importItems)); - logTime("importIntoDbAsync -- post Import()"); + logTime("importIntoDbAsync -- post Import()"); int qtyChanges = SaveContext(context); logTime("importIntoDbAsync -- post SaveChanges"); - // this is any changes at all to the database, not just new books + // this is any changes at all to the database, not just new books if (qtyChanges > 0) await Task.Run(() => finalizeLibrarySizeChange()); logTime("importIntoDbAsync -- post finalizeLibrarySizeChange"); @@ -235,112 +235,112 @@ namespace ApplicationServices return newCount; } - static void removedOrphanedEpisodes() - { - using var context = DbContexts.GetContext(); - try - { - var orphanedEpisodes = - context - .GetLibrary_Flat_NoTracking(includeParents: true) - .FindOrphanedEpisodes(); + static void removedOrphanedEpisodes() + { + using var context = DbContexts.GetContext(); + try + { + var orphanedEpisodes = + context + .GetLibrary_Flat_NoTracking(includeParents: true) + .FindOrphanedEpisodes(); - context.LibraryBooks.RemoveRange(orphanedEpisodes); - context.Books.RemoveRange(orphanedEpisodes.Select(lb => lb.Book)); + context.LibraryBooks.RemoveRange(orphanedEpisodes); + context.Books.RemoveRange(orphanedEpisodes.Select(lb => lb.Book)); - } - catch (Exception ex) - { - Serilog.Log.Logger.Error(ex, "An error occurred while trying to remove orphaned episodes from the database"); - } - } + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while trying to remove orphaned episodes from the database"); + } + } - static async Task findAndAddMissingParents(Account[] accounts) - { - using var context = DbContexts.GetContext(); + static async Task findAndAddMissingParents(Account[] accounts) + { + using var context = DbContexts.GetContext(); - var library = context.GetLibrary_Flat_NoTracking(includeParents: true); + var library = context.GetLibrary_Flat_NoTracking(includeParents: true); - try - { - var orphanedEpisodes = library.FindOrphanedEpisodes().ToList(); + try + { + var orphanedEpisodes = library.FindOrphanedEpisodes().ToList(); - if (!orphanedEpisodes.Any()) - return -1; + if (!orphanedEpisodes.Any()) + return -1; - var orphanedSeries = - orphanedEpisodes - .SelectMany(lb => lb.Book.SeriesLink) - .DistinctBy(s => s.Series.AudibleSeriesId) - .ToList(); + var orphanedSeries = + orphanedEpisodes + .SelectMany(lb => lb.Book.SeriesLink) + .DistinctBy(s => s.Series.AudibleSeriesId) + .ToList(); - // The Catalog endpoint does not require authentication. - var api = new ApiUnauthenticated(accounts[0].Locale); + // The Catalog endpoint does not require authentication. + var api = new ApiUnauthenticated(accounts[0].Locale); - var seriesParents = orphanedSeries.Select(o => o.Series.AudibleSeriesId).ToList(); - var items = await api.GetCatalogProductsAsync(seriesParents, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS); + var seriesParents = orphanedSeries.Select(o => o.Series.AudibleSeriesId).ToList(); + var items = await api.GetCatalogProductsAsync(seriesParents, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS); - List newParentsImportItems = new(); - foreach (var sp in orphanedSeries) - { - var seriesItem = items.First(i => i.Asin == sp.Series.AudibleSeriesId); + List newParentsImportItems = new(); + foreach (var sp in orphanedSeries) + { + var seriesItem = items.First(i => i.Asin == sp.Series.AudibleSeriesId); - if (seriesItem.Relationships is null) - continue; + if (seriesItem.Relationships is null) + continue; - var episode = orphanedEpisodes.First(l => l.Book.AudibleProductId == sp.Book.AudibleProductId); + var episode = orphanedEpisodes.First(l => l.Book.AudibleProductId == sp.Book.AudibleProductId); - seriesItem.PurchaseDate = new DateTimeOffset(episode.DateAdded); - seriesItem.Series = new AudibleApi.Common.Series[] - { - new AudibleApi.Common.Series{ Asin = seriesItem.Asin, Title = seriesItem.TitleWithSubtitle, Sequence = "-1"} - }; + seriesItem.PurchaseDate = new DateTimeOffset(episode.DateAdded); + seriesItem.Series = new AudibleApi.Common.Series[] + { + new AudibleApi.Common.Series{ Asin = seriesItem.Asin, Title = seriesItem.TitleWithSubtitle, Sequence = "-1"} + }; - newParentsImportItems.Add(new ImportItem { DtoItem = seriesItem, AccountId = episode.Account, LocaleName = episode.Book.Locale }); - } + newParentsImportItems.Add(new ImportItem { DtoItem = seriesItem, AccountId = episode.Account, LocaleName = episode.Book.Locale }); + } - var newCount = new LibraryBookImporter(context) - .Import(newParentsImportItems); + var newCount = new LibraryBookImporter(context) + .Import(newParentsImportItems); - await context.SaveChangesAsync(); + await context.SaveChangesAsync(); - return newCount; - } - catch (Exception ex) - { - Serilog.Log.Logger.Error(ex, "An error occurred while trying to scan for orphaned episode parents."); - return -1; - } - } + return newCount; + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while trying to scan for orphaned episode parents."); + return -1; + } + } - public static int SaveContext(LibationContext context) - { - try - { - return context.SaveChanges(); - } - catch (Microsoft.EntityFrameworkCore.DbUpdateException ex) - { - // DbUpdateException exceptions can wreck serilog. Condense it until we can find a better solution. I suspect the culprit is the "WithExceptionDetails" serilog extension + public static int SaveContext(LibationContext context) + { + try + { + return context.SaveChanges(); + } + catch (Microsoft.EntityFrameworkCore.DbUpdateException ex) + { + // DbUpdateException exceptions can wreck serilog. Condense it until we can find a better solution. I suspect the culprit is the "WithExceptionDetails" serilog extension - static string format(Exception ex) => $"\r\nMessage: {ex.Message}\r\nStack Trace:\r\n{ex.StackTrace}"; + static string format(Exception ex) => $"\r\nMessage: {ex.Message}\r\nStack Trace:\r\n{ex.StackTrace}"; - var msg = "Microsoft.EntityFrameworkCore.DbUpdateException"; - if (ex.InnerException is null) - throw new Exception($"{msg}{format(ex)}"); - throw new Exception( - $"{msg}{format(ex)}", - new Exception($"Inner Exception{format(ex.InnerException)}")); - } + var msg = "Microsoft.EntityFrameworkCore.DbUpdateException"; + if (ex.InnerException is null) + throw new Exception($"{msg}{format(ex)}"); + throw new Exception( + $"{msg}{format(ex)}", + new Exception($"Inner Exception{format(ex.InnerException)}")); + } } #endregion #region remove/restore books public static Task RemoveBooksAsync(List idsToRemove) => Task.Run(() => removeBooks(idsToRemove)); - public static int RemoveBook(string idToRemove) => removeBooks(new() { idToRemove }); + public static int RemoveBook(string idToRemove) => removeBooks(new() { idToRemove }); private static int removeBooks(List idsToRemove) - { - try + { + try { if (idsToRemove is null || !idsToRemove.Any()) return 0; @@ -370,8 +370,8 @@ namespace ApplicationServices } public static int RestoreBooks(this List libraryBooks) - { - try + { + try { if (libraryBooks is null || !libraryBooks.Any()) return 0; @@ -402,59 +402,58 @@ namespace ApplicationServices // call this whenever books are added or removed from library private static void finalizeLibrarySizeChange() => LibrarySizeChanged?.Invoke(null, null); - /// Occurs when the size of the library changes. ie: books are added or removed - public static event EventHandler LibrarySizeChanged; + /// Occurs when the size of the library changes. ie: books are added or removed + public static event EventHandler LibrarySizeChanged; - /// - /// Occurs when the size of the library does not change but book(s) details do. Especially when , , or changed values are successfully persisted. - /// - public static event EventHandler> BookUserDefinedItemCommitted; + /// + /// Occurs when the size of the library does not change but book(s) details do. Especially when , , or changed values are successfully persisted. + /// + public static event EventHandler> BookUserDefinedItemCommitted; - #region Update book details - public static int UpdateUserDefinedItem( - this Book book, - string tags = null, - LiberatedStatus? bookStatus = null, - LiberatedStatus? pdfStatus = null) - => new[] { book }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus); + #region Update book details + public static int UpdateUserDefinedItem( + this Book book, + string tags = null, + LiberatedStatus? bookStatus = null, + LiberatedStatus? pdfStatus = null) + => new[] { book }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus); - public static int UpdateUserDefinedItem( - this IEnumerable books, - string tags = null, - LiberatedStatus? bookStatus = null, - LiberatedStatus? pdfStatus = null) + public static int UpdateUserDefinedItem( + this IEnumerable books, + string tags = null, + LiberatedStatus? bookStatus = null, + LiberatedStatus? pdfStatus = null) => updateUserDefinedItem( - books, + books, udi => { // blank tags are expected. null tags are not - if (tags is not null && udi.Tags != tags) + if (tags is not null) udi.Tags = tags; - if (bookStatus is not null && udi.BookStatus != bookStatus.Value) + if (bookStatus.HasValue) udi.BookStatus = bookStatus.Value; - // even though PdfStatus is nullable, there's no case where we'd actually overwrite with null - if (pdfStatus is not null && udi.PdfStatus != pdfStatus.Value) - udi.PdfStatus = pdfStatus.Value; + // method handles null logic + udi.SetPdfStatus(pdfStatus); }); public static int UpdateBookStatus(this Book book, LiberatedStatus bookStatus) - => book.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); + => book.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); public static int UpdateBookStatus(this IEnumerable books, LiberatedStatus bookStatus) - => books.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); + => books.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); public static int UpdateBookStatus(this LibraryBook libraryBook, LiberatedStatus bookStatus) - => libraryBook.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); + => libraryBook.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); public static int UpdateBookStatus(this IEnumerable libraryBooks, LiberatedStatus bookStatus) - => libraryBooks.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); + => libraryBooks.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus); public static int UpdatePdfStatus(this Book book, LiberatedStatus pdfStatus) - => book.UpdateUserDefinedItem(udi => udi.PdfStatus = pdfStatus); + => book.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus)); public static int UpdatePdfStatus(this IEnumerable books, LiberatedStatus pdfStatus) - => books.UpdateUserDefinedItem(udi => udi.PdfStatus = pdfStatus); + => books.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus)); public static int UpdatePdfStatus(this LibraryBook libraryBook, LiberatedStatus pdfStatus) - => libraryBook.UpdateUserDefinedItem(udi => udi.PdfStatus = pdfStatus); + => libraryBook.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus)); public static int UpdatePdfStatus(this IEnumerable libraryBooks, LiberatedStatus pdfStatus) - => libraryBooks.UpdateUserDefinedItem(udi => udi.PdfStatus = pdfStatus); + => libraryBooks.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus)); public static int UpdateTags(this Book book, string tags) => book.UpdateUserDefinedItem(udi => udi.Tags = tags); @@ -466,9 +465,9 @@ namespace ApplicationServices => libraryBooks.UpdateUserDefinedItem(udi => udi.Tags = tags); public static int UpdateUserDefinedItem(this LibraryBook libraryBook, Action action) - => libraryBook.Book.updateUserDefinedItem(action); + => libraryBook.Book.updateUserDefinedItem(action); public static int UpdateUserDefinedItem(this IEnumerable libraryBooks, Action action) - => libraryBooks.Select(lb => lb.Book).updateUserDefinedItem(action); + => libraryBooks.Select(lb => lb.Book).updateUserDefinedItem(action); public static int UpdateUserDefinedItem(this Book book, Action action) => book.updateUserDefinedItem(action); public static int UpdateUserDefinedItem(this IEnumerable books, Action action) => books.updateUserDefinedItem(action); @@ -481,7 +480,7 @@ namespace ApplicationServices if (books is null || !books.Any()) return 0; - foreach (var book in books) + foreach (var book in books) action?.Invoke(book.UserDefinedItem); using var context = DbContexts.GetContext(); @@ -506,49 +505,49 @@ 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 - : AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload - : LiberatedStatus.NotLiberated; + => book.Audio_Exists() ? book.UserDefinedItem.BookStatus + : AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload + : LiberatedStatus.NotLiberated; - // exists here for feature predictability. It makes sense for this to be where Liberated_Status is - public static LiberatedStatus? Pdf_Status(Book book) => book.UserDefinedItem.PdfStatus; + // exists here for feature predictability. It makes sense for this to be where Liberated_Status is + public static LiberatedStatus? Pdf_Status(Book book) => book.UserDefinedItem.PdfStatus; - // below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those... + // below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those... - public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int booksError, int pdfsDownloaded, int pdfsNotDownloaded) - { - public int PendingBooks => booksNoProgress + booksDownloadedOnly; - public bool HasPendingBooks => PendingBooks > 0; + public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int booksError, int pdfsDownloaded, int pdfsNotDownloaded) + { + public int PendingBooks => booksNoProgress + booksDownloadedOnly; + public bool HasPendingBooks => PendingBooks > 0; - public bool HasBookResults => 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress + booksError); - public bool HasPdfResults => 0 < (pdfsNotDownloaded + pdfsDownloaded); - } - public static LibraryStats GetCounts() - { - var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking(); + public bool HasBookResults => 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress + booksError); + public bool HasPdfResults => 0 < (pdfsNotDownloaded + pdfsDownloaded); + } + public static LibraryStats GetCounts() + { + var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking(); - var results = libraryBooks - .AsParallel() - .Select(lb => Liberated_Status(lb.Book)) - .ToList(); - var booksFullyBackedUp = results.Count(r => r == LiberatedStatus.Liberated); - var booksDownloadedOnly = results.Count(r => r == LiberatedStatus.PartialDownload); - var booksNoProgress = results.Count(r => r == LiberatedStatus.NotLiberated); - var booksError = results.Count(r => r == LiberatedStatus.Error); + var results = libraryBooks + .AsParallel() + .Select(lb => Liberated_Status(lb.Book)) + .ToList(); + var booksFullyBackedUp = results.Count(r => r == LiberatedStatus.Liberated); + var booksDownloadedOnly = results.Count(r => r == LiberatedStatus.PartialDownload); + var booksNoProgress = results.Count(r => r == LiberatedStatus.NotLiberated); + var booksError = results.Count(r => r == LiberatedStatus.Error); - Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, booksError }); + Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, booksError }); - var boolResults = libraryBooks - .AsParallel() - .Where(lb => lb.Book.HasPdf()) - .Select(lb => Pdf_Status(lb.Book)) - .ToList(); - var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated); - var pdfsNotDownloaded = boolResults.Count(r => r == LiberatedStatus.NotLiberated); + var boolResults = libraryBooks + .AsParallel() + .Where(lb => lb.Book.HasPdf()) + .Select(lb => Pdf_Status(lb.Book)) + .ToList(); + 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 }); + Log.Logger.Information("PDF counts. {@DebugInfo}", new { total = boolResults.Count, pdfsDownloaded, pdfsNotDownloaded }); - return new(booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, booksError, pdfsDownloaded, pdfsNotDownloaded); - } - } + return new(booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, booksError, pdfsDownloaded, pdfsNotDownloaded); + } + } } diff --git a/Source/DataLayer/EfClasses/UserDefinedItem.cs b/Source/DataLayer/EfClasses/UserDefinedItem.cs index fd003f1e..91bf236b 100644 --- a/Source/DataLayer/EfClasses/UserDefinedItem.cs +++ b/Source/DataLayer/EfClasses/UserDefinedItem.cs @@ -146,10 +146,19 @@ namespace DataLayer } } } + public void SetPdfStatus(LiberatedStatus? pdfStatus) + { + // don't change whether pdf is actually available. if null, leave as null. if not null, only assign non-null + + // null => non-null : only when adding a supplement + + if (pdfStatus.HasValue && PdfStatus.HasValue) + PdfStatus = pdfStatus; + } public LiberatedStatus? PdfStatus { get => _pdfStatus; - set + internal set { if (_pdfStatus != value) { diff --git a/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs index 6ce72d82..41455b63 100644 --- a/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/LiberatedStatusBatchManualDialog.axaml.cs @@ -34,7 +34,13 @@ namespace LibationAvalonia.Dialogs new liberatedComboBoxItem { Status = LiberatedStatus.NotLiberated, Text = "Not Downloaded" }, }; - public LiberatedStatusBatchManualDialog() + public LiberatedStatusBatchManualDialog(bool isPdf) : this() + { + if (isPdf) + this.Title = this.Title.Replace("book", "PDF"); + } + + public LiberatedStatusBatchManualDialog() { InitializeComponent(); SelectedItem = BookStatuses[0] as liberatedComboBoxItem; diff --git a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs index 5a2423e7..fce3e875 100644 --- a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs +++ b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs @@ -108,7 +108,7 @@ namespace LibationAvalonia.ViewModels this.RaisePropertyChanged(nameof(Liberate)); break; case nameof(udi.PdfStatus): - Book.UserDefinedItem.PdfStatus = udi.PdfStatus; + Book.UserDefinedItem.SetPdfStatus(udi.PdfStatus); _pdfStatus = udi.PdfStatus; this.RaisePropertyChanged(nameof(Liberate)); break; diff --git a/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs b/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs index 1eb7f579..3abf0ba4 100644 --- a/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs +++ b/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs @@ -62,7 +62,7 @@ namespace LibationAvalonia.Views visibleLibraryBooks.UpdateTags(dialog.NewTags); } - public async void setDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + public async void setBookDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) { var dialog = new Dialogs.LiberatedStatusBatchManualDialog(); var result = await dialog.ShowDialog(this); @@ -75,7 +75,7 @@ namespace LibationAvalonia.Views this, visibleLibraryBooks, // do not use `$` string interpolation. See impl. - "Are you sure you want to replace downloaded status in {0}?", + "Are you sure you want to replace book downloaded status in {0}?", "Replace downloaded status?"); if (confirmationResult != DialogResult.Yes) @@ -84,7 +84,29 @@ namespace LibationAvalonia.Views visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus); } - public async void setDownloadedAutoToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + public async void setPdfDownloadedManualToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) + { + var dialog = new Dialogs.LiberatedStatusBatchManualDialog(isPdf: true); + var result = await dialog.ShowDialog(this); + if (result != DialogResult.OK) + return; + + var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries(); + + var confirmationResult = await MessageBox.ShowConfirmationDialog( + this, + visibleLibraryBooks, + // do not use `$` string interpolation. See impl. + "Are you sure you want to replace PDF downloaded status in {0}?", + "Replace downloaded status?"); + + if (confirmationResult != DialogResult.Yes) + return; + + visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus); + } + + public async void setDownloadedAutoToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) { var dialog = new Dialogs.LiberatedStatusBatchAutoDialog(); var result = await dialog.ShowDialog(this); diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml b/Source/LibationAvalonia/Views/MainWindow.axaml index 9ce862bf..9640b41b 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml +++ b/Source/LibationAvalonia/Views/MainWindow.axaml @@ -110,7 +110,8 @@ - + + diff --git a/Source/LibationWinForms/Dialogs/LiberatedStatusBatchManualDialog.cs b/Source/LibationWinForms/Dialogs/LiberatedStatusBatchManualDialog.cs index 5b93de2c..b4bd3eec 100644 --- a/Source/LibationWinForms/Dialogs/LiberatedStatusBatchManualDialog.cs +++ b/Source/LibationWinForms/Dialogs/LiberatedStatusBatchManualDialog.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Windows.Forms; using DataLayer; -using Dinah.Core; -using LibationFileManager; namespace LibationWinForms.Dialogs { @@ -19,6 +15,12 @@ namespace LibationWinForms.Dialogs public override string ToString() => Text; } + public LiberatedStatusBatchManualDialog(bool isPdf) : this() + { + if (isPdf) + this.Text = this.Text.Replace("book", "PDF"); + } + public LiberatedStatusBatchManualDialog() { InitializeComponent(); diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index 41458831..335d47f4 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -57,7 +57,9 @@ this.visibleBooksToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem(); this.liberateVisibleToolStripMenuItem_VisibleBooksMenu = new LibationWinForms.FormattableToolStripMenuItem(); this.replaceTagsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.setDownloadedManualToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.setBookDownloadedManualToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.setPdfDownloadedManualToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.setDownloadedAutoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -77,7 +79,6 @@ this.doneRemovingBtn = new System.Windows.Forms.Button(); this.removeBooksBtn = new System.Windows.Forms.Button(); this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl(); - this.setDownloadedAutoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -101,7 +102,7 @@ // filterBtn // this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.filterBtn.Location = new System.Drawing.Point(892, 3); + this.filterBtn.Location = new System.Drawing.Point(884, 3); this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterBtn.Name = "filterBtn"; this.filterBtn.Size = new System.Drawing.Size(88, 27); @@ -118,7 +119,7 @@ this.filterSearchTb.Location = new System.Drawing.Point(195, 5); this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(689, 25); + this.filterSearchTb.Size = new System.Drawing.Size(681, 25); this.filterSearchTb.TabIndex = 1; this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); // @@ -136,7 +137,7 @@ this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); - this.menuStrip1.Size = new System.Drawing.Size(1037, 24); + this.menuStrip1.Size = new System.Drawing.Size(1025, 24); this.menuStrip1.TabIndex = 0; this.menuStrip1.Text = "menuStrip1"; // @@ -316,7 +317,8 @@ this.visibleBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.liberateVisibleToolStripMenuItem_VisibleBooksMenu, this.replaceTagsToolStripMenuItem, - this.setDownloadedManualToolStripMenuItem, + this.setBookDownloadedManualToolStripMenuItem, + this.setPdfDownloadedManualToolStripMenuItem, this.setDownloadedAutoToolStripMenuItem, this.removeToolStripMenuItem}); this.visibleBooksToolStripMenuItem.FormatText = "&Visible Books: {0}"; @@ -328,28 +330,42 @@ // this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.FormatText = "&Liberate: {0}"; this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Name = "liberateVisibleToolStripMenuItem_VisibleBooksMenu"; - this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Size = new System.Drawing.Size(284, 22); + this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Size = new System.Drawing.Size(314, 22); this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Text = "&Liberate: {0}"; this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Click += new System.EventHandler(this.liberateVisible); // // replaceTagsToolStripMenuItem // this.replaceTagsToolStripMenuItem.Name = "replaceTagsToolStripMenuItem"; - this.replaceTagsToolStripMenuItem.Size = new System.Drawing.Size(284, 22); + this.replaceTagsToolStripMenuItem.Size = new System.Drawing.Size(314, 22); this.replaceTagsToolStripMenuItem.Text = "Replace &Tags..."; this.replaceTagsToolStripMenuItem.Click += new System.EventHandler(this.replaceTagsToolStripMenuItem_Click); // - // setDownloadedManualToolStripMenuItem + // setBookDownloadedManualToolStripMenuItem // - this.setDownloadedManualToolStripMenuItem.Name = "setDownloadedManualToolStripMenuItem"; - this.setDownloadedManualToolStripMenuItem.Size = new System.Drawing.Size(284, 22); - this.setDownloadedManualToolStripMenuItem.Text = "Set \'&Downloaded\' status manually..."; - this.setDownloadedManualToolStripMenuItem.Click += new System.EventHandler(this.setDownloadedManualToolStripMenuItem_Click); + this.setBookDownloadedManualToolStripMenuItem.Name = "setBookDownloadedManualToolStripMenuItem"; + this.setBookDownloadedManualToolStripMenuItem.Size = new System.Drawing.Size(314, 22); + this.setBookDownloadedManualToolStripMenuItem.Text = "Set book \'&Downloaded\' status manually..."; + this.setBookDownloadedManualToolStripMenuItem.Click += new System.EventHandler(this.setBookDownloadedManualToolStripMenuItem_Click); + // + // setPdfDownloadedManualToolStripMenuItem + // + this.setPdfDownloadedManualToolStripMenuItem.Name = "setPdfDownloadedManualToolStripMenuItem"; + this.setPdfDownloadedManualToolStripMenuItem.Size = new System.Drawing.Size(314, 22); + this.setPdfDownloadedManualToolStripMenuItem.Text = "Set &PDF \'Downloaded\' status manually..."; + this.setPdfDownloadedManualToolStripMenuItem.Click += new System.EventHandler(this.setPdfDownloadedManualToolStripMenuItem_Click); + // + // setDownloadedAutoToolStripMenuItem + // + this.setDownloadedAutoToolStripMenuItem.Name = "setDownloadedAutoToolStripMenuItem"; + this.setDownloadedAutoToolStripMenuItem.Size = new System.Drawing.Size(314, 22); + this.setDownloadedAutoToolStripMenuItem.Text = "Set book \'Downloaded\' status &automatically..."; + this.setDownloadedAutoToolStripMenuItem.Click += new System.EventHandler(this.setDownloadedAutoToolStripMenuItem_Click); // // removeToolStripMenuItem // this.removeToolStripMenuItem.Name = "removeToolStripMenuItem"; - this.removeToolStripMenuItem.Size = new System.Drawing.Size(284, 22); + this.removeToolStripMenuItem.Size = new System.Drawing.Size(314, 22); this.removeToolStripMenuItem.Text = "&Remove from library..."; this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click); // @@ -402,7 +418,7 @@ this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); this.statusStrip1.ShowItemToolTips = true; - this.statusStrip1.Size = new System.Drawing.Size(1033, 22); + this.statusStrip1.Size = new System.Drawing.Size(1025, 22); this.statusStrip1.TabIndex = 6; this.statusStrip1.Text = "statusStrip1"; // @@ -416,7 +432,7 @@ // springLbl // this.springLbl.Name = "springLbl"; - this.springLbl.Size = new System.Drawing.Size(519, 17); + this.springLbl.Size = new System.Drawing.Size(511, 17); this.springLbl.Spring = true; // // backupsCountsLbl @@ -460,7 +476,7 @@ // this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1); this.splitContainer1.Size = new System.Drawing.Size(1463, 640); - this.splitContainer1.SplitterDistance = 1033; + this.splitContainer1.SplitterDistance = 1025; this.splitContainer1.SplitterWidth = 8; this.splitContainer1.TabIndex = 7; // @@ -479,19 +495,19 @@ this.panel1.Location = new System.Drawing.Point(0, 24); this.panel1.Margin = new System.Windows.Forms.Padding(0); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(1033, 594); + this.panel1.Size = new System.Drawing.Size(1025, 594); this.panel1.TabIndex = 7; // // productsDisplay // - this.productsDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) + this.productsDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.productsDisplay.AutoScroll = true; this.productsDisplay.Location = new System.Drawing.Point(15, 36); this.productsDisplay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.productsDisplay.Name = "productsDisplay"; - this.productsDisplay.Size = new System.Drawing.Size(1007, 555); + this.productsDisplay.Size = new System.Drawing.Size(999, 555); this.productsDisplay.TabIndex = 9; this.productsDisplay.VisibleCountChanged += new System.EventHandler(this.productsDisplay_VisibleCountChanged); this.productsDisplay.RemovableCountChanged += new System.EventHandler(this.productsDisplay_RemovableCountChanged); @@ -501,7 +517,7 @@ // toggleQueueHideBtn // this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.toggleQueueHideBtn.Location = new System.Drawing.Point(985, 3); + this.toggleQueueHideBtn.Location = new System.Drawing.Point(977, 3); this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3); this.toggleQueueHideBtn.Name = "toggleQueueHideBtn"; this.toggleQueueHideBtn.Size = new System.Drawing.Size(33, 27); @@ -542,16 +558,9 @@ this.processBookQueue1.Location = new System.Drawing.Point(0, 0); this.processBookQueue1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.processBookQueue1.Name = "processBookQueue1"; - this.processBookQueue1.Size = new System.Drawing.Size(422, 640); + this.processBookQueue1.Size = new System.Drawing.Size(430, 640); this.processBookQueue1.TabIndex = 0; // - // setDownloadedAutoToolStripMenuItem - // - this.setDownloadedAutoToolStripMenuItem.Name = "setDownloadedAutoToolStripMenuItem"; - this.setDownloadedAutoToolStripMenuItem.Size = new System.Drawing.Size(284, 22); - this.setDownloadedAutoToolStripMenuItem.Text = "Set \'&Downloaded\' status automatically..."; - this.setDownloadedAutoToolStripMenuItem.Click += new System.EventHandler(this.setDownloadedAutoToolStripMenuItem_Click); - // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -618,7 +627,7 @@ private LibationWinForms.FormattableToolStripMenuItem visibleBooksToolStripMenuItem; private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem_VisibleBooksMenu; private System.Windows.Forms.ToolStripMenuItem replaceTagsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem setDownloadedManualToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem setBookDownloadedManualToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem setDownloadedAutoToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem; private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem_LiberateMenu; @@ -629,5 +638,6 @@ private LibationWinForms.GridView.ProductsDisplay productsDisplay; private System.Windows.Forms.Button removeBooksBtn; private System.Windows.Forms.Button doneRemovingBtn; + private System.Windows.Forms.ToolStripMenuItem setPdfDownloadedManualToolStripMenuItem; } } diff --git a/Source/LibationWinForms/Form1.VisibleBooks.cs b/Source/LibationWinForms/Form1.VisibleBooks.cs index f04689be..26619024 100644 --- a/Source/LibationWinForms/Form1.VisibleBooks.cs +++ b/Source/LibationWinForms/Form1.VisibleBooks.cs @@ -90,7 +90,7 @@ namespace LibationWinForms visibleLibraryBooks.UpdateTags(dialog.NewTags); } - private void setDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e) + private void setBookDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e) { var dialog = new LiberatedStatusBatchManualDialog(); var result = dialog.ShowDialog(); @@ -102,7 +102,7 @@ namespace LibationWinForms var confirmationResult = MessageBoxLib.ShowConfirmationDialog( visibleLibraryBooks, // do not use `$` string interpolation. See impl. - "Are you sure you want to replace downloaded status in {0}?", + "Are you sure you want to replace book downloaded status in {0}?", "Replace downloaded status?"); if (confirmationResult != DialogResult.Yes) @@ -111,6 +111,27 @@ namespace LibationWinForms visibleLibraryBooks.UpdateBookStatus(dialog.BookLiberatedStatus); } + private void setPdfDownloadedManualToolStripMenuItem_Click(object sender, EventArgs e) + { + var dialog = new LiberatedStatusBatchManualDialog(isPdf: true); + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + var visibleLibraryBooks = productsDisplay.GetVisible(); + + var confirmationResult = MessageBoxLib.ShowConfirmationDialog( + visibleLibraryBooks, + // do not use `$` string interpolation. See impl. + "Are you sure you want to replace PDF downloaded status in {0}?", + "Replace downloaded status?"); + + if (confirmationResult != DialogResult.Yes) + return; + + visibleLibraryBooks.UpdatePdfStatus(dialog.BookLiberatedStatus); + } + private async void setDownloadedAutoToolStripMenuItem_Click(object sender, EventArgs e) { var dialog = new LiberatedStatusBatchAutoDialog(); diff --git a/Source/LibationWinForms/GridView/LibraryBookEntry.cs b/Source/LibationWinForms/GridView/LibraryBookEntry.cs index c37aef3e..531a941d 100644 --- a/Source/LibationWinForms/GridView/LibraryBookEntry.cs +++ b/Source/LibationWinForms/GridView/LibraryBookEntry.cs @@ -122,7 +122,7 @@ namespace LibationWinForms.GridView NotifyPropertyChanged(nameof(Liberate)); break; case nameof(udi.PdfStatus): - Book.UserDefinedItem.PdfStatus = udi.PdfStatus; + Book.UserDefinedItem.SetPdfStatus(udi.PdfStatus); _pdfStatus = udi.PdfStatus; NotifyPropertyChanged(nameof(Liberate)); break;