New feature: liberate individual book

This commit is contained in:
Robert McRackan 2019-12-05 15:55:46 -05:00
parent 498aeaac3a
commit 0f9f0d9eae
12 changed files with 221 additions and 194 deletions

View File

@ -17,9 +17,9 @@ namespace FileLiberator
/// </summary> /// </summary>
public class BackupBook : IProcessable public class BackupBook : IProcessable
{ {
public event EventHandler<string> Begin; public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> StatusUpdate; public event EventHandler<string> StatusUpdate;
public event EventHandler<string> Completed; public event EventHandler<LibraryBook> Completed;
public DownloadBook DownloadBook { get; } = new DownloadBook(); public DownloadBook DownloadBook { get; } = new DownloadBook();
public DecryptBook DecryptBook { get; } = new DecryptBook(); public DecryptBook DecryptBook { get; } = new DecryptBook();
@ -32,10 +32,7 @@ namespace FileLiberator
// often calls events which prints to forms in the UI context // often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{ {
var productId = libraryBook.Book.AudibleProductId; Begin?.Invoke(this, libraryBook);
var displayMessage = $"[{productId}] {libraryBook.Book.Title}";
Begin?.Invoke(this, displayMessage);
try try
{ {
@ -61,7 +58,7 @@ namespace FileLiberator
} }
finally finally
{ {
Completed?.Invoke(this, displayMessage); Completed?.Invoke(this, libraryBook);
} }
} }
} }

View File

@ -21,7 +21,7 @@ namespace FileLiberator
/// </summary> /// </summary>
public class DecryptBook : IDecryptable public class DecryptBook : IDecryptable
{ {
public event EventHandler<string> Begin; public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> StatusUpdate; public event EventHandler<string> StatusUpdate;
public event EventHandler<string> DecryptBegin; public event EventHandler<string> DecryptBegin;
@ -32,7 +32,7 @@ namespace FileLiberator
public event EventHandler<int> UpdateProgress; public event EventHandler<int> UpdateProgress;
public event EventHandler<string> DecryptCompleted; public event EventHandler<string> DecryptCompleted;
public event EventHandler<string> Completed; public event EventHandler<LibraryBook> Completed;
public bool Validate(LibraryBook libraryBook) public bool Validate(LibraryBook libraryBook)
=> AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId) => AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId)
@ -42,9 +42,7 @@ namespace FileLiberator
// often calls events which prints to forms in the UI context // often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{ {
var displayMessage = $"[{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}"; Begin?.Invoke(this, libraryBook);
Begin?.Invoke(this, displayMessage);
try try
{ {
@ -76,7 +74,7 @@ namespace FileLiberator
} }
finally finally
{ {
Completed?.Invoke(this, displayMessage); Completed?.Invoke(this, libraryBook);
} }
} }

View File

