From 51f9b4f473f69b36e4eec06efe402d813463abfe Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 23 Jun 2022 20:45:09 -0600 Subject: [PATCH 1/4] More character replacement safety --- Source/FileManager/ReplacementCharacters.cs | 5 +- .../Dialogs/EditReplacementChars.Designer.cs | 54 ++++++++++--------- .../Dialogs/EditReplacementChars.cs | 6 +-- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/Source/FileManager/ReplacementCharacters.cs b/Source/FileManager/ReplacementCharacters.cs index 7cbdf322..b81d3865 100644 --- a/Source/FileManager/ReplacementCharacters.cs +++ b/Source/FileManager/ReplacementCharacters.cs @@ -7,7 +7,7 @@ using System.Linq; namespace FileManager { - public class Replacement + public class Replacement : ICloneable { public const int FIXED_COUNT = 6; @@ -30,6 +30,8 @@ namespace FileManager Mandatory = mandatory; } + public object Clone() => new Replacement(CharacterToReplace, ReplacementString, Description, Mandatory); + public void Update(char charToReplace, string replacementString, string description) { ReplacementString = replacementString; @@ -53,6 +55,7 @@ namespace FileManager public static Replacement OpenAngleBracket(string replacement) => new('<', replacement, "Open Angle Bracket"); public static Replacement CloseAngleBracket(string replacement) => new('>', replacement, "Close Angle Bracket"); public static Replacement Pipe(string replacement) => new('|', replacement, "Vertical Line"); + } [JsonConverter(typeof(ReplacementCharactersConverter))] diff --git a/Source/LibationWinForms/Dialogs/EditReplacementChars.Designer.cs b/Source/LibationWinForms/Dialogs/EditReplacementChars.Designer.cs index 07bced72..f827c2de 100644 --- a/Source/LibationWinForms/Dialogs/EditReplacementChars.Designer.cs +++ b/Source/LibationWinForms/Dialogs/EditReplacementChars.Designer.cs @@ -29,14 +29,14 @@ private void InitializeComponent() { this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.charToReplaceCol = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.replacementStringCol = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.descriptionCol = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.defaultsBtn = new System.Windows.Forms.Button(); this.loFiDefaultsBtn = new System.Windows.Forms.Button(); this.saveBtn = new System.Windows.Forms.Button(); this.cancelBtn = new System.Windows.Forms.Button(); this.minDefaultBtn = new System.Windows.Forms.Button(); - this.charToReplaceCol = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.replacementStringCol = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.descriptionCol = new System.Windows.Forms.DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); this.SuspendLayout(); // @@ -54,6 +54,7 @@ this.descriptionCol}); this.dataGridView1.Location = new System.Drawing.Point(12, 12); this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; this.dataGridView1.RowTemplate.Height = 25; this.dataGridView1.Size = new System.Drawing.Size(498, 393); this.dataGridView1.TabIndex = 0; @@ -61,6 +62,31 @@ this.dataGridView1.UserDeletingRow += new System.Windows.Forms.DataGridViewRowCancelEventHandler(this.dataGridView1_UserDeletingRow); this.dataGridView1.Resize += new System.EventHandler(this.dataGridView1_Resize); // + // charToReplaceCol + // + this.charToReplaceCol.HeaderText = "Char to Replace"; + this.charToReplaceCol.MinimumWidth = 70; + this.charToReplaceCol.Name = "charToReplaceCol"; + this.charToReplaceCol.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.charToReplaceCol.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.charToReplaceCol.Width = 70; + // + // replacementStringCol + // + this.replacementStringCol.HeaderText = "Replacement Text"; + this.replacementStringCol.MinimumWidth = 85; + this.replacementStringCol.Name = "replacementStringCol"; + this.replacementStringCol.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.replacementStringCol.Width = 85; + // + // descriptionCol + // + this.descriptionCol.HeaderText = "Description"; + this.descriptionCol.MinimumWidth = 100; + this.descriptionCol.Name = "descriptionCol"; + this.descriptionCol.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.descriptionCol.Width = 200; + // // defaultsBtn // this.defaultsBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); @@ -116,28 +142,6 @@ this.minDefaultBtn.UseVisualStyleBackColor = true; this.minDefaultBtn.Click += new System.EventHandler(this.minDefaultBtn_Click); // - // charToReplaceCol - // - this.charToReplaceCol.HeaderText = "Char to Replace"; - this.charToReplaceCol.MinimumWidth = 70; - this.charToReplaceCol.Name = "charToReplaceCol"; - this.charToReplaceCol.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.charToReplaceCol.Width = 70; - // - // replacementStringCol - // - this.replacementStringCol.HeaderText = "Replacement Text"; - this.replacementStringCol.MinimumWidth = 85; - this.replacementStringCol.Name = "replacementStringCol"; - this.replacementStringCol.Width = 85; - // - // descriptionCol - // - this.descriptionCol.HeaderText = "Description"; - this.descriptionCol.MinimumWidth = 100; - this.descriptionCol.Name = "descriptionCol"; - this.descriptionCol.Width = 200; - // // EditReplacementChars // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); diff --git a/Source/LibationWinForms/Dialogs/EditReplacementChars.cs b/Source/LibationWinForms/Dialogs/EditReplacementChars.cs index 5e16550f..88388e4a 100644 --- a/Source/LibationWinForms/Dialogs/EditReplacementChars.cs +++ b/Source/LibationWinForms/Dialogs/EditReplacementChars.cs @@ -29,8 +29,8 @@ namespace LibationWinForms.Dialogs { var r = replacements[i]; - int row = dataGridView1.Rows.Add(r.CharacterToReplace, r.ReplacementString, r.Description); - dataGridView1.Rows[row].Tag = r; + int row = dataGridView1.Rows.Add(r.CharacterToReplace.ToString(), r.ReplacementString, r.Description); + dataGridView1.Rows[row].Tag = r.Clone(); if (r.Mandatory) @@ -90,7 +90,7 @@ namespace LibationWinForms.Dialogs { dataGridView1.Rows[e.RowIndex].ErrorText = $"Only 1 {charToReplaceCol.HeaderText} per entry"; } - else if (e.RowIndex >= Replacement.FIXED_COUNT && + else if (dataGridView1.Rows[e.RowIndex].Tag is Replacement repl && !repl.Mandatory && dataGridView1.Rows .Cast() .Where(r => r.Index != e.RowIndex) From 0fe07695b29e6a568d662454616900b97f5b74c2 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 23 Jun 2022 20:45:41 -0600 Subject: [PATCH 2/4] Make better use of hierarchical chapters and add test --- Source/FileLiberator/DownloadDecryptBook.cs | 13 +- .../DownloadDecryptBookTests.cs | 202 ++++++++++++++++++ 2 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs index 257a17cf..e6112650 100644 --- a/Source/FileLiberator/DownloadDecryptBook.cs +++ b/Source/FileLiberator/DownloadDecryptBook.cs @@ -192,7 +192,7 @@ namespace FileLiberator return dlOptions; } - private List getChapters(IEnumerable chapters) + public static List getChapters(IEnumerable chapters) { List chaps = new(); @@ -200,17 +200,16 @@ namespace FileLiberator { if (c.Chapters is not null) { - var firstSub = new AudibleApi.Common.Chapter + c.Chapters[0] = new AudibleApi.Common.Chapter { - Title = $"{c.Title}: {c.Chapters[0].Title}", + Title = c.Chapters[0].Title, StartOffsetMs = c.StartOffsetMs, StartOffsetSec = c.StartOffsetSec, - LengthMs = c.LengthMs + c.Chapters[0].LengthMs + LengthMs = c.LengthMs + c.Chapters[0].LengthMs, + Chapters = c.Chapters[0].Chapters }; - chaps.Add(firstSub); - - var children = getChapters(c.Chapters[1..]); + var children = getChapters(c.Chapters); foreach (var child in children) child.Title = string.IsNullOrEmpty(c.Title) ? child.Title : $"{c.Title}: {child.Title}"; diff --git a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs new file mode 100644 index 00000000..2c0726ed --- /dev/null +++ b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AudibleApi.Common; +using Dinah.Core; +using FileManager; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + + +namespace FileLiberator.Tests +{ + [TestClass] + public class DownloadDecryptBookTests + { + [TestMethod] + public void HierarchicalChapters_Flatten() + { + var expected = new Chapter[] + { + new() + { + Title = "Book 1: Part 1: Chapter 1", + StartOffsetMs = 0, + StartOffsetSec = 0, + LengthMs = 100000, + }, + new() + { + Title = "Book 1: Part 1: Chapter 2", + StartOffsetMs = 100000, + StartOffsetSec = 100, + LengthMs = 100000, + }, + new() + { + Title = "Book 1: Part 2: Chapter 3", + StartOffsetMs = 200000, + StartOffsetSec = 200, + LengthMs = 100000, + }, + new() + { + Title = "Book 1: Part 2: Chapter 4", + StartOffsetMs = 300000, + StartOffsetSec = 300, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 3: Chapter 5", + StartOffsetMs = 400000, + StartOffsetSec = 400, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 3: Chapter 6", + StartOffsetMs = 500000, + StartOffsetSec = 500, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 4: Chapter 7", + StartOffsetMs = 600000, + StartOffsetSec = 600, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 4: Chapter 8", + StartOffsetMs = 700000, + StartOffsetSec = 700, + LengthMs = 100000, + } + }; + + var hierarchicalChapters = new Chapter[] + { + new() + { + Title = "Book 1", + StartOffsetMs = 0, + StartOffsetSec = 0, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { Title = "Part 1", + StartOffsetMs = 2000, + StartOffsetSec = 2, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { Title = "Chapter 1", + StartOffsetMs = 4000, + StartOffsetSec = 4, + LengthMs = 96000, + }, + new() + { Title = "Chapter 2", + StartOffsetMs = 100000, + StartOffsetSec = 100, + LengthMs = 100000, + }, + } + }, + new() + { Title = "Part 2", + StartOffsetMs = 200000, + StartOffsetSec = 200, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { Title = "Chapter 3", + StartOffsetMs = 202000, + StartOffsetSec = 202, + LengthMs = 98000, + }, + new() + { Title = "Chapter 4", + StartOffsetMs = 300000, + StartOffsetSec = 300, + LengthMs = 100000, + }, + } + } + } + }, + new() + { + Title = "Book 2", + StartOffsetMs = 400000, + StartOffsetSec = 400, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { Title = "Part 3", + StartOffsetMs = 402000, + StartOffsetSec = 402, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { Title = "Chapter 5", + StartOffsetMs = 404000, + StartOffsetSec = 404, + LengthMs = 96000, + }, + new() + { Title = "Chapter 6", + StartOffsetMs = 500000, + StartOffsetSec = 500, + LengthMs = 100000, + }, + } + }, + new() + { Title = "Part 4", + StartOffsetMs = 600000, + StartOffsetSec = 600, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { Title = "Chapter 7", + StartOffsetMs = 602000, + StartOffsetSec = 602, + LengthMs = 98000, + }, + new() + { Title = "Chapter 8", + StartOffsetMs = 700000, + StartOffsetSec = 700, + LengthMs = 100000, + }, + } + } + } + } + }; + + var flatChapters = DownloadDecryptBook.getChapters(hierarchicalChapters).OrderBy(c => c.StartOffsetMs).ToArray(); + + flatChapters.Length.Should().Be(flatChapters.Length); + + for (int i = 0; i < flatChapters.Length; i++) + { + flatChapters[i].Title.Should().Be(expected[i].Title); + flatChapters[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs); + flatChapters[i].StartOffsetSec.Should().Be(expected[i].StartOffsetSec); + flatChapters[i].LengthMs.Should().Be(expected[i].LengthMs); + flatChapters[i].Chapters.Should().BeNull(); + } + } + } +} From 76b5e09f722d18924e02759a9afa28ebf9bca356 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 23 Jun 2022 21:17:43 -0600 Subject: [PATCH 3/4] Add error handling around all ui click handlers for book downloads. --- .../QueryObjects/LibraryBookQueries.cs | 8 ++++ Source/LibationWinForms/Form1.Liberate.cs | 24 ++++++++--- Source/LibationWinForms/Form1.ProcessQueue.cs | 43 +++++++++++-------- Source/LibationWinForms/Form1.VisibleBooks.cs | 21 +++++++-- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/Source/DataLayer/QueryObjects/LibraryBookQueries.cs b/Source/DataLayer/QueryObjects/LibraryBookQueries.cs index 2dbd0ddc..823952e6 100644 --- a/Source/DataLayer/QueryObjects/LibraryBookQueries.cs +++ b/Source/DataLayer/QueryObjects/LibraryBookQueries.cs @@ -88,5 +88,13 @@ namespace DataLayer s.Series.AudibleSeriesId == parent.Book.AudibleProductId ) == true ).ToList(); + + public static IEnumerable UnLiberated(this IEnumerable bookList) + => bookList + .Where( + lb => + lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload + || lb.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload + ); } } diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index d9ab6667..256b977e 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -1,4 +1,5 @@ -using System; +using DataLayer; +using System; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -10,11 +11,24 @@ namespace LibationWinForms private void Configure_Liberate() { } //GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread - private async void beginBookBackupsToolStripMenuItem_Click(object _ = null, EventArgs __ = null) + private void beginBookBackupsToolStripMenuItem_Click(object _ = null, EventArgs __ = null) { - SetQueueCollapseState(false); - 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))); + try + { + SetQueueCollapseState(false); + + Serilog.Log.Logger.Information("Begin backing up all library books"); + + processBookQueue1.AddDownloadDecrypt( + ApplicationServices.DbContexts + .GetLibrary_Flat_NoTracking() + .UnLiberated() + ); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while backing up all library books"); + } } private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index 22d12329..86767aa9 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -22,27 +22,36 @@ namespace LibationWinForms this.Width = width; } - private void ProductsDisplay_LiberateClicked(object sender, LibraryBook e) + private void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook) { - if (e.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated) + try { - SetQueueCollapseState(false); - processBookQueue1.AddDownloadDecrypt(e); - } - else if (e.Book.UserDefinedItem.PdfStatus is not null and LiberatedStatus.NotLiberated) - { - SetQueueCollapseState(false); - processBookQueue1.AddDownloadPdf(e); - } - else if (e.Book.Audio_Exists()) - { - // liberated: open explorer to file - var filePath = AudibleFileStorage.Audio.GetPath(e.Book.AudibleProductId); - if (!Go.To.File(filePath?.ShortPathName)) + if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload) { - var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}"; - MessageBox.Show($"File not found" + suffix); + Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", libraryBook); + SetQueueCollapseState(false); + processBookQueue1.AddDownloadDecrypt(libraryBook); } + else if (libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated) + { + Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook); + SetQueueCollapseState(false); + processBookQueue1.AddDownloadPdf(libraryBook); + } + else if (libraryBook.Book.Audio_Exists()) + { + // liberated: open explorer to file + var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); + if (!Go.To.File(filePath?.ShortPathName)) + { + var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}"; + MessageBox.Show($"File not found" + suffix); + } + } + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook); } } diff --git a/Source/LibationWinForms/Form1.VisibleBooks.cs b/Source/LibationWinForms/Form1.VisibleBooks.cs index e44cadec..b6516870 100644 --- a/Source/LibationWinForms/Form1.VisibleBooks.cs +++ b/Source/LibationWinForms/Form1.VisibleBooks.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using ApplicationServices; +using DataLayer; using Dinah.Core.Threading; using LibationWinForms.Dialogs; @@ -48,10 +49,24 @@ namespace LibationWinForms }); } - private async void liberateVisible(object sender, EventArgs e) + private void liberateVisible(object sender, EventArgs e) { - SetQueueCollapseState(false); - await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsDisplay.GetVisible())); + try + { + SetQueueCollapseState(false); + + Serilog.Log.Logger.Information("Begin backing up visible library books"); + + processBookQueue1.AddDownloadDecrypt( + productsDisplay + .GetVisible() + .UnLiberated() + ); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while backing up visible library books"); + } } private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e) From b65875386d74925de75748f5c7309423de5b7bb4 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 23 Jun 2022 22:32:43 -0600 Subject: [PATCH 4/4] Add error handling to ProductsGrid.DataGridView_CellContentClick --- Source/FileLiberator/DownloadDecryptBook.cs | 19 ++--- .../LibationWinForms/GridView/ProductsGrid.cs | 75 ++++++++++--------- .../DownloadDecryptBookTests.cs | 6 +- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs index e6112650..a426cff2 100644 --- a/Source/FileLiberator/DownloadDecryptBook.cs +++ b/Source/FileLiberator/DownloadDecryptBook.cs @@ -165,7 +165,7 @@ namespace FileLiberator LameConfig = GetLameOptions(config) }; - var chapters = getChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList(); + var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList(); if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3) { @@ -192,7 +192,7 @@ namespace FileLiberator return dlOptions; } - public static List getChapters(IEnumerable chapters) + public static List flattenChapters(IEnumerable chapters, string titleConcat = ": ") { List chaps = new(); @@ -200,19 +200,14 @@ namespace FileLiberator { if (c.Chapters is not null) { - c.Chapters[0] = new AudibleApi.Common.Chapter - { - Title = c.Chapters[0].Title, - StartOffsetMs = c.StartOffsetMs, - StartOffsetSec = c.StartOffsetSec, - LengthMs = c.LengthMs + c.Chapters[0].LengthMs, - Chapters = c.Chapters[0].Chapters - }; + c.Chapters[0].StartOffsetMs = c.StartOffsetMs; + c.Chapters[0].StartOffsetSec = c.StartOffsetSec; + c.Chapters[0].LengthMs += c.LengthMs; - var children = getChapters(c.Chapters); + var children = flattenChapters(c.Chapters); foreach (var child in children) - child.Title = string.IsNullOrEmpty(c.Title) ? child.Title : $"{c.Title}: {child.Title}"; + child.Title = $"{c.Title}{titleConcat}{child.Title}"; chaps.AddRange(children); } diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index 578a83b8..35996f27 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -50,45 +50,52 @@ namespace LibationWinForms.GridView #region Button controls private void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) { - // handle grid button click: https://stackoverflow.com/a/13687844 - if (e.RowIndex < 0) - return; + try + { + // handle grid button click: https://stackoverflow.com/a/13687844 + if (e.RowIndex < 0) + return; - var entry = getGridEntry(e.RowIndex); - if (entry is LibraryBookEntry lbEntry) - { - if (e.ColumnIndex == liberateGVColumn.Index) - LiberateClicked?.Invoke(lbEntry); - else if (e.ColumnIndex == tagAndDetailsGVColumn.Index) - DetailsClicked?.Invoke(lbEntry); - else if (e.ColumnIndex == descriptionGVColumn.Index) - DescriptionClicked?.Invoke(lbEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false)); - else if (e.ColumnIndex == coverGVColumn.Index) - CoverClicked?.Invoke(lbEntry); - } - else if (entry is SeriesEntry sEntry) - { - if (e.ColumnIndex == liberateGVColumn.Index) + var entry = getGridEntry(e.RowIndex); + if (entry is LibraryBookEntry lbEntry) { - if (sEntry.Liberate.Expanded) - bindingList.CollapseItem(sEntry); - else - bindingList.ExpandItem(sEntry); - - sEntry.NotifyPropertyChanged(nameof(sEntry.Liberate)); - - VisibleCountChanged?.Invoke(this, bindingList.BookEntries().Count()); + if (e.ColumnIndex == liberateGVColumn.Index) + LiberateClicked?.Invoke(lbEntry); + else if (e.ColumnIndex == tagAndDetailsGVColumn.Index) + DetailsClicked?.Invoke(lbEntry); + else if (e.ColumnIndex == descriptionGVColumn.Index) + DescriptionClicked?.Invoke(lbEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false)); + else if (e.ColumnIndex == coverGVColumn.Index) + CoverClicked?.Invoke(lbEntry); } - else if (e.ColumnIndex == descriptionGVColumn.Index) - DescriptionClicked?.Invoke(sEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false)); - else if (e.ColumnIndex == coverGVColumn.Index) - CoverClicked?.Invoke(sEntry); - } + else if (entry is SeriesEntry sEntry) + { + if (e.ColumnIndex == liberateGVColumn.Index) + { + if (sEntry.Liberate.Expanded) + bindingList.CollapseItem(sEntry); + else + bindingList.ExpandItem(sEntry); - if (e.ColumnIndex == removeGVColumn.Index) + sEntry.NotifyPropertyChanged(nameof(sEntry.Liberate)); + + VisibleCountChanged?.Invoke(this, bindingList.BookEntries().Count()); + } + else if (e.ColumnIndex == descriptionGVColumn.Index) + DescriptionClicked?.Invoke(sEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false)); + else if (e.ColumnIndex == coverGVColumn.Index) + CoverClicked?.Invoke(sEntry); + } + + if (e.ColumnIndex == removeGVColumn.Index) + { + gridEntryDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); + RemovableCountChanged?.Invoke(this, EventArgs.Empty); + } + } + catch(Exception ex) { - gridEntryDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); - RemovableCountChanged?.Invoke(this, EventArgs.Empty); + Serilog.Log.Logger.Error(ex, $"An error was encountered while processing a user click in the {nameof(ProductsGrid)}"); } } diff --git a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs index 2c0726ed..a30e585f 100644 --- a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs +++ b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs @@ -185,11 +185,11 @@ namespace FileLiberator.Tests } }; - var flatChapters = DownloadDecryptBook.getChapters(hierarchicalChapters).OrderBy(c => c.StartOffsetMs).ToArray(); + var flatChapters = DownloadDecryptBook.flattenChapters(hierarchicalChapters); - flatChapters.Length.Should().Be(flatChapters.Length); + flatChapters.Count.Should().Be(expected.Length); - for (int i = 0; i < flatChapters.Length; i++) + for (int i = 0; i < flatChapters.Count; i++) { flatChapters[i].Title.Should().Be(expected[i].Title); flatChapters[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs);