From 763a6cb31ad179a910a06ae335c76339864a2e25 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 13 May 2022 00:21:41 -0600 Subject: [PATCH 01/33] Added VirtualFlowControl and BookQueue --- .../ProcessQueue/BookQueue.cs | 175 +++++++++++ .../ProcessQueue/ProcessBook.cs | 53 ++-- .../ProcessBookControl.Designer.cs | 139 +++++---- .../ProcessQueue/ProcessBookControl.cs | 176 +++++++---- .../ProcessQueue/ProcessBookControl.resx | 170 ++++++----- .../ProcessQueue/ProcessBookQueue.Designer.cs | 64 ++-- .../ProcessQueue/ProcessBookQueue.cs | 277 ++++++------------ .../VirtualFlowControl.Designer.cs | 58 ++++ .../ProcessQueue/VirtualFlowControl.cs | 146 +++++++++ .../ProcessQueue/VirtualFlowControl.resx | 60 ++++ 10 files changed, 902 insertions(+), 416 deletions(-) create mode 100644 Source/LibationWinForms/ProcessQueue/BookQueue.cs create mode 100644 Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs create mode 100644 Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs create mode 100644 Source/LibationWinForms/ProcessQueue/VirtualFlowControl.resx diff --git a/Source/LibationWinForms/ProcessQueue/BookQueue.cs b/Source/LibationWinForms/ProcessQueue/BookQueue.cs new file mode 100644 index 00000000..c36554ab --- /dev/null +++ b/Source/LibationWinForms/ProcessQueue/BookQueue.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibationWinForms.ProcessQueue +{ + internal class TrackedQueue where T : class + { + public T Current { get; private set; } + private readonly LinkedList Queued = new(); + private readonly List Completed = new(); + private readonly object lockObject = new(); + + public int Count => Queued.Count + Completed.Count + (Current == null ? 0 : 1); + + public T this[int index] + { + get + { + if (index < Completed.Count) + return Completed[index]; + index -= Completed.Count; + + if (index == 0&& Current != null) return Current; + + if (Current != null) index--; + + if (index < Queued.Count) return Queued.ElementAt(index); + + throw new IndexOutOfRangeException(); + } + } + + public List QueuedItems() + { + lock (lockObject) + return Queued.ToList(); + } + public List CompletedItems() + { + lock (lockObject) + return Completed.ToList(); + } + + public bool QueueCount + { + get + { + lock (lockObject) + return Queued.Count > 0; + } + } + + public bool CompleteCount + { + get + { + lock (lockObject) + return Completed.Count > 0; + } + } + + + public void ClearQueue() + { + lock (lockObject) + { + Queued.Clear(); + } + } + + public void ClearCompleted() + { + lock (lockObject) + { + Completed.Clear(); + } + } + + public bool Any(Func predicate) + { + lock (lockObject) + { + return (Current != null && predicate(Current)) || Completed.Any(predicate) || Queued.Any(predicate); + } + } + + public QueuePosition MoveQueuePosition(T item, QueuePositionRequest requestedPosition) + { + lock (lockObject) + { + if (Queued.Count == 0) + { + + if (Current != null && Current == item) + return QueuePosition.Current; + if (Completed.Contains(item)) + return QueuePosition.Completed; + return QueuePosition.Absent; + } + + var node = Queued.Find(item); + if (node is null) return QueuePosition.Absent; + + if ((requestedPosition == QueuePositionRequest.Fisrt || requestedPosition == QueuePositionRequest.OneUp) && Queued.First.Value == item) + return QueuePosition.Fisrt; + if ((requestedPosition == QueuePositionRequest.Last || requestedPosition == QueuePositionRequest.OneDown) && Queued.Last.Value == item) + return QueuePosition.Last; + + if (requestedPosition == QueuePositionRequest.OneUp) + { + var oneUp = node.Previous; + Queued.Remove(node); + Queued.AddBefore(oneUp, node.Value); + return Queued.First.Value == item? QueuePosition.Fisrt : QueuePosition.OneUp; + } + else if (requestedPosition == QueuePositionRequest.OneDown) + { + var oneDown = node.Next; + Queued.Remove(node); + Queued.AddAfter(oneDown, node.Value); + return Queued.Last.Value == item ? QueuePosition.Last : QueuePosition.OneDown; + } + else if (requestedPosition == QueuePositionRequest.Fisrt) + { + Queued.Remove(node); + Queued.AddFirst(node); + return QueuePosition.Fisrt; + } + else + { + Queued.Remove(node); + Queued.AddLast(node); + return QueuePosition.Last; + } + } + } + + public bool Remove(T item) + { + lock (lockObject) + { + return Queued.Remove(item); + } + } + + public bool MoveNext() + { + lock (lockObject) + { + if (Current != null) + Completed.Add(Current); + if (Queued.Count == 0) return false; + Current = Queued.First.Value; + Queued.RemoveFirst(); + return true; + } + } + public T PeekNext() + { + lock (lockObject) + return Queued.Count > 0 ? Queued.First.Value : default; + } + + public void EnqueueBook(T item) + { + lock (lockObject) + Queued.AddLast(item); + } + + } +} diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index df142e56..2792ca81 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -16,22 +16,39 @@ namespace LibationWinForms.ProcessQueue 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, QueuePosition arg); + internal delegate QueuePosition ProcessControlReorderHandler(ProcessBook sender, QueuePositionRequest arg); internal delegate void ProcessControlEventArgs(ProcessBook sender, T arg); internal delegate void ProcessControlEventArgs(ProcessBook sender, EventArgs arg); @@ -41,7 +58,7 @@ namespace LibationWinForms.ProcessQueue public event ProcessControlEventArgs Cancelled; public event ProcessControlReorderHandler RequestMove; public GridEntry Entry { get; } - public ILiberationBaseForm BookControl { get; } + //public ProcessBookControl BookControl { get; } private Func _makeFirstProc; private Processable _firstProcessable; @@ -55,20 +72,15 @@ namespace LibationWinForms.ProcessQueue public ProcessBook(GridEntry entry, LogMe logme) { Entry = entry; - BookControl = new ProcessBookControl(Entry.Title, Entry.Cover); - BookControl.CancelAction = Cancel; - BookControl.MoveUpAction = MoveUp; - BookControl.MoveDownAction = MoveDown; + //BookControl = new ProcessBookControl(Entry.Title, Entry.Cover); + //BookControl.CancelAction = Cancel; + //BookControl.RequestMoveAction = MoveRequested; Logger = logme; } - public QueuePosition? MoveUp() + public QueuePosition? MoveRequested(QueuePositionRequest requestedPosition) { - return RequestMove?.Invoke(this, QueuePosition.OneUp); - } - public QueuePosition? MoveDown() - { - return RequestMove?.Invoke(this, QueuePosition.OneDown); + return RequestMove?.Invoke(this, requestedPosition); } public void Cancel() @@ -94,11 +106,9 @@ namespace LibationWinForms.ProcessQueue ProcessBookResult result = ProcessBookResult.None; try { - var firstProc = FirstProcessable; + LinkProcessable(FirstProcessable); - LinkProcessable(firstProc); - - var statusHandler = await firstProc.ProcessSingleAsync(Entry.LibraryBook, validate: true); + var statusHandler = await FirstProcessable.ProcessSingleAsync(Entry.LibraryBook, validate: true); if (statusHandler.IsSuccess) @@ -108,6 +118,11 @@ namespace LibationWinForms.ProcessQueue 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); @@ -121,7 +136,7 @@ namespace LibationWinForms.ProcessQueue if (result == ProcessBookResult.None) result = showRetry(Entry.LibraryBook); - BookControl.SetResult(result); + //BookControl.SetResult(result); } return result; @@ -149,8 +164,8 @@ namespace LibationWinForms.ProcessQueue private void Processable_Begin(object sender, LibraryBook libraryBook) { - BookControl.RegisterFileLiberator((Processable)sender, Logger); - BookControl.Processable_Begin(sender, libraryBook); + //BookControl.RegisterFileLiberator((Processable)sender, Logger); + //BookControl.Processable_Begin(sender, libraryBook); } private async void Processable_Completed(object sender, LibraryBook e) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs index ee395dd3..e77e0211 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs @@ -30,13 +30,16 @@ { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessBookControl)); this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.bookInfoLbl = new System.Windows.Forms.Label(); this.progressBar1 = new System.Windows.Forms.ProgressBar(); this.remainingTimeLbl = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); + this.etaLbl = new System.Windows.Forms.Label(); this.cancelBtn = new System.Windows.Forms.Button(); + this.statusLbl = new System.Windows.Forms.Label(); + this.bookInfoLbl = new System.Windows.Forms.Label(); this.moveUpBtn = new System.Windows.Forms.Button(); this.moveDownBtn = new System.Windows.Forms.Button(); + this.moveFirstBtn = new System.Windows.Forms.Button(); + this.moveLastBtn = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.SuspendLayout(); // @@ -49,18 +52,6 @@ this.pictureBox1.TabIndex = 0; this.pictureBox1.TabStop = false; // - // bookInfoLbl - // - this.bookInfoLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.bookInfoLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.bookInfoLbl.Location = new System.Drawing.Point(89, 3); - this.bookInfoLbl.Name = "bookInfoLbl"; - this.bookInfoLbl.Size = new System.Drawing.Size(255, 56); - this.bookInfoLbl.TabIndex = 1; - this.bookInfoLbl.Text = "[multi-\r\nline\r\nbook\r\n info]"; - // // progressBar1 // this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) @@ -81,17 +72,17 @@ this.remainingTimeLbl.Text = "--:--"; this.remainingTimeLbl.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // label1 + // etaLbl // - this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.label1.Location = new System.Drawing.Point(304, 66); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(28, 13); - this.label1.TabIndex = 3; - this.label1.Text = "ETA:"; - this.label1.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.etaLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.etaLbl.AutoSize = true; + this.etaLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.etaLbl.Location = new System.Drawing.Point(304, 66); + this.etaLbl.Name = "etaLbl"; + this.etaLbl.Size = new System.Drawing.Size(28, 13); + this.etaLbl.TabIndex = 3; + this.etaLbl.Text = "ETA:"; + this.etaLbl.TextAlign = System.Drawing.ContentAlignment.TopRight; // // cancelBtn // @@ -101,7 +92,7 @@ this.cancelBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; this.cancelBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.cancelBtn.ForeColor = System.Drawing.SystemColors.Control; - this.cancelBtn.Location = new System.Drawing.Point(352, 3); + this.cancelBtn.Location = new System.Drawing.Point(348, 6); this.cancelBtn.Margin = new System.Windows.Forms.Padding(0); this.cancelBtn.Name = "cancelBtn"; this.cancelBtn.Size = new System.Drawing.Size(20, 20); @@ -109,53 +100,100 @@ this.cancelBtn.UseVisualStyleBackColor = false; this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click); // + // statusLbl + // + this.statusLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.statusLbl.AutoSize = true; + this.statusLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.statusLbl.Location = new System.Drawing.Point(89, 66); + this.statusLbl.Name = "statusLbl"; + this.statusLbl.Size = new System.Drawing.Size(50, 13); + this.statusLbl.TabIndex = 3; + this.statusLbl.Text = "[STATUS]"; + this.statusLbl.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // bookInfoLbl + // + this.bookInfoLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.bookInfoLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.bookInfoLbl.Location = new System.Drawing.Point(89, 6); + this.bookInfoLbl.Name = "bookInfoLbl"; + this.bookInfoLbl.Size = new System.Drawing.Size(219, 56); + this.bookInfoLbl.TabIndex = 1; + this.bookInfoLbl.Text = "[multi-\r\nline\r\nbook\r\n info]"; + // // moveUpBtn // - this.moveUpBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.moveUpBtn.BackColor = System.Drawing.Color.Transparent; + this.moveUpBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); this.moveUpBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveUpBtn.BackgroundImage"))); - this.moveUpBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; - this.moveUpBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.moveUpBtn.ForeColor = System.Drawing.SystemColors.Control; - this.moveUpBtn.Location = new System.Drawing.Point(347, 39); - this.moveUpBtn.Margin = new System.Windows.Forms.Padding(0); + this.moveUpBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveUpBtn.Location = new System.Drawing.Point(314, 24); this.moveUpBtn.Name = "moveUpBtn"; - this.moveUpBtn.Size = new System.Drawing.Size(25, 10); - this.moveUpBtn.TabIndex = 4; - this.moveUpBtn.UseVisualStyleBackColor = false; - this.moveUpBtn.Click += new System.EventHandler(this.moveUpBtn_Click); + this.moveUpBtn.Size = new System.Drawing.Size(30, 17); + this.moveUpBtn.TabIndex = 5; + this.moveUpBtn.UseVisualStyleBackColor = true; + this.moveUpBtn.Click += new System.EventHandler(this.moveUpBtn_Click_1); // // moveDownBtn // - this.moveDownBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.moveDownBtn.BackColor = System.Drawing.Color.Transparent; + this.moveDownBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); this.moveDownBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveDownBtn.BackgroundImage"))); - this.moveDownBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; - this.moveDownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.moveDownBtn.ForeColor = System.Drawing.SystemColors.Control; - this.moveDownBtn.Location = new System.Drawing.Point(347, 49); - this.moveDownBtn.Margin = new System.Windows.Forms.Padding(0); + this.moveDownBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveDownBtn.Location = new System.Drawing.Point(314, 40); this.moveDownBtn.Name = "moveDownBtn"; - this.moveDownBtn.Size = new System.Drawing.Size(25, 10); + this.moveDownBtn.Size = new System.Drawing.Size(30, 17); this.moveDownBtn.TabIndex = 5; - this.moveDownBtn.UseVisualStyleBackColor = false; + this.moveDownBtn.UseVisualStyleBackColor = true; this.moveDownBtn.Click += new System.EventHandler(this.moveDownBtn_Click); // + // moveFirstBtn + // + this.moveFirstBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.moveFirstBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveFirstBtn.BackgroundImage"))); + this.moveFirstBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveFirstBtn.Location = new System.Drawing.Point(314, 3); + this.moveFirstBtn.Name = "moveFirstBtn"; + this.moveFirstBtn.Size = new System.Drawing.Size(30, 17); + this.moveFirstBtn.TabIndex = 5; + this.moveFirstBtn.UseVisualStyleBackColor = true; + this.moveFirstBtn.Click += new System.EventHandler(this.moveFirstBtn_Click); + // + // moveLastBtn + // + this.moveLastBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.moveLastBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveLastBtn.BackgroundImage"))); + this.moveLastBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveLastBtn.Location = new System.Drawing.Point(314, 63); + this.moveLastBtn.Name = "moveLastBtn"; + this.moveLastBtn.Size = new System.Drawing.Size(30, 17); + this.moveLastBtn.TabIndex = 5; + this.moveLastBtn.UseVisualStyleBackColor = true; + this.moveLastBtn.Click += new System.EventHandler(this.moveLastBtn_Click); + // // ProcessBookControl // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.SystemColors.ControlLight; this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.Controls.Add(this.moveLastBtn); this.Controls.Add(this.moveDownBtn); + this.Controls.Add(this.moveFirstBtn); this.Controls.Add(this.moveUpBtn); this.Controls.Add(this.cancelBtn); - this.Controls.Add(this.label1); + this.Controls.Add(this.statusLbl); + this.Controls.Add(this.etaLbl); this.Controls.Add(this.remainingTimeLbl); this.Controls.Add(this.progressBar1); this.Controls.Add(this.bookInfoLbl); this.Controls.Add(this.pictureBox1); - this.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1); + this.Margin = new System.Windows.Forms.Padding(2); this.Name = "ProcessBookControl"; this.Size = new System.Drawing.Size(375, 86); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); @@ -167,12 +205,15 @@ #endregion private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.Label bookInfoLbl; private System.Windows.Forms.ProgressBar progressBar1; private System.Windows.Forms.Label remainingTimeLbl; - private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label etaLbl; private System.Windows.Forms.Button cancelBtn; + private System.Windows.Forms.Label statusLbl; + private System.Windows.Forms.Label bookInfoLbl; private System.Windows.Forms.Button moveUpBtn; private System.Windows.Forms.Button moveDownBtn; + private System.Windows.Forms.Button moveFirstBtn; + private System.Windows.Forms.Button moveLastBtn; } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index f0875715..76f73368 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -1,6 +1,7 @@ using System; using System.Drawing; using System.Windows.Forms; +using System.Windows.Forms.Layout; using DataLayer; using Dinah.Core.Net.Http; using Dinah.Core.Threading; @@ -11,7 +12,7 @@ using LibationWinForms.ProcessQueue; namespace LibationWinForms.ProcessQueue { - internal interface ILiberationBaseForm + internal interface ILiberatiofffnBaseForm { Action CancelAction { get; set; } Func MoveUpAction { get; set; } @@ -25,11 +26,11 @@ namespace LibationWinForms.ProcessQueue Padding Margin { get; set; } } - internal partial class ProcessBookControl : UserControl, ILiberationBaseForm + internal partial class ProcessBookControl : UserControl { + public ProcessBookStatus Status { get; private set; } = ProcessBookStatus.Queued; public Action CancelAction { get; set; } - public Func MoveUpAction { get; set; } - public Func MoveDownAction { get; set; } + public Func RequestMoveAction { get; set; } public string DecodeActionName { get; } = "Decoding"; private Func GetCoverArtDelegate; protected Processable Processable { get; private set; } @@ -37,47 +38,118 @@ namespace LibationWinForms.ProcessQueue public ProcessBookControl() { InitializeComponent(); - label1.Text = "Queued"; + statusLbl.Text = "Queued"; remainingTimeLbl.Visible = false; progressBar1.Visible = false; + etaLbl.Visible = false; } - public void SetResult(ProcessBookResult status) + public void SetCover(Image cover) { - var statusTxt = status switch - { - ProcessBookResult.Success => "Finished", - ProcessBookResult.Cancelled => "Cancelled", - ProcessBookResult.FailedRetry => "Error, Retry", - ProcessBookResult.FailedSkip => "Error, Skip", - ProcessBookResult.FailedAbort => "Error, Abort", - _ => throw new NotImplementedException(), - }; + pictureBox1.Image = cover; + } + public void SetTitle(string title) + { + bookInfoLbl.Text = title; + } - Color backColor = status switch + public void SetResult(ProcessBookResult result) + { + string statusText = default; + switch (result) { - ProcessBookResult.Success => Color.PaleGreen, - ProcessBookResult.Cancelled => Color.Khaki, - ProcessBookResult.FailedRetry => Color.LightCoral, - ProcessBookResult.FailedSkip => Color.LightCoral, - ProcessBookResult.FailedAbort => Color.Firebrick, - _ => throw new NotImplementedException(), - }; + case ProcessBookResult.Success: + statusText = "Finished"; + Status = ProcessBookStatus.Completed; + break; + case ProcessBookResult.Cancelled: + statusText = "Cancelled"; + Status = ProcessBookStatus.Cancelled; + break; + case ProcessBookResult.FailedRetry: + statusText = "Queued"; + Status = ProcessBookStatus.Queued; + break; + case ProcessBookResult.FailedSkip: + statusText = "Error, Skip"; + Status = ProcessBookStatus.Failed; + break; + case ProcessBookResult.FailedAbort: + statusText = "Error, Abort"; + Status = ProcessBookStatus.Failed; + break; + case ProcessBookResult.ValidationFail: + statusText = "Validate fail"; + Status = ProcessBookStatus.Failed; + break; + case ProcessBookResult.None: + statusText = "UNKNOWN"; + Status = ProcessBookStatus.Failed; + break; + } + + SetStatus(Status, statusText); + } + + public void SetStatus(ProcessBookStatus status, string statusText) + { + Color backColor = default; + switch (status) + { + case ProcessBookStatus.Completed: + backColor = Color.PaleGreen; + Status = ProcessBookStatus.Completed; + break; + case ProcessBookStatus.Cancelled: + backColor = Color.Khaki; + Status = ProcessBookStatus.Cancelled; + break; + case ProcessBookStatus.Queued: + backColor = SystemColors.Control; + Status = ProcessBookStatus.Queued; + break; + case ProcessBookStatus.Working: + backColor = SystemColors.Control; + Status = ProcessBookStatus.Working; + break; + case ProcessBookStatus.Failed: + backColor = Color.LightCoral; + Status = ProcessBookStatus.Failed; + break; + } this.UIThreadAsync(() => { - cancelBtn.Visible = false; - moveDownBtn.Visible = false; - moveUpBtn.Visible = false; - remainingTimeLbl.Visible = false; - progressBar1.Visible = false; - label1.Text = statusTxt; + SuspendLayout(); + + cancelBtn.Visible = Status is ProcessBookStatus.Queued or ProcessBookStatus.Working; + moveLastBtn.Visible = Status == ProcessBookStatus.Queued; + moveDownBtn.Visible = Status == ProcessBookStatus.Queued; + moveUpBtn.Visible = Status == ProcessBookStatus.Queued; + moveFirstBtn.Visible = Status == ProcessBookStatus.Queued; + remainingTimeLbl.Visible = Status == ProcessBookStatus.Working; + progressBar1.Visible = Status == ProcessBookStatus.Working; + etaLbl.Visible = Status == ProcessBookStatus.Working; + statusLbl.Visible = Status != ProcessBookStatus.Working; + statusLbl.Text = statusText; BackColor = backColor; + + if (status == ProcessBookStatus.Working) + { + bookInfoLbl.Width += moveLastBtn.Width + moveLastBtn.Padding.Left + moveLastBtn.Padding.Right; + } + else + { + bookInfoLbl.Width -= moveLastBtn.Width + moveLastBtn.Padding.Left + moveLastBtn.Padding.Right; + + } + ResumeLayout(); }); } public ProcessBookControl(string title, Image cover) : this() { + this.title = title; pictureBox1.Image = cover; bookInfoLbl.Text = title; } @@ -190,14 +262,11 @@ namespace LibationWinForms.ProcessQueue #region Processable event handlers public void Processable_Begin(object sender, LibraryBook libraryBook) { + Status = ProcessBookStatus.Working; + LogMe.Info($"{Environment.NewLine}{Processable.Name} Step, Begin: {libraryBook.Book}"); - this.UIThreadAsync(() => - { - label1.Text = "ETA:"; - remainingTimeLbl.Visible = true; - progressBar1.Visible = true; - }); + SetStatus(ProcessBookStatus.Working, ""); GetCoverArtDelegate = () => PictureStorage.GetPictureSynchronously( new PictureDefinition( @@ -265,38 +334,29 @@ namespace LibationWinForms.ProcessQueue CancelAction?.Invoke(); } - private void moveUpBtn_Click(object sender, EventArgs e) + private void moveLastBtn_Click(object sender, EventArgs e) { - HandleMovePositionResult(MoveUpAction?.Invoke()); + RequestMoveAction?.Invoke(QueuePositionRequest.Last); } + private void moveFirstBtn_Click(object sender, EventArgs e) + { + RequestMoveAction?.Invoke(QueuePositionRequest.Fisrt); + } + + private void moveUpBtn_Click_1(object sender, EventArgs e) + { + RequestMoveAction?.Invoke(QueuePositionRequest.OneUp); + } private void moveDownBtn_Click(object sender, EventArgs e) { - HandleMovePositionResult(MoveDownAction?.Invoke()); + RequestMoveAction?.Invoke(QueuePositionRequest.OneDown); } - private void HandleMovePositionResult(QueuePosition? result) + public override string ToString() { - if (result.HasValue) - SetQueuePosition(result.Value); - else - SetQueuePosition(QueuePosition.Absent); - } - - public void SetQueuePosition(QueuePosition status) - { - if (status is QueuePosition.Absent or QueuePosition.Current) - { - moveUpBtn.Visible = false; - moveDownBtn.Visible = false; - } - - if (status == QueuePosition.Absent) - cancelBtn.Enabled = false; - - moveUpBtn.Enabled = status != QueuePosition.Fisrt; - moveDownBtn.Enabled = status != QueuePosition.Last; + return title ?? "NO TITLE"; } } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx index 180633dc..a91b8cad 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx @@ -57,24 +57,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - True - - - True - - - True - - - True - - - True - - - True - @@ -737,62 +719,114 @@ /x9W31o+WFcHNAAAAABJRU5ErkJggg== - - True - - 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 - - True - - iVBORw0KGgoAAAANSUhEUgAAAKoAAABXCAYAAACUet5FAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 - MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAAAQ+SURBVHhe7dLLcRsxFERRrRyH - Q3DICtEZ0NOy6aKoq+H0fB+AXpxNVwFvc99ut1tEeThGVINjRDU4RlSDY0Q1OEZUg6O83X78mvyeTEPE - 4d6pwzsc76bHiTXOMBup4Pho+iSxxpFeRio4Pps+S6xxhEWRCo5k+jSxxp4WRyo4kunjxBp7sSIVHMn0 - eWKNPdiRCo5kOpBYY6tVkQqOZDryLLGGY3WkgiOZDpHEGktsilRwJNOx7yTWmPM+waYcOBIdm5FYg3xE - KtSUA0dyPzgjscaj/5EKNeXAkTwenZFYQz5FKtSUA0fyfHhGYh3bl0iFmnLgSOj4jMQ6JoxUqCkHjoSO - v5BYx/JtpEJNOXAkdHyBxDqG2UiFmnLgSOj4Qom1by8jFWrKgSOh44bE2qdFkQo15cCR0HFTYu3L4kiF - mnLgSOj4Com1D1akQk05cCR0fKXE2jY7UqGmHDgSOr5BYm3TqkiFmnLgSOj4Rom1LasjFWrKgSOh4ztI - rG3YFKlQUw4cCR3fSWKtbXOkQk05cCR0fEeJtaZdIhVqyoEjoeM7S6y17BapUFMOHAkdP0BirWHXSIWa - cuBI6PhBEuu1do9UqCkHjoSOHyixXuOQSIWacuBI6PjBEuu5DotUqCkHjoSOnyCxnuPQSIWacuBI6PhJ - EuuxDo9UqCkHjoSOnyixHuOUSIWacuBI6PjJEuu+TotUqCkHjoSOXyCx7uPUSIWacuBI6PhFEus2p0cq - 1JQDR0LHL5RY17kkUqGmHDgSOn6xxOq5LFKhphw4EjpeQGJd5tJIhZpy4EjoeBGJdd7lkQo15cCR0PFC - EisrEalQUw4cCR0vJrF+ViZSoaYcOBI6XlBi/atUpEJNOXAkdLyo0WMtF6lQUw4cCR0vbNRYS0Yq1JQD - R0LHixst1rKRCjXlwJHQ8QaMEmvpSIWacuBI6Hgjeo+1fKRCTTlwJHS8Ib3G2kSkQk05cCR0vDG9xdpM - pEJNOXAkdLxBvcTaVKRCTTlwJHS8Ua3H2lykQk05cCR0vGGtxtpkpEJNOXAkdLxxrcXabKRCTTlwJHS8 - A63E2nSkQk05cCR0vBPVY20+UqGmHDgSOt6RqrF2EalQUw4cCR3vTLVYu4lUqCkHjoSOd6hKrF1FKtSU - A0dCxzt1dazdRSrUlANHQsc7dlWsXUYq1JQDR0LHO3d2rN1GKtSUA0dCxwdwVqxdRyrUlANHQscHcXSs - 3Ucq1JQDR0LHB3JUrENEKtSUA0dCxwezd6zDRCrUlANHQscHtFesQ0Uq1JQDR0LHB7U11uEiFWrKgSOh - 4wNbG+uQkQo15cCR0PHBubEOG6lQUw4cCR2PxbEOHalQUw4cCR2PD69iHT5SoaYcOBI6Hv99F2si/Yea - cuBI6Hh88hxrIn1ATTlwJHQ8vrjHmkifUFMOHAkdD/QTtuFRUw4cI6rBMaKW29sfKR2pZX+g2KEAAAAA - SUVORK5CYII= + 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 + jfkqyMbMAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAAMgAAABZCAYAAAB7Ymt4AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 + MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAAAUASURBVHhe7Zntpq1lFIYXERER + +wAiIjqAiIiIfQYRERHRGURERMQ+hoiIiA4gIiKivxER/YroV6zGncbezxxjrrHmmvP9eJ73vS6uH+uW + vVdrz9szbuvq+vr6wP95w/zLVIC4ZX8xX4w9cHPwiGfM78z2D0Pckp+ZT5qpB24ODnnM/MD8x2z/YMSR + /dN83XxI7IGbg+O8ZOopav8SxBH91tR1dEDsgZuDm3nK/Nxs/zLEUdQV9L6pqygRe+Dm4HYY8Dia/w1x + 80ZiD9wcnIaeqO/N9ptA7NGHQ7wi9sDNwenoqfrQZMBjj+rKORjiFbEHbg7uzsvmr2b7zSGuqX49kYZ4 + ReyBm4Pz0ID/wmy/ScSl1TWjX0scHeIVsQduDi6DAY9rqSGuX0ecReyBm4PLYcDj0urXD7pizib2wM3B + NDDgcQl1rehquZjYAzcH08KAx7nUlXKnIV4Re+DmYHoY8Dilukp0ndx5iFfEHrg5mI83TQY8XqKuEV0l + kxN74OZgXp41GfB4jhcP8YrYAzcH8/O4qSey/Z9HvMnJhnhF7IGbg+XQU/mb2f4wEFsnHeIVsQduDpZF + T+aXZvtDQZxliFfEHrg5WAcGPLqzDfGK2AM3B+vBgEf9OmC2IV4Re+DmYF0Y8PtU14OuiNWIPXBz0AcM + +P2oq0HXw6rEHrg56AcG/PbVtaCrYXViD9wc9MdbJgN+W+o6WHyIV8QeuDnok+fMH8z2h4xjqqtglSFe + EXvg5qBf9BR/ZLY/bBxHXQG6Brok9sDNQf+8YjLgx1Kvv66Abok9cHMwBk+bDPgx7GaIV8QeuDkYCwZ8 + v3Y3xCtiD9wcjAcDvj+7HOIVsQduDsaEAd+HXQ/xitgDNwdjw4Bfz+6HeEXsgZuD8WHAL69e7+6HeEXs + gZuD7fC2+bfZ/kPitOq11qs9PLEHbg62hZ78H832HxWnUa+0XutNEHvg5mB76On/2Gz/cfF89Srrdd4U + sQduDrbLqyYD/jL1Gg87xCtiD9wcbBsG/PnqFR56iFfEHrg52AcM+NPVq6vXd9PEHrg52A8M+Nv9yrxn + bp7YAzcH+4IBf9xNDvGK2AM3B/uEAf/IzQ7xitgDNwf7RaeETor2w7I3Nz3EK2IP3BzAO+beBvwuhnhF + 7IGbAxA6MX4y2w/RVt3NEK+IPXBzAI5OjU/M9sO0JfVK6rUEI/bAzQFEXjO3NuD1Ou5uiFfEHrg5gGNs + acDrVdzlEK+IPXBzABUjD/jfTb2GcITYAzcHcBsjDvivzd0P8YrYAzcHcAqjDHi9du+acAuxB24O4C7o + ZNHp0n4oe1Gv3PMmnEDsgZsDuCs6XXTCtB/Otf3UfMKEE4k9cHMA56JTZu0BzxA/k9gDNwdwCTpp1hrw + DPELiD1wcwCXotNmyQHPEJ+A2AM3BzAVSwx4hvhExB64OYApmXPAM8QnJPbAzQHMwZQD/g+TIT4xsQdu + DmAuphjw35gM8RmIPXBzAHOik0inUfuhP0W9Pu+ZMBOxB+7hF7AUOpF0KrUluMmfzRdMmJG2B62HX8CS + 6FTSydSWIfrAZIgvQNuD1sMvrq7u679F3Jn32x60Hn5BQXCfUhDEQgqCWEhBEAspCGIhBUEspCCIhRQE + sZCCIBZSEMRCCoJYSEEQCykIYiEFQSykIIiFFASxkIIgFlIQxEIKglhIQRALKQhiIQVBLKQgiIU3FOT6 + 6l8KOJAbKVKmPQAAAABJRU5ErkJggg== - - True - \ No newline at end of file diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs index 62848fbe..fd0d0322 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs @@ -30,9 +30,10 @@ { this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); + this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); - this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl(); this.panel1 = new System.Windows.Forms.Panel(); this.btnCleanFinished = new System.Windows.Forms.Button(); this.cancelAllBtn = new System.Windows.Forms.Button(); @@ -40,7 +41,7 @@ this.panel2 = new System.Windows.Forms.Panel(); this.clearLogBtn = new System.Windows.Forms.Button(); this.logMeTbox = new System.Windows.Forms.TextBox(); - this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.tabPage3 = new System.Windows.Forms.TabPage(); this.statusStrip1.SuspendLayout(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); @@ -56,7 +57,7 @@ this.toolStripStatusLabel1}); this.statusStrip1.Location = new System.Drawing.Point(0, 486); this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(359, 22); + this.statusStrip1.Size = new System.Drawing.Size(404, 22); this.statusStrip1.TabIndex = 1; this.statusStrip1.Text = "statusStrip1"; // @@ -65,6 +66,12 @@ this.toolStripProgressBar1.Name = "toolStripProgressBar1"; this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 16); // + // toolStripStatusLabel1 + // + this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + this.toolStripStatusLabel1.Size = new System.Drawing.Size(287, 17); + this.toolStripStatusLabel1.Spring = true; + // // tabControl1 // this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -72,38 +79,34 @@ | System.Windows.Forms.AnchorStyles.Right))); this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); this.tabControl1.Location = new System.Drawing.Point(0, 0); this.tabControl1.Margin = new System.Windows.Forms.Padding(0); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(360, 486); + this.tabControl1.Size = new System.Drawing.Size(405, 486); this.tabControl1.TabIndex = 3; // // tabPage1 // - this.tabPage1.Controls.Add(this.flowLayoutPanel1); + this.tabPage1.Controls.Add(this.virtualFlowControl2); this.tabPage1.Controls.Add(this.panel1); this.tabPage1.Location = new System.Drawing.Point(4, 24); this.tabPage1.Name = "tabPage1"; this.tabPage1.Padding = new System.Windows.Forms.Padding(3); - this.tabPage1.Size = new System.Drawing.Size(352, 458); + this.tabPage1.Size = new System.Drawing.Size(397, 458); this.tabPage1.TabIndex = 0; this.tabPage1.Text = "Process Queue"; this.tabPage1.UseVisualStyleBackColor = true; // - // flowLayoutPanel1 + // virtualFlowControl2 // - this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.flowLayoutPanel1.AutoScroll = true; - this.flowLayoutPanel1.BackColor = System.Drawing.SystemColors.ControlDarkDark; - this.flowLayoutPanel1.Location = new System.Drawing.Point(3, 3); - this.flowLayoutPanel1.Name = "flowLayoutPanel1"; - this.flowLayoutPanel1.Size = new System.Drawing.Size(346, 419); - this.flowLayoutPanel1.TabIndex = 0; - this.flowLayoutPanel1.ClientSizeChanged += new System.EventHandler(this.flowLayoutPanel1_ClientSizeChanged); - this.flowLayoutPanel1.Layout += new System.Windows.Forms.LayoutEventHandler(this.flowLayoutPanel1_Layout); + this.virtualFlowControl2.Dock = System.Windows.Forms.DockStyle.Fill; + this.virtualFlowControl2.Location = new System.Drawing.Point(3, 3); + this.virtualFlowControl2.Name = "virtualFlowControl2"; + this.virtualFlowControl2.Size = new System.Drawing.Size(391, 422); + this.virtualFlowControl2.TabIndex = 3; + this.virtualFlowControl2.VirtualControlCount = 0; // // panel1 // @@ -113,14 +116,14 @@ this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; this.panel1.Location = new System.Drawing.Point(3, 425); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(346, 30); + this.panel1.Size = new System.Drawing.Size(391, 30); this.panel1.TabIndex = 2; // // btnCleanFinished // this.btnCleanFinished.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); - this.btnCleanFinished.Location = new System.Drawing.Point(253, 3); + this.btnCleanFinished.Location = new System.Drawing.Point(298, 3); this.btnCleanFinished.Name = "btnCleanFinished"; this.btnCleanFinished.Size = new System.Drawing.Size(90, 23); this.btnCleanFinished.TabIndex = 3; @@ -147,7 +150,7 @@ this.tabPage2.Location = new System.Drawing.Point(4, 24); this.tabPage2.Name = "tabPage2"; this.tabPage2.Padding = new System.Windows.Forms.Padding(3); - this.tabPage2.Size = new System.Drawing.Size(352, 458); + this.tabPage2.Size = new System.Drawing.Size(397, 458); this.tabPage2.TabIndex = 1; this.tabPage2.Text = "Log"; this.tabPage2.UseVisualStyleBackColor = true; @@ -159,7 +162,7 @@ this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom; this.panel2.Location = new System.Drawing.Point(3, 425); this.panel2.Name = "panel2"; - this.panel2.Size = new System.Drawing.Size(346, 30); + this.panel2.Size = new System.Drawing.Size(391, 30); this.panel2.TabIndex = 1; // // clearLogBtn @@ -189,11 +192,15 @@ this.logMeTbox.Size = new System.Drawing.Size(346, 419); this.logMeTbox.TabIndex = 0; // - // toolStripStatusLabel1 + // tabPage3 // - this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; - this.toolStripStatusLabel1.Size = new System.Drawing.Size(211, 17); - this.toolStripStatusLabel1.Spring = true; + this.tabPage3.Location = new System.Drawing.Point(4, 24); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Padding = new System.Windows.Forms.Padding(3); + this.tabPage3.Size = new System.Drawing.Size(397, 458); + this.tabPage3.TabIndex = 2; + this.tabPage3.Text = "tabPage3"; + this.tabPage3.UseVisualStyleBackColor = true; // // ProcessBookQueue // @@ -203,7 +210,7 @@ this.Controls.Add(this.tabControl1); this.Controls.Add(this.statusStrip1); this.Name = "ProcessBookQueue"; - this.Size = new System.Drawing.Size(359, 508); + this.Size = new System.Drawing.Size(404, 508); this.statusStrip1.ResumeLayout(false); this.statusStrip1.PerformLayout(); this.tabControl1.ResumeLayout(false); @@ -222,7 +229,6 @@ private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1; private System.Windows.Forms.TabControl tabControl1; private System.Windows.Forms.TabPage tabPage1; - private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.TabPage tabPage2; private System.Windows.Forms.TextBox logMeTbox; @@ -231,5 +237,7 @@ private System.Windows.Forms.Panel panel2; private System.Windows.Forms.Button clearLogBtn; private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + private System.Windows.Forms.TabPage tabPage3; + private VirtualFlowControl virtualFlowControl2; } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs index af748764..3c0b37c6 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs @@ -12,11 +12,8 @@ namespace LibationWinForms.ProcessQueue { internal partial class ProcessBookQueue : UserControl, ILogForm { - private ProcessBook CurrentBook; - private readonly LinkedList BookQueue = new(); - private readonly List CompletedBooks = new(); + TrackedQueue Queue = new(); private readonly LogMe Logger; - private readonly object lockObject = new(); public Task QueueRunner { get; private set; } @@ -29,7 +26,6 @@ namespace LibationWinForms.ProcessQueue InitializeComponent(); Logger = LogMe.RegisterForm(this); - this.popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; this.popoutBtn.Name = "popoutBtn"; this.popoutBtn.Text = "Pop Out"; @@ -38,17 +34,35 @@ namespace LibationWinForms.ProcessQueue this.popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; statusStrip1.Items.Add(popoutBtn); - } - public async Task AddDownloadDecrypt(IEnumerable entries) - { - foreach (var entry in entries) - await AddDownloadDecryptAsync(entry); + + virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData; + } + private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList panelsToFill) + { + int numToShow = Math.Min(numVisible, Queue.Count - firstIndex); + for (int i = 0; i < numToShow; i++) + { + var proc = Queue[firstIndex + i]; + + panelsToFill[i].SetCover(proc.Entry.Cover); + panelsToFill[i].SetTitle(proc.Entry.Title); + } + } + + public async Task AddDownloadDecrypt(IEnumerable entries) + { + SuspendLayout(); + foreach (var entry in entries) + await AddDownloadDecryptAsync(entry); + ResumeLayout(); + } + int count = 0; public async Task AddDownloadDecryptAsync(GridEntry gridEntry) { - if (BookExists(gridEntry.LibraryBook)) - return; + //if (Queue.Any(b=> b?.Entry?.AudibleProductId == gridEntry.AudibleProductId)) + //return; ProcessBook pbook = new ProcessBook(gridEntry, Logger); pbook.Completed += Pbook_Completed; @@ -63,24 +77,24 @@ namespace LibationWinForms.ProcessQueue if (libStatus.PdfStatus != LiberatedStatus.Liberated) pbook.AddPdfProcessable(); - EnqueueBook(pbook); + Queue.EnqueueBook(pbook); - await AddBookControlAsync(pbook.BookControl); + //await AddBookControlAsync(pbook.BookControl); + count++; + + virtualFlowControl2.VirtualControlCount = count; if (!Running) { - QueueRunner = QueueLoop(); + //QueueRunner = QueueLoop(); } + toolStripStatusLabel1.Text = count.ToString(); } private async void Pbook_Cancelled(ProcessBook sender, EventArgs e) { - lock (lockObject) - { - if (BookQueue.Contains(sender)) - BookQueue.Remove(sender); - } - await RemoveBookControlAsync(sender.BookControl); + Queue.Remove(sender); + //await RemoveBookControlAsync(sender.BookControl); } /// @@ -89,76 +103,54 @@ namespace LibationWinForms.ProcessQueue /// The requesting /// The requested position /// The resultant position - private QueuePosition RequestMove(ProcessBook sender, QueuePosition direction) + private QueuePosition RequestMove(ProcessBook sender, QueuePositionRequest requested) { - var node = BookQueue.Find(sender); - if (node == null || direction == QueuePosition.Absent) - return QueuePosition.Absent; - if (CurrentBook != null && CurrentBook == sender) - return QueuePosition.Current; - if ((direction == QueuePosition.Fisrt || direction == QueuePosition.OneUp) && BookQueue.First.Value == sender) - return QueuePosition.Fisrt; - if ((direction == QueuePosition.Last || direction == QueuePosition.OneDown) && BookQueue.Last.Value == sender) - return QueuePosition.Last; + var direction = Queue.MoveQueuePosition(sender, requested); - if (direction == QueuePosition.OneUp) - { - var oneUp = node.Previous; - BookQueue.Remove(node); - BookQueue.AddBefore(oneUp, node.Value); - } - else if (direction == QueuePosition.OneDown) - { - var oneDown = node.Next; - BookQueue.Remove(node); - BookQueue.AddAfter(oneDown, node.Value); - } - else if (direction == QueuePosition.Fisrt) - { - BookQueue.Remove(node); - BookQueue.AddFirst(node); - } - else - { - BookQueue.Remove(node); - BookQueue.AddLast(node); - } + if (direction is QueuePosition.Absent or QueuePosition.Current or QueuePosition.Completed) + return direction; + return direction; - var index = flowLayoutPanel1.Controls.IndexOf((Control)sender.BookControl); + /* - index = direction switch + var firstQueue = autosizeFlowLayout1.Controls.Cast().FirstOrDefault(c => c.Status == ProcessBookStatus.Queued); + + if (firstQueue is null) return QueuePosition.Current; + + int firstQueueIndex = autosizeFlowLayout1.Controls.IndexOf(firstQueue); + + var index = autosizeFlowLayout1.Controls.IndexOf(sender.BookControl); + + int newIndex = direction switch { - QueuePosition.Fisrt => 0, + QueuePosition.Fisrt => firstQueueIndex, QueuePosition.OneUp => index - 1, QueuePosition.OneDown => index + 1, - QueuePosition.Last => flowLayoutPanel1.Controls.Count - 1, - _ => throw new NotImplementedException(), + QueuePosition.Last => autosizeFlowLayout1.Controls.Count - 1, + _ => -1, }; - flowLayoutPanel1.Controls.SetChildIndex((Control)sender.BookControl, index); + if (newIndex < 0) return direction; + + autosizeFlowLayout1.Controls.SetChildIndex(sender.BookControl, newIndex); - if (index == 0) return QueuePosition.Fisrt; - if (index == flowLayoutPanel1.Controls.Count - 1) return QueuePosition.Last; return direction; + */ } private async Task QueueLoop() { - while (MoreInQueue()) + while (Queue.MoveNext()) { - var nextBook = NextBook(); - nextBook.BookControl.SetQueuePosition(QueuePosition.Current); - PeekBook()?.BookControl.SetQueuePosition(QueuePosition.Fisrt); + var nextBook = Queue.Current; var result = await nextBook.ProcessOneAsync(); - AddCompletedBook(nextBook); - switch (result) { case ProcessBookResult.FailedRetry: - EnqueueBook(nextBook); + Queue.EnqueueBook(nextBook); break; case ProcessBookResult.FailedAbort: return; @@ -166,108 +158,62 @@ namespace LibationWinForms.ProcessQueue } } - private bool BookExists(LibraryBook libraryBook) - { - lock (lockObject) - { - return CurrentBook?.Entry?.AudibleProductId == libraryBook.Book.AudibleProductId || - CompletedBooks.Union(BookQueue).Any(p => p.Entry.AudibleProductId == libraryBook.Book.AudibleProductId); - } - } - - private ProcessBook NextBook() - { - lock (lockObject) - { - CurrentBook = BookQueue.First.Value; - BookQueue.RemoveFirst(); - return CurrentBook; - } - } - private ProcessBook PeekBook() - { - lock (lockObject) - return BookQueue.Count > 0 ? BookQueue.First.Value : default; - } - - private void EnqueueBook(ProcessBook pbook) - { - lock (lockObject) - BookQueue.AddLast(pbook); - } - - private void AddCompletedBook(ProcessBook pbook) - { - lock (lockObject) - CompletedBooks.Add(pbook); - } - - private bool MoreInQueue() - { - lock (lockObject) - return BookQueue.Count > 0; - } private void Pbook_Completed(object sender, EventArgs e) { - if (CurrentBook == sender) - CurrentBook = default; + } private async void cancelAllBtn_Click(object sender, EventArgs e) { - List l1 = new(); - lock (lockObject) - { - l1.AddRange(BookQueue); - BookQueue.Clear(); - } - CurrentBook?.Cancel(); - CurrentBook = default; + List l1 = Queue.QueuedItems(); - await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); + Queue.ClearQueue(); + Queue.Current?.Cancel(); + + //await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); } private async void btnCleanFinished_Click(object sender, EventArgs e) { - List l1 = new(); - lock (lockObject) - { - l1.AddRange(CompletedBooks); - CompletedBooks.Clear(); - } - - await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); + List l1 = Queue.CompletedItems(); + Queue.ClearCompleted(); + //await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); } - private async Task AddBookControlAsync(ILiberationBaseForm control) + private async Task AddBookControlAsync(ProcessBookControl control) { await Task.Run(() => Invoke(() => { - SetBookControlWidth((Control)control); - flowLayoutPanel1.Controls.Add((Control)control); - flowLayoutPanel1.SetFlowBreak((Control)control, true); - Refresh(); + /* + control.Width = autosizeFlowLayout1.DesiredBookControlWidth; + autosizeFlowLayout1.Controls.Add(control); + autosizeFlowLayout1.SetFlowBreak(control, true); + */ + //Refresh(); + //System.Threading.Thread.Sleep(1000); })); } - private async Task RemoveBookControlAsync(ILiberationBaseForm control) + private async Task RemoveBookControlAsync(ProcessBookControl control) { await Task.Run(() => Invoke(() => { - flowLayoutPanel1.Controls.Remove((Control)control); + //autosizeFlowLayout1.Controls.Remove(control); })); } - private async Task RemoveBookControlsAsync(IEnumerable control) + private async Task RemoveBookControlsAsync(IEnumerable control) { await Task.Run(() => Invoke(() => { + /* SuspendLayout(); foreach (var l in control) - flowLayoutPanel1.Controls.Remove((Control)l); + autosizeFlowLayout1.Controls.Remove(l); ResumeLayout(); + */ })); } @@ -282,62 +228,5 @@ namespace LibationWinForms.ProcessQueue logMeTbox.Clear(); } - [DllImport("user32.dll", EntryPoint = "GetWindowLong")] - private static extern long GetWindowLongPtr(IntPtr hWnd, int nIndex); - - [DllImport("user32.dll")] - private static extern bool ShowScrollBar(IntPtr hWnd, SBOrientation bar, bool show); - - public const int WS_VSCROLL = 0x200000; - public const int WS_HSCROLL = 0x100000; - enum SBOrientation : int - { - SB_HORZ = 0, - SB_VERT = 1, - SB_CTL = 2, - SB_BOTH = 3 - } - - private void flowLayoutPanel1_ClientSizeChanged(object sender, EventArgs e) - { - ReorderControls(); - } - - private void flowLayoutPanel1_Layout(object sender, LayoutEventArgs e) - { - ReorderControls(); - } - - bool V_SHOWN = false; - - private void ReorderControls() - { - bool hShown = (GetWindowLongPtr(flowLayoutPanel1.Handle, -16) & WS_HSCROLL) != 0; - bool vShown = (GetWindowLongPtr(flowLayoutPanel1.Handle, -16) & WS_VSCROLL) != 0; - - if (hShown) - ShowScrollBar(flowLayoutPanel1.Handle, SBOrientation.SB_HORZ, false); - - if (vShown != V_SHOWN) - { - flowLayoutPanel1.SuspendLayout(); - - foreach (Control c in flowLayoutPanel1.Controls) - SetBookControlWidth(c); - - flowLayoutPanel1.ResumeLayout(); - V_SHOWN = vShown; - } - } - - private void SetBookControlWidth(Control book) - { - book.Width = flowLayoutPanel1.ClientRectangle.Width - book.Margin.Left - book.Margin.Right; - } - - private void toolStripSplitButton1_ButtonClick(object sender, EventArgs e) - { - - } } } diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs new file mode 100644 index 00000000..3b215429 --- /dev/null +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs @@ -0,0 +1,58 @@ +namespace LibationWinForms.ProcessQueue +{ + partial class VirtualFlowControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.BackColor = System.Drawing.Color.PaleGreen; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(359, 507); + this.panel1.TabIndex = 0; + // + // VirtualFlowControl + // + this.Controls.Add(this.panel1); + this.Name = "VirtualFlowControl"; + this.Size = new System.Drawing.Size(379, 507); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + } +} diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs new file mode 100644 index 00000000..c6a1966c --- /dev/null +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -0,0 +1,146 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace LibationWinForms.ProcessQueue +{ + + internal delegate void RequestDataDelegate(int firstIndex, int numVisible, ProcessBookControl[] panelsToFill); + internal partial class VirtualFlowControl : UserControl + { + public event RequestDataDelegate RequestData; + public int VirtualControlCount + { + get => _virtualControlCount; + set + { + if (_virtualControlCount == 0) + vScrollBar1.Value = 0; + + _virtualControlCount = value; + AdjustScrollBar(); + DoVirtualScroll(); + } + } + + private int _virtualControlCount; + private int VirtualHeight => _virtualControlCount * CONTROL_HEIGHT - vScrollBar1.Height; + + private readonly int PROCESSBOOKCONTROL_MARGIN; + private readonly int CONTROL_HEIGHT; + + private const int WM_MOUSEWHEEL = 522; + private const int NUM_ACTUAL_CONTROLS = 20; + private const int SCROLL_SMALL_CHANGE = 120; + private const int SCROLL_LARGE_CHANGE = 3 * SCROLL_SMALL_CHANGE; + + + private readonly VScrollBar vScrollBar1; + private readonly ProcessBookControl[] BookControls = new ProcessBookControl[NUM_ACTUAL_CONTROLS]; + + public VirtualFlowControl() + { + + InitializeComponent(); + + vScrollBar1 = new VScrollBar + { + Minimum = 0, + Value = 0, + SmallChange = SCROLL_SMALL_CHANGE, + LargeChange = SCROLL_LARGE_CHANGE, + Dock = DockStyle.Right + }; + + Controls.Add(vScrollBar1); + + panel1.Resize += (_, _) => AdjustScrollBar(); + + if (this.DesignMode) + return; + + vScrollBar1.Scroll += (_, _) => DoVirtualScroll(); + + for (int i = 0; i < NUM_ACTUAL_CONTROLS; i++) + { + BookControls[i] = new ProcessBookControl(); + + if (i == 0) + { + PROCESSBOOKCONTROL_MARGIN = BookControls[i].Margin.Left + BookControls[i].Margin.Right; + CONTROL_HEIGHT = BookControls[i].Height + BookControls[i].Margin.Top + BookControls[i].Margin.Bottom; + } + + BookControls[i].Location = new Point(2, CONTROL_HEIGHT * i); + BookControls[i].Width = panel1.ClientRectangle.Width - PROCESSBOOKCONTROL_MARGIN; + BookControls[i].Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top; + panel1.Controls.Add(BookControls[i]); + } + + panel1.Height += SCROLL_SMALL_CHANGE; + } + + + private void AdjustScrollBar() + { + int maxFullVisible = DisplayRectangle.Height / CONTROL_HEIGHT; + + if (VirtualControlCount <= maxFullVisible) + { + vScrollBar1.Enabled = false; + + for (int i = VirtualControlCount; i < NUM_ACTUAL_CONTROLS; i++) + BookControls[i].Visible = false; + } + else + { + vScrollBar1.Enabled = true; + //https://stackoverflow.com/a/2882878/3335599 + vScrollBar1.Maximum = VirtualHeight + vScrollBar1.LargeChange - 1; + } + } + + private void DoVirtualScroll() + { + //https://stackoverflow.com/a/2882878/3335599 + int scrollValue = Math.Max(Math.Min(VirtualHeight, vScrollBar1.Value), 0); + + int position = scrollValue % CONTROL_HEIGHT; + panel1.Location = new Point(0, -position); + + int firstVisible = scrollValue / CONTROL_HEIGHT; + + int window = DisplayRectangle.Height; + + int count = window / CONTROL_HEIGHT; + + if (window % CONTROL_HEIGHT != 0) + count++; + count = Math.Min(count, VirtualControlCount); + + RequestData?.Invoke(firstVisible, count, BookControls); + + for (int i = 0; i < BookControls.Length; i++) + BookControls[i].Visible = i <= count && VirtualControlCount > 0; + } + + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_MOUSEWHEEL) + { + //https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel + int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & 0xffff); + + if (wheelDelta > 0) + vScrollBar1.Value = Math.Min(vScrollBar1.Value + wheelDelta, vScrollBar1.Maximum); + else + vScrollBar1.Value = Math.Max(vScrollBar1.Value + wheelDelta, vScrollBar1.Minimum); + + DoVirtualScroll(); + } + + base.WndProc(ref m); + } + } +} diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.resx b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.resx new file mode 100644 index 00000000..f298a7be --- /dev/null +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file From c39e7487494b61d797471099184ee8f98d462d02 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 01:33:05 -0600 Subject: [PATCH 02/33] Finialized TrackedQueue --- .../ProcessQueue/BookQueue.cs | 175 --------------- .../ProcessQueue/TrackedQueue[T].cs | 209 ++++++++++++++++++ 2 files changed, 209 insertions(+), 175 deletions(-) delete mode 100644 Source/LibationWinForms/ProcessQueue/BookQueue.cs create mode 100644 Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs diff --git a/Source/LibationWinForms/ProcessQueue/BookQueue.cs b/Source/LibationWinForms/ProcessQueue/BookQueue.cs deleted file mode 100644 index c36554ab..00000000 --- a/Source/LibationWinForms/ProcessQueue/BookQueue.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LibationWinForms.ProcessQueue -{ - internal class TrackedQueue where T : class - { - public T Current { get; private set; } - private readonly LinkedList Queued = new(); - private readonly List Completed = new(); - private readonly object lockObject = new(); - - public int Count => Queued.Count + Completed.Count + (Current == null ? 0 : 1); - - public T this[int index] - { - get - { - if (index < Completed.Count) - return Completed[index]; - index -= Completed.Count; - - if (index == 0&& Current != null) return Current; - - if (Current != null) index--; - - if (index < Queued.Count) return Queued.ElementAt(index); - - throw new IndexOutOfRangeException(); - } - } - - public List QueuedItems() - { - lock (lockObject) - return Queued.ToList(); - } - public List CompletedItems() - { - lock (lockObject) - return Completed.ToList(); - } - - public bool QueueCount - { - get - { - lock (lockObject) - return Queued.Count > 0; - } - } - - public bool CompleteCount - { - get - { - lock (lockObject) - return Completed.Count > 0; - } - } - - - public void ClearQueue() - { - lock (lockObject) - { - Queued.Clear(); - } - } - - public void ClearCompleted() - { - lock (lockObject) - { - Completed.Clear(); - } - } - - public bool Any(Func predicate) - { - lock (lockObject) - { - return (Current != null && predicate(Current)) || Completed.Any(predicate) || Queued.Any(predicate); - } - } - - public QueuePosition MoveQueuePosition(T item, QueuePositionRequest requestedPosition) - { - lock (lockObject) - { - if (Queued.Count == 0) - { - - if (Current != null && Current == item) - return QueuePosition.Current; - if (Completed.Contains(item)) - return QueuePosition.Completed; - return QueuePosition.Absent; - } - - var node = Queued.Find(item); - if (node is null) return QueuePosition.Absent; - - if ((requestedPosition == QueuePositionRequest.Fisrt || requestedPosition == QueuePositionRequest.OneUp) && Queued.First.Value == item) - return QueuePosition.Fisrt; - if ((requestedPosition == QueuePositionRequest.Last || requestedPosition == QueuePositionRequest.OneDown) && Queued.Last.Value == item) - return QueuePosition.Last; - - if (requestedPosition == QueuePositionRequest.OneUp) - { - var oneUp = node.Previous; - Queued.Remove(node); - Queued.AddBefore(oneUp, node.Value); - return Queued.First.Value == item? QueuePosition.Fisrt : QueuePosition.OneUp; - } - else if (requestedPosition == QueuePositionRequest.OneDown) - { - var oneDown = node.Next; - Queued.Remove(node); - Queued.AddAfter(oneDown, node.Value); - return Queued.Last.Value == item ? QueuePosition.Last : QueuePosition.OneDown; - } - else if (requestedPosition == QueuePositionRequest.Fisrt) - { - Queued.Remove(node); - Queued.AddFirst(node); - return QueuePosition.Fisrt; - } - else - { - Queued.Remove(node); - Queued.AddLast(node); - return QueuePosition.Last; - } - } - } - - public bool Remove(T item) - { - lock (lockObject) - { - return Queued.Remove(item); - } - } - - public bool MoveNext() - { - lock (lockObject) - { - if (Current != null) - Completed.Add(Current); - if (Queued.Count == 0) return false; - Current = Queued.First.Value; - Queued.RemoveFirst(); - return true; - } - } - public T PeekNext() - { - lock (lockObject) - return Queued.Count > 0 ? Queued.First.Value : default; - } - - public void EnqueueBook(T item) - { - lock (lockObject) - Queued.AddLast(item); - } - - } -} diff --git a/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs b/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs new file mode 100644 index 00000000..d045f818 --- /dev/null +++ b/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LibationWinForms.ProcessQueue +{ + public enum QueuePosition + { + Fisrt, + OneUp, + OneDown, + Last + } + + /* + * This data structure is like lifting a metal chain one link at a time. + * Each time you grab and lift a new link, the remaining chain shortens + * by 1 link and the pile of chain at yuor feet grows by one link. The + * index is the link position from the first link you lifted to the last + * one in the chain. + */ + public class TrackedQueue where T : class + { + public event EventHandler CompletedCountChanged; + public event EventHandler QueuededCountChanged; + public T Current { get; private set; } + + public IReadOnlyList Queued => _queued; + public IReadOnlyList Completed => _completed; + + private readonly List _queued = new(); + private readonly List _completed = new(); + private readonly object lockObject = new(); + + public T this[int index] + { + get + { + lock (lockObject) + { + if (index < _completed.Count) + return _completed[index]; + index -= _completed.Count; + + if (index == 0 && Current != null) return Current; + + if (Current != null) index--; + + if (index < _queued.Count) return _queued.ElementAt(index); + + throw new IndexOutOfRangeException(); + } + } + } + + public int Count + { + get + { + lock (lockObject) + { + return _queued.Count + _completed.Count + (Current == null ? 0 : 1); + } + } + } + + public int IndexOf(T item) + { + lock (lockObject) + { + if (_completed.Contains(item)) + return _completed.IndexOf(item); + + if (Current == item) return _completed.Count; + + if (_queued.Contains(item)) + return _queued.IndexOf(item) + (Current is null ? 0 : 1); + return -1; + } + } + + public bool RemoveQueued(T item) + { + lock (lockObject) + { + bool removed = _queued.Remove(item); + if (removed) + QueuededCountChanged?.Invoke(this, _queued.Count); + return removed; + } + } + + public void ClearQueue() + { + lock (lockObject) + { + _queued.Clear(); + QueuededCountChanged?.Invoke(this, 0); + } + } + + public void ClearCompleted() + { + lock (lockObject) + { + _completed.Clear(); + CompletedCountChanged?.Invoke(this, 0); + } + } + + public bool Any(Func predicate) + { + lock (lockObject) + { + return (Current != null && predicate(Current)) || _completed.Any(predicate) || _queued.Any(predicate); + } + } + + public void MoveQueuePosition(T item, QueuePosition requestedPosition) + { + lock (lockObject) + { + if (_queued.Count == 0 || !_queued.Contains(item)) return; + + if ((requestedPosition == QueuePosition.Fisrt || requestedPosition == QueuePosition.OneUp) && _queued[0] == item) + return; + if ((requestedPosition == QueuePosition.Last || requestedPosition == QueuePosition.OneDown) && _queued[^1] == item) + return; + + int queueIndex = _queued.IndexOf(item); + + if (requestedPosition == QueuePosition.OneUp) + { + _queued.RemoveAt(queueIndex); + _queued.Insert(queueIndex - 1, item); + } + else if (requestedPosition == QueuePosition.OneDown) + { + _queued.RemoveAt(queueIndex); + _queued.Insert(queueIndex + 1, item); + } + else if (requestedPosition == QueuePosition.Fisrt) + { + _queued.RemoveAt(queueIndex); + _queued.Insert(0, item); + } + else + { + _queued.RemoveAt(queueIndex); + _queued.Insert(_queued.Count, item); + } + } + } + + public bool MoveNext() + { + lock (lockObject) + { + if (Current != null) + { + _completed.Add(Current); + CompletedCountChanged?.Invoke(this, _completed.Count); + } + if (_queued.Count == 0) + { + Current = null; + return false; + } + Current = _queued[0]; + _queued.RemoveAt(0); + + QueuededCountChanged?.Invoke(this, _queued.Count); + return true; + } + } + + public bool TryPeek(out T item) + { + lock (lockObject) + { + if (_queued.Count == 0) + { + item = null; + return false; + } + item = _queued[0]; + return true; + } + } + + public T Peek() + { + lock (lockObject) + { + if (_queued.Count == 0) throw new InvalidOperationException("Queue empty"); + return _queued.Count > 0 ? _queued[0] : default; + } + } + + public void Enqueue(T item) + { + lock (lockObject) + { + _queued.Add(item); + QueuededCountChanged?.Invoke(this, _queued.Count); + } + } + } +} From 77c6a2890b3f9f6180d6cd5220638f8de3f9c3ca Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 02:54:09 -0600 Subject: [PATCH 03/33] Finalized VirtualFlowControl --- .../VirtualFlowControl.Designer.cs | 7 +- .../ProcessQueue/VirtualFlowControl.cs | 170 +++++++++++++----- 2 files changed, 133 insertions(+), 44 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs index 3b215429..1220cadc 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs @@ -36,17 +36,18 @@ this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.panel1.BackColor = System.Drawing.Color.PaleGreen; + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(359, 507); + this.panel1.Size = new System.Drawing.Size(377, 505); this.panel1.TabIndex = 0; // // VirtualFlowControl // + this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.Controls.Add(this.panel1); this.Name = "VirtualFlowControl"; - this.Size = new System.Drawing.Size(379, 507); + this.Size = new System.Drawing.Size(377, 505); this.ResumeLayout(false); } diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index c6a1966c..c5c76554 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -1,20 +1,33 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { - internal delegate void RequestDataDelegate(int firstIndex, int numVisible, ProcessBookControl[] panelsToFill); + internal delegate void RequestDataDelegate(int firstIndex, int numVisible, IReadOnlyList panelsToFill); + internal delegate void ControlButtonClickedDelegate(int itemIndex, string buttonName, ProcessBookControl panelClicked); internal partial class VirtualFlowControl : UserControl { + /// + /// Triggered when the needs to update the displayed s + /// public event RequestDataDelegate RequestData; + /// + /// Triggered when one of the 's buttons has been clicked + /// + public event ControlButtonClickedDelegate ButtonClicked; + + /// + /// The number of virtual s in the + /// public int VirtualControlCount { get => _virtualControlCount; set { - if (_virtualControlCount == 0) + if (_virtualControlCount == 0) vScrollBar1.Value = 0; _virtualControlCount = value; @@ -24,19 +37,41 @@ namespace LibationWinForms.ProcessQueue } private int _virtualControlCount; - private int VirtualHeight => _virtualControlCount * CONTROL_HEIGHT - vScrollBar1.Height; - private readonly int PROCESSBOOKCONTROL_MARGIN; - private readonly int CONTROL_HEIGHT; + //https://stackoverflow.com/a/2882878/3335599 + int ScrollValue => Math.Max(Math.Min(VirtualHeight, vScrollBar1.Value), 0); + + /// + /// The virtual height of all virtual controls within this + /// + private int VirtualHeight => _virtualControlCount * VirtualControlHeight - vScrollBar1.Height + 2 * TopMargin; + + /// + /// Item index of the first virtual + /// + private int FirstVisibleVirtualIndex => ScrollValue / VirtualControlHeight; + + /// + /// The display height of this + /// + private int DisplayHeight => DisplayRectangle.Height; + + /// + /// The total height, inclusing margins, of the repeated + /// + private readonly int VirtualControlHeight; //90 + private readonly int TopMargin; private const int WM_MOUSEWHEEL = 522; - private const int NUM_ACTUAL_CONTROLS = 20; + /// + /// Total number of actual controls added to the panel. 23 is sufficient up to a 4k monitor height. + /// + private const int NUM_ACTUAL_CONTROLS = 23; private const int SCROLL_SMALL_CHANGE = 120; private const int SCROLL_LARGE_CHANGE = 3 * SCROLL_SMALL_CHANGE; - private readonly VScrollBar vScrollBar1; - private readonly ProcessBookControl[] BookControls = new ProcessBookControl[NUM_ACTUAL_CONTROLS]; + private readonly List BookControls = new(); public VirtualFlowControl() { @@ -51,39 +86,76 @@ namespace LibationWinForms.ProcessQueue LargeChange = SCROLL_LARGE_CHANGE, Dock = DockStyle.Right }; + panel1.Width -= vScrollBar1.Width; Controls.Add(vScrollBar1); - panel1.Resize += (_, _) => AdjustScrollBar(); + panel1.Resize += (_, _) => + { + AdjustScrollBar(); + DoVirtualScroll(); + }; - if (this.DesignMode) + vScrollBar1.Scroll += (_, s) => SetScrollPosition(s.NewValue); + + var control = InitControl(0); + BookControls.Add(control); + panel1.Controls.Add(control); + VirtualControlHeight = control.Height + control.Margin.Top + control.Margin.Bottom; + TopMargin = control.Margin.Top; + + if (DesignMode) return; - vScrollBar1.Scroll += (_, _) => DoVirtualScroll(); - - for (int i = 0; i < NUM_ACTUAL_CONTROLS; i++) + for (int i = 1; i < NUM_ACTUAL_CONTROLS; i++) { - BookControls[i] = new ProcessBookControl(); - - if (i == 0) - { - PROCESSBOOKCONTROL_MARGIN = BookControls[i].Margin.Left + BookControls[i].Margin.Right; - CONTROL_HEIGHT = BookControls[i].Height + BookControls[i].Margin.Top + BookControls[i].Margin.Bottom; - } - - BookControls[i].Location = new Point(2, CONTROL_HEIGHT * i); - BookControls[i].Width = panel1.ClientRectangle.Width - PROCESSBOOKCONTROL_MARGIN; - BookControls[i].Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top; - panel1.Controls.Add(BookControls[i]); + control = InitControl(VirtualControlHeight * i); + BookControls.Add(control); + panel1.Controls.Add(control); } - panel1.Height += SCROLL_SMALL_CHANGE; + vScrollBar1.SmallChange = VirtualControlHeight; + vScrollBar1.LargeChange = 3 * VirtualControlHeight; + + panel1.Height += 2*VirtualControlHeight; } + private ProcessBookControl InitControl(int locationY) + { + var control = new ProcessBookControl(); + control.Location = new Point(control.Margin.Left, locationY + control.Margin.Top); + control.Width = panel1.ClientRectangle.Width - control.Margin.Left - control.Margin.Right; + control.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top; + control.cancelBtn.Click += ControlButton_Click; + control.moveFirstBtn.Click += ControlButton_Click; + control.moveUpBtn.Click += ControlButton_Click; + control.moveDownBtn.Click += ControlButton_Click; + control.moveLastBtn.Click += ControlButton_Click; + return control; + } + + /// + /// Handles all button clicks from all , detects which one sent the click, and fires + /// + private void ControlButton_Click(object sender, EventArgs e) + { + Control button = sender as Control; + Control form = button.Parent; + while (form is not ProcessBookControl) + form = form.Parent; + + int clickedIndex = BookControls.IndexOf((ProcessBookControl)form); + + ButtonClicked?.Invoke(FirstVisibleVirtualIndex + clickedIndex, button.Name, BookControls[clickedIndex]); + } + + /// + /// Adjusts the max width and enabled status based on the and the + /// private void AdjustScrollBar() { - int maxFullVisible = DisplayRectangle.Height / CONTROL_HEIGHT; + int maxFullVisible = DisplayHeight / VirtualControlHeight; if (VirtualControlCount <= maxFullVisible) { @@ -100,44 +172,60 @@ namespace LibationWinForms.ProcessQueue } } + /// + /// Calculated the virtual controls that are in view at the currrent scroll position and windows size, then fires for the visible controls + /// private void DoVirtualScroll() { - //https://stackoverflow.com/a/2882878/3335599 - int scrollValue = Math.Max(Math.Min(VirtualHeight, vScrollBar1.Value), 0); + int firstVisible = FirstVisibleVirtualIndex; - int position = scrollValue % CONTROL_HEIGHT; + int position = ScrollValue % VirtualControlHeight; panel1.Location = new Point(0, -position); - int firstVisible = scrollValue / CONTROL_HEIGHT; + int visibleHeight = ScrollValue - firstVisible * VirtualControlHeight + DisplayHeight; - int window = DisplayRectangle.Height; + int count = visibleHeight / VirtualControlHeight; - int count = window / CONTROL_HEIGHT; - - if (window % CONTROL_HEIGHT != 0) + if (visibleHeight % VirtualControlHeight != 0) count++; + count = Math.Min(count, VirtualControlCount); RequestData?.Invoke(firstVisible, count, BookControls); - for (int i = 0; i < BookControls.Length; i++) - BookControls[i].Visible = i <= count && VirtualControlCount > 0; + for (int i = 0; i < BookControls.Count; i++) + BookControls[i].Visible = i < count; } + /// + /// Set scroll value to an integral multiple of VirtualControlHeight + /// + private void SetScrollPosition(int value) + { + int newPos = (int)Math.Round((double)value / VirtualControlHeight) * VirtualControlHeight; + if (vScrollBar1.Value != newPos) + { + vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum); + DoVirtualScroll(); + } + } protected override void WndProc(ref Message m) { + //Capture mouse wheel movement and interpret it as a scroll event if (m.Msg == WM_MOUSEWHEEL) { //https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel - int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & 0xffff); + int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & ushort.MaxValue); + + int newScrollPosition; if (wheelDelta > 0) - vScrollBar1.Value = Math.Min(vScrollBar1.Value + wheelDelta, vScrollBar1.Maximum); + newScrollPosition = Math.Min(vScrollBar1.Value + wheelDelta, vScrollBar1.Maximum); else - vScrollBar1.Value = Math.Max(vScrollBar1.Value + wheelDelta, vScrollBar1.Minimum); + newScrollPosition = Math.Max(vScrollBar1.Value + wheelDelta, vScrollBar1.Minimum); - DoVirtualScroll(); + SetScrollPosition(newScrollPosition); } base.WndProc(ref m); From 676af0210b5bc260e1249ad2b4dfebe479dd28a9 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 02:54:32 -0600 Subject: [PATCH 04/33] Finalized ProcessBookControl --- .../ProcessBookControl.Designer.cs | 37 +- .../ProcessQueue/ProcessBookControl.cs | 318 +++--------------- .../ProcessQueue/ProcessBookControl.resx | 36 ++ 3 files changed, 113 insertions(+), 278 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs index e77e0211..38cc2b67 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs @@ -98,7 +98,6 @@ this.cancelBtn.Size = new System.Drawing.Size(20, 20); this.cancelBtn.TabIndex = 4; this.cancelBtn.UseVisualStyleBackColor = false; - this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click); // // statusLbl // @@ -128,53 +127,61 @@ // this.moveUpBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); + this.moveUpBtn.BackColor = System.Drawing.Color.Transparent; this.moveUpBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveUpBtn.BackgroundImage"))); this.moveUpBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveUpBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveUpBtn.ForeColor = System.Drawing.SystemColors.Control; this.moveUpBtn.Location = new System.Drawing.Point(314, 24); this.moveUpBtn.Name = "moveUpBtn"; this.moveUpBtn.Size = new System.Drawing.Size(30, 17); this.moveUpBtn.TabIndex = 5; - this.moveUpBtn.UseVisualStyleBackColor = true; - this.moveUpBtn.Click += new System.EventHandler(this.moveUpBtn_Click_1); + this.moveUpBtn.UseVisualStyleBackColor = false; // // moveDownBtn // this.moveDownBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); + this.moveDownBtn.BackColor = System.Drawing.Color.Transparent; this.moveDownBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveDownBtn.BackgroundImage"))); this.moveDownBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveDownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveDownBtn.ForeColor = System.Drawing.SystemColors.Control; this.moveDownBtn.Location = new System.Drawing.Point(314, 40); this.moveDownBtn.Name = "moveDownBtn"; this.moveDownBtn.Size = new System.Drawing.Size(30, 17); this.moveDownBtn.TabIndex = 5; - this.moveDownBtn.UseVisualStyleBackColor = true; - this.moveDownBtn.Click += new System.EventHandler(this.moveDownBtn_Click); + this.moveDownBtn.UseVisualStyleBackColor = false; // // moveFirstBtn // this.moveFirstBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); + this.moveFirstBtn.BackColor = System.Drawing.Color.Transparent; this.moveFirstBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveFirstBtn.BackgroundImage"))); this.moveFirstBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveFirstBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveFirstBtn.ForeColor = System.Drawing.SystemColors.Control; this.moveFirstBtn.Location = new System.Drawing.Point(314, 3); this.moveFirstBtn.Name = "moveFirstBtn"; this.moveFirstBtn.Size = new System.Drawing.Size(30, 17); this.moveFirstBtn.TabIndex = 5; - this.moveFirstBtn.UseVisualStyleBackColor = true; - this.moveFirstBtn.Click += new System.EventHandler(this.moveFirstBtn_Click); + this.moveFirstBtn.UseVisualStyleBackColor = false; // // moveLastBtn // this.moveLastBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); + this.moveLastBtn.BackColor = System.Drawing.Color.Transparent; this.moveLastBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveLastBtn.BackgroundImage"))); this.moveLastBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.moveLastBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.moveLastBtn.ForeColor = System.Drawing.SystemColors.Control; this.moveLastBtn.Location = new System.Drawing.Point(314, 63); this.moveLastBtn.Name = "moveLastBtn"; this.moveLastBtn.Size = new System.Drawing.Size(30, 17); this.moveLastBtn.TabIndex = 5; - this.moveLastBtn.UseVisualStyleBackColor = true; - this.moveLastBtn.Click += new System.EventHandler(this.moveLastBtn_Click); + this.moveLastBtn.UseVisualStyleBackColor = false; // // ProcessBookControl // @@ -193,7 +200,7 @@ this.Controls.Add(this.progressBar1); this.Controls.Add(this.bookInfoLbl); this.Controls.Add(this.pictureBox1); - this.Margin = new System.Windows.Forms.Padding(2); + this.Margin = new System.Windows.Forms.Padding(4, 2, 4, 2); this.Name = "ProcessBookControl"; this.Size = new System.Drawing.Size(375, 86); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); @@ -208,12 +215,12 @@ private System.Windows.Forms.ProgressBar progressBar1; private System.Windows.Forms.Label remainingTimeLbl; private System.Windows.Forms.Label etaLbl; - private System.Windows.Forms.Button cancelBtn; private System.Windows.Forms.Label statusLbl; private System.Windows.Forms.Label bookInfoLbl; - private System.Windows.Forms.Button moveUpBtn; - private System.Windows.Forms.Button moveDownBtn; - private System.Windows.Forms.Button moveFirstBtn; - private System.Windows.Forms.Button moveLastBtn; + public System.Windows.Forms.Button cancelBtn; + public System.Windows.Forms.Button moveUpBtn; + public System.Windows.Forms.Button moveDownBtn; + public System.Windows.Forms.Button moveFirstBtn; + public System.Windows.Forms.Button moveLastBtn; } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index 76f73368..02764c26 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -1,40 +1,19 @@ using System; using System.Drawing; using System.Windows.Forms; -using System.Windows.Forms.Layout; -using DataLayer; -using Dinah.Core.Net.Http; -using Dinah.Core.Threading; -using FileLiberator; -using LibationFileManager; -using LibationWinForms.BookLiberation; -using LibationWinForms.ProcessQueue; namespace LibationWinForms.ProcessQueue { - internal interface ILiberatiofffnBaseForm - { - Action CancelAction { get; set; } - Func MoveUpAction { get; set; } - Func MoveDownAction { get; set; } - void SetResult(ProcessBookResult status); - void SetQueuePosition(QueuePosition status); - void RegisterFileLiberator(Processable streamable, LogMe logMe); - void Processable_Begin(object sender, LibraryBook libraryBook); - int Width { get; set; } - int Height { get; set; } - Padding Margin { get; set; } - } - internal partial class ProcessBookControl : UserControl { - public ProcessBookStatus Status { get; private set; } = ProcessBookStatus.Queued; - public Action CancelAction { get; set; } - public Func RequestMoveAction { get; set; } - public string DecodeActionName { get; } = "Decoding"; - private Func GetCoverArtDelegate; - protected Processable Processable { get; private set; } - protected LogMe LogMe { get; private set; } + private static int ControlNumberCounter = 0; + + /// + /// The contol's position within + /// + public int ControlNumber { get; } + private ProcessBookStatus Status { get; set; } = ProcessBookStatus.Queued; + private readonly int CancelBtnDistanceFromEdge; public ProcessBookControl() { InitializeComponent(); @@ -42,17 +21,27 @@ namespace LibationWinForms.ProcessQueue remainingTimeLbl.Visible = false; progressBar1.Visible = false; etaLbl.Visible = false; + + CancelBtnDistanceFromEdge = Width - cancelBtn.Location.X; + ControlNumber = ControlNumberCounter++; } public void SetCover(Image cover) { pictureBox1.Image = cover; } - public void SetTitle(string title) + public void SetBookInfo(string title) { bookInfoLbl.Text = title; } - + public void SetProgrss(int progress) + { + progressBar1.Value = progress; + } + public void SetRemainingTime(TimeSpan remaining) + { + remainingTimeLbl.Text = $"{remaining:mm\\:ss}"; + } public void SetResult(ProcessBookResult result) { string statusText = default; @@ -90,8 +79,7 @@ namespace LibationWinForms.ProcessQueue SetStatus(Status, statusText); } - - public void SetStatus(ProcessBookStatus status, string statusText) + public void SetStatus(ProcessBookStatus status, string statusText = null) { Color backColor = default; switch (status) @@ -118,245 +106,49 @@ namespace LibationWinForms.ProcessQueue break; } - this.UIThreadAsync(() => + SuspendLayout(); + + cancelBtn.Visible = Status is ProcessBookStatus.Queued or ProcessBookStatus.Working; + moveLastBtn.Visible = Status == ProcessBookStatus.Queued; + moveDownBtn.Visible = Status == ProcessBookStatus.Queued; + moveUpBtn.Visible = Status == ProcessBookStatus.Queued; + moveFirstBtn.Visible = Status == ProcessBookStatus.Queued; + remainingTimeLbl.Visible = Status == ProcessBookStatus.Working; + progressBar1.Visible = Status == ProcessBookStatus.Working; + etaLbl.Visible = Status == ProcessBookStatus.Working; + statusLbl.Visible = Status != ProcessBookStatus.Working; + statusLbl.Text = statusText ?? Status.ToString(); + BackColor = backColor; + + int deltaX = Width - cancelBtn.Location.X - CancelBtnDistanceFromEdge; + + if (Status is ProcessBookStatus.Queued or ProcessBookStatus.Working && deltaX != 0) { - SuspendLayout(); + //If the last book to occupy this control before resizing was not queued, + //the buttons were not Visible so the Anchor property was ignored. - cancelBtn.Visible = Status is ProcessBookStatus.Queued or ProcessBookStatus.Working; - moveLastBtn.Visible = Status == ProcessBookStatus.Queued; - moveDownBtn.Visible = Status == ProcessBookStatus.Queued; - moveUpBtn.Visible = Status == ProcessBookStatus.Queued; - moveFirstBtn.Visible = Status == ProcessBookStatus.Queued; - remainingTimeLbl.Visible = Status == ProcessBookStatus.Working; - progressBar1.Visible = Status == ProcessBookStatus.Working; - etaLbl.Visible = Status == ProcessBookStatus.Working; - statusLbl.Visible = Status != ProcessBookStatus.Working; - statusLbl.Text = statusText; - BackColor = backColor; + cancelBtn.Location = new Point(cancelBtn.Location.X + deltaX, cancelBtn.Location.Y); + moveFirstBtn.Location = new Point(moveFirstBtn.Location.X + deltaX, moveFirstBtn.Location.Y); + moveUpBtn.Location = new Point(moveUpBtn.Location.X + deltaX, moveUpBtn.Location.Y); + moveDownBtn.Location = new Point(moveDownBtn.Location.X + deltaX, moveDownBtn.Location.Y); + moveLastBtn.Location = new Point(moveLastBtn.Location.X + deltaX, moveLastBtn.Location.Y); + } - if (status == ProcessBookStatus.Working) - { - bookInfoLbl.Width += moveLastBtn.Width + moveLastBtn.Padding.Left + moveLastBtn.Padding.Right; - } - else - { - bookInfoLbl.Width -= moveLastBtn.Width + moveLastBtn.Padding.Left + moveLastBtn.Padding.Right; - - } - ResumeLayout(); - }); - } - - public ProcessBookControl(string title, Image cover) : this() - { - this.title = title; - pictureBox1.Image = cover; - bookInfoLbl.Text = title; - } - - public void RegisterFileLiberator(Processable processable, LogMe logMe = null) - { - if (processable is null) return; - - Processable = processable; - LogMe = logMe; - - Subscribe((Streamable)processable); - Subscribe(processable); - if (processable is AudioDecodable audioDecodable) - Subscribe(audioDecodable); - } - - - #region Event Subscribers and Unsubscribers - private void Subscribe(Streamable streamable) - { - UnsubscribeStreamable(this, EventArgs.Empty); - - streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged; - streamable.StreamingTimeRemaining += Streamable_StreamingTimeRemaining; - - Disposed += UnsubscribeStreamable; - } - private void Subscribe(Processable processable) - { - UnsubscribeProcessable(this, null); - - processable.Begin += Processable_Begin; - processable.Completed += Processable_Completed; - - //Don't unsubscribe from Dispose because it fires when - //Streamable.StreamingCompleted closes the form, and - //the Processable events need to live past that event. - processable.Completed += UnsubscribeProcessable; - } - private void Subscribe(AudioDecodable audioDecodable) - { - UnsubscribeAudioDecodable(this, EventArgs.Empty); - - audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt; - audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered; - audioDecodable.AuthorsDiscovered += AudioDecodable_AuthorsDiscovered; - audioDecodable.NarratorsDiscovered += AudioDecodable_NarratorsDiscovered; - audioDecodable.CoverImageDiscovered += AudioDecodable_CoverImageDiscovered; - - Disposed += UnsubscribeAudioDecodable; - } - private void UnsubscribeStreamable(object sender, EventArgs e) - { - Disposed -= UnsubscribeStreamable; - - Processable.StreamingProgressChanged -= Streamable_StreamingProgressChanged; - Processable.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining; - } - private void UnsubscribeProcessable(object sender, LibraryBook e) - { - Processable.Completed -= UnsubscribeProcessable; - Processable.Begin -= Processable_Begin; - Processable.Completed -= Processable_Completed; - } - private void UnsubscribeAudioDecodable(object sender, EventArgs e) - { - if (Processable is not AudioDecodable audioDecodable) - return; - - Disposed -= UnsubscribeAudioDecodable; - audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt; - audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered; - audioDecodable.AuthorsDiscovered -= AudioDecodable_AuthorsDiscovered; - audioDecodable.NarratorsDiscovered -= AudioDecodable_NarratorsDiscovered; - audioDecodable.CoverImageDiscovered -= AudioDecodable_CoverImageDiscovered; - - audioDecodable.Cancel(); - } - #endregion - - #region Streamable event handlers - public void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress) - { - if (!downloadProgress.ProgressPercentage.HasValue) - return; - - if (downloadProgress.ProgressPercentage == 0) - updateRemainingTime(0); + if (status == ProcessBookStatus.Working) + { + bookInfoLbl.Width = cancelBtn.Location.X - bookInfoLbl.Location.X - bookInfoLbl.Padding.Left + cancelBtn.Padding.Right; + } else - progressBar1.UIThreadAsync(() => progressBar1.Value = (int)downloadProgress.ProgressPercentage); - } + { + bookInfoLbl.Width = moveLastBtn.Location.X - bookInfoLbl.Location.X - bookInfoLbl.Padding.Left + moveLastBtn.Padding.Right; + } - public void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining) - { - updateRemainingTime((int)timeRemaining.TotalSeconds); - } - - private void updateRemainingTime(int remaining) - => remainingTimeLbl.UIThreadAsync(() => remainingTimeLbl.Text = formatTime(remaining)); - - private string formatTime(int seconds) - { - var timeSpan = TimeSpan.FromSeconds(seconds); - return $"{timeSpan:mm\\:ss}"; - } - - #endregion - - #region Processable event handlers - public void Processable_Begin(object sender, LibraryBook libraryBook) - { - Status = ProcessBookStatus.Working; - - LogMe.Info($"{Environment.NewLine}{Processable.Name} Step, Begin: {libraryBook.Book}"); - - SetStatus(ProcessBookStatus.Working, ""); - - GetCoverArtDelegate = () => PictureStorage.GetPictureSynchronously( - new PictureDefinition( - libraryBook.Book.PictureId, - PictureSize._500x500)); - - //Set default values from library - AudioDecodable_TitleDiscovered(sender, libraryBook.Book.Title); - AudioDecodable_AuthorsDiscovered(sender, libraryBook.Book.AuthorNames()); - AudioDecodable_NarratorsDiscovered(sender, libraryBook.Book.NarratorNames()); - AudioDecodable_CoverImageDiscovered(sender, - PictureStorage.GetPicture( - new PictureDefinition( - libraryBook.Book.PictureId, - PictureSize._80x80)).bytes); - } - - public void Processable_Completed(object sender, LibraryBook libraryBook) - { - LogMe.Info($"{Processable.Name} Step, Completed: {libraryBook.Book}"); - } - - #endregion - - #region AudioDecodable event handlers - - private string title; - private string authorNames; - private string narratorNames; - public void AudioDecodable_TitleDiscovered(object sender, string title) - { - this.UIThreadAsync(() => this.Text = DecodeActionName + " " + title); - this.title = title; - updateBookInfo(); - } - - public void AudioDecodable_AuthorsDiscovered(object sender, string authors) - { - authorNames = authors; - updateBookInfo(); - } - - public void AudioDecodable_NarratorsDiscovered(object sender, string narrators) - { - narratorNames = narrators; - updateBookInfo(); - } - - private void updateBookInfo() - => bookInfoLbl.UIThreadAsync(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); - - public void AudioDecodable_RequestCoverArt(object sender, Action setCoverArtDelegate) - { - setCoverArtDelegate(GetCoverArtDelegate?.Invoke()); - } - - public void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt) - { - pictureBox1.UIThreadAsync(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt)); - } - #endregion - - private void cancelBtn_Click(object sender, EventArgs e) - { - CancelAction?.Invoke(); - } - - private void moveLastBtn_Click(object sender, EventArgs e) - { - RequestMoveAction?.Invoke(QueuePositionRequest.Last); - } - - private void moveFirstBtn_Click(object sender, EventArgs e) - { - RequestMoveAction?.Invoke(QueuePositionRequest.Fisrt); - } - - private void moveUpBtn_Click_1(object sender, EventArgs e) - { - RequestMoveAction?.Invoke(QueuePositionRequest.OneUp); - } - - private void moveDownBtn_Click(object sender, EventArgs e) - { - RequestMoveAction?.Invoke(QueuePositionRequest.OneDown); + ResumeLayout(); } public override string ToString() { - return title ?? "NO TITLE"; + return bookInfoLbl.Text ?? "[NO TITLE]"; } } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx index a91b8cad..83921ba7 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx @@ -57,6 +57,21 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + True + + + True + + + True + + + True + + + True + @@ -719,6 +734,15 @@ /x9W31o+WFcHNAAAAABJRU5ErkJggg== + + True + + + True + + + True + iVBORw0KGgoAAAANSUhEUgAAAMgAAABNCAYAAADjJSv1AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 @@ -746,6 +770,9 @@ t7PIdXRT9kCF1lo8u3IPfFOKqVljg2IAAAAASUVORK5CYII= + + True + iVBORw0KGgoAAAANSUhEUgAAAMgAAABNCAYAAADjJSv1AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 @@ -773,6 +800,9 @@ wxjjQQ1jjAc1jDEe1DDGiJcX/wKcO4zm90rrbQAAAABJRU5ErkJggg== + + True + iVBORw0KGgoAAAANSUhEUgAAAMgAAABZCAYAAAB7Ymt4AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 @@ -801,6 +831,9 @@ jfkqyMbMAAAAAElFTkSuQmCC + + True + iVBORw0KGgoAAAANSUhEUgAAAMgAAABZCAYAAAB7Ymt4AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 @@ -829,4 +862,7 @@ 6l8KOJAbKVKmPQAAAABJRU5ErkJggg== + + True + \ No newline at end of file From eeb4f4681a77fab7abfba9065464d1c75a6e795e Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 03:16:48 -0600 Subject: [PATCH 05/33] Saved --- Source/AaxDecrypter/NetworkFileStream.cs | 839 +++++++++--------- Source/LibationWinForms/Form1.Designer.cs | 57 +- Source/LibationWinForms/Form1.ProcessQueue.cs | 26 +- .../ProcessQueue/ProcessBook.cs | 298 +++++-- .../ProcessQueue/ProcessBookForm.cs | 10 +- .../ProcessQueue/ProcessBookQueue.Designer.cs | 124 ++- .../ProcessQueue/ProcessBookQueue.cs | 326 +++---- .../ProcessQueue/ProcessBookQueue.resx | 570 ++++++++++++ .../VirtualFlowControl.Designer.cs | 2 +- .../ProcessQueue/VirtualFlowControl.cs | 2 +- 10 files changed, 1502 insertions(+), 752 deletions(-) diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs index 54b351c5..83f62ae7 100644 --- a/Source/AaxDecrypter/NetworkFileStream.cs +++ b/Source/AaxDecrypter/NetworkFileStream.cs @@ -9,435 +9,436 @@ using System.Threading; namespace AaxDecrypter { - /// - /// A for a single Uri. - /// - public class SingleUriCookieContainer : CookieContainer - { - private Uri baseAddress; - public Uri Uri - { - get => baseAddress; - set - { - baseAddress = new UriBuilder(value.Scheme, value.Host).Uri; - } - } - - public CookieCollection GetCookies() - { - return GetCookies(Uri); - } - } - - /// - /// A resumable, simultaneous file downloader and reader. - /// - public class NetworkFileStream : Stream, IUpdatable - { - public event EventHandler Updated; - - #region Public Properties - - /// - /// Location to save the downloaded data. - /// - [JsonProperty(Required = Required.Always)] - public string SaveFilePath { get; } - - /// - /// Http(s) address of the file to download. - /// - [JsonProperty(Required = Required.Always)] - public Uri Uri { get; private set; } - - /// - /// All cookies set by caller or by the remote server. - /// - [JsonProperty(Required = Required.Always)] - public SingleUriCookieContainer CookieContainer { get; } - - /// - /// Http headers to be sent to the server with the request. - /// - [JsonProperty(Required = Required.Always)] - public WebHeaderCollection RequestHeaders { get; private set; } - - /// - /// The position in that has been written and flushed to disk. - /// - [JsonProperty(Required = Required.Always)] - public long WritePosition { get; private set; } - - /// - /// The total length of the file to download. - /// - [JsonProperty(Required = Required.Always)] - public long ContentLength { get; private set; } - - #endregion - - #region Private Properties - private HttpWebRequest HttpRequest { get; set; } - private FileStream _writeFile { get; } - private FileStream _readFile { get; } - private Stream _networkStream { get; set; } - private bool hasBegunDownloading { get; set; } - public bool IsCancelled { get; private set; } - private EventWaitHandle downloadEnded { get; set; } - private EventWaitHandle downloadedPiece { get; set; } - - #endregion - - #region Constants - - //Download buffer size - private const int DOWNLOAD_BUFF_SZ = 4 * 1024; - - //NetworkFileStream will flush all data in _writeFile to disk after every - //DATA_FLUSH_SZ bytes are written to the file stream. - private const int DATA_FLUSH_SZ = 1024 * 1024; - - #endregion - - #region Constructor - - /// - /// A resumable, simultaneous file downloader and reader. - /// - /// Path to a location on disk to save the downloaded data from - /// Http(s) address of the file to download. - /// The position in to begin downloading. - /// Http headers to be sent to the server with the . - /// A with cookies to send with the . It will also be populated with any cookies set by the server. - public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, WebHeaderCollection requestHeaders = null, SingleUriCookieContainer cookies = null) - { - ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath)); - ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri)); - ArgumentValidator.EnsureGreaterThan(writePosition, nameof(writePosition), -1); - - if (!Directory.Exists(Path.GetDirectoryName(saveFilePath))) - throw new ArgumentException($"Specified {nameof(saveFilePath)} directory \"{Path.GetDirectoryName(saveFilePath)}\" does not exist."); - - SaveFilePath = saveFilePath; - Uri = uri; - WritePosition = writePosition; - RequestHeaders = requestHeaders ?? new WebHeaderCollection(); - CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri }; - - _writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite) - { - Position = WritePosition - }; - - _readFile = new FileStream(SaveFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - SetUriForSameFile(uri); - } - - #endregion - - #region Downloader - - /// - /// Update the . - /// - private void Update() - { - RequestHeaders = HttpRequest.Headers; - Updated?.Invoke(this, EventArgs.Empty); - } - - /// - /// Set a different to the same file targeted by this instance of - /// - /// New host must match existing host. - public void SetUriForSameFile(Uri uriToSameFile) - { - ArgumentValidator.EnsureNotNullOrWhiteSpace(uriToSameFile?.AbsoluteUri, nameof(uriToSameFile)); - - if (uriToSameFile.Host != Uri.Host) - throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}"); - if (hasBegunDownloading) - throw new InvalidOperationException("Cannot change Uri after download has started."); - - Uri = uriToSameFile; - HttpRequest = WebRequest.CreateHttp(Uri); - - HttpRequest.CookieContainer = CookieContainer; - HttpRequest.Headers = RequestHeaders; - //If NetworkFileStream is resuming, Header will already contain a range. - HttpRequest.Headers.Remove("Range"); - HttpRequest.AddRange(WritePosition); - } - - /// - /// Begins downloading to in a background thread. - /// - private void BeginDownloading() - { - downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset); - - if (ContentLength != 0 && WritePosition == ContentLength) - { - hasBegunDownloading = true; - downloadEnded.Set(); - return; - } - - if (ContentLength != 0 && WritePosition > ContentLength) - throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); - - var response = HttpRequest.GetResponse() as HttpWebResponse; - - if (response.StatusCode != HttpStatusCode.PartialContent) - throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); - - //Content length is the length of the range request, and it is only equal - //to the complete file length if requesting Range: bytes=0- - if (WritePosition == 0) - ContentLength = response.ContentLength; - - _networkStream = response.GetResponseStream(); - downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset); - - //Download the file in the background. - new Thread(() => DownloadFile()) - { IsBackground = true } - .Start(); - - hasBegunDownloading = true; - return; - } - - /// - /// Downlod to . - /// - private void DownloadFile() - { - var downloadPosition = WritePosition; - var nextFlush = downloadPosition + DATA_FLUSH_SZ; - - var buff = new byte[DOWNLOAD_BUFF_SZ]; - do - { - var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); - _writeFile.Write(buff, 0, bytesRead); - - downloadPosition += bytesRead; - - if (downloadPosition > nextFlush) - { - _writeFile.Flush(); - WritePosition = downloadPosition; - Update(); - nextFlush = downloadPosition + DATA_FLUSH_SZ; - downloadedPiece.Set(); - } - - } while (downloadPosition < ContentLength && !IsCancelled); - - _writeFile.Close(); - _networkStream.Close(); - WritePosition = downloadPosition; - Update(); - - downloadedPiece.Set(); - downloadEnded.Set(); - - if (!IsCancelled && WritePosition < ContentLength) - throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); - - if (WritePosition > ContentLength) - throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10})."); - - } - - #endregion - - #region Json Connverters - - public static JsonSerializerSettings GetJsonSerializerSettings() - { - var settings = new JsonSerializerSettings(); - settings.Converters.Add(new CookieContainerConverter()); - settings.Converters.Add(new WebHeaderCollectionConverter()); - return settings; - } - - internal class CookieContainerConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - => objectType == typeof(SingleUriCookieContainer); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var jObj = JObject.Load(reader); - - var result = new SingleUriCookieContainer() - { - Uri = new Uri(jObj["Uri"].Value()), - Capacity = jObj["Capacity"].Value(), - MaxCookieSize = jObj["MaxCookieSize"].Value(), - PerDomainCapacity = jObj["PerDomainCapacity"].Value() - }; - - var cookieList = jObj["Cookies"].ToList(); - - foreach (var cookie in cookieList) - { - result.Add( - new Cookie - { - Comment = cookie["Comment"].Value(), - HttpOnly = cookie["HttpOnly"].Value(), - Discard = cookie["Discard"].Value(), - Domain = cookie["Domain"].Value(), - Expired = cookie["Expired"].Value(), - Expires = cookie["Expires"].Value(), - Name = cookie["Name"].Value(), - Path = cookie["Path"].Value(), - Port = cookie["Port"].Value(), - Secure = cookie["Secure"].Value(), - Value = cookie["Value"].Value(), - Version = cookie["Version"].Value(), - }); - } - - return result; - } - - public override bool CanWrite => true; - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var cookies = value as SingleUriCookieContainer; - var obj = (JObject)JToken.FromObject(value); - var container = cookies.GetCookies(); - var propertyNames = container.Select(c => JToken.FromObject(c)); - obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames))); - obj.WriteTo(writer); - } - } - - internal class WebHeaderCollectionConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - => objectType == typeof(WebHeaderCollection); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var jObj = JObject.Load(reader); - var result = new WebHeaderCollection(); - - foreach (var kvp in jObj) - result.Add(kvp.Key, kvp.Value.Value()); - - return result; - } - - public override bool CanWrite => true; - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var jObj = new JObject(); - var type = value.GetType(); - var headers = value as WebHeaderCollection; - var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k])); - jObj.Add(jHeaders); - jObj.WriteTo(writer); - } - } - - #endregion - - #region Download Stream Reader - - [JsonIgnore] - public override bool CanRead => true; - - [JsonIgnore] - public override bool CanSeek => true; - - [JsonIgnore] - public override bool CanWrite => false; - - [JsonIgnore] - public override long Length - { - get - { - if (!hasBegunDownloading) - BeginDownloading(); - return ContentLength; - } - } - - [JsonIgnore] - public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); } - - [JsonIgnore] - public override bool CanTimeout => false; - - [JsonIgnore] - public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = value; } - - [JsonIgnore] - public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; } - - public override void Flush() => throw new NotImplementedException(); - public override void SetLength(long value) => throw new NotImplementedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); - - public override int Read(byte[] buffer, int offset, int count) - { - if (!hasBegunDownloading) - BeginDownloading(); - - var toRead = Math.Min(count, Length - Position); - WaitToPosition(Position + toRead); - return _readFile.Read(buffer, offset, count); - } - - public override long Seek(long offset, SeekOrigin origin) - { - var newPosition = origin switch + /// + /// A for a single Uri. + /// + public class SingleUriCookieContainer : CookieContainer + { + private Uri baseAddress; + public Uri Uri + { + get => baseAddress; + set + { + baseAddress = new UriBuilder(value.Scheme, value.Host).Uri; + } + } + + public CookieCollection GetCookies() + { + return GetCookies(Uri); + } + } + + /// + /// A resumable, simultaneous file downloader and reader. + /// + public class NetworkFileStream : Stream, IUpdatable + { + public event EventHandler Updated; + + #region Public Properties + + /// + /// Location to save the downloaded data. + /// + [JsonProperty(Required = Required.Always)] + public string SaveFilePath { get; } + + /// + /// Http(s) address of the file to download. + /// + [JsonProperty(Required = Required.Always)] + public Uri Uri { get; private set; } + + /// + /// All cookies set by caller or by the remote server. + /// + [JsonProperty(Required = Required.Always)] + public SingleUriCookieContainer CookieContainer { get; } + + /// + /// Http headers to be sent to the server with the request. + /// + [JsonProperty(Required = Required.Always)] + public WebHeaderCollection RequestHeaders { get; private set; } + + /// + /// The position in that has been written and flushed to disk. + /// + [JsonProperty(Required = Required.Always)] + public long WritePosition { get; private set; } + + /// + /// The total length of the file to download. + /// + [JsonProperty(Required = Required.Always)] + public long ContentLength { get; private set; } + + #endregion + + #region Private Properties + private HttpWebRequest HttpRequest { get; set; } + private FileStream _writeFile { get; } + private FileStream _readFile { get; } + private Stream _networkStream { get; set; } + private bool hasBegunDownloading { get; set; } + public bool IsCancelled { get; private set; } + private EventWaitHandle downloadEnded { get; set; } + private EventWaitHandle downloadedPiece { get; set; } + + #endregion + + #region Constants + + //Download buffer size + private const int DOWNLOAD_BUFF_SZ = 32 * 1024; + + //NetworkFileStream will flush all data in _writeFile to disk after every + //DATA_FLUSH_SZ bytes are written to the file stream. + private const int DATA_FLUSH_SZ = 1024 * 1024; + + #endregion + + #region Constructor + + /// + /// A resumable, simultaneous file downloader and reader. + /// + /// Path to a location on disk to save the downloaded data from + /// Http(s) address of the file to download. + /// The position in to begin downloading. + /// Http headers to be sent to the server with the . + /// A with cookies to send with the . It will also be populated with any cookies set by the server. + public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, WebHeaderCollection requestHeaders = null, SingleUriCookieContainer cookies = null) + { + ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath)); + ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri)); + ArgumentValidator.EnsureGreaterThan(writePosition, nameof(writePosition), -1); + + if (!Directory.Exists(Path.GetDirectoryName(saveFilePath))) + throw new ArgumentException($"Specified {nameof(saveFilePath)} directory \"{Path.GetDirectoryName(saveFilePath)}\" does not exist."); + + SaveFilePath = saveFilePath; + Uri = uri; + WritePosition = writePosition; + RequestHeaders = requestHeaders ?? new WebHeaderCollection(); + CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri }; + + _writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite) + { + Position = WritePosition + }; + + _readFile = new FileStream(SaveFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + SetUriForSameFile(uri); + } + + #endregion + + #region Downloader + + /// + /// Update the . + /// + private void Update() + { + RequestHeaders = HttpRequest.Headers; + Updated?.Invoke(this, EventArgs.Empty); + } + + /// + /// Set a different to the same file targeted by this instance of + /// + /// New host must match existing host. + public void SetUriForSameFile(Uri uriToSameFile) + { + ArgumentValidator.EnsureNotNullOrWhiteSpace(uriToSameFile?.AbsoluteUri, nameof(uriToSameFile)); + + if (uriToSameFile.Host != Uri.Host) + throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}"); + if (hasBegunDownloading) + throw new InvalidOperationException("Cannot change Uri after download has started."); + + Uri = uriToSameFile; + HttpRequest = WebRequest.CreateHttp(Uri); + + HttpRequest.CookieContainer = CookieContainer; + HttpRequest.Headers = RequestHeaders; + //If NetworkFileStream is resuming, Header will already contain a range. + HttpRequest.Headers.Remove("Range"); + HttpRequest.AddRange(WritePosition); + } + + /// + /// Begins downloading to in a background thread. + /// + private void BeginDownloading() + { + downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset); + + if (ContentLength != 0 && WritePosition == ContentLength) + { + hasBegunDownloading = true; + downloadEnded.Set(); + return; + } + + if (ContentLength != 0 && WritePosition > ContentLength) + throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); + + var response = HttpRequest.GetResponse() as HttpWebResponse; + + if (response.StatusCode != HttpStatusCode.PartialContent) + throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); + + //Content length is the length of the range request, and it is only equal + //to the complete file length if requesting Range: bytes=0- + if (WritePosition == 0) + ContentLength = response.ContentLength; + + _networkStream = response.GetResponseStream(); + downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset); + + //Download the file in the background. + new Thread(() => DownloadFile()) + { IsBackground = true } + .Start(); + + hasBegunDownloading = true; + return; + } + + /// + /// Downlod to . + /// + private void DownloadFile() + { + var downloadPosition = WritePosition; + var nextFlush = downloadPosition + DATA_FLUSH_SZ; + + var buff = new byte[DOWNLOAD_BUFF_SZ]; + do + { + Thread.Sleep(10); + var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); + _writeFile.Write(buff, 0, bytesRead); + + downloadPosition += bytesRead; + + if (downloadPosition > nextFlush) + { + _writeFile.Flush(); + WritePosition = downloadPosition; + Update(); + nextFlush = downloadPosition + DATA_FLUSH_SZ; + downloadedPiece.Set(); + } + + } while (downloadPosition < ContentLength && !IsCancelled); + + _writeFile.Close(); + _networkStream.Close(); + WritePosition = downloadPosition; + Update(); + + downloadedPiece.Set(); + downloadEnded.Set(); + + if (!IsCancelled && WritePosition < ContentLength) + throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); + + if (WritePosition > ContentLength) + throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10})."); + + } + + #endregion + + #region Json Connverters + + public static JsonSerializerSettings GetJsonSerializerSettings() + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new CookieContainerConverter()); + settings.Converters.Add(new WebHeaderCollectionConverter()); + return settings; + } + + internal class CookieContainerConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + => objectType == typeof(SingleUriCookieContainer); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jObj = JObject.Load(reader); + + var result = new SingleUriCookieContainer() + { + Uri = new Uri(jObj["Uri"].Value()), + Capacity = jObj["Capacity"].Value(), + MaxCookieSize = jObj["MaxCookieSize"].Value(), + PerDomainCapacity = jObj["PerDomainCapacity"].Value() + }; + + var cookieList = jObj["Cookies"].ToList(); + + foreach (var cookie in cookieList) + { + result.Add( + new Cookie + { + Comment = cookie["Comment"].Value(), + HttpOnly = cookie["HttpOnly"].Value(), + Discard = cookie["Discard"].Value(), + Domain = cookie["Domain"].Value(), + Expired = cookie["Expired"].Value(), + Expires = cookie["Expires"].Value(), + Name = cookie["Name"].Value(), + Path = cookie["Path"].Value(), + Port = cookie["Port"].Value(), + Secure = cookie["Secure"].Value(), + Value = cookie["Value"].Value(), + Version = cookie["Version"].Value(), + }); + } + + return result; + } + + public override bool CanWrite => true; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var cookies = value as SingleUriCookieContainer; + var obj = (JObject)JToken.FromObject(value); + var container = cookies.GetCookies(); + var propertyNames = container.Select(c => JToken.FromObject(c)); + obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames))); + obj.WriteTo(writer); + } + } + + internal class WebHeaderCollectionConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + => objectType == typeof(WebHeaderCollection); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jObj = JObject.Load(reader); + var result = new WebHeaderCollection(); + + foreach (var kvp in jObj) + result.Add(kvp.Key, kvp.Value.Value()); + + return result; + } + + public override bool CanWrite => true; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var jObj = new JObject(); + var type = value.GetType(); + var headers = value as WebHeaderCollection; + var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k])); + jObj.Add(jHeaders); + jObj.WriteTo(writer); + } + } + + #endregion + + #region Download Stream Reader + + [JsonIgnore] + public override bool CanRead => true; + + [JsonIgnore] + public override bool CanSeek => true; + + [JsonIgnore] + public override bool CanWrite => false; + + [JsonIgnore] + public override long Length + { + get + { + if (!hasBegunDownloading) + BeginDownloading(); + return ContentLength; + } + } + + [JsonIgnore] + public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); } + + [JsonIgnore] + public override bool CanTimeout => false; + + [JsonIgnore] + public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = value; } + + [JsonIgnore] + public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; } + + public override void Flush() => throw new NotImplementedException(); + public override void SetLength(long value) => throw new NotImplementedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + if (!hasBegunDownloading) + BeginDownloading(); + + var toRead = Math.Min(count, Length - Position); + WaitToPosition(Position + toRead); + return _readFile.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + var newPosition = origin switch { SeekOrigin.Current => Position + offset, SeekOrigin.End => ContentLength + offset, _ => offset, }; - WaitToPosition(newPosition); - return _readFile.Position = newPosition; - } + WaitToPosition(newPosition); + return _readFile.Position = newPosition; + } - /// - /// Blocks until the file has downloaded to at least , then returns. - /// - /// The minimum required flished data length in . - private void WaitToPosition(long requiredPosition) + /// + /// Blocks until the file has downloaded to at least , then returns. + /// + /// The minimum required flished data length in . + private void WaitToPosition(long requiredPosition) { - while (requiredPosition > WritePosition && !IsCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; - } + while (requiredPosition > WritePosition && !IsCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; + } - public override void Close() - { - IsCancelled = true; + public override void Close() + { + IsCancelled = true; - while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; + while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; - _readFile.Close(); - _writeFile.Close(); - _networkStream?.Close(); - Update(); - } + _readFile.Close(); + _writeFile.Close(); + _networkStream?.Close(); + Update(); + } - #endregion - ~NetworkFileStream() - { - downloadEnded?.Close(); - downloadedPiece?.Close(); - } - } + #endregion + ~NetworkFileStream() + { + downloadEnded?.Close(); + downloadedPiece?.Close(); + } + } } diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index b54990b3..cabc4447 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -72,6 +72,8 @@ this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.addQuickFilterBtn = new System.Windows.Forms.Button(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.button1 = new System.Windows.Forms.Button(); + this.panel1 = new System.Windows.Forms.Panel(); this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessBookQueue(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); @@ -79,17 +81,16 @@ this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel2.SuspendLayout(); this.splitContainer1.SuspendLayout(); + this.panel1.SuspendLayout(); this.SuspendLayout(); // // gridPanel // - this.gridPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.gridPanel.Location = new System.Drawing.Point(15, 60); + this.gridPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.gridPanel.Location = new System.Drawing.Point(0, 0); this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.gridPanel.Name = "gridPanel"; - this.gridPanel.Size = new System.Drawing.Size(865, 556); + this.gridPanel.Size = new System.Drawing.Size(864, 560); this.gridPanel.TabIndex = 5; // // filterHelpBtn @@ -106,7 +107,7 @@ // filterBtn // this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.filterBtn.Location = new System.Drawing.Point(792, 27); + this.filterBtn.Location = new System.Drawing.Point(791, 27); this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3); this.filterBtn.Name = "filterBtn"; this.filterBtn.Size = new System.Drawing.Size(88, 27); @@ -122,7 +123,7 @@ this.filterSearchTb.Location = new System.Drawing.Point(220, 30); this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(564, 23); + this.filterSearchTb.Size = new System.Drawing.Size(563, 23); this.filterSearchTb.TabIndex = 1; this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); // @@ -140,7 +141,7 @@ this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); - this.menuStrip1.Size = new System.Drawing.Size(895, 24); + this.menuStrip1.Size = new System.Drawing.Size(894, 24); this.menuStrip1.TabIndex = 0; this.menuStrip1.Text = "menuStrip1"; // @@ -396,7 +397,7 @@ this.statusStrip1.Location = new System.Drawing.Point(0, 619); this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); - this.statusStrip1.Size = new System.Drawing.Size(895, 22); + this.statusStrip1.Size = new System.Drawing.Size(894, 22); this.statusStrip1.TabIndex = 6; this.statusStrip1.Text = "statusStrip1"; // @@ -409,7 +410,7 @@ // springLbl // this.springLbl.Name = "springLbl"; - this.springLbl.Size = new System.Drawing.Size(436, 17); + this.springLbl.Size = new System.Drawing.Size(435, 17); this.springLbl.Spring = true; // // backupsCountsLbl @@ -429,7 +430,7 @@ this.addQuickFilterBtn.Location = new System.Drawing.Point(49, 27); this.addQuickFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.addQuickFilterBtn.Name = "addQuickFilterBtn"; - this.addQuickFilterBtn.Size = new System.Drawing.Size(163, 27); + this.addQuickFilterBtn.Size = new System.Drawing.Size(99, 27); this.addQuickFilterBtn.TabIndex = 4; this.addQuickFilterBtn.Text = "Add To Quick Filters"; this.addQuickFilterBtn.UseVisualStyleBackColor = true; @@ -443,8 +444,9 @@ // // splitContainer1.Panel1 // + this.splitContainer1.Panel1.Controls.Add(this.button1); + this.splitContainer1.Panel1.Controls.Add(this.panel1); this.splitContainer1.Panel1.Controls.Add(this.menuStrip1); - this.splitContainer1.Panel1.Controls.Add(this.gridPanel); this.splitContainer1.Panel1.Controls.Add(this.filterSearchTb); this.splitContainer1.Panel1.Controls.Add(this.addQuickFilterBtn); this.splitContainer1.Panel1.Controls.Add(this.filterBtn); @@ -455,17 +457,41 @@ // this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1); this.splitContainer1.Size = new System.Drawing.Size(1231, 641); - this.splitContainer1.SplitterDistance = 895; + this.splitContainer1.SplitterDistance = 894; this.splitContainer1.SplitterWidth = 8; this.splitContainer1.TabIndex = 7; // + // button1 + // + this.button1.Location = new System.Drawing.Point(155, 27); + this.button1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(58, 22); + this.button1.TabIndex = 8; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.Controls.Add(this.gridPanel); + this.panel1.Location = new System.Drawing.Point(15, 59); + this.panel1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(864, 560); + this.panel1.TabIndex = 7; + // // processBookQueue1 // this.processBookQueue1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.processBookQueue1.Dock = System.Windows.Forms.DockStyle.Fill; this.processBookQueue1.Location = new System.Drawing.Point(0, 0); + this.processBookQueue1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.processBookQueue1.Name = "processBookQueue1"; - this.processBookQueue1.Size = new System.Drawing.Size(328, 641); + this.processBookQueue1.Size = new System.Drawing.Size(329, 641); this.processBookQueue1.TabIndex = 0; // // Form1 @@ -489,6 +515,7 @@ this.splitContainer1.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); this.splitContainer1.ResumeLayout(false); + this.panel1.ResumeLayout(false); this.ResumeLayout(false); } @@ -539,5 +566,7 @@ private System.Windows.Forms.ToolStripMenuItem liberateVisible2ToolStripMenuItem; private System.Windows.Forms.SplitContainer splitContainer1; private LibationWinForms.ProcessQueue.ProcessBookQueue processBookQueue1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button button1; } } diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index 2cdd7e72..54218c78 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -1,19 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using LibationFileManager; +using LibationFileManager; using LibationWinForms.ProcessQueue; +using System; +using System.Windows.Forms; namespace LibationWinForms { - public partial class Form1 - { - private void Configure_ProcessQueue() - { - //splitContainer1.Panel2Collapsed = true; - processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; + public partial class Form1 + { + private void Configure_ProcessQueue() + { + //splitContainer1.Panel2Collapsed = true; + processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; } private void ProcessBookQueue1_PopOut(object sender, EventArgs e) @@ -42,5 +39,10 @@ namespace LibationWinForms this.Focus(); } } + + private void button1_Click(object sender, EventArgs e) + { + processBookQueue1.AddDownloadDecrypt(productsGrid.List); + } } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index 2792ca81..ffc1246d 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -5,6 +5,7 @@ using LibationFileManager; using LibationWinForms.BookLiberation; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -21,6 +22,7 @@ namespace LibationWinForms.ProcessQueue FailedSkip, FailedAbort } + public enum ProcessBookStatus { Queued, @@ -30,98 +32,56 @@ namespace LibationWinForms.ProcessQueue 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 class ProcessBook { public event EventHandler Completed; - public event ProcessControlEventArgs Cancelled; - public event ProcessControlReorderHandler RequestMove; - public GridEntry Entry { get; } - //public ProcessBookControl BookControl { get; } + public event EventHandler DataAvailable; - private Func _makeFirstProc; - private Processable _firstProcessable; - private bool cancelled = false; - private bool running = false; - public Processable FirstProcessable => _firstProcessable ??= _makeFirstProc?.Invoke(); + public Processable CurrentProcessable => _currentProcessable ??= Processes.Dequeue().Invoke(); + public ProcessBookResult Result { get; private set; } = ProcessBookResult.None; + public ProcessBookStatus Status { get; private set; } = ProcessBookStatus.Queued; + public string BookText { get; private set; } + public Image Cover { get; private set; } + public int Progress { get; private set; } + public TimeSpan TimeRemaining { get; private set; } + public LibraryBook LibraryBook { get; } + + private Processable _currentProcessable; + private Func GetCoverArtDelegate; private readonly Queue> Processes = new(); + private readonly LogMe Logger; - LogMe Logger; - - public ProcessBook(GridEntry entry, LogMe logme) + public ProcessBook(LibraryBook libraryBook, Image coverImage, LogMe logme) { - Entry = entry; - //BookControl = new ProcessBookControl(Entry.Title, Entry.Cover); - //BookControl.CancelAction = Cancel; - //BookControl.RequestMoveAction = MoveRequested; + LibraryBook = libraryBook; + Cover = coverImage; 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); + title = LibraryBook.Book.Title; + authorNames = LibraryBook.Book.AuthorNames(); + narratorNames = LibraryBook.Book.NarratorNames(); + BookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"; } public async Task ProcessOneAsync() { - running = true; - ProcessBookResult result = ProcessBookResult.None; try { - LinkProcessable(FirstProcessable); - - var statusHandler = await FirstProcessable.ProcessSingleAsync(Entry.LibraryBook, validate: true); + LinkProcessable(CurrentProcessable); + var statusHandler = await CurrentProcessable.ProcessSingleAsync(LibraryBook, validate: true); if (statusHandler.IsSuccess) - return result = ProcessBookResult.Success; - else if (cancelled) + return Result = ProcessBookResult.Success; + else if (statusHandler.Errors.Contains("Cancelled")) { - Logger.Info($"Process was cancelled {Entry.LibraryBook.Book}"); - return result = ProcessBookResult.Cancelled; + Logger.Info($"Process was cancelled {LibraryBook.Book}"); + return Result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { - Logger.Info($"Validation failed {Entry.LibraryBook.Book}"); - return result = ProcessBookResult.ValidationFail; + Logger.Info($"Validation failed {LibraryBook.Book}"); + return Result = ProcessBookResult.ValidationFail; } foreach (var errorMessage in statusHandler.Errors) @@ -133,51 +93,189 @@ namespace LibationWinForms.ProcessQueue } finally { - if (result == ProcessBookResult.None) - result = showRetry(Entry.LibraryBook); + if (Result == ProcessBookResult.None) + Result = showRetry(LibraryBook); - //BookControl.SetResult(result); + Status = Result switch + { + ProcessBookResult.Success => ProcessBookStatus.Completed, + ProcessBookResult.Cancelled => ProcessBookStatus.Cancelled, + ProcessBookResult.FailedRetry => ProcessBookStatus.Queued, + _ => ProcessBookStatus.Failed, + }; + + DataAvailable?.Invoke(this, EventArgs.Empty); } - return result; + return Result; } - public void AddPdfProcessable() => AddProcessable(); - public void AddDownloadDecryptProcessable() => AddProcessable(); - public void AddConvertMp3Processable() => AddProcessable(); + public void Cancel() + { + try + { + if (CurrentProcessable is AudioDecodable audioDecodable) + { + //There's some threadding bug that causes this to hang if executed synchronously. + Task.Run(audioDecodable.Cancel); + DataAvailable?.Invoke(this, EventArgs.Empty); + } + } + catch (Exception ex) + { + Logger.Error(ex, "Error while cancelling"); + } + } + + public void AddDownloadPdf() => AddProcessable(); + public void AddDownloadDecryptBook() => AddProcessable(); + public void AddConvertToMp3() => AddProcessable(); private void AddProcessable() where T : Processable, new() { - if (FirstProcessable == null) - { - _makeFirstProc = () => new T(); - } - else - Processes.Enqueue(() => new T()); + Processes.Enqueue(() => new T()); } + public override string ToString() => LibraryBook.ToString(); + + #region Subscribers and Unsubscribers private void LinkProcessable(Processable strProc) { strProc.Begin += Processable_Begin; strProc.Completed += Processable_Completed; + strProc.StreamingProgressChanged += Streamable_StreamingProgressChanged; + strProc.StreamingTimeRemaining += Streamable_StreamingTimeRemaining; + + if (strProc is AudioDecodable audioDecodable) + { + audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt; + audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered; + audioDecodable.AuthorsDiscovered += AudioDecodable_AuthorsDiscovered; + audioDecodable.NarratorsDiscovered += AudioDecodable_NarratorsDiscovered; + audioDecodable.CoverImageDiscovered += AudioDecodable_CoverImageDiscovered; + } } + private void UnlinkProcessable(Processable strProc) + { + strProc.Begin -= Processable_Begin; + strProc.Completed -= Processable_Completed; + strProc.StreamingProgressChanged -= Streamable_StreamingProgressChanged; + strProc.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining; + + if (strProc is AudioDecodable audioDecodable) + { + audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt; + audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered; + audioDecodable.AuthorsDiscovered -= AudioDecodable_AuthorsDiscovered; + audioDecodable.NarratorsDiscovered -= AudioDecodable_NarratorsDiscovered; + audioDecodable.CoverImageDiscovered -= AudioDecodable_CoverImageDiscovered; + } + } + + #endregion + + #region AudioDecodable event handlers + + private string title; + private string authorNames; + private string narratorNames; + private void AudioDecodable_TitleDiscovered(object sender, string title) + { + this.title = title; + updateBookInfo(); + } + + private void AudioDecodable_AuthorsDiscovered(object sender, string authors) + { + authorNames = authors; + updateBookInfo(); + } + + private void AudioDecodable_NarratorsDiscovered(object sender, string narrators) + { + narratorNames = narrators; + updateBookInfo(); + } + + private void updateBookInfo() + { + BookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"; + DataAvailable?.Invoke(this, EventArgs.Empty); + } + + public void AudioDecodable_RequestCoverArt(object sender, Action setCoverArtDelegate) + { + byte[] coverData = GetCoverArtDelegate(); + setCoverArtDelegate(coverData); + AudioDecodable_CoverImageDiscovered(this, coverData); + } + + private void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt) + { + Cover = Dinah.Core.Drawing.ImageReader.ToImage(coverArt); + DataAvailable?.Invoke(this, EventArgs.Empty); + } + + #endregion + + #region Streamable event handlers + private void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining) + { + TimeRemaining = timeRemaining; + DataAvailable?.Invoke(this, EventArgs.Empty); + } + + private void Streamable_StreamingProgressChanged(object sender, Dinah.Core.Net.Http.DownloadProgress downloadProgress) + { + if (!downloadProgress.ProgressPercentage.HasValue) + return; + + if (downloadProgress.ProgressPercentage == 0) + TimeRemaining = TimeSpan.Zero; + else + Progress = (int)downloadProgress.ProgressPercentage; + + DataAvailable?.Invoke(this, EventArgs.Empty); + } + + #endregion + + #region Processable event handlers + private void Processable_Begin(object sender, LibraryBook libraryBook) { - //BookControl.RegisterFileLiberator((Processable)sender, Logger); - //BookControl.Processable_Begin(sender, libraryBook); + Status = ProcessBookStatus.Working; + + Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}"); + + GetCoverArtDelegate = () => PictureStorage.GetPictureSynchronously( + new PictureDefinition( + libraryBook.Book.PictureId, + PictureSize._500x500)); + + title = libraryBook.Book.Title; + authorNames = libraryBook.Book.AuthorNames(); + narratorNames = libraryBook.Book.NarratorNames(); + Cover = Dinah.Core.Drawing.ImageReader.ToImage(PictureStorage.GetPicture( + new PictureDefinition( + libraryBook.Book.PictureId, + PictureSize._80x80)).bytes); + + updateBookInfo(); } - private async void Processable_Completed(object sender, LibraryBook e) + private async void Processable_Completed(object sender, LibraryBook libraryBook) { - ((Processable)sender).Begin -= Processable_Begin; + + Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}"); + UnlinkProcessable((Processable)sender); if (Processes.Count > 0) { - var nextProcessFunc = Processes.Dequeue(); - var nextProcess = nextProcessFunc(); - LinkProcessable(nextProcess); - var result = await nextProcess.ProcessSingleAsync(e, true); + _currentProcessable = null; + LinkProcessable(CurrentProcessable); + var result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true); if (result.HasErrors) { @@ -185,16 +283,18 @@ namespace LibationWinForms.ProcessQueue Logger.Error(errorMessage); Completed?.Invoke(this, EventArgs.Empty); - running = false; } } else { Completed?.Invoke(this, EventArgs.Empty); - running = false; } } + #endregion + + #region Failure Handler + private ProcessBookResult showRetry(LibraryBook libraryBook) { Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed"); @@ -247,7 +347,7 @@ $@" Title: {libraryBook.Book.Title} } - protected string SkipDialogText => @" + private string SkipDialogText => @" An error occurred while trying to process this book. {0} @@ -257,8 +357,10 @@ 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 MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; - protected MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1; - protected DialogResult SkipResult => DialogResult.Ignore; + private MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; + private MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1; + private DialogResult SkipResult => DialogResult.Ignore; } + + #endregion } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookForm.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookForm.cs index ebdf2784..b1444c84 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookForm.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookForm.cs @@ -1,12 +1,4 @@ -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; +using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs index fd0d0322..aed3aa67 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs @@ -28,20 +28,25 @@ /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessBookQueue)); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); + this.queueNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); + this.completedNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); + this.errorNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); + this.panel3 = new System.Windows.Forms.Panel(); this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl(); this.panel1 = new System.Windows.Forms.Panel(); this.btnCleanFinished = new System.Windows.Forms.Button(); this.cancelAllBtn = new System.Windows.Forms.Button(); this.tabPage2 = new System.Windows.Forms.TabPage(); + this.panel4 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel(); this.clearLogBtn = new System.Windows.Forms.Button(); this.logMeTbox = new System.Windows.Forms.TextBox(); - this.tabPage3 = new System.Windows.Forms.TabPage(); this.statusStrip1.SuspendLayout(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); @@ -52,78 +57,111 @@ // // statusStrip1 // + this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolStripProgressBar1, + this.queueNumberLbl, + this.completedNumberLbl, + this.errorNumberLbl, this.toolStripStatusLabel1}); - this.statusStrip1.Location = new System.Drawing.Point(0, 486); + this.statusStrip1.Location = new System.Drawing.Point(0, 483); this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(404, 22); + this.statusStrip1.Size = new System.Drawing.Size(404, 25); this.statusStrip1.TabIndex = 1; this.statusStrip1.Text = "statusStrip1"; // // toolStripProgressBar1 // this.toolStripProgressBar1.Name = "toolStripProgressBar1"; - this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 16); + this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 19); + // + // queueNumberLbl + // + this.queueNumberLbl.Image = ((System.Drawing.Image)(resources.GetObject("queueNumberLbl.Image"))); + this.queueNumberLbl.Name = "queueNumberLbl"; + this.queueNumberLbl.Size = new System.Drawing.Size(51, 20); + this.queueNumberLbl.Text = "[Q#]"; + // + // completedNumberLbl + // + this.completedNumberLbl.Image = ((System.Drawing.Image)(resources.GetObject("completedNumberLbl.Image"))); + this.completedNumberLbl.Name = "completedNumberLbl"; + this.completedNumberLbl.Size = new System.Drawing.Size(56, 20); + this.completedNumberLbl.Text = "[DL#]"; + // + // errorNumberLbl + // + this.errorNumberLbl.Image = ((System.Drawing.Image)(resources.GetObject("errorNumberLbl.Image"))); + this.errorNumberLbl.Name = "errorNumberLbl"; + this.errorNumberLbl.Size = new System.Drawing.Size(62, 20); + this.errorNumberLbl.Text = "[ERR#]"; // // toolStripStatusLabel1 // this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; - this.toolStripStatusLabel1.Size = new System.Drawing.Size(287, 17); + this.toolStripStatusLabel1.Size = new System.Drawing.Size(118, 20); this.toolStripStatusLabel1.Spring = true; // // tabControl1 // - this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage2); - this.tabControl1.Controls.Add(this.tabPage3); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControl1.Location = new System.Drawing.Point(0, 0); this.tabControl1.Margin = new System.Windows.Forms.Padding(0); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(405, 486); + this.tabControl1.Size = new System.Drawing.Size(404, 483); this.tabControl1.TabIndex = 3; // // tabPage1 // + this.tabPage1.Controls.Add(this.panel3); this.tabPage1.Controls.Add(this.virtualFlowControl2); this.tabPage1.Controls.Add(this.panel1); this.tabPage1.Location = new System.Drawing.Point(4, 24); this.tabPage1.Name = "tabPage1"; this.tabPage1.Padding = new System.Windows.Forms.Padding(3); - this.tabPage1.Size = new System.Drawing.Size(397, 458); + this.tabPage1.Size = new System.Drawing.Size(396, 455); this.tabPage1.TabIndex = 0; this.tabPage1.Text = "Process Queue"; this.tabPage1.UseVisualStyleBackColor = true; // + // panel3 + // + this.panel3.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel3.Location = new System.Drawing.Point(3, 422); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(390, 5); + this.panel3.TabIndex = 4; + // // virtualFlowControl2 // + this.virtualFlowControl2.AccessibleRole = System.Windows.Forms.AccessibleRole.None; + this.virtualFlowControl2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.virtualFlowControl2.Dock = System.Windows.Forms.DockStyle.Fill; this.virtualFlowControl2.Location = new System.Drawing.Point(3, 3); this.virtualFlowControl2.Name = "virtualFlowControl2"; - this.virtualFlowControl2.Size = new System.Drawing.Size(391, 422); + this.virtualFlowControl2.Size = new System.Drawing.Size(390, 424); this.virtualFlowControl2.TabIndex = 3; this.virtualFlowControl2.VirtualControlCount = 0; // // panel1 // - this.panel1.BackColor = System.Drawing.SystemColors.ControlDark; + this.panel1.BackColor = System.Drawing.SystemColors.Control; + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.Controls.Add(this.btnCleanFinished); this.panel1.Controls.Add(this.cancelAllBtn); this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; - this.panel1.Location = new System.Drawing.Point(3, 425); + this.panel1.Location = new System.Drawing.Point(3, 427); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(391, 30); + this.panel1.Size = new System.Drawing.Size(390, 25); this.panel1.TabIndex = 2; // // btnCleanFinished // - this.btnCleanFinished.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Right))); - this.btnCleanFinished.Location = new System.Drawing.Point(298, 3); + this.btnCleanFinished.Dock = System.Windows.Forms.DockStyle.Right; + this.btnCleanFinished.Location = new System.Drawing.Point(298, 0); this.btnCleanFinished.Name = "btnCleanFinished"; this.btnCleanFinished.Size = new System.Drawing.Size(90, 23); this.btnCleanFinished.TabIndex = 3; @@ -133,9 +171,8 @@ // // cancelAllBtn // - this.cancelAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.cancelAllBtn.Location = new System.Drawing.Point(3, 3); + this.cancelAllBtn.Dock = System.Windows.Forms.DockStyle.Left; + this.cancelAllBtn.Location = new System.Drawing.Point(0, 0); this.cancelAllBtn.Name = "cancelAllBtn"; this.cancelAllBtn.Size = new System.Drawing.Size(75, 23); this.cancelAllBtn.TabIndex = 2; @@ -145,31 +182,40 @@ // // tabPage2 // + this.tabPage2.Controls.Add(this.panel4); this.tabPage2.Controls.Add(this.panel2); this.tabPage2.Controls.Add(this.logMeTbox); this.tabPage2.Location = new System.Drawing.Point(4, 24); this.tabPage2.Name = "tabPage2"; this.tabPage2.Padding = new System.Windows.Forms.Padding(3); - this.tabPage2.Size = new System.Drawing.Size(397, 458); + this.tabPage2.Size = new System.Drawing.Size(396, 455); this.tabPage2.TabIndex = 1; this.tabPage2.Text = "Log"; this.tabPage2.UseVisualStyleBackColor = true; // + // panel4 + // + this.panel4.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel4.Location = new System.Drawing.Point(3, 422); + this.panel4.Name = "panel4"; + this.panel4.Size = new System.Drawing.Size(390, 5); + this.panel4.TabIndex = 2; + // // panel2 // - this.panel2.BackColor = System.Drawing.SystemColors.ControlDark; + this.panel2.BackColor = System.Drawing.SystemColors.Control; + this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel2.Controls.Add(this.clearLogBtn); this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom; - this.panel2.Location = new System.Drawing.Point(3, 425); + this.panel2.Location = new System.Drawing.Point(3, 427); this.panel2.Name = "panel2"; - this.panel2.Size = new System.Drawing.Size(391, 30); + this.panel2.Size = new System.Drawing.Size(390, 25); this.panel2.TabIndex = 1; // // clearLogBtn // - this.clearLogBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.clearLogBtn.Location = new System.Drawing.Point(3, 3); + this.clearLogBtn.Dock = System.Windows.Forms.DockStyle.Left; + this.clearLogBtn.Location = new System.Drawing.Point(0, 0); this.clearLogBtn.Name = "clearLogBtn"; this.clearLogBtn.Size = new System.Drawing.Size(75, 23); this.clearLogBtn.TabIndex = 0; @@ -179,9 +225,7 @@ // // logMeTbox // - this.logMeTbox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); + this.logMeTbox.Dock = System.Windows.Forms.DockStyle.Fill; this.logMeTbox.Location = new System.Drawing.Point(3, 3); this.logMeTbox.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0); this.logMeTbox.MaxLength = 10000000; @@ -189,19 +233,9 @@ this.logMeTbox.Name = "logMeTbox"; this.logMeTbox.ReadOnly = true; this.logMeTbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.logMeTbox.Size = new System.Drawing.Size(346, 419); + this.logMeTbox.Size = new System.Drawing.Size(390, 449); this.logMeTbox.TabIndex = 0; // - // tabPage3 - // - this.tabPage3.Location = new System.Drawing.Point(4, 24); - this.tabPage3.Name = "tabPage3"; - this.tabPage3.Padding = new System.Windows.Forms.Padding(3); - this.tabPage3.Size = new System.Drawing.Size(397, 458); - this.tabPage3.TabIndex = 2; - this.tabPage3.Text = "tabPage3"; - this.tabPage3.UseVisualStyleBackColor = true; - // // ProcessBookQueue // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -237,7 +271,11 @@ private System.Windows.Forms.Panel panel2; private System.Windows.Forms.Button clearLogBtn; private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; - private System.Windows.Forms.TabPage tabPage3; private VirtualFlowControl virtualFlowControl2; + private System.Windows.Forms.ToolStripStatusLabel queueNumberLbl; + private System.Windows.Forms.ToolStripStatusLabel completedNumberLbl; + private System.Windows.Forms.ToolStripStatusLabel errorNumberLbl; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.Panel panel4; } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs index 3c0b37c6..c30014f8 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs @@ -1,142 +1,205 @@ -using DataLayer; -using Dinah.Core.Threading; +using Dinah.Core.Threading; using LibationWinForms.BookLiberation; using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms.ProcessQueue -{ +{ internal partial class ProcessBookQueue : UserControl, ILogForm { - TrackedQueue Queue = new(); + private TrackedQueue Queue = new(); private readonly LogMe Logger; + private int QueuedCount + { + set + { + queueNumberLbl.Text = value.ToString(); + queueNumberLbl.Visible = value > 0; + } + } + private int ErrorCount + { + set + { + errorNumberLbl.Text = value.ToString(); + errorNumberLbl.Visible = value > 0; + } + } + + private int CompletedCount + { + set + { + completedNumberLbl.Text = value.ToString(); + completedNumberLbl.Visible = value > 0; + } + } + public Task QueueRunner { get; private set; } public bool Running => !QueueRunner?.IsCompleted ?? false; public ToolStripButton popoutBtn = new(); + private int FirstVisible = 0; + private int NumVisible = 0; + private IReadOnlyList Panels; + public ProcessBookQueue() { InitializeComponent(); Logger = LogMe.RegisterForm(this); - this.popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; - this.popoutBtn.Name = "popoutBtn"; - this.popoutBtn.Text = "Pop Out"; - this.popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.popoutBtn.Alignment = ToolStripItemAlignment.Right; - this.popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; + popoutBtn.Name = "popoutBtn"; + popoutBtn.Text = "Pop Out"; + popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + popoutBtn.Alignment = ToolStripItemAlignment.Right; + popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; statusStrip1.Items.Add(popoutBtn); virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData; + virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked; + Queue.QueuededCountChanged += Queue_QueuededCountChanged; + Queue.CompletedCountChanged += Queue_CompletedCountChanged; + + QueuedCount = 0; + ErrorCount = 0; + CompletedCount = 0; + } + + private void Queue_CompletedCountChanged(object sender, int e) + { + int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.ValidationFail); + int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success); + + ErrorCount = errCount; + CompletedCount = completeCount; + UpdateProgressBar(); + } + private void Queue_QueuededCountChanged(object sender, int cueCount) + { + QueuedCount = cueCount; + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateProgressBar(); + } + private void UpdateProgressBar() + { + toolStripProgressBar1.Maximum = Queue.Count; + toolStripProgressBar1.Value = Queue.Completed.Count; + } + private void VirtualFlowControl2_ButtonClicked(int itemIndex, string buttonName, ProcessBookControl panelClicked) + { + ProcessBook item = Queue[itemIndex]; + if (buttonName == "cancelBtn") + { + item.Cancel(); + Queue.RemoveQueued(item); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateControl(itemIndex); + } + else if (buttonName == "moveFirstBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.Fisrt); + UpdateAllControls(); + } + else if (buttonName == "moveUpBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.OneUp); + UpdateControl(itemIndex - 1); + UpdateControl(itemIndex); + } + else if (buttonName == "moveDownBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.OneDown); + UpdateControl(itemIndex + 1); + UpdateControl(itemIndex); + } + else if (buttonName == "moveLastBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.Last); + UpdateAllControls(); + } + } + + private void UpdateControl(int queueIndex) + { + int i = queueIndex - FirstVisible; + + if (i < 0 || i > NumVisible) return; + + var proc = Queue[queueIndex]; + + Panels[i].Invoke(() => + { + Panels[i].SuspendLayout(); + Panels[i].SetCover(proc.Cover); + Panels[i].SetBookInfo(proc.BookText); + + if (proc.Result != ProcessBookResult.None) + { + Panels[i].SetResult(proc.Result); + return; + } + + Panels[i].SetStatus(proc.Status); + Panels[i].SetProgrss(proc.Progress); + Panels[i].SetRemainingTime(proc.TimeRemaining); + Panels[i].ResumeLayout(); + }); + } + + private void UpdateAllControls() + { + int numToShow = Math.Min(NumVisible, Queue.Count - FirstVisible); + + for (int i = 0; i < numToShow; i++) + UpdateControl(FirstVisible + i); } private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList panelsToFill) { - int numToShow = Math.Min(numVisible, Queue.Count - firstIndex); - for (int i = 0; i < numToShow; i++) - { - var proc = Queue[firstIndex + i]; - - panelsToFill[i].SetCover(proc.Entry.Cover); - panelsToFill[i].SetTitle(proc.Entry.Title); - } + FirstVisible = firstIndex; + NumVisible = numVisible; + Panels = panelsToFill; + UpdateAllControls(); } - public async Task AddDownloadDecrypt(IEnumerable entries) + public void AddDownloadDecrypt(IEnumerable entries) { - SuspendLayout(); foreach (var entry in entries) - await AddDownloadDecryptAsync(entry); - ResumeLayout(); + AddDownloadDecryptAsync(entry); } - int count = 0; - public async Task AddDownloadDecryptAsync(GridEntry gridEntry) + + public void AddDownloadDecryptAsync(GridEntry gridEntry) { - //if (Queue.Any(b=> b?.Entry?.AudibleProductId == gridEntry.AudibleProductId)) - //return; + if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) + return; - ProcessBook pbook = new ProcessBook(gridEntry, Logger); - pbook.Completed += Pbook_Completed; - pbook.Cancelled += Pbook_Cancelled; - pbook.RequestMove += (o,d) => RequestMove(o, d); + ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger); + pbook.DataAvailable += Pbook_DataAvailable; - var libStatus = gridEntry.Liberate; + pbook.AddDownloadDecryptBook(); + pbook.AddDownloadPdf(); - if (libStatus.BookStatus != LiberatedStatus.Liberated) - pbook.AddDownloadDecryptProcessable(); - - if (libStatus.PdfStatus != LiberatedStatus.Liberated) - pbook.AddPdfProcessable(); - - Queue.EnqueueBook(pbook); - - //await AddBookControlAsync(pbook.BookControl); - count++; - - virtualFlowControl2.VirtualControlCount = count; + Queue.Enqueue(pbook); if (!Running) { - //QueueRunner = QueueLoop(); + QueueRunner = QueueLoop(); } - toolStripStatusLabel1.Text = count.ToString(); } - private async void Pbook_Cancelled(ProcessBook sender, EventArgs e) + private void Pbook_DataAvailable(object sender, EventArgs e) { - Queue.Remove(sender); - //await RemoveBookControlAsync(sender.BookControl); - } - - /// - /// Handles requests by to change its order in the queue - /// - /// The requesting - /// The requested position - /// The resultant position - private QueuePosition RequestMove(ProcessBook sender, QueuePositionRequest requested) - { - - var direction = Queue.MoveQueuePosition(sender, requested); - - if (direction is QueuePosition.Absent or QueuePosition.Current or QueuePosition.Completed) - return direction; - return direction; - - /* - - var firstQueue = autosizeFlowLayout1.Controls.Cast().FirstOrDefault(c => c.Status == ProcessBookStatus.Queued); - - if (firstQueue is null) return QueuePosition.Current; - - int firstQueueIndex = autosizeFlowLayout1.Controls.IndexOf(firstQueue); - - var index = autosizeFlowLayout1.Controls.IndexOf(sender.BookControl); - - int newIndex = direction switch - { - QueuePosition.Fisrt => firstQueueIndex, - QueuePosition.OneUp => index - 1, - QueuePosition.OneDown => index + 1, - QueuePosition.Last => autosizeFlowLayout1.Controls.Count - 1, - _ => -1, - }; - - if (newIndex < 0) return direction; - - autosizeFlowLayout1.Controls.SetChildIndex(sender.BookControl, newIndex); - - return direction; - */ + int index = Queue.IndexOf((ProcessBook)sender); + UpdateControl(index); } private async Task QueueLoop() @@ -147,80 +210,28 @@ namespace LibationWinForms.ProcessQueue var result = await nextBook.ProcessOneAsync(); - switch (result) - { - case ProcessBookResult.FailedRetry: - Queue.EnqueueBook(nextBook); - break; - case ProcessBookResult.FailedAbort: - return; - } + if (result == ProcessBookResult.FailedRetry) + Queue.Enqueue(nextBook); + else if (result == ProcessBookResult.FailedAbort) + return; } + + Queue_CompletedCountChanged(this, 0); } - - - private void Pbook_Completed(object sender, EventArgs e) + private void cancelAllBtn_Click(object sender, EventArgs e) { - - } - - private async void cancelAllBtn_Click(object sender, EventArgs e) - { - List l1 = Queue.QueuedItems(); - Queue.ClearQueue(); Queue.Current?.Cancel(); - - //await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateAllControls(); } - private async void btnCleanFinished_Click(object sender, EventArgs e) + private void btnCleanFinished_Click(object sender, EventArgs e) { - List l1 = Queue.CompletedItems(); Queue.ClearCompleted(); - //await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); - } - - private async Task AddBookControlAsync(ProcessBookControl control) - { - await Task.Run(() => Invoke(() => - { - /* - control.Width = autosizeFlowLayout1.DesiredBookControlWidth; - autosizeFlowLayout1.Controls.Add(control); - autosizeFlowLayout1.SetFlowBreak(control, true); - */ - //Refresh(); - //System.Threading.Thread.Sleep(1000); - })); - } - - private async Task RemoveBookControlAsync(ProcessBookControl control) - { - await Task.Run(() => Invoke(() => - { - //autosizeFlowLayout1.Controls.Remove(control); - })); - } - - private async Task RemoveBookControlsAsync(IEnumerable control) - { - await Task.Run(() => Invoke(() => - { - /* - SuspendLayout(); - foreach (var l in control) - autosizeFlowLayout1.Controls.Remove(l); - ResumeLayout(); - */ - })); - } - - public void WriteLine(string text) - { - if (!IsDisposed) - logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateAllControls(); } private void clearLogBtn_Click(object sender, EventArgs e) @@ -228,5 +239,10 @@ namespace LibationWinForms.ProcessQueue logMeTbox.Clear(); } + public void WriteLine(string text) + { + if (!IsDisposed) + logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); + } } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx index 5cb320f3..445745d5 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx @@ -60,4 +60,574 @@ 17, 17 + + + + iVBORw0KGgoAAAANSUhEUgAAAm4AAAJuCAYAAAAJqI4TAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 + MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAACyySURBVHhe7d0/zL5tXd9xwQik + okkDuEhs0kGj3URxFYOmSYeq0a1xdDUdmq4MHVwZuzqadCLibCNOTVAcUTb/LCKDYIJBepziHeTg+zzH + 735+93Xen+9xvD7JK8Hv4lM5r+d850pz3T/w7W9/GwCABsojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK + IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK + IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK + IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK + IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK + IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK + IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5ymMnts3++3D9F8o7+2/D3ftfQ/XP + wnf95+Hu/buh+mfhu/7vYButaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA + 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 + nHBbE26ZhFum1w63zw++RHjBVQ1wovLYydgVbr8/fOz6H6zthNuacMsk3DK9Vrh9dfAlwgNWNcCJymMn + Y9cHxfpPuK0Jt0zCLdNrhNv1G6Ef/c5/tJde1QAnKo+d2DYTbmvCLZNwy/Qa4WYPXNUAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r + wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4 + rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZ + i0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcp + j6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3 + Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B7 + 0Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMIt + k3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3 + TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4 + rQm3TMItk3B70Kp3Ivcpj6yNfbqZTw7JE25rwi2TcMvUJdyufzdX/86OVb0TuU95ZG3s3/4LooP/NyRP + uK0Jt0zCLVOXcLv+3Vz988ea34fcqzyyNj/IDQi3/oRbJuGWSbg9yPw+5F7lkbX5QW5AuPUn3DIJt0zC + 7UHm9yH3Ko+szQ9yA8KtP+GWSbhlEm4PMr8PuVd5ZG1+kBsQbv0Jt0zCLZNwe5D5fci9yiNr84PcgHDr + T7hlEm6ZhNuDzO9D7lUeWZsf5AaEW3/CLZNwyyTcHmR+H3Kv8sja/CA3INz6E26ZhFsm4fYg8/uQe5VH + 1uYHuQHh1p9wyyTcMgm3B5nfh9yrPLI2P8gNCLf+hFsm4ZZJuD3I/D7kXuWRtflBbkC49SfcMgm3TMLt + Qeb3Ifcqj6zND3IDwq0/4ZZJuGUSbg8yvw+5V3lkbX6QGxBu/Qm3TMItk3B7kPl9yL3KI2vzg9yAcOtP + uGUSbpmE24PM70PuVR5Zmx/kBoRbf8Itk3DLJNweZH4fcq/yyNr8IDcg3PoTbpmEWybh9iDz+5B7lUfW + 5ge5AeHWn3DLJNwyCbcHmd+H3Ks8sjY/yA0It/6EWybhlkm4Pcj8PuRe5ZG1+UFuQLj1J9wyCbdMwu1B + 5vch9yqPrM0PcgPCrT/hlkm4ZRJuDzK/D7lXeWRtfpAbEG79CbdMwi2TcHuQ+X3Ivcoja/OD3IBw60+4 + ZRJumYTbg8zvQ+5VHlmbH+QGhFt/wi2TcMsk3B5kfh9yr/LI2vwgNyDc+hNumYRbJuH2IPP7kHuVR9bm + B7kB4dafcMsk3DIJtweZ34fcqzyyNj/IDQi3/oRbJuGWSbg9yPw+5F7lkbX5QW5AuPUn3DIJt0zC7UHm + 9yH3Ko+szQ9yA8KtP+GWSbhlEm4PMr8PuVd5ZG1+kBsQbv0Jt0zCLZNwe5D5fci9yiNr84PcgHDrT7hl + Em6ZhNuDzO9D7lUeWZsf5AaEW3/CLZNwyyTcHmR+H3Kv8sja/CA3INz6E26ZhFsm4fYg8/uQe5VH1uYH + uQHh1p9wyyTcMgm3B5nfh9yrPLI2P8gNCLf+hFsm4ZZJuD3I/D7kXuWRtflBbkC49SfcMgm3TMLtQeb3 + Ifcqj6zND3IDwq0/4ZZJuGUSbg8yvw+5V3lkbX6QGxBu/Qm3TMItk3B7kPl9yL3KI2vzg9yAcOtPuGUS + bpmE24PM70PuVR5Zmx/kBoRbf8Itk3DLJNweZH4fcq/yyNr8IDcg3PoTbpmEWybh9iDz+5B7lUfW5ge5 + AeHWn3DLJNwyCbcHmd+H3Ks8sjY/yA0It/6EWybhlkm4Pcj8PuRe5ZG1+UFuQLj1J9wyCbdMwu1B5vch + 9yqPrM0PcgPCrT/hlkm4ZRJuDzK/D7lXeWRtfpAbEG79CbdMwi2TcHuQ+X3Ivcoja/OD3IBw60+4ZRJu + mYTbg8zvQ+5VHlmbH+QGhFt/wi2TcMsk3B5kfh9yr/LI2vwgNyDc+hNumYRbJuH2IPP7kHuVR9bmB7kB + 4dafcMsk3DIJtweZ34fcqzyyNj/IDQi3/oRbJuGWSbg9yPw+5F7lkbX5QW5AuPUn3DIJt0zC7UHm9yH3 + Ko+szQ9yA8KtP+GWSbhlEm4PMr8PuVd5ZG1+kBsQbv0Jt0zCLZNwe5D5fci9yiNr84PcgHDrT7hlEm6Z + hNuDzO9D7lUeWZsf5AbSw+3Tw//mXf3CcPf+61D9s/Bd/2m4ex8Yqn8Wvut/DB0m3HiW8sja/CA3kB5u + ZmYnTrjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4 + mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbuCrw+8DEOX6d3P17+xY8/uQe5VH1uYHGQBOML8PuVd5 + ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQ + e5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4w + vw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB + 4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bm + BxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5 + ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQ + e5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4w + vw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB + 4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bG + /iMAnKZ6J3Kf8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y + CABAnvIIAECe8ggAQJ7yCABAmm//wP8HTmEikkRXgigAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAAj0AAAI9CAYAAADRkckBAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1 + MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAACyHSURBVHhe7d1ptGVVee5xKIq+ + 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 + gg== + + + + + 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 + w1EmAAAAAElFTkSuQmCC + + \ No newline at end of file diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs index 1220cadc..77f793fc 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs @@ -36,7 +36,7 @@ this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.panel1.BackColor = System.Drawing.SystemColors.ControlDark; this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(377, 505); diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index c5c76554..f17a148a 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -117,7 +117,7 @@ namespace LibationWinForms.ProcessQueue vScrollBar1.SmallChange = VirtualControlHeight; vScrollBar1.LargeChange = 3 * VirtualControlHeight; - panel1.Height += 2*VirtualControlHeight; + panel1.Height += 2 * VirtualControlHeight; } private ProcessBookControl InitControl(int locationY) From 9d81c86c1b0dd10a1685314b452685d70187befd Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 04:10:54 -0600 Subject: [PATCH 06/33] Increase buffer size --- Source/AaxDecrypter/NetworkFileStream.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs index 83f62ae7..259b3526 100644 --- a/Source/AaxDecrypter/NetworkFileStream.cs +++ b/Source/AaxDecrypter/NetworkFileStream.cs @@ -221,7 +221,6 @@ namespace AaxDecrypter var buff = new byte[DOWNLOAD_BUFF_SZ]; do { - Thread.Sleep(10); var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); _writeFile.Write(buff, 0, bytesRead); From dec1035258b52c6409091960aee03a9d1df4357b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 04:11:44 -0600 Subject: [PATCH 07/33] Minor UI tweak --- .../ProcessQueue/ProcessBookControl.cs | 14 ++++++++++---- .../ProcessQueue/ProcessBookQueue.cs | 4 ++-- .../ProcessQueue/VirtualFlowControl.cs | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index 02764c26..bccef916 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -14,6 +14,7 @@ namespace LibationWinForms.ProcessQueue public int ControlNumber { get; } private ProcessBookStatus Status { get; set; } = ProcessBookStatus.Queued; private readonly int CancelBtnDistanceFromEdge; + private readonly int ProgressBarDistanceFromEdge; public ProcessBookControl() { InitializeComponent(); @@ -23,6 +24,7 @@ namespace LibationWinForms.ProcessQueue etaLbl.Visible = false; CancelBtnDistanceFromEdge = Width - cancelBtn.Location.X; + ProgressBarDistanceFromEdge = Width - progressBar1.Location.X - progressBar1.Width; ControlNumber = ControlNumberCounter++; } @@ -60,7 +62,7 @@ namespace LibationWinForms.ProcessQueue Status = ProcessBookStatus.Queued; break; case ProcessBookResult.FailedSkip: - statusText = "Error, Skip"; + statusText = "Error, Skippping"; Status = ProcessBookStatus.Failed; break; case ProcessBookResult.FailedAbort: @@ -68,7 +70,7 @@ namespace LibationWinForms.ProcessQueue Status = ProcessBookStatus.Failed; break; case ProcessBookResult.ValidationFail: - statusText = "Validate fail"; + statusText = "Validion fail"; Status = ProcessBookStatus.Failed; break; case ProcessBookResult.None: @@ -124,14 +126,18 @@ namespace LibationWinForms.ProcessQueue if (Status is ProcessBookStatus.Queued or ProcessBookStatus.Working && deltaX != 0) { - //If the last book to occupy this control before resizing was not queued, - //the buttons were not Visible so the Anchor property was ignored. + //If the last book to occupy this control before resizing was not + //queued, the buttons were not Visible so the Anchor property was + //ignored. Manually resize and reposition everyhting cancelBtn.Location = new Point(cancelBtn.Location.X + deltaX, cancelBtn.Location.Y); moveFirstBtn.Location = new Point(moveFirstBtn.Location.X + deltaX, moveFirstBtn.Location.Y); moveUpBtn.Location = new Point(moveUpBtn.Location.X + deltaX, moveUpBtn.Location.Y); moveDownBtn.Location = new Point(moveDownBtn.Location.X + deltaX, moveDownBtn.Location.Y); moveLastBtn.Location = new Point(moveLastBtn.Location.X + deltaX, moveLastBtn.Location.Y); + etaLbl.Location = new Point(etaLbl.Location.X + deltaX, etaLbl.Location.Y); + remainingTimeLbl.Location = new Point(remainingTimeLbl.Location.X + deltaX, remainingTimeLbl.Location.Y); + progressBar1.Width = Width - ProgressBarDistanceFromEdge - progressBar1.Location.X; } if (status == ProcessBookStatus.Working) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs index c30014f8..29ac5ce0 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs @@ -174,10 +174,10 @@ namespace LibationWinForms.ProcessQueue public void AddDownloadDecrypt(IEnumerable entries) { foreach (var entry in entries) - AddDownloadDecryptAsync(entry); + AddDownloadDecrypt(entry); } - public void AddDownloadDecryptAsync(GridEntry gridEntry) + public void AddDownloadDecrypt(GridEntry gridEntry) { if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) return; diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index f17a148a..e9054919 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -86,7 +86,7 @@ namespace LibationWinForms.ProcessQueue LargeChange = SCROLL_LARGE_CHANGE, Dock = DockStyle.Right }; - panel1.Width -= vScrollBar1.Width; + panel1.Width -= vScrollBar1.Width + panel1.Margin.Right; Controls.Add(vScrollBar1); From 3c9121b4afec2719f0a33630b4842ecc5243f966 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 10:03:57 -0600 Subject: [PATCH 08/33] Improve scroll visualization --- .../ProcessQueue/VirtualFlowControl.cs | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index e9054919..2e0b34b6 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -38,28 +38,31 @@ namespace LibationWinForms.ProcessQueue private int _virtualControlCount; - //https://stackoverflow.com/a/2882878/3335599 - int ScrollValue => Math.Max(Math.Min(VirtualHeight, vScrollBar1.Value), 0); + int ScrollValue => Math.Max(vScrollBar1.Value, 0); /// - /// The virtual height of all virtual controls within this + /// Virtual height of all virtual controls within this /// - private int VirtualHeight => _virtualControlCount * VirtualControlHeight - vScrollBar1.Height + 2 * TopMargin; - + private int VirtualHeight => (VirtualControlCount + 1 /*Allow extra blank space after last control*/) * VirtualControlHeight - DisplayHeight + 2 * TopMargin; /// - /// Item index of the first virtual + /// Index of the first virtual /// private int FirstVisibleVirtualIndex => ScrollValue / VirtualControlHeight; - /// /// The display height of this /// private int DisplayHeight => DisplayRectangle.Height; - /// /// The total height, inclusing margins, of the repeated /// - private readonly int VirtualControlHeight; //90 + private readonly int VirtualControlHeight; + /// + /// Amount the control moves with a small scroll change + /// + private int SmallChangeValue => VirtualControlHeight * SMALL_SCROLL_CHANGE_MULTIPLE; + /// + /// Margin between the top and the top of the Panle and the bottom and the bottom of the panel + /// private readonly int TopMargin; private const int WM_MOUSEWHEEL = 522; @@ -67,23 +70,22 @@ namespace LibationWinForms.ProcessQueue /// Total number of actual controls added to the panel. 23 is sufficient up to a 4k monitor height. /// private const int NUM_ACTUAL_CONTROLS = 23; - private const int SCROLL_SMALL_CHANGE = 120; - private const int SCROLL_LARGE_CHANGE = 3 * SCROLL_SMALL_CHANGE; + /// + /// Multiple of that is moved for each small scroll change + /// + private const int SMALL_SCROLL_CHANGE_MULTIPLE = 1; private readonly VScrollBar vScrollBar1; private readonly List BookControls = new(); public VirtualFlowControl() { - InitializeComponent(); vScrollBar1 = new VScrollBar { Minimum = 0, Value = 0, - SmallChange = SCROLL_SMALL_CHANGE, - LargeChange = SCROLL_LARGE_CHANGE, Dock = DockStyle.Right }; panel1.Width -= vScrollBar1.Width + panel1.Margin.Right; @@ -99,11 +101,12 @@ namespace LibationWinForms.ProcessQueue vScrollBar1.Scroll += (_, s) => SetScrollPosition(s.NewValue); var control = InitControl(0); - BookControls.Add(control); - panel1.Controls.Add(control); VirtualControlHeight = control.Height + control.Margin.Top + control.Margin.Bottom; TopMargin = control.Margin.Top; + BookControls.Add(control); + panel1.Controls.Add(control); + if (DesignMode) return; @@ -114,8 +117,7 @@ namespace LibationWinForms.ProcessQueue panel1.Controls.Add(control); } - vScrollBar1.SmallChange = VirtualControlHeight; - vScrollBar1.LargeChange = 3 * VirtualControlHeight; + vScrollBar1.SmallChange = SMALL_SCROLL_CHANGE_MULTIPLE * VirtualControlHeight; panel1.Height += 2 * VirtualControlHeight; } @@ -167,13 +169,18 @@ namespace LibationWinForms.ProcessQueue else { vScrollBar1.Enabled = true; + + //Large scroll change is almost one full window height + vScrollBar1.LargeChange = Math.Max(DisplayHeight / VirtualControlHeight - 2, SMALL_SCROLL_CHANGE_MULTIPLE) * VirtualControlHeight; + //https://stackoverflow.com/a/2882878/3335599 vScrollBar1.Maximum = VirtualHeight + vScrollBar1.LargeChange - 1; } } /// - /// Calculated the virtual controls that are in view at the currrent scroll position and windows size, then fires for the visible controls + /// Calculated the virtual controls that are in view at the currrent scroll position and windows size, + /// positions to simulate scroll activity, then fires for the visible controls /// private void DoVirtualScroll() { @@ -182,19 +189,18 @@ namespace LibationWinForms.ProcessQueue int position = ScrollValue % VirtualControlHeight; panel1.Location = new Point(0, -position); - int visibleHeight = ScrollValue - firstVisible * VirtualControlHeight + DisplayHeight; + int numVisible = DisplayHeight / VirtualControlHeight; - int count = visibleHeight / VirtualControlHeight; + if (DisplayHeight % VirtualControlHeight != 0) + numVisible++; - if (visibleHeight % VirtualControlHeight != 0) - count++; + numVisible = Math.Min(numVisible, VirtualControlCount); + numVisible = Math.Min(numVisible, VirtualControlCount - firstVisible); - count = Math.Min(count, VirtualControlCount); - - RequestData?.Invoke(firstVisible, count, BookControls); + RequestData?.Invoke(firstVisible, numVisible, BookControls); for (int i = 0; i < BookControls.Count; i++) - BookControls[i].Visible = i < count; + BookControls[i].Visible = i < numVisible; } /// @@ -202,10 +208,11 @@ namespace LibationWinForms.ProcessQueue /// private void SetScrollPosition(int value) { - int newPos = (int)Math.Round((double)value / VirtualControlHeight) * VirtualControlHeight; + int newPos = (int)Math.Round((double)value / SmallChangeValue) * SmallChangeValue; if (vScrollBar1.Value != newPos) { - vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum); + //https://stackoverflow.com/a/2882878/3335599 + vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum - vScrollBar1.LargeChange +1); DoVirtualScroll(); } } @@ -218,12 +225,16 @@ namespace LibationWinForms.ProcessQueue //https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & ushort.MaxValue); + int numSmallPositionMoves = Math.Abs(wheelDelta) / 120; + + int scrollDelta = Math.Sign(wheelDelta) * numSmallPositionMoves * SmallChangeValue; + int newScrollPosition; - if (wheelDelta > 0) - newScrollPosition = Math.Min(vScrollBar1.Value + wheelDelta, vScrollBar1.Maximum); + if (scrollDelta > 0) + newScrollPosition = Math.Min(vScrollBar1.Value + scrollDelta, vScrollBar1.Maximum); else - newScrollPosition = Math.Max(vScrollBar1.Value + wheelDelta, vScrollBar1.Minimum); + newScrollPosition = Math.Max(vScrollBar1.Value + scrollDelta, vScrollBar1.Minimum); SetScrollPosition(newScrollPosition); } From 9692a802d036910a24a0c433dbd2091948022f1f Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 11:11:20 -0600 Subject: [PATCH 09/33] Update documentation and add parameters --- .../ProcessQueue/TrackedQueue[T].cs | 12 ++- .../ProcessQueue/VirtualFlowControl.cs | 74 ++++++++++++------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs b/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs index d045f818..a1f7dbe2 100644 --- a/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs +++ b/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs @@ -14,10 +14,14 @@ namespace LibationWinForms.ProcessQueue /* * This data structure is like lifting a metal chain one link at a time. - * Each time you grab and lift a new link, the remaining chain shortens - * by 1 link and the pile of chain at yuor feet grows by one link. The - * index is the link position from the first link you lifted to the last - * one in the chain. + * Each time you grab and lift a new link (MoveNext call): + * + * 1) you're holding a new link in your hand (Current) + * 2) the remaining chain to be lifted shortens by 1 link (Queued) + * 3) the pile of chain at your feet grows by 1 link (Completed) + * + * The index is the link position from the first link you lifted to the + * last one in the chain. */ public class TrackedQueue where T : class { diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index 2e0b34b6..b5e689b8 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -6,8 +6,8 @@ using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { - internal delegate void RequestDataDelegate(int firstIndex, int numVisible, IReadOnlyList panelsToFill); - internal delegate void ControlButtonClickedDelegate(int itemIndex, string buttonName, ProcessBookControl panelClicked); + internal delegate void RequestDataDelegate(int queueIndex, int numVisible, IReadOnlyList panelsToFill); + internal delegate void ControlButtonClickedDelegate(int queueIndex, string buttonName, ProcessBookControl panelClicked); internal partial class VirtualFlowControl : UserControl { /// @@ -19,6 +19,8 @@ namespace LibationWinForms.ProcessQueue /// public event ControlButtonClickedDelegate ButtonClicked; + #region Dynamic Properties + /// /// The number of virtual s in the /// @@ -39,11 +41,18 @@ namespace LibationWinForms.ProcessQueue private int _virtualControlCount; int ScrollValue => Math.Max(vScrollBar1.Value, 0); - + /// + /// Amount the control moves with a small scroll change + /// + private int SmallScrollChange => VirtualControlHeight * SMALL_SCROLL_CHANGE_MULTIPLE; + /// + /// Amount the control moves with a large scroll change. Equal to the number of whole s in the panel, less 1. + /// + private int LargeScrollChange => Math.Max(DisplayHeight / VirtualControlHeight - 1, SMALL_SCROLL_CHANGE_MULTIPLE) * VirtualControlHeight; /// /// Virtual height of all virtual controls within this /// - private int VirtualHeight => (VirtualControlCount + 1 /*Allow extra blank space after last control*/) * VirtualControlHeight - DisplayHeight + 2 * TopMargin; + private int VirtualHeight => (VirtualControlCount + NUM_BLANK_SPACES_AT_BOTTOM) * VirtualControlHeight - DisplayHeight + 2 * TopMargin; /// /// Index of the first virtual /// @@ -52,20 +61,27 @@ namespace LibationWinForms.ProcessQueue /// The display height of this /// private int DisplayHeight => DisplayRectangle.Height; + + #endregion + + #region Instance variables + /// /// The total height, inclusing margins, of the repeated /// private readonly int VirtualControlHeight; /// - /// Amount the control moves with a small scroll change - /// - private int SmallChangeValue => VirtualControlHeight * SMALL_SCROLL_CHANGE_MULTIPLE; - /// - /// Margin between the top and the top of the Panle and the bottom and the bottom of the panel + /// Margin between the top and the top of the Panel, and the bottom and the bottom of the panel /// private readonly int TopMargin; - private const int WM_MOUSEWHEEL = 522; + private readonly VScrollBar vScrollBar1; + private readonly List BookControls = new(); + + #endregion + + #region Global behavior settings + /// /// Total number of actual controls added to the panel. 23 is sufficient up to a 4k monitor height. /// @@ -74,9 +90,12 @@ namespace LibationWinForms.ProcessQueue /// Multiple of that is moved for each small scroll change /// private const int SMALL_SCROLL_CHANGE_MULTIPLE = 1; + /// + /// Amount of space at the bottom of the , in multiples of + /// + private const int NUM_BLANK_SPACES_AT_BOTTOM = 2; - private readonly VScrollBar vScrollBar1; - private readonly List BookControls = new(); + #endregion public VirtualFlowControl() { @@ -88,17 +107,16 @@ namespace LibationWinForms.ProcessQueue Value = 0, Dock = DockStyle.Right }; - panel1.Width -= vScrollBar1.Width + panel1.Margin.Right; - Controls.Add(vScrollBar1); + vScrollBar1.Scroll += (_, s) => SetScrollPosition(s.NewValue); + panel1.Width -= vScrollBar1.Width + panel1.Margin.Right; panel1.Resize += (_, _) => { AdjustScrollBar(); DoVirtualScroll(); }; - vScrollBar1.Scroll += (_, s) => SetScrollPosition(s.NewValue); var control = InitControl(0); VirtualControlHeight = control.Height + control.Margin.Top + control.Margin.Bottom; @@ -117,9 +135,8 @@ namespace LibationWinForms.ProcessQueue panel1.Controls.Add(control); } - vScrollBar1.SmallChange = SMALL_SCROLL_CHANGE_MULTIPLE * VirtualControlHeight; - - panel1.Height += 2 * VirtualControlHeight; + vScrollBar1.SmallChange = SmallScrollChange; + panel1.Height += NUM_BLANK_SPACES_AT_BOTTOM * VirtualControlHeight; } private ProcessBookControl InitControl(int locationY) @@ -138,7 +155,7 @@ namespace LibationWinForms.ProcessQueue } /// - /// Handles all button clicks from all , detects which one sent the click, and fires + /// Handles all button clicks from all , detects which one sent the click, and fires to notify the model of the click /// private void ControlButton_Click(object sender, EventArgs e) { @@ -169,9 +186,7 @@ namespace LibationWinForms.ProcessQueue else { vScrollBar1.Enabled = true; - - //Large scroll change is almost one full window height - vScrollBar1.LargeChange = Math.Max(DisplayHeight / VirtualControlHeight - 2, SMALL_SCROLL_CHANGE_MULTIPLE) * VirtualControlHeight; + vScrollBar1.LargeChange = LargeScrollChange; //https://stackoverflow.com/a/2882878/3335599 vScrollBar1.Maximum = VirtualHeight + vScrollBar1.LargeChange - 1; @@ -180,7 +195,7 @@ namespace LibationWinForms.ProcessQueue /// /// Calculated the virtual controls that are in view at the currrent scroll position and windows size, - /// positions to simulate scroll activity, then fires for the visible controls + /// positions to simulate scroll activity, then fires to notify the model to update all visible controls /// private void DoVirtualScroll() { @@ -204,19 +219,22 @@ namespace LibationWinForms.ProcessQueue } /// - /// Set scroll value to an integral multiple of VirtualControlHeight + /// Set scroll value to an integral multiple of /// private void SetScrollPosition(int value) { - int newPos = (int)Math.Round((double)value / SmallChangeValue) * SmallChangeValue; + int newPos = (int)Math.Round((double)value / SmallScrollChange) * SmallScrollChange; if (vScrollBar1.Value != newPos) { //https://stackoverflow.com/a/2882878/3335599 - vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum - vScrollBar1.LargeChange +1); + vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum - vScrollBar1.LargeChange + 1); DoVirtualScroll(); } } + + private const int WM_MOUSEWHEEL = 522; + private const int WHEEL_DELTA = 120; protected override void WndProc(ref Message m) { //Capture mouse wheel movement and interpret it as a scroll event @@ -225,9 +243,9 @@ namespace LibationWinForms.ProcessQueue //https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & ushort.MaxValue); - int numSmallPositionMoves = Math.Abs(wheelDelta) / 120; + int numSmallPositionMoves = Math.Abs(wheelDelta) / WHEEL_DELTA; - int scrollDelta = Math.Sign(wheelDelta) * numSmallPositionMoves * SmallChangeValue; + int scrollDelta = Math.Sign(wheelDelta) * numSmallPositionMoves * SmallScrollChange; int newScrollPosition; From a8bca3de98a266622209e51c058df2e0a9e4ba06 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 11:11:51 -0600 Subject: [PATCH 10/33] Fix progressbar wiggling --- .../ProcessQueue/ProcessBookControl.Designer.cs | 1 + Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs index 38cc2b67..8a3db3dd 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs @@ -57,6 +57,7 @@ this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.progressBar1.Location = new System.Drawing.Point(88, 65); + this.progressBar1.MarqueeAnimationSpeed = 0; this.progressBar1.Name = "progressBar1"; this.progressBar1.Size = new System.Drawing.Size(212, 17); this.progressBar1.TabIndex = 2; diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index bccef916..093f0070 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -38,6 +38,10 @@ namespace LibationWinForms.ProcessQueue } public void SetProgrss(int progress) { + //Disabvle slow fill + //https://stackoverflow.com/a/5332770/3335599 + if (progress < progressBar1.Maximum) + progressBar1.Value = progress + 1; progressBar1.Value = progress; } public void SetRemainingTime(TimeSpan remaining) From 7f08da96bb10ca1196e713c6e88efd9fce892f3a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 11:20:19 -0600 Subject: [PATCH 11/33] Documentation and organization --- ...ner.cs => ProcessQueueControl.Designer.cs} | 30 +- ...essBookQueue.cs => ProcessQueueControl.cs} | 291 +++++++++++------- ...ookQueue.resx => ProcessQueueControl.resx} | 3 + 3 files changed, 213 insertions(+), 111 deletions(-) rename Source/LibationWinForms/ProcessQueue/{ProcessBookQueue.Designer.cs => ProcessQueueControl.Designer.cs} (91%) rename Source/LibationWinForms/ProcessQueue/{ProcessBookQueue.cs => ProcessQueueControl.cs} (67%) rename Source/LibationWinForms/ProcessQueue/{ProcessBookQueue.resx => ProcessQueueControl.resx} (99%) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs similarity index 91% rename from Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs rename to Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs index aed3aa67..0b5b003b 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs @@ -1,6 +1,6 @@ namespace LibationWinForms.ProcessQueue { - partial class ProcessBookQueue + partial class ProcessQueueControl { /// /// Required designer variable. @@ -28,13 +28,15 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessBookQueue)); + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessQueueControl)); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); this.queueNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.completedNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.errorNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.runningTimeLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); this.panel3 = new System.Windows.Forms.Panel(); @@ -47,6 +49,7 @@ this.panel2 = new System.Windows.Forms.Panel(); this.clearLogBtn = new System.Windows.Forms.Button(); this.logMeTbox = new System.Windows.Forms.TextBox(); + this.counterTimer = new System.Windows.Forms.Timer(this.components); this.statusStrip1.SuspendLayout(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); @@ -63,12 +66,13 @@ this.queueNumberLbl, this.completedNumberLbl, this.errorNumberLbl, - this.toolStripStatusLabel1}); + this.toolStripStatusLabel1, + this.runningTimeLbl}); this.statusStrip1.Location = new System.Drawing.Point(0, 483); this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Size = new System.Drawing.Size(404, 25); this.statusStrip1.TabIndex = 1; - this.statusStrip1.Text = "statusStrip1"; + this.statusStrip1.Text = "baseStatusStrip"; // // toolStripProgressBar1 // @@ -99,9 +103,16 @@ // toolStripStatusLabel1 // this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; - this.toolStripStatusLabel1.Size = new System.Drawing.Size(118, 20); + this.toolStripStatusLabel1.Size = new System.Drawing.Size(77, 20); this.toolStripStatusLabel1.Spring = true; // + // runningTimeLbl + // + this.runningTimeLbl.AutoSize = false; + this.runningTimeLbl.Name = "runningTimeLbl"; + this.runningTimeLbl.Size = new System.Drawing.Size(41, 20); + this.runningTimeLbl.Text = "[TIME]"; + // // tabControl1 // this.tabControl1.Controls.Add(this.tabPage1); @@ -167,7 +178,7 @@ this.btnCleanFinished.TabIndex = 3; this.btnCleanFinished.Text = "Clear Finished"; this.btnCleanFinished.UseVisualStyleBackColor = true; - this.btnCleanFinished.Click += new System.EventHandler(this.btnCleanFinished_Click); + this.btnCleanFinished.Click += new System.EventHandler(this.btnClearFinished_Click); // // cancelAllBtn // @@ -236,6 +247,11 @@ this.logMeTbox.Size = new System.Drawing.Size(390, 449); this.logMeTbox.TabIndex = 0; // + // counterTimer + // + this.counterTimer.Interval = 950; + this.counterTimer.Tick += new System.EventHandler(this.CounterTimer_Tick); + // // ProcessBookQueue // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -277,5 +293,7 @@ private System.Windows.Forms.ToolStripStatusLabel errorNumberLbl; private System.Windows.Forms.Panel panel3; private System.Windows.Forms.Panel panel4; + private System.Windows.Forms.ToolStripStatusLabel runningTimeLbl; + private System.Windows.Forms.Timer counterTimer; } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs similarity index 67% rename from Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs rename to Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 29ac5ce0..cfa89137 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -8,7 +8,7 @@ using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { - internal partial class ProcessBookQueue : UserControl, ILogForm + internal partial class ProcessQueueControl : UserControl, ILogForm { private TrackedQueue Queue = new(); private readonly LogMe Logger; @@ -45,15 +45,12 @@ namespace LibationWinForms.ProcessQueue public ToolStripButton popoutBtn = new(); - private int FirstVisible = 0; - private int NumVisible = 0; - private IReadOnlyList Panels; - - public ProcessBookQueue() + public ProcessQueueControl() { InitializeComponent(); Logger = LogMe.RegisterForm(this); + runningTimeLbl.Text = string.Empty; popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; popoutBtn.Name = "popoutBtn"; popoutBtn.Text = "Pop Out"; @@ -74,6 +71,85 @@ namespace LibationWinForms.ProcessQueue CompletedCount = 0; } + public void AddDownloadDecrypt(IEnumerable entries) + { + foreach (var entry in entries) + AddDownloadDecrypt(entry); + } + + public void AddConvertMp3(IEnumerable entries) + { + foreach (var entry in entries) + AddConvertMp3(entry); + } + + public void AddDownloadDecrypt(GridEntry gridEntry) + { + if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) + return; + + ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger); + pbook.DataAvailable += Pbook_DataAvailable; + + pbook.AddDownloadDecryptBook(); + pbook.AddDownloadPdf(); + + Queue.Enqueue(pbook); + + if (!Running) + { + QueueRunner = QueueLoop(); + } + } + + public void AddConvertMp3(GridEntry gridEntry) + { + if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) + return; + + ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger); + pbook.DataAvailable += Pbook_DataAvailable; + + pbook.AddConvertToMp3(); + + Queue.Enqueue(pbook); + + if (!Running) + { + QueueRunner = QueueLoop(); + } + } + + DateTime StartintTime; + private async Task QueueLoop() + { + StartintTime = DateTime.Now; + counterTimer.Start(); + + while (Queue.MoveNext()) + { + var nextBook = Queue.Current; + + var result = await nextBook.ProcessOneAsync(); + + if (result == ProcessBookResult.FailedRetry) + Queue.Enqueue(nextBook); + else if (result == ProcessBookResult.FailedAbort) + return; + } + + Queue_CompletedCountChanged(this, 0); + counterTimer.Stop(); + } + + public void WriteLine(string text) + { + if (!IsDisposed) + logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); + } + + #region Control event handlers + private void Queue_CompletedCountChanged(object sender, int e) { int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.ValidationFail); @@ -94,45 +170,70 @@ namespace LibationWinForms.ProcessQueue toolStripProgressBar1.Maximum = Queue.Count; toolStripProgressBar1.Value = Queue.Completed.Count; } - private void VirtualFlowControl2_ButtonClicked(int itemIndex, string buttonName, ProcessBookControl panelClicked) + + private void cancelAllBtn_Click(object sender, EventArgs e) { - ProcessBook item = Queue[itemIndex]; - if (buttonName == "cancelBtn") - { - item.Cancel(); - Queue.RemoveQueued(item); - virtualFlowControl2.VirtualControlCount = Queue.Count; - UpdateControl(itemIndex); - } - else if (buttonName == "moveFirstBtn") - { - Queue.MoveQueuePosition(item, QueuePosition.Fisrt); - UpdateAllControls(); - } - else if (buttonName == "moveUpBtn") - { - Queue.MoveQueuePosition(item, QueuePosition.OneUp); - UpdateControl(itemIndex - 1); - UpdateControl(itemIndex); - } - else if (buttonName == "moveDownBtn") - { - Queue.MoveQueuePosition(item, QueuePosition.OneDown); - UpdateControl(itemIndex + 1); - UpdateControl(itemIndex); - } - else if (buttonName == "moveLastBtn") - { - Queue.MoveQueuePosition(item, QueuePosition.Last); - UpdateAllControls(); - } + Queue.ClearQueue(); + Queue.Current?.Cancel(); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateAllControls(); } + private void btnClearFinished_Click(object sender, EventArgs e) + { + Queue.ClearCompleted(); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateAllControls(); + + if (!Running) + runningTimeLbl.Text = string.Empty; + } + + private void CounterTimer_Tick(object sender, EventArgs e) + { + string timeToStr(TimeSpan time) + { + string minsSecs = $"{time:mm\\:ss}"; + if (time.TotalHours >= 1) + return $"{time.TotalHours:F0}:{minsSecs}"; + return minsSecs; + } + + if (Running) + runningTimeLbl.Text = timeToStr(DateTime.Now - StartintTime); + } + + private void clearLogBtn_Click(object sender, EventArgs e) + { + logMeTbox.Clear(); + } + + #endregion + + #region View-Model update event handling + + /// + /// Index of the first visible in the + /// + private int FirstVisible = 0; + /// + /// Number of visible in the + /// + private int NumVisible = 0; + /// + /// Controls displaying the state, starting with + /// + private IReadOnlyList Panels; + + /// + /// Updates the display of a single at within + /// + /// index of the within the private void UpdateControl(int queueIndex) { int i = queueIndex - FirstVisible; - if (i < 0 || i > NumVisible) return; + if (i > NumVisible || i < 0) return; var proc = Queue[queueIndex]; @@ -163,6 +264,49 @@ namespace LibationWinForms.ProcessQueue UpdateControl(FirstVisible + i); } + + /// + /// View notified the model that a botton was clicked + /// + /// index of the within + /// The clicked control to update + private void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked) + { + ProcessBook item = Queue[queueIndex]; + if (buttonName == "cancelBtn") + { + item.Cancel(); + Queue.RemoveQueued(item); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateControl(queueIndex); + } + else if (buttonName == "moveFirstBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.Fisrt); + UpdateAllControls(); + } + else if (buttonName == "moveUpBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.OneUp); + UpdateControl(queueIndex - 1); + UpdateControl(queueIndex); + } + else if (buttonName == "moveDownBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.OneDown); + UpdateControl(queueIndex + 1); + UpdateControl(queueIndex); + } + else if (buttonName == "moveLastBtn") + { + Queue.MoveQueuePosition(item, QueuePosition.Last); + UpdateAllControls(); + } + } + + /// + /// View needs updating + /// private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList panelsToFill) { FirstVisible = firstIndex; @@ -171,78 +315,15 @@ namespace LibationWinForms.ProcessQueue UpdateAllControls(); } - public void AddDownloadDecrypt(IEnumerable entries) - { - foreach (var entry in entries) - AddDownloadDecrypt(entry); - } - - public void AddDownloadDecrypt(GridEntry gridEntry) - { - if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) - return; - - ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger); - pbook.DataAvailable += Pbook_DataAvailable; - - pbook.AddDownloadDecryptBook(); - pbook.AddDownloadPdf(); - - Queue.Enqueue(pbook); - - if (!Running) - { - QueueRunner = QueueLoop(); - } - } - + /// + /// Model updates the view + /// private void Pbook_DataAvailable(object sender, EventArgs e) { int index = Queue.IndexOf((ProcessBook)sender); UpdateControl(index); } - private async Task QueueLoop() - { - while (Queue.MoveNext()) - { - var nextBook = Queue.Current; - - var result = await nextBook.ProcessOneAsync(); - - if (result == ProcessBookResult.FailedRetry) - Queue.Enqueue(nextBook); - else if (result == ProcessBookResult.FailedAbort) - return; - } - - Queue_CompletedCountChanged(this, 0); - } - - private void cancelAllBtn_Click(object sender, EventArgs e) - { - Queue.ClearQueue(); - Queue.Current?.Cancel(); - virtualFlowControl2.VirtualControlCount = Queue.Count; - UpdateAllControls(); - } - - private void btnCleanFinished_Click(object sender, EventArgs e) - { - Queue.ClearCompleted(); - virtualFlowControl2.VirtualControlCount = Queue.Count; - UpdateAllControls(); - } - - private void clearLogBtn_Click(object sender, EventArgs e) - { - logMeTbox.Clear(); - } - - public void WriteLine(string text) - { - if (!IsDisposed) - logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); - } + #endregion } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx similarity index 99% rename from Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx rename to Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx index 445745d5..15c031bb 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookQueue.resx +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx @@ -630,4 +630,7 @@ w1EmAAAAAElFTkSuQmCC + + 133, 17 + \ No newline at end of file From a7b7e3efea2303c1bf070176941bc26c80877252 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 13:52:10 -0600 Subject: [PATCH 12/33] Converted to INotifyPropertyChanged for more targeted view update --- .../ProcessQueue/ProcessBook.cs | 95 ++++++++++++------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index ffc1246d..d0f158e4 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -5,8 +5,10 @@ using LibationFileManager; using LibationWinForms.BookLiberation; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Drawing; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows.Forms; @@ -32,35 +34,66 @@ namespace LibationWinForms.ProcessQueue Failed } - public class ProcessBook + /// + /// This is the viewmodel for queued processables + /// + public class ProcessBook : INotifyPropertyChanged { public event EventHandler Completed; - public event EventHandler DataAvailable; + public event PropertyChangedEventHandler PropertyChanged; - public Processable CurrentProcessable => _currentProcessable ??= Processes.Dequeue().Invoke(); - public ProcessBookResult Result { get; private set; } = ProcessBookResult.None; - public ProcessBookStatus Status { get; private set; } = ProcessBookStatus.Queued; - public string BookText { get; private set; } - public Image Cover { get; private set; } - public int Progress { get; private set; } - public TimeSpan TimeRemaining { get; private set; } - public LibraryBook LibraryBook { get; } + private ProcessBookResult _result = ProcessBookResult.None; + private ProcessBookStatus _status = ProcessBookStatus.Queued; + private string _bookText; + private int _progress; + private TimeSpan _timeRemaining; + private LibraryBook _libraryBook; + private Image _cover; + public ProcessBookResult Result { get => _result; private set { _result = value; NotifyPropertyChanged(); } } + public ProcessBookStatus Status { get => _status; private set { _status = value; NotifyPropertyChanged(); } } + public string BookText { get => _bookText; private set { _bookText = value; NotifyPropertyChanged(); } } + public int Progress { get => _progress; private set { _progress = value; NotifyPropertyChanged(); } } + public TimeSpan TimeRemaining { get => _timeRemaining; private set { _timeRemaining = value; NotifyPropertyChanged(); } } + public LibraryBook LibraryBook { get => _libraryBook; private set { _libraryBook = value; NotifyPropertyChanged(); } } + public Image Cover { get => _cover; private set { _cover = value; NotifyPropertyChanged(); } } + + + private Processable CurrentProcessable => _currentProcessable ??= Processes.Dequeue().Invoke(); + private Processable NextProcessable() => _currentProcessable = null; private Processable _currentProcessable; private Func GetCoverArtDelegate; private readonly Queue> Processes = new(); private readonly LogMe Logger; - public ProcessBook(LibraryBook libraryBook, Image coverImage, LogMe logme) + public void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + public ProcessBook(LibraryBook libraryBook, LogMe logme) { LibraryBook = libraryBook; - Cover = coverImage; Logger = logme; title = LibraryBook.Book.Title; authorNames = LibraryBook.Book.AuthorNames(); narratorNames = LibraryBook.Book.NarratorNames(); - BookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"; + _bookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"; + + (bool isDefault, byte[] picture) = PictureStorage.GetPicture(new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._80x80)); + + if (isDefault) + PictureStorage.PictureCached += PictureStorage_PictureCached; + _cover = Dinah.Core.Drawing.ImageReader.ToImage(picture); + + } + + private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e) + { + if (e.Definition.PictureId == LibraryBook.Book.PictureId) + { + Cover = Dinah.Core.Drawing.ImageReader.ToImage(e.Picture); + PictureStorage.PictureCached -= PictureStorage_PictureCached; + } } public async Task ProcessOneAsync() @@ -103,8 +136,6 @@ namespace LibationWinForms.ProcessQueue ProcessBookResult.FailedRetry => ProcessBookStatus.Queued, _ => ProcessBookStatus.Failed, }; - - DataAvailable?.Invoke(this, EventArgs.Empty); } return Result; @@ -118,7 +149,7 @@ namespace LibationWinForms.ProcessQueue { //There's some threadding bug that causes this to hang if executed synchronously. Task.Run(audioDecodable.Cancel); - DataAvailable?.Invoke(this, EventArgs.Empty); + } } catch (Exception ex) @@ -135,18 +166,19 @@ namespace LibationWinForms.ProcessQueue { Processes.Enqueue(() => new T()); } + public override string ToString() => LibraryBook.ToString(); #region Subscribers and Unsubscribers - private void LinkProcessable(Processable strProc) + private void LinkProcessable(Processable processable) { - strProc.Begin += Processable_Begin; - strProc.Completed += Processable_Completed; - strProc.StreamingProgressChanged += Streamable_StreamingProgressChanged; - strProc.StreamingTimeRemaining += Streamable_StreamingTimeRemaining; + processable.Begin += Processable_Begin; + processable.Completed += Processable_Completed; + processable.StreamingProgressChanged += Streamable_StreamingProgressChanged; + processable.StreamingTimeRemaining += Streamable_StreamingTimeRemaining; - if (strProc is AudioDecodable audioDecodable) + if (processable is AudioDecodable audioDecodable) { audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt; audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered; @@ -156,14 +188,14 @@ namespace LibationWinForms.ProcessQueue } } - private void UnlinkProcessable(Processable strProc) + private void UnlinkProcessable(Processable processable) { - strProc.Begin -= Processable_Begin; - strProc.Completed -= Processable_Completed; - strProc.StreamingProgressChanged -= Streamable_StreamingProgressChanged; - strProc.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining; + processable.Begin -= Processable_Begin; + processable.Completed -= Processable_Completed; + processable.StreamingProgressChanged -= Streamable_StreamingProgressChanged; + processable.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining; - if (strProc is AudioDecodable audioDecodable) + if (processable is AudioDecodable audioDecodable) { audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt; audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered; @@ -201,7 +233,6 @@ namespace LibationWinForms.ProcessQueue private void updateBookInfo() { BookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"; - DataAvailable?.Invoke(this, EventArgs.Empty); } public void AudioDecodable_RequestCoverArt(object sender, Action setCoverArtDelegate) @@ -214,7 +245,6 @@ namespace LibationWinForms.ProcessQueue private void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt) { Cover = Dinah.Core.Drawing.ImageReader.ToImage(coverArt); - DataAvailable?.Invoke(this, EventArgs.Empty); } #endregion @@ -223,7 +253,6 @@ namespace LibationWinForms.ProcessQueue private void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining) { TimeRemaining = timeRemaining; - DataAvailable?.Invoke(this, EventArgs.Empty); } private void Streamable_StreamingProgressChanged(object sender, Dinah.Core.Net.Http.DownloadProgress downloadProgress) @@ -235,8 +264,6 @@ namespace LibationWinForms.ProcessQueue TimeRemaining = TimeSpan.Zero; else Progress = (int)downloadProgress.ProgressPercentage; - - DataAvailable?.Invoke(this, EventArgs.Empty); } #endregion @@ -273,7 +300,7 @@ namespace LibationWinForms.ProcessQueue if (Processes.Count > 0) { - _currentProcessable = null; + NextProcessable(); LinkProcessable(CurrentProcessable); var result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true); From 50c35ed5192d6fa20b3a324ac0699a8780c2c9eb Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 13:52:54 -0600 Subject: [PATCH 13/33] Change log to gridview and new INotifyPropertyChanged event --- .../ProcessQueue/ProcessBookForm.Designer.cs | 2 +- .../ProcessQueueControl.Designer.cs | 84 ++++++++++++----- .../ProcessQueue/ProcessQueueControl.cs | 89 +++++++++++-------- .../ProcessQueue/ProcessQueueControl.resx | 6 ++ 4 files changed, 121 insertions(+), 60 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookForm.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookForm.Designer.cs index 036022fb..548f06d2 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookForm.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookForm.Designer.cs @@ -37,7 +37,7 @@ this.ClientSize = new System.Drawing.Size(522, 638); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.Name = "ProcessBookForm"; - this.Text = "ProcessBookForm"; + this.Text = "Book Processing Queue"; this.ResumeLayout(false); } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs index 0b5b003b..3e99394f 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs @@ -30,6 +30,7 @@ { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessQueueControl)); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); this.queueNumberLbl = new System.Windows.Forms.ToolStripStatusLabel(); @@ -45,16 +46,20 @@ this.btnCleanFinished = new System.Windows.Forms.Button(); this.cancelAllBtn = new System.Windows.Forms.Button(); this.tabPage2 = new System.Windows.Forms.TabPage(); + this.logDGV = new System.Windows.Forms.DataGridView(); + this.timestampColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.logEntryColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.panel4 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel(); this.clearLogBtn = new System.Windows.Forms.Button(); - this.logMeTbox = new System.Windows.Forms.TextBox(); this.counterTimer = new System.Windows.Forms.Timer(this.components); + this.logCopyBtn = new System.Windows.Forms.Button(); this.statusStrip1.SuspendLayout(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); this.panel1.SuspendLayout(); this.tabPage2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.logDGV)).BeginInit(); this.panel2.SuspendLayout(); this.SuspendLayout(); // @@ -193,9 +198,9 @@ // // tabPage2 // + this.tabPage2.Controls.Add(this.logDGV); this.tabPage2.Controls.Add(this.panel4); this.tabPage2.Controls.Add(this.panel2); - this.tabPage2.Controls.Add(this.logMeTbox); this.tabPage2.Location = new System.Drawing.Point(4, 24); this.tabPage2.Name = "tabPage2"; this.tabPage2.Padding = new System.Windows.Forms.Padding(3); @@ -204,6 +209,41 @@ this.tabPage2.Text = "Log"; this.tabPage2.UseVisualStyleBackColor = true; // + // logDGV + // + this.logDGV.AllowUserToAddRows = false; + this.logDGV.AllowUserToDeleteRows = false; + this.logDGV.AllowUserToOrderColumns = true; + this.logDGV.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells; + this.logDGV.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.logDGV.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.timestampColumn, + this.logEntryColumn}); + this.logDGV.Dock = System.Windows.Forms.DockStyle.Fill; + this.logDGV.Location = new System.Drawing.Point(3, 3); + this.logDGV.Name = "logDGV"; + this.logDGV.RowHeadersVisible = false; + this.logDGV.RowTemplate.Height = 40; + this.logDGV.Size = new System.Drawing.Size(390, 419); + this.logDGV.TabIndex = 3; + this.logDGV.Resize += new System.EventHandler(this.LogDGV_Resize); + // + // timestampColumn + // + this.timestampColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells; + this.timestampColumn.HeaderText = "Timestamp"; + this.timestampColumn.Name = "timestampColumn"; + this.timestampColumn.ReadOnly = true; + this.timestampColumn.Width = 91; + // + // logEntryColumn + // + dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.logEntryColumn.DefaultCellStyle = dataGridViewCellStyle1; + this.logEntryColumn.HeaderText = "Log"; + this.logEntryColumn.Name = "logEntryColumn"; + this.logEntryColumn.ReadOnly = true; + // // panel4 // this.panel4.Dock = System.Windows.Forms.DockStyle.Bottom; @@ -216,6 +256,7 @@ // this.panel2.BackColor = System.Drawing.SystemColors.Control; this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.panel2.Controls.Add(this.logCopyBtn); this.panel2.Controls.Add(this.clearLogBtn); this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom; this.panel2.Location = new System.Drawing.Point(3, 427); @@ -228,38 +269,36 @@ this.clearLogBtn.Dock = System.Windows.Forms.DockStyle.Left; this.clearLogBtn.Location = new System.Drawing.Point(0, 0); this.clearLogBtn.Name = "clearLogBtn"; - this.clearLogBtn.Size = new System.Drawing.Size(75, 23); + this.clearLogBtn.Size = new System.Drawing.Size(60, 23); this.clearLogBtn.TabIndex = 0; - this.clearLogBtn.Text = "Clear Log"; + this.clearLogBtn.Text = "Clear"; this.clearLogBtn.UseVisualStyleBackColor = true; this.clearLogBtn.Click += new System.EventHandler(this.clearLogBtn_Click); // - // logMeTbox - // - this.logMeTbox.Dock = System.Windows.Forms.DockStyle.Fill; - this.logMeTbox.Location = new System.Drawing.Point(3, 3); - this.logMeTbox.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0); - this.logMeTbox.MaxLength = 10000000; - this.logMeTbox.Multiline = true; - this.logMeTbox.Name = "logMeTbox"; - this.logMeTbox.ReadOnly = true; - this.logMeTbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.logMeTbox.Size = new System.Drawing.Size(390, 449); - this.logMeTbox.TabIndex = 0; - // // counterTimer // this.counterTimer.Interval = 950; this.counterTimer.Tick += new System.EventHandler(this.CounterTimer_Tick); // - // ProcessBookQueue + // logCopyBtn + // + this.logCopyBtn.Dock = System.Windows.Forms.DockStyle.Right; + this.logCopyBtn.Location = new System.Drawing.Point(331, 0); + this.logCopyBtn.Name = "logCopyBtn"; + this.logCopyBtn.Size = new System.Drawing.Size(57, 23); + this.logCopyBtn.TabIndex = 1; + this.logCopyBtn.Text = "Copy"; + this.logCopyBtn.UseVisualStyleBackColor = true; + this.logCopyBtn.Click += new System.EventHandler(this.LogCopyBtn_Click); + // + // ProcessQueueControl // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.Controls.Add(this.tabControl1); this.Controls.Add(this.statusStrip1); - this.Name = "ProcessBookQueue"; + this.Name = "ProcessQueueControl"; this.Size = new System.Drawing.Size(404, 508); this.statusStrip1.ResumeLayout(false); this.statusStrip1.PerformLayout(); @@ -267,7 +306,7 @@ this.tabPage1.ResumeLayout(false); this.panel1.ResumeLayout(false); this.tabPage2.ResumeLayout(false); - this.tabPage2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.logDGV)).EndInit(); this.panel2.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -281,7 +320,6 @@ private System.Windows.Forms.TabPage tabPage1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.TabPage tabPage2; - private System.Windows.Forms.TextBox logMeTbox; private System.Windows.Forms.Button btnCleanFinished; private System.Windows.Forms.Button cancelAllBtn; private System.Windows.Forms.Panel panel2; @@ -295,5 +333,9 @@ private System.Windows.Forms.Panel panel4; private System.Windows.Forms.ToolStripStatusLabel runningTimeLbl; private System.Windows.Forms.Timer counterTimer; + private System.Windows.Forms.DataGridView logDGV; + private System.Windows.Forms.DataGridViewTextBoxColumn timestampColumn; + private System.Windows.Forms.DataGridViewTextBoxColumn logEntryColumn; + private System.Windows.Forms.Button logCopyBtn; } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index cfa89137..2df35ef8 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -1,7 +1,7 @@ -using Dinah.Core.Threading; -using LibationWinForms.BookLiberation; +using LibationWinForms.BookLiberation; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -42,7 +42,6 @@ namespace LibationWinForms.ProcessQueue public Task QueueRunner { get; private set; } public bool Running => !QueueRunner?.IsCompleted ?? false; - public ToolStripButton popoutBtn = new(); public ProcessQueueControl() @@ -71,47 +70,43 @@ namespace LibationWinForms.ProcessQueue CompletedCount = 0; } - public void AddDownloadDecrypt(IEnumerable entries) + public void AddDownloadDecrypt(IEnumerable entries) { foreach (var entry in entries) AddDownloadDecrypt(entry); } - public void AddConvertMp3(IEnumerable entries) + public void AddConvertMp3(IEnumerable entries) { foreach (var entry in entries) AddConvertMp3(entry); } - public void AddDownloadDecrypt(GridEntry gridEntry) + public void AddDownloadDecrypt(DataLayer.LibraryBook libraryBook) { - if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) + if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId)) return; - ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger); - pbook.DataAvailable += Pbook_DataAvailable; - + ProcessBook pbook = new(libraryBook, Logger); + pbook.PropertyChanged += Pbook_DataAvailable; pbook.AddDownloadDecryptBook(); pbook.AddDownloadPdf(); - - Queue.Enqueue(pbook); - - if (!Running) - { - QueueRunner = QueueLoop(); - } + AddToQueue(pbook); } - public void AddConvertMp3(GridEntry gridEntry) + public void AddConvertMp3(DataLayer.LibraryBook libraryBook) { - if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId)) + if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId)) return; - ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger); - pbook.DataAvailable += Pbook_DataAvailable; - + ProcessBook pbook = new(libraryBook, Logger); + pbook.PropertyChanged += Pbook_DataAvailable; pbook.AddConvertToMp3(); + AddToQueue(pbook); + } + private void AddToQueue(ProcessBook pbook) + { Queue.Enqueue(pbook); if (!Running) @@ -144,8 +139,10 @@ namespace LibationWinForms.ProcessQueue public void WriteLine(string text) { - if (!IsDisposed) - logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); + if (IsDisposed) return; + + var timeStamp = DateTime.Now; + logDGV.Rows.Add(timeStamp, text.Trim()); } #region Control event handlers @@ -205,7 +202,18 @@ namespace LibationWinForms.ProcessQueue private void clearLogBtn_Click(object sender, EventArgs e) { - logMeTbox.Clear(); + logDGV.Rows.Clear(); + } + + private void LogCopyBtn_Click(object sender, EventArgs e) + { + string logText = string.Join("\r\n", logDGV.Rows.Cast().Select(r => $"{r.Cells[0].Value}\t{r.Cells[1].Value}")); + Clipboard.SetDataObject(logText, false, 5, 150); + } + + private void LogDGV_Resize(object sender, EventArgs e) + { + logDGV.Columns[1].Width = logDGV.Width - logDGV.Columns[0].Width; } #endregion @@ -229,7 +237,7 @@ namespace LibationWinForms.ProcessQueue /// Updates the display of a single at within /// /// index of the within the - private void UpdateControl(int queueIndex) + private void UpdateControl(int queueIndex, string propertyName = null) { int i = queueIndex - FirstVisible; @@ -240,8 +248,10 @@ namespace LibationWinForms.ProcessQueue Panels[i].Invoke(() => { Panels[i].SuspendLayout(); - Panels[i].SetCover(proc.Cover); - Panels[i].SetBookInfo(proc.BookText); + if (propertyName is null || propertyName == nameof(proc.Cover)) + Panels[i].SetCover(proc.Cover); + if (propertyName is null || propertyName == nameof(proc.BookText)) + Panels[i].SetBookInfo(proc.BookText); if (proc.Result != ProcessBookResult.None) { @@ -249,9 +259,12 @@ namespace LibationWinForms.ProcessQueue return; } - Panels[i].SetStatus(proc.Status); - Panels[i].SetProgrss(proc.Progress); - Panels[i].SetRemainingTime(proc.TimeRemaining); + if (propertyName is null || propertyName == nameof(proc.Status)) + Panels[i].SetStatus(proc.Status); + if (propertyName is null || propertyName == nameof(proc.Progress)) + Panels[i].SetProgrss(proc.Progress); + if (propertyName is null || propertyName == nameof(proc.TimeRemaining)) + Panels[i].SetRemainingTime(proc.TimeRemaining); Panels[i].ResumeLayout(); }); } @@ -273,31 +286,31 @@ namespace LibationWinForms.ProcessQueue private void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked) { ProcessBook item = Queue[queueIndex]; - if (buttonName == "cancelBtn") + if (buttonName == nameof(panelClicked.cancelBtn)) { item.Cancel(); Queue.RemoveQueued(item); virtualFlowControl2.VirtualControlCount = Queue.Count; UpdateControl(queueIndex); } - else if (buttonName == "moveFirstBtn") + else if (buttonName == nameof(panelClicked.moveFirstBtn)) { Queue.MoveQueuePosition(item, QueuePosition.Fisrt); UpdateAllControls(); } - else if (buttonName == "moveUpBtn") + else if (buttonName == nameof(panelClicked.moveUpBtn)) { Queue.MoveQueuePosition(item, QueuePosition.OneUp); UpdateControl(queueIndex - 1); UpdateControl(queueIndex); } - else if (buttonName == "moveDownBtn") + else if (buttonName == nameof(panelClicked.moveDownBtn)) { Queue.MoveQueuePosition(item, QueuePosition.OneDown); UpdateControl(queueIndex + 1); UpdateControl(queueIndex); } - else if (buttonName == "moveLastBtn") + else if (buttonName == nameof(panelClicked.moveLastBtn)) { Queue.MoveQueuePosition(item, QueuePosition.Last); UpdateAllControls(); @@ -318,10 +331,10 @@ namespace LibationWinForms.ProcessQueue /// /// Model updates the view /// - private void Pbook_DataAvailable(object sender, EventArgs e) + private void Pbook_DataAvailable(object sender, PropertyChangedEventArgs e) { int index = Queue.IndexOf((ProcessBook)sender); - UpdateControl(index); + UpdateControl(index, e.PropertyName); } #endregion diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx index 15c031bb..00da05c2 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx @@ -630,6 +630,12 @@ w1EmAAAAAElFTkSuQmCC + + True + + + True + 133, 17 From 73a5d76503e4a181daefa7a308029e92ad75c668 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 14:39:46 -0600 Subject: [PATCH 14/33] Make thread safe and integrate with Libation UI --- Source/LibationWinForms/Form1.Designer.cs | 5 +- Source/LibationWinForms/Form1.Liberate.cs | 12 ++-- Source/LibationWinForms/Form1.ProcessQueue.cs | 10 +--- .../ProcessQueue/ProcessQueueControl.cs | 56 ++++++++++++++++--- Source/LibationWinForms/grid/ProductsGrid.cs | 9 +-- 5 files changed, 66 insertions(+), 26 deletions(-) diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index cabc4447..2687df4d 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -74,7 +74,7 @@ this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.button1 = new System.Windows.Forms.Button(); this.panel1 = new System.Windows.Forms.Panel(); - this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessBookQueue(); + this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -470,7 +470,6 @@ this.button1.TabIndex = 8; this.button1.Text = "button1"; this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); // // panel1 // @@ -565,7 +564,7 @@ private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem liberateVisible2ToolStripMenuItem; private System.Windows.Forms.SplitContainer splitContainer1; - private LibationWinForms.ProcessQueue.ProcessBookQueue processBookQueue1; + private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button button1; } diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index 43f91546..b954e5d6 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms @@ -7,11 +8,12 @@ namespace LibationWinForms { private void Configure_Liberate() { } - private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(); + //GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread + private void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => Task.Run(()=>processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking())); - private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(); + private void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking())); private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) { @@ -24,7 +26,7 @@ namespace LibationWinForms MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result == DialogResult.Yes) - await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync(); + await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking())); } } } diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index 54218c78..f2379cd5 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -1,4 +1,5 @@ -using LibationFileManager; +using ApplicationServices; +using LibationFileManager; using LibationWinForms.ProcessQueue; using System; using System.Windows.Forms; @@ -9,7 +10,7 @@ namespace LibationWinForms { private void Configure_ProcessQueue() { - //splitContainer1.Panel2Collapsed = true; + productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; } @@ -39,10 +40,5 @@ namespace LibationWinForms this.Focus(); } } - - private void button1_Click(object sender, EventArgs e) - { - processBookQueue1.AddDownloadDecrypt(productsGrid.List); - } } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 2df35ef8..8c35ca70 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -12,7 +13,7 @@ namespace LibationWinForms.ProcessQueue { private TrackedQueue Queue = new(); private readonly LogMe Logger; - + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; private int QueuedCount { set @@ -70,16 +71,48 @@ namespace LibationWinForms.ProcessQueue CompletedCount = 0; } + public void AddDownloadPdf(IEnumerable entries) + { + Action> makeAll = (lb) => + { + foreach (var entry in entries) + AddDownloadPdf(entry); + }; + //IEnumerable are run on non-ui thread, so send collection to UI first + PassToUIThread(entries, makeAll); + } + public void AddDownloadDecrypt(IEnumerable entries) { - foreach (var entry in entries) - AddDownloadDecrypt(entry); + Action> makeAll = (lb) => + { + foreach (var entry in entries) + AddDownloadDecrypt(entry); + }; + //IEnumerable are run on non-ui thread, so send collection to UI first + PassToUIThread(entries, makeAll); } public void AddConvertMp3(IEnumerable entries) { - foreach (var entry in entries) - AddConvertMp3(entry); + Action> makeAll = (lb) => + { + foreach (var entry in entries) + AddConvertMp3(entry); + }; + //IEnumerable are run on non-ui thread, so send collection to UI first + PassToUIThread(entries, makeAll); + } + + public void AddDownloadPdf(DataLayer.LibraryBook libraryBook) + { + if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId)) + return; + + ProcessBook pbook = new(libraryBook, Logger); + pbook.PropertyChanged += Pbook_DataAvailable; + pbook.AddDownloadPdf(); + AddToQueue(pbook); } public void AddDownloadDecrypt(DataLayer.LibraryBook libraryBook) @@ -105,6 +138,15 @@ namespace LibationWinForms.ProcessQueue AddToQueue(pbook); } + private void PassToUIThread(IEnumerable libraryBooks, Action> onComplete) + { + void OnSendOrPostCallback(object asyncArgs) + { + onComplete((IEnumerable)asyncArgs); + } + SyncContext.Send(OnSendOrPostCallback, libraryBooks); + } + private void AddToQueue(ProcessBook pbook) { Queue.Enqueue(pbook); @@ -164,8 +206,8 @@ namespace LibationWinForms.ProcessQueue } private void UpdateProgressBar() { - toolStripProgressBar1.Maximum = Queue.Count; - toolStripProgressBar1.Value = Queue.Completed.Count; + toolStripProgressBar1.Maximum = Queue.Count; + toolStripProgressBar1.Value = Queue.Completed.Count; } private void cancelAllBtn_Click(object sender, EventArgs e) diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 93caca4d..b088262e 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -36,8 +36,10 @@ namespace LibationWinForms // VS has improved since then with .net6+ but I haven't checked again #endregion + public partial class ProductsGrid : UserControl { + public event EventHandler LiberateClicked; /// Number of visible rows has changed public event EventHandler VisibleCountChanged; @@ -76,7 +78,7 @@ namespace LibationWinForms return; if (e.ColumnIndex == liberateGVColumn.Index) - await Liberate_Click(getGridEntry(e.RowIndex)); + Liberate_Click(getGridEntry(e.RowIndex)); else if (e.ColumnIndex == tagAndDetailsGVColumn.Index) Details_Click(getGridEntry(e.RowIndex)); else if (e.ColumnIndex == descriptionGVColumn.Index) @@ -128,7 +130,7 @@ namespace LibationWinForms displayWindow.Show(this); } - private static async Task Liberate_Click(GridEntry liveGridEntry) + private void Liberate_Click(GridEntry liveGridEntry) { var libraryBook = liveGridEntry.LibraryBook; @@ -144,8 +146,7 @@ namespace LibationWinForms return; } - // else: liberate - await liveGridEntry.DownloadBook(); + LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook); } private static void Details_Click(GridEntry liveGridEntry) From 84eb3a3508a5a6ac338cb7b2314d3665c366ee57 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 15:33:06 -0600 Subject: [PATCH 15/33] Remove debug button --- Source/LibationWinForms/Form1.Designer.cs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index 2687df4d..de58e88f 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -72,7 +72,6 @@ this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.addQuickFilterBtn = new System.Windows.Forms.Button(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); - this.button1 = new System.Windows.Forms.Button(); this.panel1 = new System.Windows.Forms.Panel(); this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl(); this.menuStrip1.SuspendLayout(); @@ -120,10 +119,10 @@ // this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.filterSearchTb.Location = new System.Drawing.Point(220, 30); + this.filterSearchTb.Location = new System.Drawing.Point(194, 30); this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(563, 23); + this.filterSearchTb.Size = new System.Drawing.Size(589, 23); this.filterSearchTb.TabIndex = 1; this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); // @@ -430,7 +429,7 @@ this.addQuickFilterBtn.Location = new System.Drawing.Point(49, 27); this.addQuickFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.addQuickFilterBtn.Name = "addQuickFilterBtn"; - this.addQuickFilterBtn.Size = new System.Drawing.Size(99, 27); + this.addQuickFilterBtn.Size = new System.Drawing.Size(137, 27); this.addQuickFilterBtn.TabIndex = 4; this.addQuickFilterBtn.Text = "Add To Quick Filters"; this.addQuickFilterBtn.UseVisualStyleBackColor = true; @@ -444,7 +443,6 @@ // // splitContainer1.Panel1 // - this.splitContainer1.Panel1.Controls.Add(this.button1); this.splitContainer1.Panel1.Controls.Add(this.panel1); this.splitContainer1.Panel1.Controls.Add(this.menuStrip1); this.splitContainer1.Panel1.Controls.Add(this.filterSearchTb); @@ -461,16 +459,6 @@ this.splitContainer1.SplitterWidth = 8; this.splitContainer1.TabIndex = 7; // - // button1 - // - this.button1.Location = new System.Drawing.Point(155, 27); - this.button1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(58, 22); - this.button1.TabIndex = 8; - this.button1.Text = "button1"; - this.button1.UseVisualStyleBackColor = true; - // // panel1 // this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -566,6 +554,5 @@ private System.Windows.Forms.SplitContainer splitContainer1; private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1; private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.Button button1; } } From a40fb7f4bd439e7526404331b4f66b8e7d8a323e Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 15:57:36 -0600 Subject: [PATCH 16/33] Colorsto variables --- .../ProcessQueue/ProcessBookControl.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index 093f0070..a31b8a86 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -15,6 +15,12 @@ namespace LibationWinForms.ProcessQueue private ProcessBookStatus Status { get; set; } = ProcessBookStatus.Queued; private readonly int CancelBtnDistanceFromEdge; private readonly int ProgressBarDistanceFromEdge; + + public static Color FailedColor = Color.LightCoral; + public static Color CancelledColor = Color.Khaki; + public static Color QueuedColor = SystemColors.Control; + public static Color SuccessColor = Color.PaleGreen; + public ProcessBookControl() { InitializeComponent(); @@ -32,10 +38,12 @@ namespace LibationWinForms.ProcessQueue { pictureBox1.Image = cover; } + public void SetBookInfo(string title) { bookInfoLbl.Text = title; } + public void SetProgrss(int progress) { //Disabvle slow fill @@ -44,10 +52,12 @@ namespace LibationWinForms.ProcessQueue progressBar1.Value = progress + 1; progressBar1.Value = progress; } + public void SetRemainingTime(TimeSpan remaining) { remainingTimeLbl.Text = $"{remaining:mm\\:ss}"; } + public void SetResult(ProcessBookResult result) { string statusText = default; @@ -85,29 +95,30 @@ namespace LibationWinForms.ProcessQueue SetStatus(Status, statusText); } + public void SetStatus(ProcessBookStatus status, string statusText = null) { Color backColor = default; switch (status) { case ProcessBookStatus.Completed: - backColor = Color.PaleGreen; + backColor = SuccessColor; Status = ProcessBookStatus.Completed; break; case ProcessBookStatus.Cancelled: - backColor = Color.Khaki; + backColor = CancelledColor; Status = ProcessBookStatus.Cancelled; break; case ProcessBookStatus.Queued: - backColor = SystemColors.Control; + backColor = QueuedColor; Status = ProcessBookStatus.Queued; break; case ProcessBookStatus.Working: - backColor = SystemColors.Control; + backColor = QueuedColor; Status = ProcessBookStatus.Working; break; case ProcessBookStatus.Failed: - backColor = Color.LightCoral; + backColor = FailedColor; Status = ProcessBookStatus.Failed; break; } From 84a8fb0074bcaf2c52b53260bd6200b45cc58e20 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 16:13:19 -0600 Subject: [PATCH 17/33] Minor refactor --- Source/LibationWinForms/ProcessQueue/ProcessBook.cs | 9 +-------- .../LibationWinForms/ProcessQueue/ProcessBookControl.cs | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index d0f158e4..edae6212 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -47,7 +47,6 @@ namespace LibationWinForms.ProcessQueue private string _bookText; private int _progress; private TimeSpan _timeRemaining; - private LibraryBook _libraryBook; private Image _cover; public ProcessBookResult Result { get => _result; private set { _result = value; NotifyPropertyChanged(); } } @@ -55,10 +54,9 @@ namespace LibationWinForms.ProcessQueue public string BookText { get => _bookText; private set { _bookText = value; NotifyPropertyChanged(); } } public int Progress { get => _progress; private set { _progress = value; NotifyPropertyChanged(); } } public TimeSpan TimeRemaining { get => _timeRemaining; private set { _timeRemaining = value; NotifyPropertyChanged(); } } - public LibraryBook LibraryBook { get => _libraryBook; private set { _libraryBook = value; NotifyPropertyChanged(); } } public Image Cover { get => _cover; private set { _cover = value; NotifyPropertyChanged(); } } - + public LibraryBook LibraryBook { get; private set; } private Processable CurrentProcessable => _currentProcessable ??= Processes.Dequeue().Invoke(); private Processable NextProcessable() => _currentProcessable = null; private Processable _currentProcessable; @@ -284,11 +282,6 @@ namespace LibationWinForms.ProcessQueue title = libraryBook.Book.Title; authorNames = libraryBook.Book.AuthorNames(); narratorNames = libraryBook.Book.NarratorNames(); - Cover = Dinah.Core.Drawing.ImageReader.ToImage(PictureStorage.GetPicture( - new PictureDefinition( - libraryBook.Book.PictureId, - PictureSize._80x80)).bytes); - updateBookInfo(); } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index a31b8a86..7eee6913 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -46,7 +46,7 @@ namespace LibationWinForms.ProcessQueue public void SetProgrss(int progress) { - //Disabvle slow fill + //Disable slow fill //https://stackoverflow.com/a/5332770/3335599 if (progress < progressBar1.Maximum) progressBar1.Value = progress + 1; From d18d8c0ba400e57e22e05b3bd2c4d5f341f50dc8 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 16:23:34 -0600 Subject: [PATCH 18/33] Filter --- Source/LibationWinForms/Form1.Liberate.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index b954e5d6..30215723 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -10,10 +11,12 @@ namespace LibationWinForms //GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread private void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => Task.Run(()=>processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking())); + => Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.NotLiberated))); private void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking())); + => Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated))); private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) { From 5b05c018d56f853189fbf50620c272f5e48b16df Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 20:00:23 -0600 Subject: [PATCH 19/33] Remove ValidationFail books from queue display. Nothing to see there. --- .../ProcessQueue/ProcessQueueControl.cs | 4 ++++ .../ProcessQueue/TrackedQueue[T].cs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 8c35ca70..0de13f3a 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -171,12 +171,16 @@ namespace LibationWinForms.ProcessQueue if (result == ProcessBookResult.FailedRetry) Queue.Enqueue(nextBook); + else if (result == ProcessBookResult.ValidationFail) + Queue.ClearCurrent(); else if (result == ProcessBookResult.FailedAbort) return; } Queue_CompletedCountChanged(this, 0); counterTimer.Stop(); + virtualFlowControl2.VirtualControlCount = Queue.Count; + UpdateAllControls(); } public void WriteLine(string text) diff --git a/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs b/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs index a1f7dbe2..20bbe7e8 100644 --- a/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs +++ b/Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs @@ -94,6 +94,23 @@ namespace LibationWinForms.ProcessQueue } } + public void ClearCurrent() + { + lock(lockObject) + Current = null; + } + + public bool RemoveCompleted(T item) + { + lock (lockObject) + { + bool removed = _completed.Remove(item); + if (removed) + CompletedCountChanged?.Invoke(this, _completed.Count); + return removed; + } + } + public void ClearQueue() { lock (lockObject) From 49d10273a68ecde6d40e3139591d3f58e64ca8fa Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 20:44:53 -0600 Subject: [PATCH 20/33] Add button to hide queue --- Source/LibationWinForms/Form1.Designer.cs | 21 +++++++-- Source/LibationWinForms/Form1.ProcessQueue.cs | 46 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index de58e88f..66f1cb4e 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -72,6 +72,7 @@ this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.addQuickFilterBtn = new System.Windows.Forms.Button(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.hideQueueBtn = new System.Windows.Forms.Button(); this.panel1 = new System.Windows.Forms.Panel(); this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl(); this.menuStrip1.SuspendLayout(); @@ -106,8 +107,8 @@ // filterBtn // this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.filterBtn.Location = new System.Drawing.Point(791, 27); - this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3); + this.filterBtn.Location = new System.Drawing.Point(750, 27); + this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterBtn.Name = "filterBtn"; this.filterBtn.Size = new System.Drawing.Size(88, 27); this.filterBtn.TabIndex = 2; @@ -122,7 +123,7 @@ this.filterSearchTb.Location = new System.Drawing.Point(194, 30); this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(589, 23); + this.filterSearchTb.Size = new System.Drawing.Size(548, 23); this.filterSearchTb.TabIndex = 1; this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); // @@ -443,6 +444,7 @@ // // splitContainer1.Panel1 // + this.splitContainer1.Panel1.Controls.Add(this.hideQueueBtn); this.splitContainer1.Panel1.Controls.Add(this.panel1); this.splitContainer1.Panel1.Controls.Add(this.menuStrip1); this.splitContainer1.Panel1.Controls.Add(this.filterSearchTb); @@ -459,6 +461,18 @@ this.splitContainer1.SplitterWidth = 8; this.splitContainer1.TabIndex = 7; // + // hideQueueBtn + // + this.hideQueueBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.hideQueueBtn.Location = new System.Drawing.Point(846, 27); + this.hideQueueBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3); + this.hideQueueBtn.Name = "hideQueueBtn"; + this.hideQueueBtn.Size = new System.Drawing.Size(33, 27); + this.hideQueueBtn.TabIndex = 8; + this.hideQueueBtn.Text = "❰❰❰"; + this.hideQueueBtn.UseVisualStyleBackColor = true; + this.hideQueueBtn.Click += new System.EventHandler(this.HideQueueBtn_Click); + // // panel1 // this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -554,5 +568,6 @@ private System.Windows.Forms.SplitContainer splitContainer1; private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1; private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button hideQueueBtn; } } diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index f2379cd5..2954f224 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -2,6 +2,8 @@ using LibationFileManager; using LibationWinForms.ProcessQueue; using System; +using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms @@ -12,6 +14,42 @@ namespace LibationWinForms { productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; + + Task.Run(() => + { + Task.Delay(3000).Wait(); + var lb = ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking().Select(lb => lb.Book).ToList(); + + foreach (var b in lb) + { + b.UserDefinedItem.BookStatus = DataLayer.LiberatedStatus.NotLiberated; + } + LibraryCommands.UpdateUserDefinedItem(lb); + + }); + + } + + int WidthChange = 0; + private void HideQueueBtn_Click(object sender, EventArgs e) + { + if (splitContainer1.Panel2Collapsed) + { + WidthChange = WidthChange == 0 ? splitContainer1.Panel2.Width + splitContainer1.SplitterWidth : WidthChange; + Width += WidthChange; + splitContainer1.Panel2.Controls.Add(processBookQueue1); + splitContainer1.Panel2Collapsed = false; + processBookQueue1.popoutBtn.Visible = true; + hideQueueBtn.Text = "❰❰❰"; + } + else + { + WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth; + splitContainer1.Panel2.Controls.Remove(processBookQueue1); + splitContainer1.Panel2Collapsed = true; + Width -= WidthChange; + hideQueueBtn.Text = "❱❱❱"; + } } private void ProcessBookQueue1_PopOut(object sender, EventArgs e) @@ -26,6 +64,10 @@ namespace LibationWinForms dockForm.PassControl(processBookQueue1); dockForm.Show(); this.Width -= dockForm.WidthChange; + hideQueueBtn.Visible = false; + int deltax = filterBtn.Margin.Right + hideQueueBtn.Width + hideQueueBtn.Margin.Left; + filterBtn.Location= new System.Drawing.Point(filterBtn.Location.X + deltax, filterBtn.Location.Y); + filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X + deltax, filterSearchTb.Location.Y); } private void DockForm_FormClosing(object sender, FormClosingEventArgs e) @@ -38,6 +80,10 @@ namespace LibationWinForms processBookQueue1.popoutBtn.Visible = true; dockForm.SaveSizeAndLocation(Configuration.Instance); this.Focus(); + hideQueueBtn.Visible = true; + int deltax = filterBtn.Margin.Right + hideQueueBtn.Width + hideQueueBtn.Margin.Left; + filterBtn.Location = new System.Drawing.Point(filterBtn.Location.X - deltax, filterBtn.Location.Y); + filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X - deltax, filterSearchTb.Location.Y); } } } From 341678d9795ebc6ccf5aca0ea0491a15d0d0017b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 20:47:13 -0600 Subject: [PATCH 21/33] Remove my testing code. oops. --- Source/LibationWinForms/Form1.ProcessQueue.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index 2954f224..acf4ac66 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -14,20 +14,6 @@ namespace LibationWinForms { productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; - - Task.Run(() => - { - Task.Delay(3000).Wait(); - var lb = ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking().Select(lb => lb.Book).ToList(); - - foreach (var b in lb) - { - b.UserDefinedItem.BookStatus = DataLayer.LiberatedStatus.NotLiberated; - } - LibraryCommands.UpdateUserDefinedItem(lb); - - }); - } int WidthChange = 0; From b24df24b102436fd9cd075557e83dcd005b9de38 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 23:44:15 -0600 Subject: [PATCH 22/33] Detect Conversion cancelled --- Source/FileLiberator/ConvertToMp3.cs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Source/FileLiberator/ConvertToMp3.cs b/Source/FileLiberator/ConvertToMp3.cs index 35f3411e..09eb1b1e 100644 --- a/Source/FileLiberator/ConvertToMp3.cs +++ b/Source/FileLiberator/ConvertToMp3.cs @@ -17,17 +17,24 @@ namespace FileLiberator public override string Name => "Convert to Mp3"; private Mp4File m4bBook; - private long fileSize; + private long fileSize; private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3"); - public override void Cancel() => m4bBook?.Cancel(); + private bool cancelled = false; + public override void Cancel() + { + m4bBook?.Cancel(); + cancelled = true; + } - public override bool Validate(LibraryBook libraryBook) - { + public static bool ValidateMp3(LibraryBook libraryBook) + { var path = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); return path?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path)); } + public override bool Validate(LibraryBook libraryBook) => ValidateMp3(libraryBook); + public override async Task ProcessAsync(LibraryBook libraryBook) { OnBegin(libraryBook); @@ -57,12 +64,12 @@ namespace FileLiberator var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path); OnFileCreated(libraryBook, realMp3Path); - var statusHandler = new StatusHandler(); - if (result == ConversionResult.Failed) - statusHandler.AddError("Conversion failed"); - - return statusHandler; + return new StatusHandler { "Conversion failed" }; + else if (result == ConversionResult.Cancelled) + return new StatusHandler { "Cancelled" }; + else + return new StatusHandler(); } finally { From 1ab628dee84901b9d2204d54dc6f35f06049a60f Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 23:45:13 -0600 Subject: [PATCH 23/33] Better invocation. Post instead of Send --- Source/LibationWinForms/Form1.Liberate.cs | 4 +- .../ProcessQueue/ProcessQueueControl.cs | 54 +++++-------------- 2 files changed, 16 insertions(+), 42 deletions(-) diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index 30215723..da85747f 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -29,7 +29,9 @@ namespace LibationWinForms MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result == DialogResult.Yes) - await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking())); + await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + .Where(lb=>lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated))); + //Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing. } } } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 0de13f3a..0a222160 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -13,7 +12,6 @@ namespace LibationWinForms.ProcessQueue { private TrackedQueue Queue = new(); private readonly LogMe Logger; - private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; private int QueuedCount { set @@ -40,7 +38,6 @@ namespace LibationWinForms.ProcessQueue } } - public Task QueueRunner { get; private set; } public bool Running => !QueueRunner?.IsCompleted ?? false; public ToolStripButton popoutBtn = new(); @@ -73,35 +70,20 @@ namespace LibationWinForms.ProcessQueue public void AddDownloadPdf(IEnumerable entries) { - Action> makeAll = (lb) => - { - foreach (var entry in entries) - AddDownloadPdf(entry); - }; - //IEnumerable are run on non-ui thread, so send collection to UI first - PassToUIThread(entries, makeAll); + foreach (var entry in entries) + AddDownloadPdf(entry); } public void AddDownloadDecrypt(IEnumerable entries) { - Action> makeAll = (lb) => - { - foreach (var entry in entries) - AddDownloadDecrypt(entry); - }; - //IEnumerable are run on non-ui thread, so send collection to UI first - PassToUIThread(entries, makeAll); + foreach (var entry in entries) + AddDownloadDecrypt(entry); } public void AddConvertMp3(IEnumerable entries) { - Action> makeAll = (lb) => - { - foreach (var entry in entries) - AddConvertMp3(entry); - }; - //IEnumerable are run on non-ui thread, so send collection to UI first - PassToUIThread(entries, makeAll); + foreach (var entry in entries) + AddConvertMp3(entry); } public void AddDownloadPdf(DataLayer.LibraryBook libraryBook) @@ -138,23 +120,14 @@ namespace LibationWinForms.ProcessQueue AddToQueue(pbook); } - private void PassToUIThread(IEnumerable libraryBooks, Action> onComplete) - { - void OnSendOrPostCallback(object asyncArgs) - { - onComplete((IEnumerable)asyncArgs); - } - SyncContext.Send(OnSendOrPostCallback, libraryBooks); - } - private void AddToQueue(ProcessBook pbook) { - Queue.Enqueue(pbook); - - if (!Running) + BeginInvoke(() => { - QueueRunner = QueueLoop(); - } + Queue.Enqueue(pbook); + if (!Running) + QueueRunner = QueueLoop(); + }); } DateTime StartintTime; @@ -176,7 +149,6 @@ namespace LibationWinForms.ProcessQueue else if (result == ProcessBookResult.FailedAbort) return; } - Queue_CompletedCountChanged(this, 0); counterTimer.Stop(); virtualFlowControl2.VirtualControlCount = Queue.Count; @@ -210,8 +182,8 @@ namespace LibationWinForms.ProcessQueue } private void UpdateProgressBar() { - toolStripProgressBar1.Maximum = Queue.Count; - toolStripProgressBar1.Value = Queue.Completed.Count; + toolStripProgressBar1.Maximum = Queue.Count; + toolStripProgressBar1.Value = Queue.Completed.Count; } private void cancelAllBtn_Click(object sender, EventArgs e) From c0ef3ccbea29c856d64f5b198b7b43be1b96d2b0 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 09:00:52 -0600 Subject: [PATCH 24/33] Tiny bugfix --- Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs | 1 - Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 0a222160..17ac8815 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -309,7 +309,6 @@ namespace LibationWinForms.ProcessQueue item.Cancel(); Queue.RemoveQueued(item); virtualFlowControl2.VirtualControlCount = Queue.Count; - UpdateControl(queueIndex); } else if (buttonName == nameof(panelClicked.moveFirstBtn)) { diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index b5e689b8..e942def1 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -179,6 +179,7 @@ namespace LibationWinForms.ProcessQueue if (VirtualControlCount <= maxFullVisible) { vScrollBar1.Enabled = false; + vScrollBar1.Value = 0; for (int i = VirtualControlCount; i < NUM_ACTUAL_CONTROLS; i++) BookControls[i].Visible = false; From 0ff8da2cf0b965d2371d247d49e410291d173c73 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 09:30:44 -0600 Subject: [PATCH 25/33] Add await and make cancel async --- Source/LibationWinForms/Form1.Liberate.cs | 8 ++++---- Source/LibationWinForms/ProcessQueue/ProcessBook.cs | 5 ++--- .../LibationWinForms/ProcessQueue/ProcessQueueControl.cs | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index da85747f..d1369d7a 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -10,12 +10,12 @@ namespace LibationWinForms private void Configure_Liberate() { } //GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread - private void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => await Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.NotLiberated))); - private void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => await Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated))); private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index edae6212..e76c151b 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -139,15 +139,14 @@ namespace LibationWinForms.ProcessQueue return Result; } - public void Cancel() + public async Task Cancel() { try { if (CurrentProcessable is AudioDecodable audioDecodable) { //There's some threadding bug that causes this to hang if executed synchronously. - Task.Run(audioDecodable.Cancel); - + await Task.Run(audioDecodable.Cancel); } } catch (Exception ex) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 17ac8815..d8b5db76 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -301,12 +301,12 @@ namespace LibationWinForms.ProcessQueue /// /// index of the within /// The clicked control to update - private void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked) + private async void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked) { ProcessBook item = Queue[queueIndex]; if (buttonName == nameof(panelClicked.cancelBtn)) { - item.Cancel(); + await item.Cancel(); Queue.RemoveQueued(item); virtualFlowControl2.VirtualControlCount = Queue.Count; } From c9e850515e1b121840170ee5285b524e476aba84 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 09:56:46 -0600 Subject: [PATCH 26/33] More useful logging --- Source/AaxDecrypter/NetworkFileStream.cs | 1 + Source/AppScaffolding/AppScaffolding.csproj | 2 +- Source/LibationWinForms/Form1.ProcessQueue.cs | 14 ++++++++++++++ .../LibationWinForms/ProcessQueue/ProcessBook.cs | 10 +++++----- Source/LibationWinForms/grid/ProductsGrid.cs | 2 +- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs index 259b3526..71a95d09 100644 --- a/Source/AaxDecrypter/NetworkFileStream.cs +++ b/Source/AaxDecrypter/NetworkFileStream.cs @@ -221,6 +221,7 @@ namespace AaxDecrypter var buff = new byte[DOWNLOAD_BUFF_SZ]; do { + Thread.Sleep(5); var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); _writeFile.Write(buff, 0, bytesRead); diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index ad40a990..341d6da7 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net6.0-windows - 7.5.0.1 + 7.5.0.8 diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index acf4ac66..2954f224 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -14,6 +14,20 @@ namespace LibationWinForms { productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; + + Task.Run(() => + { + Task.Delay(3000).Wait(); + var lb = ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking().Select(lb => lb.Book).ToList(); + + foreach (var b in lb) + { + b.UserDefinedItem.BookStatus = DataLayer.LiberatedStatus.NotLiberated; + } + LibraryCommands.UpdateUserDefinedItem(lb); + + }); + } int WidthChange = 0; diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index e76c151b..1aa6ad44 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -106,21 +106,21 @@ namespace LibationWinForms.ProcessQueue return Result = ProcessBookResult.Success; else if (statusHandler.Errors.Contains("Cancelled")) { - Logger.Info($"Process was cancelled {LibraryBook.Book}"); + Logger.Info($"{CurrentProcessable.Name}: Process was cancelled {LibraryBook.Book}"); return Result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { - Logger.Info($"Validation failed {LibraryBook.Book}"); + Logger.Info($"{CurrentProcessable.Name}: Validation failed {LibraryBook.Book}"); return Result = ProcessBookResult.ValidationFail; } foreach (var errorMessage in statusHandler.Errors) - Logger.Error(errorMessage); + Logger.Error($"{CurrentProcessable.Name}: {errorMessage}"); } catch (Exception ex) { - Logger.Error(ex); + Logger.Error(ex, CurrentProcessable.Name); } finally { @@ -151,7 +151,7 @@ namespace LibationWinForms.ProcessQueue } catch (Exception ex) { - Logger.Error(ex, "Error while cancelling"); + Logger.Error(ex, $"{CurrentProcessable.Name}: Error while cancelling"); } } diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index b088262e..4a360be8 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -130,7 +130,7 @@ namespace LibationWinForms displayWindow.Show(this); } - private void Liberate_Click(GridEntry liveGridEntry) + private async void Liberate_Click(GridEntry liveGridEntry) { var libraryBook = liveGridEntry.LibraryBook; From 0a6a78bc58e552c9b9215365f3e4847a2a98b278 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 09:56:56 -0600 Subject: [PATCH 27/33] Revert "More useful logging" This reverts commit c9e850515e1b121840170ee5285b524e476aba84. --- Source/AaxDecrypter/NetworkFileStream.cs | 1 - Source/AppScaffolding/AppScaffolding.csproj | 2 +- Source/LibationWinForms/Form1.ProcessQueue.cs | 14 -------------- .../LibationWinForms/ProcessQueue/ProcessBook.cs | 10 +++++----- Source/LibationWinForms/grid/ProductsGrid.cs | 2 +- 5 files changed, 7 insertions(+), 22 deletions(-) diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs index 71a95d09..259b3526 100644 --- a/Source/AaxDecrypter/NetworkFileStream.cs +++ b/Source/AaxDecrypter/NetworkFileStream.cs @@ -221,7 +221,6 @@ namespace AaxDecrypter var buff = new byte[DOWNLOAD_BUFF_SZ]; do { - Thread.Sleep(5); var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); _writeFile.Write(buff, 0, bytesRead); diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index 341d6da7..ad40a990 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net6.0-windows - 7.5.0.8 + 7.5.0.1 diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index 2954f224..acf4ac66 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -14,20 +14,6 @@ namespace LibationWinForms { productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; - - Task.Run(() => - { - Task.Delay(3000).Wait(); - var lb = ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking().Select(lb => lb.Book).ToList(); - - foreach (var b in lb) - { - b.UserDefinedItem.BookStatus = DataLayer.LiberatedStatus.NotLiberated; - } - LibraryCommands.UpdateUserDefinedItem(lb); - - }); - } int WidthChange = 0; diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index 1aa6ad44..e76c151b 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -106,21 +106,21 @@ namespace LibationWinForms.ProcessQueue return Result = ProcessBookResult.Success; else if (statusHandler.Errors.Contains("Cancelled")) { - Logger.Info($"{CurrentProcessable.Name}: Process was cancelled {LibraryBook.Book}"); + Logger.Info($"Process was cancelled {LibraryBook.Book}"); return Result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { - Logger.Info($"{CurrentProcessable.Name}: Validation failed {LibraryBook.Book}"); + Logger.Info($"Validation failed {LibraryBook.Book}"); return Result = ProcessBookResult.ValidationFail; } foreach (var errorMessage in statusHandler.Errors) - Logger.Error($"{CurrentProcessable.Name}: {errorMessage}"); + Logger.Error(errorMessage); } catch (Exception ex) { - Logger.Error(ex, CurrentProcessable.Name); + Logger.Error(ex); } finally { @@ -151,7 +151,7 @@ namespace LibationWinForms.ProcessQueue } catch (Exception ex) { - Logger.Error(ex, $"{CurrentProcessable.Name}: Error while cancelling"); + Logger.Error(ex, "Error while cancelling"); } } diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index 4a360be8..b088262e 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -130,7 +130,7 @@ namespace LibationWinForms displayWindow.Show(this); } - private async void Liberate_Click(GridEntry liveGridEntry) + private void Liberate_Click(GridEntry liveGridEntry) { var libraryBook = liveGridEntry.LibraryBook; From a46041c95814c4a3f5cbd211b617b00390aebef2 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 09:58:36 -0600 Subject: [PATCH 28/33] More useful logging --- .../LibationWinForms/ProcessQueue/ProcessBook.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index e76c151b..ea0e14d5 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -49,8 +49,8 @@ namespace LibationWinForms.ProcessQueue private TimeSpan _timeRemaining; private Image _cover; - public ProcessBookResult Result { get => _result; private set { _result = value; NotifyPropertyChanged(); } } - public ProcessBookStatus Status { get => _status; private set { _status = value; NotifyPropertyChanged(); } } + public ProcessBookResult Result { get => _result; private set { _result = value; NotifyPropertyChanged(); } } + public ProcessBookStatus Status { get => _status; private set { _status = value; NotifyPropertyChanged(); } } public string BookText { get => _bookText; private set { _bookText = value; NotifyPropertyChanged(); } } public int Progress { get => _progress; private set { _progress = value; NotifyPropertyChanged(); } } public TimeSpan TimeRemaining { get => _timeRemaining; private set { _timeRemaining = value; NotifyPropertyChanged(); } } @@ -106,21 +106,21 @@ namespace LibationWinForms.ProcessQueue return Result = ProcessBookResult.Success; else if (statusHandler.Errors.Contains("Cancelled")) { - Logger.Info($"Process was cancelled {LibraryBook.Book}"); + Logger.Info($"{CurrentProcessable.Name}: Process was cancelled {LibraryBook.Book}"); return Result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { - Logger.Info($"Validation failed {LibraryBook.Book}"); + Logger.Info($"{CurrentProcessable.Name}: Validation failed {LibraryBook.Book}"); return Result = ProcessBookResult.ValidationFail; } foreach (var errorMessage in statusHandler.Errors) - Logger.Error(errorMessage); + Logger.Error($"{CurrentProcessable.Name}: {errorMessage}"); } catch (Exception ex) { - Logger.Error(ex); + Logger.Error(ex, CurrentProcessable.Name); } finally { @@ -151,7 +151,7 @@ namespace LibationWinForms.ProcessQueue } catch (Exception ex) { - Logger.Error(ex, "Error while cancelling"); + Logger.Error(ex, $"{CurrentProcessable.Name}: Error while cancelling"); } } From a490df0f7e9dbb06830b74c1c01a1b8b4c8c727b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 11:10:37 -0600 Subject: [PATCH 29/33] Fix possible index range error --- Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index d8b5db76..b0fa7aa3 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -318,14 +318,16 @@ namespace LibationWinForms.ProcessQueue else if (buttonName == nameof(panelClicked.moveUpBtn)) { Queue.MoveQueuePosition(item, QueuePosition.OneUp); - UpdateControl(queueIndex - 1); UpdateControl(queueIndex); + if (queueIndex > 0) + UpdateControl(queueIndex - 1); } else if (buttonName == nameof(panelClicked.moveDownBtn)) { Queue.MoveQueuePosition(item, QueuePosition.OneDown); - UpdateControl(queueIndex + 1); UpdateControl(queueIndex); + if (queueIndex + 1 < Queue.Count) + UpdateControl(queueIndex + 1); } else if (buttonName == nameof(panelClicked.moveLastBtn)) { From 8d6d26c9d2f8218d2be0c884de9706f4ed02f0b1 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 11:16:41 -0600 Subject: [PATCH 30/33] Improve logging --- Source/LibationWinForms/ProcessQueue/ProcessBook.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index ea0e14d5..75cb21e9 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -96,6 +96,7 @@ namespace LibationWinForms.ProcessQueue public async Task ProcessOneAsync() { + string procName = CurrentProcessable.Name; try { LinkProcessable(CurrentProcessable); @@ -106,21 +107,21 @@ namespace LibationWinForms.ProcessQueue return Result = ProcessBookResult.Success; else if (statusHandler.Errors.Contains("Cancelled")) { - Logger.Info($"{CurrentProcessable.Name}: Process was cancelled {LibraryBook.Book}"); + Logger.Info($"{procName}: Process was cancelled {LibraryBook.Book}"); return Result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { - Logger.Info($"{CurrentProcessable.Name}: Validation failed {LibraryBook.Book}"); + Logger.Info($"{procName}: Validation failed {LibraryBook.Book}"); return Result = ProcessBookResult.ValidationFail; } foreach (var errorMessage in statusHandler.Errors) - Logger.Error($"{CurrentProcessable.Name}: {errorMessage}"); + Logger.Error($"{procName}: {errorMessage}"); } catch (Exception ex) { - Logger.Error(ex, CurrentProcessable.Name); + Logger.Error(ex, procName); } finally { From 88cbcf6baff112f89b96d1296ce37fde41a3b08b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 11:25:33 -0600 Subject: [PATCH 31/33] Make scrll look more natural when removing items from control --- Source/AppScaffolding/AppScaffolding.csproj | 2 +- Source/LibationWinForms/Form1.ProcessQueue.cs | 17 +++++++++++++++-- .../ProcessQueue/VirtualFlowControl.cs | 5 ++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index ad40a990..36492330 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net6.0-windows - 7.5.0.1 + 7.5.0.5 diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index acf4ac66..bb0456c4 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -1,5 +1,4 @@ -using ApplicationServices; -using LibationFileManager; +using LibationFileManager; using LibationWinForms.ProcessQueue; using System; using System.Linq; @@ -14,6 +13,20 @@ namespace LibationWinForms { productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; + + Task.Run(() => + { + Task.Delay(3000).Wait(); + var lb = ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking().Select(lb => lb.Book).ToList(); + + foreach (var b in lb) + { + b.UserDefinedItem.BookStatus = DataLayer.LiberatedStatus.NotLiberated; + } + ApplicationServices.LibraryCommands.UpdateUserDefinedItem(lb); + + }); + } int WidthChange = 0; diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index e942def1..d031d191 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -190,7 +190,10 @@ namespace LibationWinForms.ProcessQueue vScrollBar1.LargeChange = LargeScrollChange; //https://stackoverflow.com/a/2882878/3335599 - vScrollBar1.Maximum = VirtualHeight + vScrollBar1.LargeChange - 1; + int newMaximum = VirtualHeight + vScrollBar1.LargeChange - 1; + if (newMaximum < vScrollBar1.Maximum) + vScrollBar1.Value = Math.Max(vScrollBar1.Value - (vScrollBar1.Maximum - newMaximum), 0); + vScrollBar1.Maximum = newMaximum; } } From a8a54aa4438a02096ab30fac1d270e608f0e1d0c Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 11:26:07 -0600 Subject: [PATCH 32/33] Revert "Make scrll look more natural when removing items from control" This reverts commit 88cbcf6baff112f89b96d1296ce37fde41a3b08b. --- Source/AppScaffolding/AppScaffolding.csproj | 2 +- Source/LibationWinForms/Form1.ProcessQueue.cs | 17 ++--------------- .../ProcessQueue/VirtualFlowControl.cs | 5 +---- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index 36492330..ad40a990 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net6.0-windows - 7.5.0.5 + 7.5.0.1 diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index bb0456c4..acf4ac66 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -1,4 +1,5 @@ -using LibationFileManager; +using ApplicationServices; +using LibationFileManager; using LibationWinForms.ProcessQueue; using System; using System.Linq; @@ -13,20 +14,6 @@ namespace LibationWinForms { productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb); processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; - - Task.Run(() => - { - Task.Delay(3000).Wait(); - var lb = ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking().Select(lb => lb.Book).ToList(); - - foreach (var b in lb) - { - b.UserDefinedItem.BookStatus = DataLayer.LiberatedStatus.NotLiberated; - } - ApplicationServices.LibraryCommands.UpdateUserDefinedItem(lb); - - }); - } int WidthChange = 0; diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index d031d191..e942def1 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -190,10 +190,7 @@ namespace LibationWinForms.ProcessQueue vScrollBar1.LargeChange = LargeScrollChange; //https://stackoverflow.com/a/2882878/3335599 - int newMaximum = VirtualHeight + vScrollBar1.LargeChange - 1; - if (newMaximum < vScrollBar1.Maximum) - vScrollBar1.Value = Math.Max(vScrollBar1.Value - (vScrollBar1.Maximum - newMaximum), 0); - vScrollBar1.Maximum = newMaximum; + vScrollBar1.Maximum = VirtualHeight + vScrollBar1.LargeChange - 1; } } From e079be0ad7e74e5a8483e5c10be19857f2035311 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 May 2022 11:27:49 -0600 Subject: [PATCH 33/33] Make scrll look more natural when removing items from control --- Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index e942def1..d031d191 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -190,7 +190,10 @@ namespace LibationWinForms.ProcessQueue vScrollBar1.LargeChange = LargeScrollChange; //https://stackoverflow.com/a/2882878/3335599 - vScrollBar1.Maximum = VirtualHeight + vScrollBar1.LargeChange - 1; + int newMaximum = VirtualHeight + vScrollBar1.LargeChange - 1; + if (newMaximum < vScrollBar1.Maximum) + vScrollBar1.Value = Math.Max(vScrollBar1.Value - (vScrollBar1.Maximum - newMaximum), 0); + vScrollBar1.Maximum = newMaximum; } }