diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 33159e83..7cda4a7d 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.120 + 5.4.9.140 diff --git a/LibationWinForms/AsyncNotifyPropertyChanged.cs b/LibationWinForms/AsyncNotifyPropertyChanged.cs index 9de0911d..1c8c8775 100644 --- a/LibationWinForms/AsyncNotifyPropertyChanged.cs +++ b/LibationWinForms/AsyncNotifyPropertyChanged.cs @@ -4,34 +4,34 @@ using System.Threading; namespace LibationWinForms { - public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; - private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; - private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; + public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; - protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - { - var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); + protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + { + var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); - if (InvokeRequired) - { - SyncContext.Post( - PostPropertyChangedCallback, - new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); - } - else - { - OnPropertyChanged(propertyChangedArgs); - } - } - private void PostPropertyChangedCallback(object asyncArgs) - { - var e = asyncArgs as AsyncCompletedEventArgs; + if (InvokeRequired) + { + SyncContext.Post( + PostPropertyChangedCallback, + new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); + } + else + { + OnPropertyChanged(propertyChangedArgs); + } + } + private void PostPropertyChangedCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; - OnPropertyChanged(e.UserState as PropertyChangedEventArgs); - } - private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); - } + OnPropertyChanged(e.UserState as PropertyChangedEventArgs); + } + private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); + } } diff --git a/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs b/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs index 95261858..85842e28 100644 --- a/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs +++ b/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs @@ -1,34 +1,34 @@ -using System; +using Dinah.Core.Windows.Forms; +using System; using System.Windows.Forms; -using Dinah.Core.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class AutomatedBackupsForm : Form - { - public bool KeepGoingChecked => keepGoingCb.Checked; + public partial class AutomatedBackupsForm : Form + { + public bool KeepGoingChecked => keepGoingCb.Checked; - public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked; + public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked; - public AutomatedBackupsForm() - { - InitializeComponent(); - } + public AutomatedBackupsForm() + { + InitializeComponent(); + } - public void WriteLine(string text) - { - if (!IsDisposed) - logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); - } + public void WriteLine(string text) + { + if (!IsDisposed) + logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); + } public void FinalizeUI() - { - keepGoingCb.Enabled = false; + { + keepGoingCb.Enabled = false; - if (!IsDisposed) - logTb.AppendText(""); - } + if (!IsDisposed) + logTb.AppendText(""); + } - private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false; - } + private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false; + } } diff --git a/LibationWinForms/BookLiberation/DecryptForm.cs b/LibationWinForms/BookLiberation/DecryptForm.cs index e6919b16..baaca4c7 100644 --- a/LibationWinForms/BookLiberation/DecryptForm.cs +++ b/LibationWinForms/BookLiberation/DecryptForm.cs @@ -1,54 +1,54 @@ -using System; +using Dinah.Core.Windows.Forms; +using System; using System.Windows.Forms; -using Dinah.Core.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DecryptForm : Form - { + public partial class DecryptForm : Form + { public DecryptForm() => InitializeComponent(); - // book info - private string title; - private string authorNames; - private string narratorNames; + // book info + private string title; + private string authorNames; + private string narratorNames; - public void SetTitle(string actionName, string title) - { - this.UIThread(() => this.Text = actionName + " " + title); - this.title = title; - updateBookInfo(); - } - public void SetAuthorNames(string authorNames) - { - this.authorNames = authorNames; - updateBookInfo(); - } - public void SetNarratorNames(string narratorNames) - { - this.narratorNames = narratorNames; - updateBookInfo(); - } + public void SetTitle(string actionName, string title) + { + this.UIThread(() => this.Text = actionName + " " + title); + this.title = title; + updateBookInfo(); + } + public void SetAuthorNames(string authorNames) + { + this.authorNames = authorNames; + updateBookInfo(); + } + public void SetNarratorNames(string narratorNames) + { + this.narratorNames = narratorNames; + updateBookInfo(); + } - // thread-safe UI updates - private void updateBookInfo() - => bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); + // thread-safe UI updates + private void updateBookInfo() + => bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); - public void SetCoverImage(System.Drawing.Image coverImage) - => pictureBox1.UIThread(() => pictureBox1.Image = coverImage); + public void SetCoverImage(System.Drawing.Image coverImage) + => pictureBox1.UIThread(() => pictureBox1.Image = coverImage); - public void UpdateProgress(int percentage) - { - if (percentage == 0) - updateRemainingTime(0); - else - progressBar1.UIThread(() => progressBar1.Value = percentage); - } + public void UpdateProgress(int percentage) + { + if (percentage == 0) + updateRemainingTime(0); + else + progressBar1.UIThread(() => progressBar1.Value = percentage); + } - public void UpdateRemainingTime(TimeSpan remaining) - => updateRemainingTime((int)remaining.TotalSeconds); + public void UpdateRemainingTime(TimeSpan remaining) + => updateRemainingTime((int)remaining.TotalSeconds); - private void updateRemainingTime(int remaining) - => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); - } + private void updateRemainingTime(int remaining) + => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); + } } diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index ffdf28e0..e8bcde73 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -1,59 +1,59 @@ -using System; +using Dinah.Core.Windows.Forms; +using System; using System.Windows.Forms; -using Dinah.Core.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DownloadForm : Form - { - public DownloadForm() - { - InitializeComponent(); + public partial class DownloadForm : Form + { + public DownloadForm() + { + InitializeComponent(); - progressLbl.Text = ""; - filenameLbl.Text = ""; - } + progressLbl.Text = ""; + filenameLbl.Text = ""; + } - // thread-safe UI updates - public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title); + // thread-safe UI updates + public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title); - public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive) - { - // this won't happen with download file. it will happen with download string - if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0) - return; + public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive) + { + // this won't happen with download file. it will happen with download string + if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0) + return; - progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}"); + progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}"); - var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0; - var i = int.Parse(Math.Truncate(d).ToString()); - progressBar1.UIThread(() => progressBar1.Value = i); + var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0; + var i = int.Parse(Math.Truncate(d).ToString()); + progressBar1.UIThread(() => progressBar1.Value = i); - lastDownloadProgress = DateTime.Now; - } + lastDownloadProgress = DateTime.Now; + } - #region timer - private Timer timer { get; } = new Timer { Interval = 1000 }; - private void DownloadForm_Load(object sender, EventArgs e) - { - timer.Tick += new EventHandler(timer_Tick); - timer.Start(); - } - private DateTime lastDownloadProgress = DateTime.Now; - private void timer_Tick(object sender, EventArgs e) - { - // if no update in the last 30 seconds, display frozen label - lastUpdateLbl.UIThread(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now); - if (lastUpdateLbl.Visible) - { - var diff = DateTime.Now - lastDownloadProgress; - var min = (int)diff.TotalMinutes; - var minText = min > 0 ? $"{min}min " : ""; + #region timer + private Timer timer { get; } = new Timer { Interval = 1000 }; + private void DownloadForm_Load(object sender, EventArgs e) + { + timer.Tick += new EventHandler(timer_Tick); + timer.Start(); + } + private DateTime lastDownloadProgress = DateTime.Now; + private void timer_Tick(object sender, EventArgs e) + { + // if no update in the last 30 seconds, display frozen label + lastUpdateLbl.UIThread(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now); + if (lastUpdateLbl.Visible) + { + var diff = DateTime.Now - lastDownloadProgress; + var min = (int)diff.TotalMinutes; + var minText = min > 0 ? $"{min}min " : ""; - lastUpdateLbl.UIThread(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago"); - } - } - private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop(); - #endregion - } + lastUpdateLbl.UIThread(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago"); + } + } + private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop(); + #endregion + } } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index f378bae7..7496d81a 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -1,505 +1,506 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using DataLayer; +using DataLayer; using Dinah.Core; using Dinah.Core.ErrorHandling; using Dinah.Core.Windows.Forms; using FileLiberator; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; namespace LibationWinForms.BookLiberation { - // decouple serilog and form. include convenience factory method - public class LogMe - { - public event EventHandler LogInfo; - public event EventHandler LogErrorString; - public event EventHandler<(Exception, string)> LogError; + // decouple serilog and form. include convenience factory method + public class LogMe + { + public event EventHandler LogInfo; + public event EventHandler LogErrorString; + public event EventHandler<(Exception, string)> LogError; - private LogMe() - { - LogInfo += (_, text) => Serilog.Log.Logger.Information($"Automated backup: {text}"); - LogErrorString += (_, text) => Serilog.Log.Logger.Error(text); - LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error"); - } + private LogMe() + { + LogInfo += (_, text) => Serilog.Log.Logger.Information($"Automated backup: {text}"); + LogErrorString += (_, text) => Serilog.Log.Logger.Error(text); + LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error"); + } - public static LogMe RegisterForm(AutomatedBackupsForm form = null) - { - var logMe = new LogMe(); + public static LogMe RegisterForm(AutomatedBackupsForm form = null) + { + var logMe = new LogMe(); - if (form is null) - return logMe; + if (form is null) + return logMe; - logMe.LogInfo += (_, text) => form?.WriteLine(text); + logMe.LogInfo += (_, text) => form?.WriteLine(text); - logMe.LogErrorString += (_, text) => form?.WriteLine(text); - - logMe.LogError += (_, tuple) => - { - form?.WriteLine(tuple.Item2 ?? "Automated backup: error"); - form?.WriteLine("ERROR: " + tuple.Item1.Message); - }; - - return logMe; - } - - public void Info(string text) => LogInfo?.Invoke(this, text); - public void Error(string text) => LogErrorString?.Invoke(this, text); - public void Error(Exception ex, string text = null) => LogError?.Invoke(this, (ex, text)); - } - - public static class ProcessorAutomationController - { - public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); - - var backupBook = getWiredUpBackupBook(completedAction); - - (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook); - - // continue even if libraryBook is null. we'll display even that in the processing box - await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync(); - - unsubscribeEvents(); - } - - public static async Task BackupAllBooksAsync(EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); - - var backupBook = getWiredUpBackupBook(completedAction); - var automatedBackupsForm = new AutomatedBackupsForm(); - - (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook, automatedBackupsForm); - - await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); - - unsubscribeEvents(); - } - - public static async Task ConvertAllBooksAsync() - { - Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); - - var convertBook = new ConvertToMp3(); - convertBook.Begin += (_, l) => wireUpEvents(convertBook, l, "Converting"); - - var automatedBackupsForm = new AutomatedBackupsForm(); - - var logMe = LogMe.RegisterForm(automatedBackupsForm); - - void statusUpdate(object _, string str) => logMe.Info("- " + str); - void convertBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Begin: {libraryBook.Book}"); - void convertBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - convertBook.Begin += convertBookBegin; - convertBook.StatusUpdate += statusUpdate; - convertBook.Completed += convertBookCompleted; - - await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); - - convertBook.Begin -= convertBookBegin; - convertBook.StatusUpdate -= statusUpdate; - convertBook.Completed -= convertBookCompleted; - } - - private static BackupBook getWiredUpBackupBook(EventHandler completedAction) - { - var backupBook = new BackupBook(); - - backupBook.DownloadDecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DownloadDecryptBook, l); - backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf); - - if (completedAction != null) - { - backupBook.DownloadDecryptBook.Completed += completedAction; - backupBook.DownloadPdf.Completed += completedAction; - } - - return backupBook; - } - - private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) - { - #region create logger - var logMe = LogMe.RegisterForm(automatedBackupsForm); - #endregion - - #region define how model actions will affect form behavior - void statusUpdate(object _, string str) => logMe.Info("- " + str); - void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}"); - // extra line after book is completely finished - void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - void downloadPdfBegin(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Begin: {libraryBook.Book}"); - // extra line after book is completely finished - void downloadPdfCompleted(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion - - #region subscribe new form to model's events - backupBook.DownloadDecryptBook.Begin += decryptBookBegin; - backupBook.DownloadDecryptBook.StatusUpdate += statusUpdate; - backupBook.DownloadDecryptBook.Completed += decryptBookCompleted; - backupBook.DownloadPdf.Begin += downloadPdfBegin; - backupBook.DownloadPdf.StatusUpdate += statusUpdate; - backupBook.DownloadPdf.Completed += downloadPdfCompleted; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - Action unsubscribe = () => - { - backupBook.DownloadDecryptBook.Begin -= decryptBookBegin; - backupBook.DownloadDecryptBook.StatusUpdate -= statusUpdate; - backupBook.DownloadDecryptBook.Completed -= decryptBookCompleted; - backupBook.DownloadPdf.Begin -= downloadPdfBegin; - backupBook.DownloadPdf.StatusUpdate -= statusUpdate; - backupBook.DownloadPdf.Completed -= downloadPdfCompleted; - }; - #endregion - - return (unsubscribe, logMe); - } - - public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); - - var downloadPdf = getWiredUpDownloadPdf(completedAction); - - (AutomatedBackupsForm automatedBackupsForm, LogMe logMe) = attachToBackupsForm(downloadPdf); - await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); - } - - private static DownloadPdf getWiredUpDownloadPdf(EventHandler completedAction) - { - var downloadPdf = new DownloadPdf(); - - downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf); - - if (completedAction != null) - downloadPdf.Completed += completedAction; - - return downloadPdf; - } - - public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) - { - var downloadDialog = new DownloadForm(); - downloadDialog.UpdateFilename(destination); - downloadDialog.Show(); - - new System.Threading.Thread(() => - { - var downloadFile = new DownloadFile(); - - downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() => - downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive) - ); - downloadFile.DownloadCompleted += (_, __) => downloadDialog.UIThread(() => - { - downloadDialog.Close(); - if (showDownloadCompletedDialog) - MessageBox.Show("File downloaded"); - }); - - downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); - }) - { IsBackground = true } - .Start(); - } - - // subscribed to Begin event because a new form should be created+processed+closed on each iteration - private static void wireUpEvents(IDownloadableProcessable downloadable) - { - #region create form - var downloadDialog = new DownloadForm(); - #endregion - - // extra complexity for wiring up download form: - // case 1: download is needed - // dialog created. subscribe to events - // downloadable.DownloadBegin fires. shows dialog - // downloadable.DownloadCompleted fires. closes dialog. which fires FormClosing, FormClosed, Disposed - // Disposed unsubscribe from events - // case 2: download is not needed - // dialog created. subscribe to events - // dialog is never shown nor closed - // downloadable.Completed fires. disposes dialog and unsubscribes from events - - #region define how model actions will affect form behavior - void downloadBegin(object _, string str) - { - downloadDialog.UpdateFilename(str); - downloadDialog.Show(); - } - - // close form on DOWNLOAD completed, not final Completed. Else for BackupBook this form won't close until DECRYPT is also complete - void fileDownloadCompleted(object _, string __) => downloadDialog.Close(); - - void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress) - => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive); - - void unsubscribe(object _ = null, EventArgs __ = null) - { - downloadable.DownloadBegin -= downloadBegin; - downloadable.DownloadCompleted -= fileDownloadCompleted; - downloadable.DownloadProgressChanged -= downloadProgressChanged; - downloadable.Completed -= dialogDispose; - } - - // 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 - void dialogDispose(object _, object __) - { - if (!downloadDialog.IsDisposed) - downloadDialog.Dispose(); - } - #endregion - - #region subscribe new form to model's events - downloadable.DownloadBegin += downloadBegin; - downloadable.DownloadCompleted += fileDownloadCompleted; - downloadable.DownloadProgressChanged += downloadProgressChanged; - downloadable.Completed += dialogDispose; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - // FormClosing is more UI safe but won't fire unless the form is shown and closed - // if form was shown, Disposed will fire for FormClosing, FormClosed, and Disposed - // if not shown, it will still fire for Disposed - downloadDialog.Disposed += unsubscribe; - #endregion - } - - // subscribed to Begin event because a new form should be created+processed+closed on each iteration - private static void wireUpEvents(IDecryptable decryptBook, LibraryBook libraryBook, string actionName = "Decrypting") - { - #region create form - var decryptDialog = new DecryptForm(); - #endregion - - #region Set initially displayed book properties from library info. - decryptDialog.SetTitle(actionName, libraryBook.Book.Title); - decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); - decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); - decryptDialog.SetCoverImage( - WindowsDesktopUtilities.WinAudibleImageServer.GetImage( - libraryBook.Book.PictureId, - FileManager.PictureSize._80x80 - )); - #endregion - - #region define how model actions will affect form behavior - void decryptBegin(object _, string __) => decryptDialog.Show(); - - void titleDiscovered(object _, string title) => decryptDialog.SetTitle(actionName, title); - void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors); - void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); - void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(Dinah.Core.Drawing.ImageReader.ToImage(coverBytes)); - void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); - void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); - void decryptCompleted(object _, string __) => decryptDialog.Close(); - - void requestCoverArt(object _, Action setCoverArtDelegate) - { - var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); - (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); - - if (isDefault) - { - void pictureCached(object _, string pictureId) - { - if (pictureId == libraryBook.Book.PictureId) - { - FileManager.PictureStorage.PictureCached -= pictureCached; - - var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); - (_, picture) = FileManager.PictureStorage.GetPicture(picDef); - - setCoverArtDelegate(picture); - } - }; - FileManager.PictureStorage.PictureCached += pictureCached; - } - else - setCoverArtDelegate(picture); - } - #endregion - - #region subscribe new form to model's events - decryptBook.DecryptBegin += decryptBegin; - - decryptBook.TitleDiscovered += titleDiscovered; - decryptBook.AuthorsDiscovered += authorsDiscovered; - decryptBook.NarratorsDiscovered += narratorsDiscovered; - decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered; - decryptBook.UpdateProgress += updateProgress; - decryptBook.UpdateRemainingTime += updateRemainingTime; - decryptBook.RequestCoverArt += requestCoverArt; - - decryptBook.DecryptCompleted += decryptCompleted; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - decryptDialog.FormClosing += (_, __) => - { - decryptBook.DecryptBegin -= decryptBegin; - - decryptBook.TitleDiscovered -= titleDiscovered; - decryptBook.AuthorsDiscovered -= authorsDiscovered; - decryptBook.NarratorsDiscovered -= narratorsDiscovered; - decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered; - decryptBook.UpdateProgress -= updateProgress; - decryptBook.UpdateRemainingTime -= updateRemainingTime; - decryptBook.RequestCoverArt -= requestCoverArt; - - decryptBook.DecryptCompleted -= decryptCompleted; - decryptBook.Cancel(); - }; - #endregion - } - - private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable) - { - #region create form and logger - var automatedBackupsForm = new AutomatedBackupsForm(); - var logMe = LogMe.RegisterForm(automatedBackupsForm); - #endregion - - #region define how model actions will affect form behavior - void begin(object _, LibraryBook libraryBook) => logMe.Info($"Begin: {libraryBook.Book}"); - void statusUpdate(object _, string str) => logMe.Info("- " + str); - // extra line after book is completely finished - void completed(object _, LibraryBook libraryBook) => logMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion - - #region subscribe new form to model's events - downloadable.Begin += begin; - downloadable.StatusUpdate += statusUpdate; - downloadable.Completed += completed; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - automatedBackupsForm.FormClosing += (_, __) => - { - downloadable.Begin -= begin; - downloadable.StatusUpdate -= statusUpdate; - downloadable.Completed -= completed; - }; - #endregion - - return (automatedBackupsForm, logMe); - } - } - - abstract class BackupRunner - { - protected LogMe LogMe { get; } - protected IProcessable Processable { get; } - protected AutomatedBackupsForm AutomatedBackupsForm { get; } - - protected BackupRunner(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm = null) - { - LogMe = logMe; - Processable = processable; - AutomatedBackupsForm = automatedBackupsForm; - } - - protected abstract Task RunAsync(); - - protected abstract string SkipDialogText { get; } - protected abstract MessageBoxButtons SkipDialogButtons { get; } - protected abstract DialogResult CreateSkipFileResult { get; } - - public async Task RunBackupAsync() - { - AutomatedBackupsForm?.Show(); - - try - { - await RunAsync(); - } - catch (Exception ex) - { - LogMe.Error(ex); - } - - AutomatedBackupsForm?.FinalizeUI(); - LogMe.Info("DONE"); - } - - protected async Task ProcessOneAsync(Func> func, LibraryBook libraryBook) - { - string logMessage; - - try - { - var statusHandler = await func(libraryBook); - - if (statusHandler.IsSuccess) - return true; - - foreach (var errorMessage in statusHandler.Errors) - LogMe.Error(errorMessage); - - logMessage = statusHandler.Errors.Aggregate((a, b) => $"{a}\r\n{b}"); - } - catch (Exception ex) - { - LogMe.Error(ex); - - logMessage = ex.Message + "\r\n|\r\n" + ex.StackTrace; - } - - LogMe.Error("ERROR. All books have not been processed. Most recent book: processing failed"); - - string details; - try - { - static string trunc(string str) - => string.IsNullOrWhiteSpace(str) ? "[empty]" - : (str.Length > 50) ? $"{str.Truncate(47)}..." - : str; - - details = + logMe.LogErrorString += (_, text) => form?.WriteLine(text); + + logMe.LogError += (_, tuple) => + { + form?.WriteLine(tuple.Item2 ?? "Automated backup: error"); + form?.WriteLine("ERROR: " + tuple.Item1.Message); + }; + + return logMe; + } + + public void Info(string text) => LogInfo?.Invoke(this, text); + public void Error(string text) => LogErrorString?.Invoke(this, text); + public void Error(Exception ex, string text = null) => LogError?.Invoke(this, (ex, text)); + } + + public static class ProcessorAutomationController + { + public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); + + var backupBook = getWiredUpBackupBook(completedAction); + + (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook); + + // continue even if libraryBook is null. we'll display even that in the processing box + await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync(); + + unsubscribeEvents(); + } + + public static async Task BackupAllBooksAsync(EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); + + var backupBook = getWiredUpBackupBook(completedAction); + var automatedBackupsForm = new AutomatedBackupsForm(); + + (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook, automatedBackupsForm); + + await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); + + unsubscribeEvents(); + } + + public static async Task ConvertAllBooksAsync() + { + Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); + + var convertBook = new ConvertToMp3(); + convertBook.Begin += (_, l) => wireUpEvents(convertBook, l, "Converting"); + + var automatedBackupsForm = new AutomatedBackupsForm(); + + var logMe = LogMe.RegisterForm(automatedBackupsForm); + + void statusUpdate(object _, string str) => logMe.Info("- " + str); + void convertBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Begin: {libraryBook.Book}"); + void convertBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + convertBook.Begin += convertBookBegin; + convertBook.StatusUpdate += statusUpdate; + convertBook.Completed += convertBookCompleted; + + await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); + + convertBook.Begin -= convertBookBegin; + convertBook.StatusUpdate -= statusUpdate; + convertBook.Completed -= convertBookCompleted; + } + + private static BackupBook getWiredUpBackupBook(EventHandler completedAction) + { + var backupBook = new BackupBook(); + + backupBook.DownloadDecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DownloadDecryptBook, l); + backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf); + + if (completedAction != null) + { + backupBook.DownloadDecryptBook.Completed += completedAction; + backupBook.DownloadPdf.Completed += completedAction; + } + + return backupBook; + } + + private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) + { + #region create logger + var logMe = LogMe.RegisterForm(automatedBackupsForm); + #endregion + + #region define how model actions will affect form behavior + void statusUpdate(object _, string str) => logMe.Info("- " + str); + void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}"); + // extra line after book is completely finished + void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + void downloadPdfBegin(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Begin: {libraryBook.Book}"); + // extra line after book is completely finished + void downloadPdfCompleted(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + #endregion + + #region subscribe new form to model's events + backupBook.DownloadDecryptBook.Begin += decryptBookBegin; + backupBook.DownloadDecryptBook.StatusUpdate += statusUpdate; + backupBook.DownloadDecryptBook.Completed += decryptBookCompleted; + backupBook.DownloadPdf.Begin += downloadPdfBegin; + backupBook.DownloadPdf.StatusUpdate += statusUpdate; + backupBook.DownloadPdf.Completed += downloadPdfCompleted; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + Action unsubscribe = () => + { + backupBook.DownloadDecryptBook.Begin -= decryptBookBegin; + backupBook.DownloadDecryptBook.StatusUpdate -= statusUpdate; + backupBook.DownloadDecryptBook.Completed -= decryptBookCompleted; + backupBook.DownloadPdf.Begin -= downloadPdfBegin; + backupBook.DownloadPdf.StatusUpdate -= statusUpdate; + backupBook.DownloadPdf.Completed -= downloadPdfCompleted; + }; + #endregion + + return (unsubscribe, logMe); + } + + public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); + + var downloadPdf = getWiredUpDownloadPdf(completedAction); + + (AutomatedBackupsForm automatedBackupsForm, LogMe logMe) = attachToBackupsForm(downloadPdf); + await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); + } + + private static DownloadPdf getWiredUpDownloadPdf(EventHandler completedAction) + { + var downloadPdf = new DownloadPdf(); + + downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf); + + if (completedAction != null) + downloadPdf.Completed += completedAction; + + return downloadPdf; + } + + public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) + { + var downloadDialog = new DownloadForm(); + downloadDialog.UpdateFilename(destination); + downloadDialog.Show(); + + new System.Threading.Thread(() => + { + var downloadFile = new DownloadFile(); + + downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() => + downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive) + ); + downloadFile.DownloadCompleted += (_, __) => downloadDialog.UIThread(() => + { + downloadDialog.Close(); + if (showDownloadCompletedDialog) + MessageBox.Show("File downloaded"); + }); + + downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); + }) + { IsBackground = true } + .Start(); + } + + // subscribed to Begin event because a new form should be created+processed+closed on each iteration + private static void wireUpEvents(IDownloadableProcessable downloadable) + { + #region create form + var downloadDialog = new DownloadForm(); + #endregion + + // extra complexity for wiring up download form: + // case 1: download is needed + // dialog created. subscribe to events + // downloadable.DownloadBegin fires. shows dialog + // downloadable.DownloadCompleted fires. closes dialog. which fires FormClosing, FormClosed, Disposed + // Disposed unsubscribe from events + // case 2: download is not needed + // dialog created. subscribe to events + // dialog is never shown nor closed + // downloadable.Completed fires. disposes dialog and unsubscribes from events + + #region define how model actions will affect form behavior + void downloadBegin(object _, string str) + { + downloadDialog.UpdateFilename(str); + downloadDialog.Show(); + } + + // close form on DOWNLOAD completed, not final Completed. Else for BackupBook this form won't close until DECRYPT is also complete + void fileDownloadCompleted(object _, string __) => downloadDialog.Close(); + + void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress) + => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive); + + void unsubscribe(object _ = null, EventArgs __ = null) + { + downloadable.DownloadBegin -= downloadBegin; + downloadable.DownloadCompleted -= fileDownloadCompleted; + downloadable.DownloadProgressChanged -= downloadProgressChanged; + downloadable.Completed -= dialogDispose; + } + + // 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 + void dialogDispose(object _, object __) + { + if (!downloadDialog.IsDisposed) + downloadDialog.Dispose(); + } + #endregion + + #region subscribe new form to model's events + downloadable.DownloadBegin += downloadBegin; + downloadable.DownloadCompleted += fileDownloadCompleted; + downloadable.DownloadProgressChanged += downloadProgressChanged; + downloadable.Completed += dialogDispose; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + // FormClosing is more UI safe but won't fire unless the form is shown and closed + // if form was shown, Disposed will fire for FormClosing, FormClosed, and Disposed + // if not shown, it will still fire for Disposed + downloadDialog.Disposed += unsubscribe; + #endregion + } + + // subscribed to Begin event because a new form should be created+processed+closed on each iteration + private static void wireUpEvents(IDecryptable decryptBook, LibraryBook libraryBook, string actionName = "Decrypting") + { + #region create form + var decryptDialog = new DecryptForm(); + #endregion + + #region Set initially displayed book properties from library info. + decryptDialog.SetTitle(actionName, libraryBook.Book.Title); + decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); + decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); + decryptDialog.SetCoverImage( + WindowsDesktopUtilities.WinAudibleImageServer.GetImage( + libraryBook.Book.PictureId, + FileManager.PictureSize._80x80 + )); + #endregion + + #region define how model actions will affect form behavior + void decryptBegin(object _, string __) => decryptDialog.Show(); + + void titleDiscovered(object _, string title) => decryptDialog.SetTitle(actionName, title); + void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors); + void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); + void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(Dinah.Core.Drawing.ImageReader.ToImage(coverBytes)); + void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); + void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); + void decryptCompleted(object _, string __) => decryptDialog.Close(); + + void requestCoverArt(object _, Action setCoverArtDelegate) + { + var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); + (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); + + if (isDefault) + { + void pictureCached(object _, string pictureId) + { + if (pictureId == libraryBook.Book.PictureId) + { + FileManager.PictureStorage.PictureCached -= pictureCached; + + var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); + (_, picture) = FileManager.PictureStorage.GetPicture(picDef); + + setCoverArtDelegate(picture); + } + }; + FileManager.PictureStorage.PictureCached += pictureCached; + } + else + setCoverArtDelegate(picture); + } + #endregion + + #region subscribe new form to model's events + decryptBook.DecryptBegin += decryptBegin; + + decryptBook.TitleDiscovered += titleDiscovered; + decryptBook.AuthorsDiscovered += authorsDiscovered; + decryptBook.NarratorsDiscovered += narratorsDiscovered; + decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered; + decryptBook.UpdateProgress += updateProgress; + decryptBook.UpdateRemainingTime += updateRemainingTime; + decryptBook.RequestCoverArt += requestCoverArt; + + decryptBook.DecryptCompleted += decryptCompleted; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + decryptDialog.FormClosing += (_, __) => + { + decryptBook.DecryptBegin -= decryptBegin; + + decryptBook.TitleDiscovered -= titleDiscovered; + decryptBook.AuthorsDiscovered -= authorsDiscovered; + decryptBook.NarratorsDiscovered -= narratorsDiscovered; + decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered; + decryptBook.UpdateProgress -= updateProgress; + decryptBook.UpdateRemainingTime -= updateRemainingTime; + decryptBook.RequestCoverArt -= requestCoverArt; + + decryptBook.DecryptCompleted -= decryptCompleted; + decryptBook.Cancel(); + }; + #endregion + } + + private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable) + { + #region create form and logger + var automatedBackupsForm = new AutomatedBackupsForm(); + var logMe = LogMe.RegisterForm(automatedBackupsForm); + #endregion + + #region define how model actions will affect form behavior + void begin(object _, LibraryBook libraryBook) => logMe.Info($"Begin: {libraryBook.Book}"); + void statusUpdate(object _, string str) => logMe.Info("- " + str); + // extra line after book is completely finished + void completed(object _, LibraryBook libraryBook) => logMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); + #endregion + + #region subscribe new form to model's events + downloadable.Begin += begin; + downloadable.StatusUpdate += statusUpdate; + downloadable.Completed += completed; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + automatedBackupsForm.FormClosing += (_, __) => + { + downloadable.Begin -= begin; + downloadable.StatusUpdate -= statusUpdate; + downloadable.Completed -= completed; + }; + #endregion + + return (automatedBackupsForm, logMe); + } + } + + internal abstract class BackupRunner + { + protected LogMe LogMe { get; } + protected IProcessable Processable { get; } + protected AutomatedBackupsForm AutomatedBackupsForm { get; } + + protected BackupRunner(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm = null) + { + LogMe = logMe; + Processable = processable; + AutomatedBackupsForm = automatedBackupsForm; + } + + protected abstract Task RunAsync(); + + protected abstract string SkipDialogText { get; } + protected abstract MessageBoxButtons SkipDialogButtons { get; } + protected abstract DialogResult CreateSkipFileResult { get; } + + public async Task RunBackupAsync() + { + AutomatedBackupsForm?.Show(); + + try + { + await RunAsync(); + } + catch (Exception ex) + { + LogMe.Error(ex); + } + + AutomatedBackupsForm?.FinalizeUI(); + LogMe.Info("DONE"); + } + + protected async Task ProcessOneAsync(Func> func, LibraryBook libraryBook) + { + string logMessage; + + try + { + var statusHandler = await func(libraryBook); + + if (statusHandler.IsSuccess) + return true; + + foreach (var errorMessage in statusHandler.Errors) + LogMe.Error(errorMessage); + + logMessage = statusHandler.Errors.Aggregate((a, b) => $"{a}\r\n{b}"); + } + catch (Exception ex) + { + LogMe.Error(ex); + + logMessage = ex.Message + "\r\n|\r\n" + ex.StackTrace; + } + + LogMe.Error("ERROR. All books have not been processed. Most recent book: processing failed"); + + string details; + try + { + static string trunc(string str) + => string.IsNullOrWhiteSpace(str) ? "[empty]" + : (str.Length > 50) ? $"{str.Truncate(47)}..." + : str; + + details = $@" Title: {libraryBook.Book.Title} ID: {libraryBook.Book.AudibleProductId} Author: {trunc(libraryBook.Book.AuthorNames)} Narr: {trunc(libraryBook.Book.NarratorNames)}"; - } - catch - { - details = "[Error retrieving details]"; - } + } + catch + { + details = "[Error retrieving details]"; + } - var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question); + var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question); - if (dialogResult == DialogResult.Abort) - return false; + if (dialogResult == DialogResult.Abort) + return false; - if (dialogResult == CreateSkipFileResult) - { - ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error, null); - var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage); - LogMe.Info($@" + if (dialogResult == CreateSkipFileResult) + { + ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error, null); + var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage); + LogMe.Info($@" Created new 'skip' file [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title} {path} ".Trim()); - } + } - return true; - } - } - class BackupSingle : BackupRunner - { - private LibraryBook _libraryBook { get; } + return true; + } + } - protected override string SkipDialogText => @" + internal class BackupSingle : BackupRunner + { + private LibraryBook _libraryBook { get; } + + protected override string SkipDialogText => @" An error occurred while trying to process this book. Skip this book permanently? {0} @@ -507,24 +508,25 @@ An error occurred while trying to process this book. Skip this book permanently? - Click NO to skip the book this time only. We'll try again later. ".Trim(); - protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.YesNo; - protected override DialogResult CreateSkipFileResult => DialogResult.Yes; + protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.YesNo; + protected override DialogResult CreateSkipFileResult => DialogResult.Yes; - public BackupSingle(LogMe logMe, IProcessable processable, LibraryBook libraryBook) - : base(logMe, processable) - { - _libraryBook = libraryBook; - } + public BackupSingle(LogMe logMe, IProcessable processable, LibraryBook libraryBook) + : base(logMe, processable) + { + _libraryBook = libraryBook; + } - protected override async Task RunAsync() - { - if (_libraryBook is not null) - await ProcessOneAsync(Processable.ProcessSingleAsync, _libraryBook); - } - } - class BackupLoop : BackupRunner - { - protected override string SkipDialogText => @" + protected override async Task RunAsync() + { + if (_libraryBook is not null) + await ProcessOneAsync(Processable.ProcessSingleAsync, _libraryBook); + } + } + + internal class BackupLoop : BackupRunner + { + protected override string SkipDialogText => @" An error occurred while trying to process this book. {0} @@ -534,33 +536,33 @@ An error occurred while trying to process this book. - IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.) ".Trim(); - protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; - protected override DialogResult CreateSkipFileResult => DialogResult.Ignore; + protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; + protected override DialogResult CreateSkipFileResult => DialogResult.Ignore; - public BackupLoop(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm) - : base(logMe, processable, automatedBackupsForm) { } + public BackupLoop(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm) + : base(logMe, processable, automatedBackupsForm) { } - 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()) - { - var keepGoing = await ProcessOneAsync(Processable.ProcessBookAsync_NoValidation, libraryBook); - if (!keepGoing) - return; + 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()) + { + var keepGoing = await ProcessOneAsync(Processable.ProcessBookAsync_NoValidation, libraryBook); + if (!keepGoing) + return; - if (AutomatedBackupsForm.IsDisposed) - break; + if (AutomatedBackupsForm.IsDisposed) + break; - if (!AutomatedBackupsForm.KeepGoing) - { - if (!AutomatedBackupsForm.KeepGoingChecked) - LogMe.Info("'Keep going' is unchecked"); - return; - } - } + if (!AutomatedBackupsForm.KeepGoing) + { + if (!AutomatedBackupsForm.KeepGoingChecked) + LogMe.Info("'Keep going' is unchecked"); + return; + } + } - LogMe.Info("Done. All books have been processed"); - } - } + LogMe.Info("Done. All books have been processed"); + } + } } diff --git a/LibationWinForms/Dialogs/AccountsDialog.cs b/LibationWinForms/Dialogs/AccountsDialog.cs index 0884bcfc..cfc5988e 100644 --- a/LibationWinForms/Dialogs/AccountsDialog.cs +++ b/LibationWinForms/Dialogs/AccountsDialog.cs @@ -1,21 +1,21 @@ -using System; +using AudibleApi; +using InternalUtilities; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; -using AudibleApi; -using InternalUtilities; namespace LibationWinForms.Dialogs { public partial class AccountsDialog : Form { - const string COL_Delete = nameof(DeleteAccount); - const string COL_LibraryScan = nameof(LibraryScan); - const string COL_AccountId = nameof(AccountId); - const string COL_AccountName = nameof(AccountName); - const string COL_Locale = nameof(Locale); + private const string COL_Delete = nameof(DeleteAccount); + private const string COL_LibraryScan = nameof(LibraryScan); + private const string COL_AccountId = nameof(AccountId); + private const string COL_AccountName = nameof(AccountName); + private const string COL_Locale = nameof(Locale); - Form1 _parent { get; } + private Form1 _parent { get; } public AccountsDialog(Form1 parent) { @@ -100,7 +100,7 @@ namespace LibationWinForms.Dialogs this.Close(); } - class AccountDto + private class AccountDto { public string AccountId { get; set; } public string AccountName { get; set; } diff --git a/LibationWinForms/Dialogs/AccountsDialog.resx b/LibationWinForms/Dialogs/AccountsDialog.resx index 18b23138..f1117452 100644 --- a/LibationWinForms/Dialogs/AccountsDialog.resx +++ b/LibationWinForms/Dialogs/AccountsDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/BookDetailsDialog.cs b/LibationWinForms/Dialogs/BookDetailsDialog.cs index 27c5ab21..9a415200 100644 --- a/LibationWinForms/Dialogs/BookDetailsDialog.cs +++ b/LibationWinForms/Dialogs/BookDetailsDialog.cs @@ -3,25 +3,25 @@ using System.Windows.Forms; namespace LibationWinForms.Dialogs { - public partial class BookDetailsDialog : Form - { - public string NewTags { get; private set; } + public partial class BookDetailsDialog : Form + { + public string NewTags { get; private set; } - public BookDetailsDialog() - { - InitializeComponent(); - } - public BookDetailsDialog(string title, string rawTags) : this() - { - this.Text = $"Edit Tags - {title}"; + public BookDetailsDialog() + { + InitializeComponent(); + } + public BookDetailsDialog(string title, string rawTags) : this() + { + this.Text = $"Edit Tags - {title}"; - this.newTagsTb.Text = rawTags; - } + this.newTagsTb.Text = rawTags; + } - private void SaveBtn_Click(object sender, EventArgs e) - { - NewTags = this.newTagsTb.Text; - DialogResult = DialogResult.OK; - } - } + private void SaveBtn_Click(object sender, EventArgs e) + { + NewTags = this.newTagsTb.Text; + DialogResult = DialogResult.OK; + } + } } diff --git a/LibationWinForms/Dialogs/BookDetailsDialog.resx b/LibationWinForms/Dialogs/BookDetailsDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/BookDetailsDialog.resx +++ b/LibationWinForms/Dialogs/BookDetailsDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs index 5684d485..c0b5b28c 100644 --- a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs +++ b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs @@ -1,9 +1,7 @@ -using System; +using FileManager; +using System; using System.Collections.Generic; -using System.Linq; using System.Windows.Forms; -using Dinah.Core; -using FileManager; namespace LibationWinForms.Dialogs { @@ -58,7 +56,7 @@ namespace LibationWinForms.Dialogs = knownDir != Configuration.KnownDirectories.None // this could be a well known dir which isn't an option in this particular dropdown. This will always be true of LibationFiles && this.directorySelectControl.SelectDirectory(knownDir); - + customDirectoryRb.Checked = !isKnown; knownDirectoryRb.Checked = isKnown; this.customTb.Text = isKnown ? "" : customDir; diff --git a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx +++ b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/DirectorySelectControl.cs b/LibationWinForms/Dialogs/DirectorySelectControl.cs index ab8def7a..639886ba 100644 --- a/LibationWinForms/Dialogs/DirectorySelectControl.cs +++ b/LibationWinForms/Dialogs/DirectorySelectControl.cs @@ -1,9 +1,9 @@ -using System; +using Dinah.Core; +using FileManager; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; -using Dinah.Core; -using FileManager; namespace LibationWinForms.Dialogs { @@ -13,7 +13,7 @@ namespace LibationWinForms.Dialogs { public string Description { get; } public Configuration.KnownDirectories Value { get; } - private DirectorySelectControl _parentControl; + private readonly DirectorySelectControl _parentControl; public string FullPath => _parentControl.AddSubDirectoryToPath(Configuration.GetKnownDirectoryPath(Value)); diff --git a/LibationWinForms/Dialogs/DirectorySelectControl.resx b/LibationWinForms/Dialogs/DirectorySelectControl.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/DirectorySelectControl.resx +++ b/LibationWinForms/Dialogs/DirectorySelectControl.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/EditQuickFilters.cs b/LibationWinForms/Dialogs/EditQuickFilters.cs index abb183ce..999e9b2f 100644 --- a/LibationWinForms/Dialogs/EditQuickFilters.cs +++ b/LibationWinForms/Dialogs/EditQuickFilters.cs @@ -1,101 +1,100 @@ -using System; +using FileManager; +using System; using System.Linq; using System.Windows.Forms; -using FileManager; namespace LibationWinForms.Dialogs { public partial class EditQuickFilters : Form - { - const string BLACK_UP_POINTING_TRIANGLE = "\u25B2"; - const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC"; + { + private const string BLACK_UP_POINTING_TRIANGLE = "\u25B2"; + private const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC"; + private const string COL_Original = nameof(Original); + private const string COL_Delete = nameof(Delete); + private const string COL_Filter = nameof(Filter); + private const string COL_MoveUp = nameof(MoveUp); + private const string COL_MoveDown = nameof(MoveDown); - const string COL_Original = nameof(Original); - const string COL_Delete = nameof(Delete); - const string COL_Filter = nameof(Filter); - const string COL_MoveUp = nameof(MoveUp); - const string COL_MoveDown = nameof(MoveDown); + private Form1 _parent { get; } - Form1 _parent { get; } + public EditQuickFilters(Form1 parent) + { + _parent = parent; - public EditQuickFilters(Form1 parent) - { - _parent = parent; + InitializeComponent(); - InitializeComponent(); + dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; - dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + populateGridValues(); + } - populateGridValues(); - } + private void populateGridValues() + { + var filters = QuickFilters.Filters; + if (!filters.Any()) + return; - private void populateGridValues() - { - var filters = QuickFilters.Filters; - if (!filters.Any()) - return; + foreach (var filter in filters) + dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE); + } - foreach (var filter in filters) - dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE); - } + private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) + { + e.Row.Cells[COL_Delete].Value = "X"; + e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE; + e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE; + } - private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) - { - e.Row.Cells[COL_Delete].Value = "X"; - e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE; - e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE; - } + private void saveBtn_Click(object sender, EventArgs e) + { + var list = dataGridView1.Rows + .OfType() + .Select(r => r.Cells[COL_Filter].Value?.ToString()) + .ToList(); + QuickFilters.ReplaceAll(list); - private void saveBtn_Click(object sender, EventArgs e) - { - var list = dataGridView1.Rows - .OfType() - .Select(r => r.Cells[COL_Filter].Value?.ToString()) - .ToList(); - QuickFilters.ReplaceAll(list); + _parent.UpdateFilterDropDown(); + this.DialogResult = DialogResult.OK; + this.Close(); + } - _parent.UpdateFilterDropDown(); - this.DialogResult = DialogResult.OK; - this.Close(); - } + private void cancelBtn_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } - private void cancelBtn_Click(object sender, EventArgs e) - { - this.DialogResult = DialogResult.Cancel; - this.Close(); - } + private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + var dgv = (DataGridView)sender; - private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) - { - var dgv = (DataGridView)sender; - - var col = dgv.Columns[e.ColumnIndex]; - if (col is DataGridViewButtonColumn && e.RowIndex >= 0) - { - var row = dgv.Rows[e.RowIndex]; - switch (col.Name) - { - case COL_Delete: - // if final/edit row: do nothing - if (e.RowIndex < dgv.RowCount - 1) - dgv.Rows.Remove(row); - break; - case COL_MoveUp: - // if top: do nothing - if (e.RowIndex < 1) - break; - dgv.Rows.Remove(row); - dgv.Rows.Insert(e.RowIndex - 1, row); - break; - case COL_MoveDown: - // if final/edit row or bottom filter row: do nothing - if (e.RowIndex >= dgv.RowCount - 2) - break; - dgv.Rows.Remove(row); - dgv.Rows.Insert(e.RowIndex + 1, row); - break; - } - } - } - } + var col = dgv.Columns[e.ColumnIndex]; + if (col is DataGridViewButtonColumn && e.RowIndex >= 0) + { + var row = dgv.Rows[e.RowIndex]; + switch (col.Name) + { + case COL_Delete: + // if final/edit row: do nothing + if (e.RowIndex < dgv.RowCount - 1) + dgv.Rows.Remove(row); + break; + case COL_MoveUp: + // if top: do nothing + if (e.RowIndex < 1) + break; + dgv.Rows.Remove(row); + dgv.Rows.Insert(e.RowIndex - 1, row); + break; + case COL_MoveDown: + // if final/edit row or bottom filter row: do nothing + if (e.RowIndex >= dgv.RowCount - 2) + break; + dgv.Rows.Remove(row); + dgv.Rows.Insert(e.RowIndex + 1, row); + break; + } + } + } + } } diff --git a/LibationWinForms/Dialogs/EditQuickFilters.resx b/LibationWinForms/Dialogs/EditQuickFilters.resx index 714d166d..9c876821 100644 --- a/LibationWinForms/Dialogs/EditQuickFilters.resx +++ b/LibationWinForms/Dialogs/EditQuickFilters.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/IndexLibraryDialog.cs b/LibationWinForms/Dialogs/IndexLibraryDialog.cs index e534cc28..0026fe79 100644 --- a/LibationWinForms/Dialogs/IndexLibraryDialog.cs +++ b/LibationWinForms/Dialogs/IndexLibraryDialog.cs @@ -1,8 +1,8 @@ -using System; -using System.Windows.Forms; -using ApplicationServices; +using ApplicationServices; using InternalUtilities; using LibationWinForms.Login; +using System; +using System.Windows.Forms; namespace LibationWinForms.Dialogs { diff --git a/LibationWinForms/Dialogs/IndexLibraryDialog.resx b/LibationWinForms/Dialogs/IndexLibraryDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/IndexLibraryDialog.resx +++ b/LibationWinForms/Dialogs/IndexLibraryDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/LibationFilesDialog.cs b/LibationWinForms/Dialogs/LibationFilesDialog.cs index c6c67383..09ec8096 100644 --- a/LibationWinForms/Dialogs/LibationFilesDialog.cs +++ b/LibationWinForms/Dialogs/LibationFilesDialog.cs @@ -1,6 +1,6 @@ -using System; +using FileManager; +using System; using System.Windows.Forms; -using FileManager; namespace LibationWinForms.Dialogs { diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs index 426bae71..68fb2a99 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs +++ b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs @@ -1,7 +1,7 @@ -using System; -using System.Windows.Forms; -using Dinah.Core; +using Dinah.Core; using InternalUtilities; +using System; +using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login { diff --git a/LibationWinForms/Dialogs/Login/MfaDialog.cs b/LibationWinForms/Dialogs/Login/MfaDialog.cs index add77588..e369b367 100644 --- a/LibationWinForms/Dialogs/Login/MfaDialog.cs +++ b/LibationWinForms/Dialogs/Login/MfaDialog.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login @@ -14,7 +8,7 @@ namespace LibationWinForms.Dialogs.Login { private RadioButton[] radioButtons { get; } - AudibleApi.MfaConfig _mfaConfig { get; } + private AudibleApi.MfaConfig _mfaConfig { get; } public MfaDialog(AudibleApi.MfaConfig mfaConfig) { @@ -32,7 +26,8 @@ namespace LibationWinForms.Dialogs.Login setRadioButton(1, this.radioButton2); setRadioButton(2, this.radioButton3); - Serilog.Log.Logger.Information("{@DebugInfo}", new { + Serilog.Log.Logger.Information("{@DebugInfo}", new + { paramButtonCount = mfaConfig.Buttons.Count, visibleRadioButtonCount = radioButtons.Count(rb => rb.Visible) }); @@ -65,7 +60,8 @@ namespace LibationWinForms.Dialogs.Login { var selected = radioButtons.FirstOrDefault(rb => rb.Checked); - Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { + Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new + { rb1_visible = radioButton1.Visible, rb1_checked = radioButton1.Checked, diff --git a/LibationWinForms/Dialogs/Login/WinformResponder.cs b/LibationWinForms/Dialogs/Login/WinformResponder.cs index 28507f4d..d57a01d7 100644 --- a/LibationWinForms/Dialogs/Login/WinformResponder.cs +++ b/LibationWinForms/Dialogs/Login/WinformResponder.cs @@ -1,5 +1,4 @@ -using System; -using AudibleApi; +using AudibleApi; using InternalUtilities; using LibationWinForms.Dialogs.Login; diff --git a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs index 14f2e76f..6ef2e320 100644 --- a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs +++ b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs @@ -1,7 +1,7 @@ -using System; +using Dinah.Core; +using System; using System.Drawing; using System.Windows.Forms; -using Dinah.Core; namespace LibationWinForms.Dialogs { diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index a31ca838..40457837 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -3,27 +3,27 @@ using DataLayer; using InternalUtilities; using LibationWinForms.Login; using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Windows.Forms; -using System.Collections; namespace LibationWinForms.Dialogs { - public partial class RemoveBooksDialog : Form + public partial class RemoveBooksDialog : Form { public bool BooksRemoved { get; private set; } private Account[] _accounts { get; } - private List _libraryBooks; - private SortableBindingList2 _removableGridEntries; - private string _labelFormat; + private readonly List _libraryBooks; + private readonly SortableBindingList2 _removableGridEntries; + private readonly string _labelFormat; private int SelectedCount => SelectedEntries?.Count() ?? 0; private IEnumerable SelectedEntries => _removableGridEntries?.Where(b => b.Remove); - public RemoveBooksDialog(params Account[] accounts) + public RemoveBooksDialog(params Account[] accounts) { _libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking(); _accounts = accounts; @@ -32,8 +32,8 @@ namespace LibationWinForms.Dialogs _labelFormat = label1.Text; _dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); - _dataGridView.CellValueChanged += DataGridView1_CellValueChanged; - _dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); + _dataGridView.CellValueChanged += DataGridView1_CellValueChanged; + _dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); var orderedGridEntries = _libraryBooks .Select(lb => new RemovableGridEntry(lb)) @@ -46,7 +46,7 @@ namespace LibationWinForms.Dialogs _dataGridView.Enabled = false; } - private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) + private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex == 0) UpdateSelection(); @@ -78,9 +78,9 @@ namespace LibationWinForms.Dialogs ex); } finally - { + { _dataGridView.Enabled = true; - } + } } private void btnRemoveBooks_Click(object sender, EventArgs e) @@ -122,15 +122,15 @@ namespace LibationWinForms.Dialogs } } private void UpdateSelection() - { + { _dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending); var selectedCount = SelectedCount; label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty); btnRemoveBooks.Enabled = selectedCount > 0; - } - } + } + } - internal class RemovableGridEntry : GridEntry + internal class RemovableGridEntry : GridEntry { private static readonly IComparer BoolComparer = new ObjectComparer(); @@ -153,18 +153,18 @@ namespace LibationWinForms.Dialogs } } - public override object GetMemberValue(string memberName) - { + public override object GetMemberValue(string memberName) + { if (memberName == nameof(Remove)) return Remove; - return base.GetMemberValue(memberName); - } + return base.GetMemberValue(memberName); + } - public override IComparer GetMemberComparer(Type memberType) + public override IComparer GetMemberComparer(Type memberType) { if (memberType == typeof(bool)) return BoolComparer; return base.GetMemberComparer(memberType); - } - } + } + } } diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx index a3058bc8..5ffc920f 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.resx +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -1,4 +1,5 @@ - + + @@ -57,7 +58,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 17, 17 - \ No newline at end of file diff --git a/LibationWinForms/Dialogs/ScanAccountsDialog.cs b/LibationWinForms/Dialogs/ScanAccountsDialog.cs index 6f31ef06..eebd720e 100644 --- a/LibationWinForms/Dialogs/ScanAccountsDialog.cs +++ b/LibationWinForms/Dialogs/ScanAccountsDialog.cs @@ -1,10 +1,7 @@ -using System; +using InternalUtilities; +using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; using System.Windows.Forms; -using InternalUtilities; namespace LibationWinForms.Dialogs { @@ -12,7 +9,7 @@ namespace LibationWinForms.Dialogs { public List CheckedAccounts { get; } = new List(); - Form1 _parent { get; } + private Form1 _parent { get; } public ScanAccountsDialog(Form1 parent) { @@ -21,7 +18,7 @@ namespace LibationWinForms.Dialogs InitializeComponent(); } - class listItem + private class listItem { public Account Account { get; set; } public string Text { get; set; } diff --git a/LibationWinForms/Dialogs/ScanAccountsDialog.resx b/LibationWinForms/Dialogs/ScanAccountsDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/ScanAccountsDialog.resx +++ b/LibationWinForms/Dialogs/ScanAccountsDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/SearchSyntaxDialog.cs b/LibationWinForms/Dialogs/SearchSyntaxDialog.cs index 1300a74a..bd8a2b87 100644 --- a/LibationWinForms/Dialogs/SearchSyntaxDialog.cs +++ b/LibationWinForms/Dialogs/SearchSyntaxDialog.cs @@ -3,18 +3,18 @@ using System.Windows.Forms; namespace LibationWinForms.Dialogs { - public partial class SearchSyntaxDialog : Form - { - public SearchSyntaxDialog() - { - InitializeComponent(); + public partial class SearchSyntaxDialog : Form + { + public SearchSyntaxDialog() + { + InitializeComponent(); - label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields()); - label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields()); - label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields()); - label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields()); - } + label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields()); + label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields()); + label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields()); + label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields()); + } - private void CloseBtn_Click(object sender, EventArgs e) => this.Close(); - } + private void CloseBtn_Click(object sender, EventArgs e) => this.Close(); + } } diff --git a/LibationWinForms/Dialogs/SearchSyntaxDialog.resx b/LibationWinForms/Dialogs/SearchSyntaxDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/SearchSyntaxDialog.resx +++ b/LibationWinForms/Dialogs/SearchSyntaxDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/SettingsDialog.cs b/LibationWinForms/Dialogs/SettingsDialog.cs index fee85e1d..81e9c0c8 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.cs @@ -1,15 +1,15 @@ -using System; +using Dinah.Core; +using FileManager; +using System; using System.IO; using System.Windows.Forms; -using Dinah.Core; -using FileManager; namespace LibationWinForms.Dialogs { public partial class SettingsDialog : Form { - Configuration config { get; } = Configuration.Instance; - Func desc { get; } = Configuration.GetDescription; + private Configuration config { get; } = Configuration.Instance; + private Func desc { get; } = Configuration.GetDescription; public SettingsDialog() => InitializeComponent(); @@ -57,13 +57,13 @@ namespace LibationWinForms.Dialogs inProgressSelectControl.SelectDirectory(config.InProgress); } - private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e) - { + private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e) + { convertLosslessRb.Enabled = allowLibationFixupCbox.Checked; convertLossyRb.Enabled = allowLibationFixupCbox.Checked; if (!allowLibationFixupCbox.Checked) - { + { convertLosslessRb.Checked = true; } } diff --git a/LibationWinForms/Dialogs/SettingsDialog.resx b/LibationWinForms/Dialogs/SettingsDialog.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.resx +++ b/LibationWinForms/Dialogs/SettingsDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/SetupDialog.resx b/LibationWinForms/Dialogs/SetupDialog.resx index 859984c0..9e9a5108 100644 --- a/LibationWinForms/Dialogs/SetupDialog.resx +++ b/LibationWinForms/Dialogs/SetupDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 30bba304..6f9223d3 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using ApplicationServices; +using ApplicationServices; using DataLayer; using Dinah.Core; using Dinah.Core.Drawing; @@ -11,371 +6,376 @@ using Dinah.Core.Windows.Forms; using FileManager; using InternalUtilities; using LibationWinForms.Dialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; namespace LibationWinForms { - public partial class Form1 : Form - { - private string backupsCountsLbl_Format { get; } - private string pdfsCountsLbl_Format { get; } + public partial class Form1 : Form + { + private string backupsCountsLbl_Format { get; } + private string pdfsCountsLbl_Format { get; } private string visibleCountLbl_Format { get; } private string beginBookBackupsToolStripMenuItem_format { get; } private string beginPdfBackupsToolStripMenuItem_format { get; } public Form1() - { - InitializeComponent(); + { + InitializeComponent(); - // back up string formats - backupsCountsLbl_Format = backupsCountsLbl.Text; - pdfsCountsLbl_Format = pdfsCountsLbl.Text; - visibleCountLbl_Format = visibleCountLbl.Text; + // back up string formats + backupsCountsLbl_Format = backupsCountsLbl.Text; + pdfsCountsLbl_Format = pdfsCountsLbl.Text; + visibleCountLbl_Format = visibleCountLbl.Text; beginBookBackupsToolStripMenuItem_format = beginBookBackupsToolStripMenuItem.Text; - beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text; + beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text; - // after backing up formats: can set default/temp visible text - backupsCountsLbl.Text = "[Calculating backed up book quantities]"; - pdfsCountsLbl.Text = "[Calculating backed up PDFs]"; + // after backing up formats: can set default/temp visible text + backupsCountsLbl.Text = "[Calculating backed up book quantities]"; + pdfsCountsLbl.Text = "[Calculating backed up PDFs]"; setVisibleCount(null, 0); - if (this.DesignMode) - return; + if (this.DesignMode) + return; - // independent UI updates - this.Load += setBackupCountsAsync; - this.Load += (_, __) => RestoreSizeAndLocation(); - this.Load += (_, __) => RefreshImportMenu(); + // independent UI updates + this.Load += setBackupCountsAsync; + this.Load += (_, __) => RestoreSizeAndLocation(); + this.Load += (_, __) => RefreshImportMenu(); - var format = System.Drawing.Imaging.ImageFormat.Jpeg; - PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format)); - PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format)); - PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format)); - } + var format = System.Drawing.Imaging.ImageFormat.Jpeg; + PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format)); + PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format)); + PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format)); + } - private void Form1_Load(object sender, EventArgs e) + private void Form1_Load(object sender, EventArgs e) { - if (this.DesignMode) - return; + if (this.DesignMode) + return; - reloadGrid(); - - // also applies filter. ONLY call AFTER loading grid - loadInitialQuickFilterState(); - } + reloadGrid(); - private void Form1_FormClosing(object sender, FormClosingEventArgs e) - { - SaveSizeAndLocation(); - } + // also applies filter. ONLY call AFTER loading grid + loadInitialQuickFilterState(); + } + + private void Form1_FormClosing(object sender, FormClosingEventArgs e) + { + SaveSizeAndLocation(); + } private void RestoreSizeAndLocation() { - var config = Configuration.Instance; + var config = Configuration.Instance; - var width = config.MainFormWidth; - var height = config.MainFormHeight; + var width = config.MainFormWidth; + var height = config.MainFormHeight; - // too small -- something must have gone wrong. use defaults - if (width < 25 || height < 25) - { - width = 1023; - height = 578; - } + // too small -- something must have gone wrong. use defaults + if (width < 25 || height < 25) + { + width = 1023; + height = 578; + } - // Fit to the current screen size in case the screen resolution changed since the size was last persisted - if (width > Screen.PrimaryScreen.WorkingArea.Width) - width = Screen.PrimaryScreen.WorkingArea.Width; - if (height > Screen.PrimaryScreen.WorkingArea.Height) - height = Screen.PrimaryScreen.WorkingArea.Height; + // Fit to the current screen size in case the screen resolution changed since the size was last persisted + if (width > Screen.PrimaryScreen.WorkingArea.Width) + width = Screen.PrimaryScreen.WorkingArea.Width; + if (height > Screen.PrimaryScreen.WorkingArea.Height) + height = Screen.PrimaryScreen.WorkingArea.Height; - var x = config.MainFormX; - var y = config.MainFormY; + var x = config.MainFormX; + var y = config.MainFormY; - var rect = new System.Drawing.Rectangle(x, y, width, height); + var rect = new System.Drawing.Rectangle(x, y, width, height); - // is proposed rect on a screen? - if (Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect))) - { - this.StartPosition = FormStartPosition.Manual; - this.DesktopBounds = rect; - } - else - { - this.StartPosition = FormStartPosition.WindowsDefaultLocation; - this.Size = rect.Size; - } + // is proposed rect on a screen? + if (Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect))) + { + this.StartPosition = FormStartPosition.Manual; + this.DesktopBounds = rect; + } + else + { + this.StartPosition = FormStartPosition.WindowsDefaultLocation; + this.Size = rect.Size; + } - // FINAL: for Maximized: start normal state, set size and location, THEN set max state - this.WindowState = config.MainFormIsMaximized ? FormWindowState.Maximized : FormWindowState.Normal; - } + // FINAL: for Maximized: start normal state, set size and location, THEN set max state + this.WindowState = config.MainFormIsMaximized ? FormWindowState.Maximized : FormWindowState.Normal; + } - private void SaveSizeAndLocation() - { - System.Drawing.Point location; - System.Drawing.Size size; + private void SaveSizeAndLocation() + { + System.Drawing.Point location; + System.Drawing.Size size; - // save location and size if the state is normal - if (this.WindowState == FormWindowState.Normal) - { - location = this.Location; - size = this.Size; - } - else - { - // save the RestoreBounds if the form is minimized or maximized - location = this.RestoreBounds.Location; - size = this.RestoreBounds.Size; - } - - var config = Configuration.Instance; + // save location and size if the state is normal + if (this.WindowState == FormWindowState.Normal) + { + location = this.Location; + size = this.Size; + } + else + { + // save the RestoreBounds if the form is minimized or maximized + location = this.RestoreBounds.Location; + size = this.RestoreBounds.Size; + } - config.MainFormX = location.X; - config.MainFormY = location.Y; + var config = Configuration.Instance; - config.MainFormWidth = size.Width; - config.MainFormHeight = size.Height; + config.MainFormX = location.X; + config.MainFormY = location.Y; - config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized; - } + config.MainFormWidth = size.Width; + config.MainFormHeight = size.Height; - #region reload grid - bool isProcessingGridSelect = false; - private void reloadGrid() - { - // suppressed filter while init'ing UI - var prev_isProcessingGridSelect = isProcessingGridSelect; - isProcessingGridSelect = true; - setGrid(); - isProcessingGridSelect = prev_isProcessingGridSelect; + config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized; + } - // UI init complete. now we can apply filter - doFilter(lastGoodFilter); - } + #region reload grid + private bool isProcessingGridSelect = false; + private void reloadGrid() + { + // suppressed filter while init'ing UI + var prev_isProcessingGridSelect = isProcessingGridSelect; + isProcessingGridSelect = true; + setGrid(); + isProcessingGridSelect = prev_isProcessingGridSelect; - ProductsGrid currProductsGrid; - private void setGrid() - { - SuspendLayout(); - { - if (currProductsGrid != null) - { - gridPanel.Controls.Remove(currProductsGrid); - currProductsGrid.VisibleCountChanged -= setVisibleCount; - currProductsGrid.BackupCountsChanged -= setBackupCountsAsync; - currProductsGrid.Dispose(); - } + // UI init complete. now we can apply filter + doFilter(lastGoodFilter); + } - currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill }; - currProductsGrid.VisibleCountChanged += setVisibleCount; - currProductsGrid.BackupCountsChanged += setBackupCountsAsync; - gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid)); - currProductsGrid.Display(); - } - ResumeLayout(); - } - #endregion + private ProductsGrid currProductsGrid; + private void setGrid() + { + SuspendLayout(); + { + if (currProductsGrid != null) + { + gridPanel.Controls.Remove(currProductsGrid); + currProductsGrid.VisibleCountChanged -= setVisibleCount; + currProductsGrid.BackupCountsChanged -= setBackupCountsAsync; + currProductsGrid.Dispose(); + } - #region bottom: qty books visible - private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format(visibleCountLbl_Format, qty); - #endregion + currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill }; + currProductsGrid.VisibleCountChanged += setVisibleCount; + currProductsGrid.BackupCountsChanged += setBackupCountsAsync; + gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid)); + currProductsGrid.Display(); + } + ResumeLayout(); + } + #endregion - #region bottom: backup counts - private async void setBackupCountsAsync(object _, object __) - { - LibraryCommands.LibraryStats libraryStats = null; - await Task.Run(() => libraryStats = LibraryCommands.GetCounts()); + #region bottom: qty books visible + private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format(visibleCountLbl_Format, qty); + #endregion - setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress); - setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded); - } - private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress) - { - // enable/disable export - var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress); - exportLibraryToolStripMenuItem.Enabled = hasResults; + #region bottom: backup counts + private async void setBackupCountsAsync(object _, object __) + { + LibraryCommands.LibraryStats libraryStats = null; + await Task.Run(() => libraryStats = LibraryCommands.GetCounts()); - // update bottom numbers - var pending = booksNoProgress + booksDownloadedOnly; - var statusStripText - = !hasResults ? "No books. Begin by importing your library" - : pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp) - : $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up"; + setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress); + setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded); + } + private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress) + { + // enable/disable export + var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress); + exportLibraryToolStripMenuItem.Enabled = hasResults; - // update menu item - var menuItemText - = pending > 0 - ? $"{pending} remaining" - : "All books have been liberated"; + // update bottom numbers + var pending = booksNoProgress + booksDownloadedOnly; + var statusStripText + = !hasResults ? "No books. Begin by importing your library" + : pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp) + : $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up"; - // update UI - statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText); - menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0); - menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText)); - } - private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded) - { - // update bottom numbers - var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded); - var statusStripText - = !hasResults ? "" - : pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded) - : $"| All {pdfsDownloaded} PDFs downloaded"; + // update menu item + var menuItemText + = pending > 0 + ? $"{pending} remaining" + : "All books have been liberated"; - // update menu item - var menuItemText - = pdfsNotDownloaded > 0 - ? $"{pdfsNotDownloaded} remaining" - : "All PDFs have been downloaded"; + // update UI + statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText); + menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0); + menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText)); + } + private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded) + { + // update bottom numbers + var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded); + var statusStripText + = !hasResults ? "" + : pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded) + : $"| All {pdfsDownloaded} PDFs downloaded"; - // update UI - statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText); - menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0); - menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText)); - } - #endregion + // update menu item + var menuItemText + = pdfsNotDownloaded > 0 + ? $"{pdfsNotDownloaded} remaining" + : "All PDFs have been downloaded"; - #region filter - private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog(); + // update UI + statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText); + menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0); + menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText)); + } + #endregion - private void AddFilterBtn_Click(object sender, EventArgs e) - { - QuickFilters.Add(this.filterSearchTb.Text); - UpdateFilterDropDown(); - } + #region filter + private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog(); - private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e) - { - if (e.KeyChar == (char)Keys.Return) - { - doFilter(); + private void AddFilterBtn_Click(object sender, EventArgs e) + { + QuickFilters.Add(this.filterSearchTb.Text); + UpdateFilterDropDown(); + } - // silence the 'ding' - e.Handled = true; - } - } - private void filterBtn_Click(object sender, EventArgs e) => doFilter(); + private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == (char)Keys.Return) + { + doFilter(); - string lastGoodFilter = ""; - private void doFilter(string filterString) - { - this.filterSearchTb.Text = filterString; - doFilter(); - } - private void doFilter() - { - if (isProcessingGridSelect || currProductsGrid == null) - return; + // silence the 'ding' + e.Handled = true; + } + } + private void filterBtn_Click(object sender, EventArgs e) => doFilter(); - try - { - currProductsGrid.Filter(filterSearchTb.Text); - lastGoodFilter = filterSearchTb.Text; - } - catch (Exception ex) - { - MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); + private string lastGoodFilter = ""; + private void doFilter(string filterString) + { + this.filterSearchTb.Text = filterString; + doFilter(); + } + private void doFilter() + { + if (isProcessingGridSelect || currProductsGrid == null) + return; - // re-apply last good filter - doFilter(lastGoodFilter); - } - } - #endregion + try + { + currProductsGrid.Filter(filterSearchTb.Text); + lastGoodFilter = filterSearchTb.Text; + } + catch (Exception ex) + { + MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); - #region Import menu - public void RefreshImportMenu() - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var count = persister.AccountsSettings.Accounts.Count; + // re-apply last good filter + doFilter(lastGoodFilter); + } + } + #endregion - noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0; - scanLibraryToolStripMenuItem.Visible = count == 1; - scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1; - scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1; + #region Import menu + public void RefreshImportMenu() + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var count = persister.AccountsSettings.Accounts.Count; - removeLibraryBooksToolStripMenuItem.Visible = count != 0; + noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0; + scanLibraryToolStripMenuItem.Visible = count == 1; + scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1; + scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1; - if (count == 1) - { - removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click; - } + removeLibraryBooksToolStripMenuItem.Visible = count != 0; - removeSomeAccountsToolStripMenuItem.Visible = count > 1; - removeAllAccountsToolStripMenuItem.Visible = count > 1; - } + if (count == 1) + { + removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click; + } - private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e) - { - MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); - new AccountsDialog(this).ShowDialog(); - } + removeSomeAccountsToolStripMenuItem.Visible = count > 1; + removeAllAccountsToolStripMenuItem.Visible = count > 1; + } - private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); - scanLibraries(firstAccount); - } + private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e) + { + MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); + new AccountsDialog(this).ShowDialog(); + } - private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var allAccounts = persister.AccountsSettings.GetAll(); - scanLibraries(allAccounts); - } + private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); + scanLibraries(firstAccount); + } - private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var scanAccountsDialog = new ScanAccountsDialog(this); + private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var allAccounts = persister.AccountsSettings.GetAll(); + scanLibraries(allAccounts); + } - if (scanAccountsDialog.ShowDialog() != DialogResult.OK) - return; + private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var scanAccountsDialog = new ScanAccountsDialog(this); - if (!scanAccountsDialog.CheckedAccounts.Any()) - return; + if (scanAccountsDialog.ShowDialog() != DialogResult.OK) + return; - scanLibraries(scanAccountsDialog.CheckedAccounts); - } + if (!scanAccountsDialog.CheckedAccounts.Any()) + return; - private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); - scanLibrariesRemovedBooks(firstAccount); - } + scanLibraries(scanAccountsDialog.CheckedAccounts); + } - private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var allAccounts = persister.AccountsSettings.GetAll(); - scanLibrariesRemovedBooks(allAccounts.ToArray()); - } + private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); + scanLibrariesRemovedBooks(firstAccount); + } - private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var scanAccountsDialog = new ScanAccountsDialog(this); + private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var allAccounts = persister.AccountsSettings.GetAll(); + scanLibrariesRemovedBooks(allAccounts.ToArray()); + } - if (scanAccountsDialog.ShowDialog() != DialogResult.OK) - return; + private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var scanAccountsDialog = new ScanAccountsDialog(this); - if (!scanAccountsDialog.CheckedAccounts.Any()) - return; + if (scanAccountsDialog.ShowDialog() != DialogResult.OK) + return; - scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); - } + if (!scanAccountsDialog.CheckedAccounts.Any()) + return; - private void scanLibrariesRemovedBooks(params Account[] accounts) - { - using var dialog = new RemoveBooksDialog(accounts); - dialog.ShowDialog(); + scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); + } - if (dialog.BooksRemoved) - reloadGrid(); - } + private void scanLibrariesRemovedBooks(params Account[] accounts) + { + using var dialog = new RemoveBooksDialog(accounts); + dialog.ShowDialog(); - private void scanLibraries(IEnumerable accounts) => scanLibraries(accounts.ToArray()); - private void scanLibraries(params Account[] accounts) + if (dialog.BooksRemoved) + reloadGrid(); + } + + private void scanLibraries(IEnumerable accounts) => scanLibraries(accounts.ToArray()); + private void scanLibraries(params Account[] accounts) { using var dialog = new IndexLibraryDialog(accounts); dialog.ShowDialog(); @@ -387,112 +387,112 @@ namespace LibationWinForms if (totalProcessed > 0) reloadGrid(); - } - #endregion + } + #endregion - #region liberate menu - private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow); + #region liberate menu + private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow); - private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow); + private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow); - private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync(); + 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 + private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId); + #endregion - #region Export menu - private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e) - { - try - { - var saveFileDialog = new SaveFileDialog - { - Title = "Where to export Library", - Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*" - }; + #region Export menu + private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + var saveFileDialog = new SaveFileDialog + { + Title = "Where to export Library", + Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*" + }; - if (saveFileDialog.ShowDialog() != DialogResult.OK) - return; + if (saveFileDialog.ShowDialog() != DialogResult.OK) + return; - // FilterIndex is 1-based, NOT 0-based - switch (saveFileDialog.FilterIndex) - { - case 1: // xlsx - default: - LibraryExporter.ToXlsx(saveFileDialog.FileName); - break; - case 2: // csv - LibraryExporter.ToCsv(saveFileDialog.FileName); - break; - case 3: // json - LibraryExporter.ToJson(saveFileDialog.FileName); - break; - } + // FilterIndex is 1-based, NOT 0-based + switch (saveFileDialog.FilterIndex) + { + case 1: // xlsx + default: + LibraryExporter.ToXlsx(saveFileDialog.FileName); + break; + case 2: // csv + LibraryExporter.ToCsv(saveFileDialog.FileName); + break; + case 3: // json + LibraryExporter.ToJson(saveFileDialog.FileName); + break; + } - MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName); - } - catch (Exception ex) - { - MessageBoxAlertAdmin.Show("Error attempting to export your library.", "Error exporting", ex); - } - } - #endregion + MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName); + } + catch (Exception ex) + { + MessageBoxAlertAdmin.Show("Error attempting to export your library.", "Error exporting", ex); + } + } + #endregion - #region quick filters menu - private void loadInitialQuickFilterState() - { - // set inital state. do once only - firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault; + #region quick filters menu + private void loadInitialQuickFilterState() + { + // set inital state. do once only + firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault; - // load default filter. do once only - if (QuickFilters.UseDefault) - doFilter(QuickFilters.Filters.FirstOrDefault()); + // load default filter. do once only + if (QuickFilters.UseDefault) + doFilter(QuickFilters.Filters.FirstOrDefault()); - // do after every save - UpdateFilterDropDown(); - } + // do after every save + UpdateFilterDropDown(); + } - private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e) - { - firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked; - QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked; - } + private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e) + { + firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked; + QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked; + } - object quickFilterTag { get; } = new object(); - public void UpdateFilterDropDown() - { - // remove old - for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--) - { - var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i]; - if (menuItem.Tag == quickFilterTag) - quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem); - } + private object quickFilterTag { get; } = new object(); + public void UpdateFilterDropDown() + { + // remove old + for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--) + { + var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i]; + if (menuItem.Tag == quickFilterTag) + quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem); + } - // re-populate - var index = 0; - foreach (var filter in QuickFilters.Filters) - { - var menuItem = new ToolStripMenuItem - { - Tag = quickFilterTag, - Text = $"&{++index}: {filter}" - }; - menuItem.Click += (_, __) => doFilter(filter); - quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem); - } - } + // re-populate + var index = 0; + foreach (var filter in QuickFilters.Filters) + { + var menuItem = new ToolStripMenuItem + { + Tag = quickFilterTag, + Text = $"&{++index}: {filter}" + }; + menuItem.Click += (_, __) => doFilter(filter); + quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem); + } + } - private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog(); - #endregion + private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog(); + #endregion - #region settings menu - private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog(); + #region settings menu + private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog(); - private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); - #endregion - } + private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); + #endregion + } } diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 64a802f7..ae04e870 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -1,12 +1,12 @@ -using System; +using ApplicationServices; +using DataLayer; +using Dinah.Core.Drawing; +using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; -using ApplicationServices; -using DataLayer; -using Dinah.Core.Drawing; namespace LibationWinForms { @@ -106,7 +106,7 @@ namespace LibationWinForms /// /// Create getters for all member object values by name /// - Dictionary> CreateMemberValueDictionary() => new() + private Dictionary> CreateMemberValueDictionary() => new() { { nameof(Title), () => GetSortName(Book.Title) }, { nameof(Series), () => GetSortName(Book.SeriesNames) }, diff --git a/LibationWinForms/IObjectMemberComparable.cs b/LibationWinForms/IObjectMemberComparable.cs index ade585ab..bfacb0cd 100644 --- a/LibationWinForms/IObjectMemberComparable.cs +++ b/LibationWinForms/IObjectMemberComparable.cs @@ -3,7 +3,7 @@ using System.Collections; namespace LibationWinForms { - interface IObjectMemberComparable + internal interface IObjectMemberComparable { IComparer GetMemberComparer(Type memberType); object GetMemberValue(string memberName); diff --git a/LibationWinForms/MessageBoxAlertAdmin.cs b/LibationWinForms/MessageBoxAlertAdmin.cs index 8e97f09c..5ba8cbde 100644 --- a/LibationWinForms/MessageBoxAlertAdmin.cs +++ b/LibationWinForms/MessageBoxAlertAdmin.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LibationWinForms.Dialogs; +using LibationWinForms.Dialogs; +using System; namespace LibationWinForms { diff --git a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs index 7b33a22c..c4f04ee6 100644 --- a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs +++ b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs @@ -1,7 +1,6 @@ -using System; -using System.Windows.Forms; -using Dinah.Core.Logging; +using Dinah.Core.Logging; using Serilog; +using System.Windows.Forms; namespace LibationWinForms { diff --git a/LibationWinForms/ObjectComparer[T].cs b/LibationWinForms/ObjectComparer[T].cs index 2bc69d8d..2ca0ddb9 100644 --- a/LibationWinForms/ObjectComparer[T].cs +++ b/LibationWinForms/ObjectComparer[T].cs @@ -3,7 +3,7 @@ using System.Collections; namespace LibationWinForms { - class ObjectComparer : IComparer where T : IComparable + internal class ObjectComparer : IComparer where T : IComparable { public int Compare(object x, object y) => ((T)x).CompareTo((T)y); } diff --git a/LibationWinForms/ObjectMemberComparer[T].cs b/LibationWinForms/ObjectMemberComparer[T].cs index aea41e21..3024477e 100644 --- a/LibationWinForms/ObjectMemberComparer[T].cs +++ b/LibationWinForms/ObjectMemberComparer[T].cs @@ -3,19 +3,19 @@ using System.ComponentModel; namespace LibationWinForms { - class ObjectMemberComparer : IComparer where T : IObjectMemberComparable - { - public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; - public string PropertyName { get; set; } + internal class ObjectMemberComparer : IComparer where T : IObjectMemberComparable + { + public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; + public string PropertyName { get; set; } - public int Compare(T x, T y) - { - var val1 = x.GetMemberValue(PropertyName); - var val2 = y.GetMemberValue(PropertyName); + public int Compare(T x, T y) + { + var val1 = x.GetMemberValue(PropertyName); + var val2 = y.GetMemberValue(PropertyName); - return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2); - } + return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2); + } - private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; - } + private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; + } } diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 0f826435..4afe1f64 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -1,200 +1,200 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using ApplicationServices; +using ApplicationServices; using DataLayer; using Dinah.Core; using Dinah.Core.Windows.Forms; using LibationWinForms.Dialogs; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; namespace LibationWinForms { - // INSTRUCTIONS TO UPDATE DATA_GRID_VIEW - // - delete current DataGridView - // - view > other windows > data sources - // - refresh - // OR - // - Add New Data Source - // Object. Next - // LibationWinForms - // AudibleDTO - // GridEntry - // - go to Design view - // - click on Data Sources > ProductItem. drowdown: DataGridView - // - drag/drop ProductItem on design surface - // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS + // INSTRUCTIONS TO UPDATE DATA_GRID_VIEW + // - delete current DataGridView + // - view > other windows > data sources + // - refresh + // OR + // - Add New Data Source + // Object. Next + // LibationWinForms + // AudibleDTO + // GridEntry + // - go to Design view + // - click on Data Sources > ProductItem. drowdown: DataGridView + // - drag/drop ProductItem on design surface + // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS - public partial class ProductsGrid : UserControl - { - public event EventHandler VisibleCountChanged; - public event EventHandler BackupCountsChanged; + public partial class ProductsGrid : UserControl + { + public event EventHandler VisibleCountChanged; + public event EventHandler BackupCountsChanged; - // alias - private DataGridView _dataGridView => gridEntryDataGridView; + // alias + private DataGridView _dataGridView => gridEntryDataGridView; public ProductsGrid() { InitializeComponent(); - // sorting breaks filters. must reapply filters after sorting - _dataGridView.Sorted += (_, __) => Filter(); - _dataGridView.CellContentClick += DataGridView_CellContentClick; + // sorting breaks filters. must reapply filters after sorting + _dataGridView.Sorted += (_, __) => Filter(); + _dataGridView.CellContentClick += DataGridView_CellContentClick; - EnableDoubleBuffering(); - } - private void EnableDoubleBuffering() - { - var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + EnableDoubleBuffering(); + } + private void EnableDoubleBuffering() + { + var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - propertyInfo.SetValue(_dataGridView, true, null); - } + propertyInfo.SetValue(_dataGridView, true, null); + } - #region Button controls + #region Button controls - private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) - { - // handle grid button click: https://stackoverflow.com/a/13687844 - if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn) - return; + private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + // handle grid button click: https://stackoverflow.com/a/13687844 + if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn) + return; - var liveGridEntry = getGridEntry(e.RowIndex); + var liveGridEntry = getGridEntry(e.RowIndex); - switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) - { - case nameof(liveGridEntry.Liberate): - await Liberate_Click(liveGridEntry); - break; - case nameof(liveGridEntry.DisplayTags): - EditTags_Click(liveGridEntry); - break; - } - } + switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) + { + case nameof(liveGridEntry.Liberate): + await Liberate_Click(liveGridEntry); + break; + case nameof(liveGridEntry.DisplayTags): + EditTags_Click(liveGridEntry); + break; + } + } - private async Task Liberate_Click(GridEntry liveGridEntry) - { - var libraryBook = liveGridEntry.LibraryBook; + private async Task Liberate_Click(GridEntry liveGridEntry) + { + var libraryBook = liveGridEntry.LibraryBook; - // liberated: open explorer to file - if (TransitionalFileLocator.Audio_Exists(libraryBook.Book)) - { - var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book); - if (!Go.To.File(filePath)) - MessageBox.Show($"File not found:\r\n{filePath}"); - return; - } + // liberated: open explorer to file + if (TransitionalFileLocator.Audio_Exists(libraryBook.Book)) + { + var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book); + if (!Go.To.File(filePath)) + MessageBox.Show($"File not found:\r\n{filePath}"); + return; + } - // else: liberate - await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId)); - } + // else: liberate + await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId)); + } - private void EditTags_Click(GridEntry liveGridEntry) - { - var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags); - if (bookDetailsForm.ShowDialog() != DialogResult.OK) - return; + private void EditTags_Click(GridEntry liveGridEntry) + { + var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags); + if (bookDetailsForm.ShowDialog() != DialogResult.OK) + return; - var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags); - if (qtyChanges == 0) - return; + var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags); + if (qtyChanges == 0) + return; - //Re-apply filters - Filter(); - } + //Re-apply filters + Filter(); + } - #endregion + #endregion - #region UI display functions + #region UI display functions private bool hasBeenDisplayed = false; - public void Display() - { - if (hasBeenDisplayed) - return; - hasBeenDisplayed = true; + public void Display() + { + if (hasBeenDisplayed) + return; + hasBeenDisplayed = true; - // - // transform into sorted GridEntry.s BEFORE binding - // - using var context = DbContexts.GetContext(); - var lib = context.GetLibrary_Flat_NoTracking(); + // + // transform into sorted GridEntry.s BEFORE binding + // + using var context = DbContexts.GetContext(); + var lib = context.GetLibrary_Flat_NoTracking(); - // if no data. hide all columns. return - if (!lib.Any()) - { - for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--) - _dataGridView.Columns.RemoveAt(i); - return; - } + // if no data. hide all columns. return + if (!lib.Any()) + { + for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--) + _dataGridView.Columns.RemoveAt(i); + return; + } - var orderedGridEntries = lib - .Select(lb => new GridEntry(lb)).ToList() - // default load order - .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) - //// more advanced example: sort by author, then series, then title - //.OrderBy(ge => ge.Authors) - // .ThenBy(ge => ge.Series) - // .ThenBy(ge => ge.Title) - .ToList(); + var orderedGridEntries = lib + .Select(lb => new GridEntry(lb)).ToList() + // default load order + .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) + //// more advanced example: sort by author, then series, then title + //.OrderBy(ge => ge.Authors) + // .ThenBy(ge => ge.Series) + // .ThenBy(ge => ge.Title) + .ToList(); - // BIND - gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); + // BIND + gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); - // FILTER - Filter(); + // FILTER + Filter(); - BackupCountsChanged?.Invoke(this, EventArgs.Empty); - } + BackupCountsChanged?.Invoke(this, EventArgs.Empty); + } - public void RefreshRow(string productId) - { - var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId); + public void RefreshRow(string productId) + { + var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId); - // update cells incl Liberate button text - _dataGridView.InvalidateRow(rowIndex); + // update cells incl Liberate button text + _dataGridView.InvalidateRow(rowIndex); - // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change - Filter(); + // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change + Filter(); - BackupCountsChanged?.Invoke(this, EventArgs.Empty); - } + BackupCountsChanged?.Invoke(this, EventArgs.Empty); + } - #endregion + #endregion - #region Filter + #region Filter - string _filterSearchString; - private void Filter() => Filter(_filterSearchString); - public void Filter(string searchString) - { - _filterSearchString = searchString; + private string _filterSearchString; + private void Filter() => Filter(_filterSearchString); + public void Filter(string searchString) + { + _filterSearchString = searchString; if (_dataGridView.Rows.Count == 0) return; - var searchResults = SearchEngineCommands.Search(searchString); - var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); + var searchResults = SearchEngineCommands.Search(searchString); + var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); - // https://stackoverflow.com/a/18942430 - var bindingContext = BindingContext[_dataGridView.DataSource]; - bindingContext.SuspendBinding(); - { - for (var r = _dataGridView.RowCount - 1; r >= 0; r--) - _dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); - } + // https://stackoverflow.com/a/18942430 + var bindingContext = BindingContext[_dataGridView.DataSource]; + bindingContext.SuspendBinding(); + { + for (var r = _dataGridView.RowCount - 1; r >= 0; r--) + _dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); + } - //Causes repainting of the DataGridView - bindingContext.ResumeBinding(); + //Causes repainting of the DataGridView + bindingContext.ResumeBinding(); VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible)); - } + } - #endregion + #endregion - #region DataGridView Macro + #region DataGridView Macro - private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); - private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); + private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); + private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); - #endregion - } + #endregion + } } diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 838dda29..377cdf86 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -1,75 +1,74 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace LibationWinForms { - class SortableBindingList2 : BindingList where T : IObjectMemberComparable - { - private bool isSorted; - private ListSortDirection listSortDirection; - private PropertyDescriptor propertyDescriptor; + internal class SortableBindingList2 : BindingList where T : IObjectMemberComparable + { + private bool isSorted; + private ListSortDirection listSortDirection; + private PropertyDescriptor propertyDescriptor; - public SortableBindingList2() : base(new List()) { } - public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } + public SortableBindingList2() : base(new List()) { } + public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } - private ObjectMemberComparer Comparer { get; } = new(); - protected override bool SupportsSortingCore => true; - protected override bool SupportsSearchingCore => true; - protected override bool IsSortedCore => isSorted; - protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; - protected override ListSortDirection SortDirectionCore => listSortDirection; + private ObjectMemberComparer Comparer { get; } = new(); + protected override bool SupportsSortingCore => true; + protected override bool SupportsSearchingCore => true; + protected override bool IsSortedCore => isSorted; + protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; + protected override ListSortDirection SortDirectionCore => listSortDirection; - protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) - { - List itemsList = (List)Items; + protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) + { + List itemsList = (List)Items; - Comparer.PropertyName = property.Name; - Comparer.Direction = direction; + Comparer.PropertyName = property.Name; + Comparer.Direction = direction; - //Array.Sort() and List.Sort() are unstable sorts. OrderBy is stable. - var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); + //Array.Sort() and List.Sort() are unstable sorts. OrderBy is stable. + var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); - itemsList.Clear(); - itemsList.AddRange(sortedItems); + itemsList.Clear(); + itemsList.AddRange(sortedItems); - propertyDescriptor = property; - listSortDirection = direction; - isSorted = true; + propertyDescriptor = property; + listSortDirection = direction; + isSorted = true; - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } - protected override void RemoveSortCore() - { - isSorted = false; - propertyDescriptor = base.SortPropertyCore; - listSortDirection = base.SortDirectionCore; + protected override void RemoveSortCore() + { + isSorted = false; + propertyDescriptor = base.SortPropertyCore; + listSortDirection = base.SortDirectionCore; - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - //NOTE: Libation does not currently use BindingSource.Find anywhere, - //so this override may be removed (along with SupportsSearchingCore) - protected override int FindCore(PropertyDescriptor property, object key) - { - int count = Count; + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + //NOTE: Libation does not currently use BindingSource.Find anywhere, + //so this override may be removed (along with SupportsSearchingCore) + protected override int FindCore(PropertyDescriptor property, object key) + { + int count = Count; - System.Collections.IComparer valueComparer = null; + System.Collections.IComparer valueComparer = null; - for (int i = 0; i < count; ++i) - { - T element = this[i]; - var elemValue = element.GetMemberValue(property.Name); - valueComparer ??= element.GetMemberComparer(elemValue.GetType()); + for (int i = 0; i < count; ++i) + { + T element = this[i]; + var elemValue = element.GetMemberValue(property.Name); + valueComparer ??= element.GetMemberComparer(elemValue.GetType()); - if (valueComparer.Compare(elemValue, key) == 0) - { - return i; - } - } + if (valueComparer.Compare(elemValue, key) == 0) + { + return i; + } + } - return -1; - } - } + return -1; + } + } }