using DataLayer; using Dinah.Core; using FileLiberator; using LibationFileManager; using LibationWinForms.BookLiberation; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { public enum ProcessBookResult { None, Success, Cancelled, ValidationFail, FailedRetry, FailedSkip, FailedAbort } public enum ProcessBookStatus { Queued, Cancelled, Working, Completed, Failed } internal enum QueuePosition { Absent, Completed, Current, Fisrt, OneUp, OneDown, Last } internal enum QueuePositionRequest { Fisrt, OneUp, OneDown, Last } internal delegate QueuePosition ProcessControlReorderHandler(ProcessBook sender, QueuePositionRequest arg); internal delegate void ProcessControlEventArgs(ProcessBook sender, T arg); internal delegate void ProcessControlEventArgs(ProcessBook sender, EventArgs arg); internal class ProcessBook { public event EventHandler Completed; public event ProcessControlEventArgs Cancelled; public event ProcessControlReorderHandler RequestMove; public GridEntry Entry { get; } //public ProcessBookControl BookControl { get; } private Func _makeFirstProc; private Processable _firstProcessable; private bool cancelled = false; private bool running = false; public Processable FirstProcessable => _firstProcessable ??= _makeFirstProc?.Invoke(); private readonly Queue> Processes = new(); LogMe Logger; public ProcessBook(GridEntry entry, LogMe logme) { Entry = entry; //BookControl = new ProcessBookControl(Entry.Title, Entry.Cover); //BookControl.CancelAction = Cancel; //BookControl.RequestMoveAction = MoveRequested; Logger = logme; } public QueuePosition? MoveRequested(QueuePositionRequest requestedPosition) { return RequestMove?.Invoke(this, requestedPosition); } public void Cancel() { cancelled = true; try { if (FirstProcessable is AudioDecodable audioDecodable) audioDecodable.Cancel(); } catch(Exception ex) { Logger.Error(ex, "Error while cancelling"); } if (!running) Cancelled?.Invoke(this, EventArgs.Empty); } public async Task ProcessOneAsync() { running = true; ProcessBookResult result = ProcessBookResult.None; try { LinkProcessable(FirstProcessable); var statusHandler = await FirstProcessable.ProcessSingleAsync(Entry.LibraryBook, validate: true); if (statusHandler.IsSuccess) return result = ProcessBookResult.Success; else if (cancelled) { Logger.Info($"Process was cancelled {Entry.LibraryBook.Book}"); return result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { Logger.Info($"Validation failed {Entry.LibraryBook.Book}"); return result = ProcessBookResult.ValidationFail; } foreach (var errorMessage in statusHandler.Errors) Logger.Error(errorMessage); } catch (Exception ex) { Logger.Error(ex); } finally { if (result == ProcessBookResult.None) result = showRetry(Entry.LibraryBook); //BookControl.SetResult(result); } return result; } public void AddPdfProcessable() => AddProcessable(); public void AddDownloadDecryptProcessable() => AddProcessable(); public void AddConvertMp3Processable() => AddProcessable(); private void AddProcessable() where T : Processable, new() { if (FirstProcessable == null) { _makeFirstProc = () => new T(); } else Processes.Enqueue(() => new T()); } private void LinkProcessable(Processable strProc) { strProc.Begin += Processable_Begin; strProc.Completed += Processable_Completed; } private void Processable_Begin(object sender, LibraryBook libraryBook) { //BookControl.RegisterFileLiberator((Processable)sender, Logger); //BookControl.Processable_Begin(sender, libraryBook); } private async void Processable_Completed(object sender, LibraryBook e) { ((Processable)sender).Begin -= Processable_Begin; if (Processes.Count > 0) { var nextProcessFunc = Processes.Dequeue(); var nextProcess = nextProcessFunc(); LinkProcessable(nextProcess); var result = await nextProcess.ProcessSingleAsync(e, true); if (result.HasErrors) { foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed")) Logger.Error(errorMessage); Completed?.Invoke(this, EventArgs.Empty); running = false; } } else { Completed?.Invoke(this, EventArgs.Empty); running = false; } } private ProcessBookResult showRetry(LibraryBook libraryBook) { Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed"); DialogResult? dialogResult = Configuration.Instance.BadBook switch { Configuration.BadBookAction.Abort => DialogResult.Abort, Configuration.BadBookAction.Retry => DialogResult.Retry, Configuration.BadBookAction.Ignore => DialogResult.Ignore, Configuration.BadBookAction.Ask => null, _ => null }; 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]"; } // if null then ask user dialogResult ??= MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton); if (dialogResult == DialogResult.Abort) return ProcessBookResult.FailedAbort; if (dialogResult == SkipResult) { libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error; ApplicationServices.LibraryCommands.UpdateUserDefinedItem(libraryBook.Book); Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}"); return ProcessBookResult.FailedSkip; } return ProcessBookResult.FailedRetry; } protected string SkipDialogText => @" An error occurred while trying to process this book. {0} - ABORT: Stop processing books. - RETRY: retry this book later. Just skip it for now. Continue processing books. (Will try this book again later.) - IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.) ".Trim(); protected MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; protected MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1; protected DialogResult SkipResult => DialogResult.Ignore; } }