@ -8,8 +8,8 @@ namespace FileLiberator
{ {
public abstract class DownloadableBase : IDownloadable public abstract class DownloadableBase : IDownloadable
{ {
public event EventHandler<string> Begin; public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> Completed; public event EventHandler<LibraryBook> Completed;
public event EventHandler<string> DownloadBegin; public event EventHandler<string> DownloadBegin;
public event EventHandler<DownloadProgress> DownloadProgressChanged; public event EventHandler<DownloadProgress> DownloadProgressChanged;
@ -26,9 +26,7 @@ namespace FileLiberator
// often calls events which prints to forms in the UI context // often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{ {
var displayMessage = $"[{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}"; Begin?.Invoke(this, libraryBook);
Begin?.Invoke(this, displayMessage);
try try
{ {
@ -36,7 +34,7 @@ namespace FileLiberator
} }
finally finally
{ {
Completed?.Invoke(this, displayMessage); Completed?.Invoke(this, libraryBook);
} }
} }

View File

@ -1,11 +1,12 @@
using System; using System;
using Dinah.Core.Net.Http;
namespace FileLiberator namespace FileLiberator
{ {
public interface IDownloadable : IProcessable public interface IDownloadable : IProcessable
{ {
event EventHandler<string> DownloadBegin; event EventHandler<string> DownloadBegin;
event EventHandler<Dinah.Core.Net.Http.DownloadProgress> DownloadProgressChanged; event EventHandler<DownloadProgress> DownloadProgressChanged;
event EventHandler<string> DownloadCompleted; event EventHandler<string> DownloadCompleted;
} }
} }

View File

@ -7,12 +7,12 @@ namespace FileLiberator
{ {
public interface IProcessable public interface IProcessable
{ {
event EventHandler<string> Begin; event EventHandler<LibraryBook> Begin;
/// <summary>General string message to display. DON'T rely on this for success, failure, or control logic</summary> /// <summary>General string message to display. DON'T rely on this for success, failure, or control logic</summary>
event EventHandler<string> StatusUpdate; event EventHandler<string> StatusUpdate;
event EventHandler<string> Completed; event EventHandler<LibraryBook> Completed;
/// <returns>True == Valid</returns> /// <returns>True == Valid</returns>
bool Validate(LibraryBook libraryBook); bool Validate(LibraryBook libraryBook);

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DataLayer; using DataLayer;
using Dinah.Core.ErrorHandling; using Dinah.Core.ErrorHandling;
@ -17,11 +18,34 @@ namespace FileLiberator
/// <returns>Returns either the status handler from the process, or null if all books have been processed</returns> /// <returns>Returns either the status handler from the process, or null if all books have been processed</returns>
public static async Task<StatusHandler> ProcessFirstValidAsync(this IProcessable processable) public static async Task<StatusHandler> ProcessFirstValidAsync(this IProcessable processable)
{ {
var libraryBook = processable.GetNextValid(); var libraryBook = processable.getNextValidBook();
if (libraryBook == null) if (libraryBook == null)
return null; return null;
// this should never happen. check anyway. ProcessFirstValidAsync returning null is the signal that we're done. we can't let another IProcessable accidentally send this commans return await processBookAsync(processable, libraryBook);
}
/// <summary>Process the first valid product. Create default context</summary>
/// <returns>Returns either the status handler from the process, or null if all books have been processed</returns>
public static async Task<StatusHandler> ProcessSingleAsync(this IProcessable processable, string productId)
{
using var context = LibationContext.Create();
var libraryBook = context
.Library
.GetLibrary()
.SingleOrDefault(lb => lb.Book.AudibleProductId == productId);
if (libraryBook == null)
return null;
if (!processable.Validate(libraryBook))
return new StatusHandler { "Validation failed" };
return await processBookAsync(processable, libraryBook);
}
private static async Task<StatusHandler> processBookAsync(IProcessable processable, LibraryBook libraryBook)
{
// this should never happen. check anyway. ProcessFirstValidAsync returning null is the signal that we're done. we can't let another IProcessable accidentally send this command
var status = await processable.ProcessAsync(libraryBook); var status = await processable.ProcessAsync(libraryBook);
if (status == null) if (status == null)
throw new Exception("Processable should never return a null status"); throw new Exception("Processable should never return a null status");
@ -29,7 +53,7 @@ namespace FileLiberator
return status; return status;
} }
public static LibraryBook GetNextValid(this IProcessable processable) private static LibraryBook getNextValidBook(this IProcessable processable)
{ {
var libraryBooks = LibraryQueries.GetLibrary_Flat_NoTracking(); var libraryBooks = LibraryQueries.GetLibrary_Flat_NoTracking();

View File

@ -6,7 +6,18 @@ namespace LibationWinForm.BookLiberation
{ {
public partial class AutomatedBackupsForm : Form public partial class AutomatedBackupsForm : Form
{ {
public bool KeepGoingIsChecked => keepGoingCb.Checked; public bool KeepGoingVisible
{
get => keepGoingCb.Visible;
set => keepGoingCb.Visible = value;
}
public bool KeepGoingChecked => keepGoingCb.Checked;
public bool KeepGoing
=> keepGoingCb.Visible
&& keepGoingCb.Enabled
&& keepGoingCb.Checked;
public AutomatedBackupsForm() public AutomatedBackupsForm()
{ {

View File

@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DataLayer;
using Dinah.Core.ErrorHandling;
using FileLiberator;
namespace LibationWinForm.BookLiberation
{
public class BookLiberatorControllerExamples
{
async Task BackupBookAsync(string productId)
{
using var context = LibationContext.Create();
var libraryBook = context
.Library
.GetLibrary()
.SingleOrDefault(lb => lb.Book.AudibleProductId == productId);
if (libraryBook == null)
return;
var backupBook = new BackupBook();
backupBook.DownloadBook.Completed += SetBackupCountsAsync;
backupBook.DecryptBook.Completed += SetBackupCountsAsync;
await ProcessValidateLibraryBookAsync(backupBook, libraryBook);
}
static async Task<StatusHandler> ProcessValidateLibraryBookAsync(IProcessable processable, LibraryBook libraryBook)
{
if (!processable.Validate(libraryBook))
return new StatusHandler { "Validation failed" };
return await processable.ProcessAsync(libraryBook);
}
// Download First Book (Download encrypted/DRM file)
async Task DownloadFirstBookAsync()
{
var downloadBook = ProcessorAutomationController.GetWiredUpDownloadBook();
downloadBook.Completed += SetBackupCountsAsync;
await downloadBook.ProcessFirstValidAsync();
}
// Decrypt First Book (Remove DRM from downloaded file)
async Task DecryptFirstBookAsync()
{
var decryptBook = ProcessorAutomationController.GetWiredUpDecryptBook();
decryptBook.Completed += SetBackupCountsAsync;
await decryptBook.ProcessFirstValidAsync();
}
// Backup First Book (Decrypt a non-liberated book. Download if needed)
async Task BackupFirstBookAsync()
{
var backupBook = ProcessorAutomationController.GetWiredUpBackupBook();
backupBook.DownloadBook.Completed += SetBackupCountsAsync;
backupBook.DecryptBook.Completed += SetBackupCountsAsync;
await backupBook.ProcessFirstValidAsync();
}
async void SetBackupCountsAsync(object obj, string str) => throw new NotImplementedException();
}
}

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using DataLayer;
using Dinah.Core.ErrorHandling;
using FileLiberator; using FileLiberator;
namespace LibationWinForm.BookLiberation namespace LibationWinForm.BookLiberation
@ -12,37 +14,38 @@ namespace LibationWinForm.BookLiberation
// 1) we can't forget to do it // 1) we can't forget to do it
// 2) we can't accidentally do it mult times becaues we lost track of complexity // 2) we can't accidentally do it mult times becaues we lost track of complexity
// //
public static BackupBook GetWiredUpBackupBook() public static BackupBook GetWiredUpBackupBook(EventHandler<LibraryBook> completedAction = null)
{ {
var backupBook = new BackupBook(); var backupBook = new BackupBook();
backupBook.DownloadBook.Begin += (_, __) => wireUpDownloadable(backupBook.DownloadBook); backupBook.DownloadBook.Begin += (_, __) => wireUpEvents(backupBook.DownloadBook);
backupBook.DecryptBook.Begin += (_, __) => wireUpDecryptable(backupBook.DecryptBook); backupBook.DecryptBook.Begin += (_, __) => wireUpEvents(backupBook.DecryptBook);
backupBook.DownloadPdf.Begin += (_, __) => wireUpDecryptable(backupBook.DecryptBook); backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf);
if (completedAction != null)
{
backupBook.DownloadBook.Completed += completedAction;
backupBook.DecryptBook.Completed += completedAction;
backupBook.DownloadPdf.Completed += completedAction;
}
return backupBook; return backupBook;
} }
public static DecryptBook GetWiredUpDecryptBook()
{ public static DownloadPdf GetWiredUpDownloadPdf(EventHandler<LibraryBook> completedAction = null)
var decryptBook = new DecryptBook();
decryptBook.Begin += (_, __) => wireUpDecryptable(decryptBook);
return decryptBook;
}
public static DownloadBook GetWiredUpDownloadBook()
{
var downloadBook = new DownloadBook();
downloadBook.Begin += (_, __) => wireUpDownloadable(downloadBook);
return downloadBook;
}
public static DownloadPdf GetWiredUpDownloadPdf()
{ {
var downloadPdf = new DownloadPdf(); var downloadPdf = new DownloadPdf();
downloadPdf.Begin += (_, __) => wireUpDownloadable(downloadPdf);
downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf);
if (completedAction != null)
downloadPdf.Completed += completedAction;
return downloadPdf; return downloadPdf;
} }
// subscribed to Begin event because a new form should be created+processed+closed on each iteration // subscribed to Begin event because a new form should be created+processed+closed on each iteration
private static void wireUpDownloadable(IDownloadable downloadable) private static void wireUpEvents(IDownloadable downloadable)
{ {
#region create form #region create form
var downloadDialog = new DownloadForm(); var downloadDialog = new DownloadForm();
@ -82,7 +85,7 @@ namespace LibationWinForm.BookLiberation
// unless we dispose, if the form is created but un-used/never-shown then weird UI stuff can happen // unless we dispose, if the form is created but un-used/never-shown then weird UI stuff can happen
// also, since event unsubscribe occurs on FormClosing and an unused form is never closed, then the events will never be unsubscribed // also, since event unsubscribe occurs on FormClosing and an unused form is never closed, then the events will never be unsubscribed
void dialogDispose(object _, string __) void dialogDispose(object _, object __)
{ {
if (!downloadDialog.IsDisposed) if (!downloadDialog.IsDisposed)
downloadDialog.Dispose(); downloadDialog.Dispose();
@ -106,7 +109,7 @@ namespace LibationWinForm.BookLiberation
} }
// subscribed to Begin event because a new form should be created+processed+closed on each iteration // subscribed to Begin event because a new form should be created+processed+closed on each iteration
private static void wireUpDecryptable(IDecryptable decryptBook) private static void wireUpEvents(IDecryptable decryptBook)
{ {
#region create form #region create form
var decryptDialog = new DecryptForm(); var decryptDialog = new DecryptForm();
@ -153,17 +156,23 @@ namespace LibationWinForm.BookLiberation
#endregion #endregion
} }
public static async Task RunAutomaticDownload(IDownloadable downloadable) public static async Task RunAutomaticDownloadAsync(IDownloadable downloadable)
{
AutomatedBackupsForm automatedBackupsForm = attachToBackupsForm(downloadable);
await runBackupLoopAsync(downloadable, automatedBackupsForm);
}
private static AutomatedBackupsForm attachToBackupsForm(IDownloadable downloadable)
{ {
#region create form #region create form
var automatedBackupsForm = new AutomatedBackupsForm(); var automatedBackupsForm = new AutomatedBackupsForm();
#endregion #endregion
#region define how model actions will affect form behavior #region define how model actions will affect form behavior
void begin(object _, string str) => automatedBackupsForm.AppendText("Begin: " + str); void begin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Begin: {libraryBook.Book}");
void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str); void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str);
// extra line after book is completely finished // extra line after book is completely finished
void completed(object _, string str) => automatedBackupsForm.AppendText("Completed: " + str + Environment.NewLine); void completed(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Completed: {libraryBook.Book}{Environment.NewLine}");
#endregion #endregion
#region subscribe new form to model's events #region subscribe new form to model's events
@ -182,25 +191,38 @@ namespace LibationWinForm.BookLiberation
}; };
#endregion #endregion
await runBackupLoop(downloadable, automatedBackupsForm); return automatedBackupsForm;
} }
public static async Task RunAutomaticBackup(BackupBook backupBook) public static async Task RunAutomaticBackupAsync(BackupBook backupBook)
{
var automatedBackupsForm = attachToBackupsForm(backupBook);
await runBackupLoopAsync(backupBook, automatedBackupsForm);
}
public static async Task RunSingleBackupAsync(BackupBook backupBook, string productId)
{
var automatedBackupsForm = attachToBackupsForm(backupBook);
automatedBackupsForm.KeepGoingVisible = false;
await runSingleBackupAsync(backupBook, automatedBackupsForm, productId);
}
private static AutomatedBackupsForm attachToBackupsForm(BackupBook backupBook)
{ {
#region create form #region create form
var automatedBackupsForm = new AutomatedBackupsForm(); var automatedBackupsForm = new AutomatedBackupsForm();
#endregion #endregion
#region define how model actions will affect form behavior #region define how model actions will affect form behavior
void downloadBookBegin(object _, string str) => automatedBackupsForm.AppendText("DownloadStep_Begin: " + str); void downloadBookBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Download Step, Begin: {libraryBook.Book}");
void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str); void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str);
void downloadBookCompleted(object _, string str) => automatedBackupsForm.AppendText("DownloadStep_Completed: " + str); void downloadBookCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Download Step, Completed: {libraryBook.Book}");
void decryptBookBegin(object _, string str) => automatedBackupsForm.AppendText("DecryptStep_Begin: " + str); void decryptBookBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Decrypt Step, Begin: {libraryBook.Book}");
// extra line after book is completely finished // extra line after book is completely finished
void decryptBookCompleted(object _, string str) => automatedBackupsForm.AppendText("DecryptStep_Completed: " + str + Environment.NewLine); void decryptBookCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}");
void downloadPdfBegin(object _, string str) => automatedBackupsForm.AppendText("PdfStep_Begin: " + str); void downloadPdfBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"PDF Step, Begin: {libraryBook.Book}");
// extra line after book is completely finished // extra line after book is completely finished
void downloadPdfCompleted(object _, string str) => automatedBackupsForm.AppendText("PdfStep_Completed: " + str + Environment.NewLine); void downloadPdfCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}");
#endregion #endregion
#region subscribe new form to model's events #region subscribe new form to model's events
@ -231,42 +253,23 @@ namespace LibationWinForm.BookLiberation
}; };
#endregion #endregion
await runBackupLoop(backupBook, automatedBackupsForm); return automatedBackupsForm;
} }
// automated backups looper feels like a composible IProcessable: logic, UI, begin + process child + end // automated backups looper feels like a composible IProcessable: logic, UI, begin + process child + end
// however the process step doesn't follow the pattern: Validate(product) + Process(product) // however the process step doesn't follow the pattern: Validate(product) + Process(product)
private static async Task runBackupLoop(IProcessable processable, AutomatedBackupsForm automatedBackupsForm) private static async Task runBackupLoopAsync(IProcessable processable, AutomatedBackupsForm automatedBackupsForm)
{ {
automatedBackupsForm.Show(); automatedBackupsForm.Show();
try try
{ {
do var shouldContinue = true;
while (shouldContinue)
{ {
var statusHandler = await processable.ProcessFirstValidAsync(); var statusHandler = await processable.ProcessFirstValidAsync();
shouldContinue = validateStatus(statusHandler, automatedBackupsForm);
if (statusHandler == null)
{
automatedBackupsForm.AppendText("Done. All books have been processed");
break;
} }
if (statusHandler.HasErrors)
{
automatedBackupsForm.AppendText("ERROR. All books have not been processed. Most recent valid book: processing failed");
foreach (var errorMessage in statusHandler.Errors)
automatedBackupsForm.AppendText(errorMessage);
break;
}
if (!automatedBackupsForm.KeepGoingIsChecked)
{
automatedBackupsForm.AppendText("'Keep going' is unchecked");
break;
}
}
while (automatedBackupsForm.KeepGoingIsChecked);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -275,5 +278,48 @@ namespace LibationWinForm.BookLiberation
automatedBackupsForm.FinalizeUI(); automatedBackupsForm.FinalizeUI();
} }
private static async Task runSingleBackupAsync(IProcessable processable, AutomatedBackupsForm automatedBackupsForm, string productId)
{
automatedBackupsForm.Show();
try
{
var statusHandler = await processable.ProcessSingleAsync(productId);
validateStatus(statusHandler, automatedBackupsForm);
}
catch (Exception ex)
{
automatedBackupsForm.AppendError(ex);
}
automatedBackupsForm.FinalizeUI();
}
private static bool validateStatus(StatusHandler statusHandler, AutomatedBackupsForm automatedBackupsForm)
{
if (statusHandler == null)
{
automatedBackupsForm.AppendText("Done. All books have been processed");
return false;
}
if (statusHandler.HasErrors)
{
automatedBackupsForm.AppendText("ERROR. All books have not been processed. Most recent valid book: processing failed");
foreach (var errorMessage in statusHandler.Errors)
automatedBackupsForm.AppendText(errorMessage);
return false;
}
if (!automatedBackupsForm.KeepGoing)
{
if (automatedBackupsForm.KeepGoingVisible && !automatedBackupsForm.KeepGoingChecked)
automatedBackupsForm.AppendText("'Keep going' is unchecked");
return false;
}
return true;
}
} }
} }

View File

@ -59,7 +59,7 @@ namespace LibationWinForm
backupsCountsLbl.Text = "[Calculating backed up book quantities]"; backupsCountsLbl.Text = "[Calculating backed up book quantities]";
pdfsCountsLbl.Text = "[Calculating backed up PDFs]"; pdfsCountsLbl.Text = "[Calculating backed up PDFs]";
setBackupCounts(); setBackupCounts(null, null);
} }
} }
@ -86,11 +86,13 @@ namespace LibationWinForm
{ {
gridPanel.Controls.Remove(currProductsGrid); gridPanel.Controls.Remove(currProductsGrid);
currProductsGrid.VisibleCountChanged -= setVisibleCount; currProductsGrid.VisibleCountChanged -= setVisibleCount;
currProductsGrid.BackupCountsChanged -= setBackupCounts;
currProductsGrid.Dispose(); currProductsGrid.Dispose();
} }
currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill }; currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill };
currProductsGrid.VisibleCountChanged += setVisibleCount; currProductsGrid.VisibleCountChanged += setVisibleCount;
currProductsGrid.BackupCountsChanged += setBackupCounts;
gridPanel.Controls.Add(currProductsGrid); gridPanel.Controls.Add(currProductsGrid);
currProductsGrid.Display(); currProductsGrid.Display();
} }
@ -103,7 +105,7 @@ namespace LibationWinForm
#endregion #endregion
#region bottom: backup counts #region bottom: backup counts
private void setBackupCounts() private void setBackupCounts(object _, object __)
{ {
var books = LibraryQueries.GetLibrary_Flat_NoTracking() var books = LibraryQueries.GetLibrary_Flat_NoTracking()
.Select(sp => sp.Book) .Select(sp => sp.Book)
@ -233,33 +235,25 @@ namespace LibationWinForm
MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}"); MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
// update backup counts if we have new library items
if (newAdded > 0)
setBackupCounts();
if (totalProcessed > 0) if (totalProcessed > 0)
reloadGrid(); reloadGrid();
} }
#endregion #endregion
#region liberate menu #region liberate menu
private void setBackupCounts(object _, string __) => setBackupCounts();
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
{ {
var backupBook = BookLiberation.ProcessorAutomationController.GetWiredUpBackupBook(); var backupBook = BookLiberation.ProcessorAutomationController.GetWiredUpBackupBook(updateGridRow);
backupBook.DownloadBook.Completed += setBackupCounts; await BookLiberation.ProcessorAutomationController.RunAutomaticBackupAsync(backupBook);
backupBook.DecryptBook.Completed += setBackupCounts;
backupBook.DownloadPdf.Completed += setBackupCounts;
await BookLiberation.ProcessorAutomationController.RunAutomaticBackup(backupBook);
} }
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
{ {
var downloadPdf = BookLiberation.ProcessorAutomationController.GetWiredUpDownloadPdf(); var downloadPdf = BookLiberation.ProcessorAutomationController.GetWiredUpDownloadPdf(updateGridRow);
downloadPdf.Completed += setBackupCounts; await BookLiberation.ProcessorAutomationController.RunAutomaticDownloadAsync(downloadPdf);
await BookLiberation.ProcessorAutomationController.RunAutomaticDownload(downloadPdf);
} }
private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
#endregion #endregion
#region quick filters menu #region quick filters menu

View File

@ -27,6 +27,7 @@ namespace LibationWinForm
public partial class ProductsGrid : UserControl public partial class ProductsGrid : UserControl
{ {
public event EventHandler<int> VisibleCountChanged; public event EventHandler<int> VisibleCountChanged;
public event EventHandler BackupCountsChanged;
private const string EDIT_TAGS = "Edit Tags"; private const string EDIT_TAGS = "Edit Tags";
private const string LIBERATE = "Liberate"; private const string LIBERATE = "Liberate";
@ -77,14 +78,19 @@ namespace LibationWinForm
e.Value = value; e.Value = value;
} }
private void hiddenFormatting(object _, DataGridViewCellFormattingEventArgs e) private void hiddenFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{ {
var dgv = (DataGridView)sender;
// no action needed for buttons
if (e.RowIndex < 0 || dgv.Columns[e.ColumnIndex] is DataGridViewButtonColumn)
return;
var isHidden = GetGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden"); var isHidden = GetGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden");
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Style dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Style
= isHidden = isHidden
? new DataGridViewCellStyle { ForeColor = Color.LightGray } ? new DataGridViewCellStyle { ForeColor = Color.LightGray }
: dataGridView.DefaultCellStyle; : dgv.DefaultCellStyle;
} }
#endregion #endregion
@ -107,16 +113,30 @@ namespace LibationWinForm
dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = GetGridEntry(e.RowIndex).Download_Status; dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = GetGridEntry(e.RowIndex).Download_Status;
} }
private void liberate_Click(object sender, DataGridViewCellEventArgs e) private async void liberate_Click(object sender, DataGridViewCellEventArgs e)
{ {
var dgv = (DataGridView)sender; var dgv = (DataGridView)sender;
if (!isColumnValid(dgv, e.RowIndex, e.ColumnIndex, LIBERATE)) if (!isColumnValid(dgv, e.RowIndex, e.ColumnIndex, LIBERATE))
return; return;
var productId = GetGridEntry(e.RowIndex).GetBook().AudibleProductId;
var backupBook = BookLiberation.ProcessorAutomationController.GetWiredUpBackupBook((_, __) => RefreshRow(productId));
await BookLiberation.ProcessorAutomationController.RunSingleBackupAsync(backupBook, productId);
} }
#endregion #endregion
public void RefreshRow(string productId)
{
var rowId = GetRowId((ge) => ge.GetBook().AudibleProductId == productId);
// update cells incl Liberate button text
dataGridView.InvalidateRow(rowId);
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
}
#region tag buttons #region tag buttons
private void addEditTagsButtons() private void addEditTagsButtons()
{ {
@ -279,6 +299,8 @@ namespace LibationWinForm
// FILTER // FILTER
// //
filter(); filter();
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
} }
#region filter #region filter

View File

@ -1,6 +1,7 @@
-- begin VERSIONING --------------------------------------------------------------------------------------------------------------------- -- begin VERSIONING ---------------------------------------------------------------------------------------------------------------------
https://github.com/rmcrackan/Libation/releases https://github.com/rmcrackan/Libation/releases
v3.1-beta.9 : New feature: liberate individual book
v3.1-beta.8 : Bugfix: decrypt file conflict v3.1-beta.8 : Bugfix: decrypt file conflict
v3.1-beta.7 : Bugfix: decrypt book with no author v3.1-beta.7 : Bugfix: decrypt book with no author
v3.1-beta.6 : Improved logging v3.1-beta.6 : Improved logging