From 2567ccb44c07813d1f259107c981251fec22c9d4 Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Thu, 11 Nov 2021 16:43:44 -0500 Subject: [PATCH] Enhancement: add if-series conditional logic to custom file naming. Issue #151 --- AppScaffolding/AppScaffolding.csproj | 2 +- ApplicationServices/LibraryCommands.cs | 4 +- AudibleUtilities/AudibleUtilities.csproj | 2 +- FileManager/FileManager.csproj | 2 +- LibationFileManager/TemplateTags.cs | 8 +- LibationFileManager/Templates.cs | 16 ++- .../Dialogs/EditTemplateDialog.Designer.cs | 11 +-- LibationWinForms/GridEntry.cs | 97 +++++++++---------- LibationWinForms/LibationWinForms.csproj | 2 +- LibationWinForms/ProductsGrid.cs | 4 +- .../TemplatesTests.cs | 22 ++++- 11 files changed, 100 insertions(+), 70 deletions(-) diff --git a/AppScaffolding/AppScaffolding.csproj b/AppScaffolding/AppScaffolding.csproj index a30ef065..c6ee6f0c 100644 --- a/AppScaffolding/AppScaffolding.csproj +++ b/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net5.0 - 6.4.3.1 + 6.4.4.1 diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs index 29f413f2..963e4ce2 100644 --- a/ApplicationServices/LibraryCommands.cs +++ b/ApplicationServices/LibraryCommands.cs @@ -167,8 +167,8 @@ namespace ApplicationServices { logTime("importIntoDbAsync -- pre db"); using var context = DbContexts.GetContext(); - var libraryImporter = new LibraryBookImporter(context); - var newCount = await Task.Run(() => libraryImporter.Import(importItems)); + var libraryBookImporter = new LibraryBookImporter(context); + var newCount = await Task.Run(() => libraryBookImporter.Import(importItems)); logTime("importIntoDbAsync -- post Import()"); var qtyChanges = context.SaveChanges(); logTime("importIntoDbAsync -- post SaveChanges"); diff --git a/AudibleUtilities/AudibleUtilities.csproj b/AudibleUtilities/AudibleUtilities.csproj index 5d5f1c81..467a4404 100644 --- a/AudibleUtilities/AudibleUtilities.csproj +++ b/AudibleUtilities/AudibleUtilities.csproj @@ -5,7 +5,7 @@ - + diff --git a/FileManager/FileManager.csproj b/FileManager/FileManager.csproj index 3a6458c4..4ac2e23d 100644 --- a/FileManager/FileManager.csproj +++ b/FileManager/FileManager.csproj @@ -5,7 +5,7 @@ - + diff --git a/LibationFileManager/TemplateTags.cs b/LibationFileManager/TemplateTags.cs index c3476fe7..ac5005d2 100644 --- a/LibationFileManager/TemplateTags.cs +++ b/LibationFileManager/TemplateTags.cs @@ -21,8 +21,8 @@ namespace LibationFileManager // putting these first is the incredibly lazy way to make them show up first in the EditTemplateDialog public static TemplateTags ChCount { get; } = new TemplateTags("ch count", "Number of chapters", true); public static TemplateTags ChTitle { get; } = new TemplateTags("ch title", "Chapter title", true); - public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter number", true); - public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter number with leading zeros", true); + public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter #", true); + public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter # with leading zeros", true); public static TemplateTags Id { get; } = new TemplateTags("id", "Audible ID"); public static TemplateTags Title { get; } = new TemplateTags("title", "Full title"); @@ -36,5 +36,9 @@ namespace LibationFileManager public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series"); public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book"); public static TemplateTags Locale { get; } = new TemplateTags("locale", "Region/country"); + + // Special case. Isn't mapped to a replacement in Templates.cs + // Included here for display by EditTemplateDialog + public static TemplateTags IfSeries { get; } = new TemplateTags("if series->...<-if series", "Only include if part of a series"); } } diff --git a/LibationFileManager/Templates.cs b/LibationFileManager/Templates.cs index c75e4b81..4c427de5 100644 --- a/LibationFileManager/Templates.cs +++ b/LibationFileManager/Templates.cs @@ -1,9 +1,10 @@ -using Dinah.Core; -using FileManager; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; +using Dinah.Core; +using FileManager; namespace LibationFileManager { @@ -106,12 +107,21 @@ namespace LibationFileManager : getFileNamingTemplate(libraryBookDto, template, null, null) .GetFilePath(); + private static Regex ifSeriesRegex { get; } = new Regex("(.*?)<-if series>", RegexOptions.Compiled | RegexOptions.IgnoreCase); + internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension) { ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto)); dirFullPath = dirFullPath?.Trim() ?? ""; + + // for non-series, remove and <-if series> tags and everything in between + // for series, remove and <-if series> tags, what's in between will remain + template = ifSeriesRegex.Replace( + template, + string.IsNullOrWhiteSpace(libraryBookDto.SeriesName) ? "" : "$1"); + var t = template + FileUtility.GetStandardizedExtension(extension); var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t); diff --git a/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs b/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs index 5afd6a12..deea82d3 100644 --- a/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs +++ b/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs @@ -114,12 +114,12 @@ // columnHeader1 // this.columnHeader1.Text = "Tag"; - this.columnHeader1.Width = 90; + this.columnHeader1.Width = 137; // // columnHeader2 // this.columnHeader2.Text = "Description"; - this.columnHeader2.Width = 230; + this.columnHeader2.Width = 170; // // richTextBox1 // @@ -137,14 +137,13 @@ // warningsLbl // this.warningsLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.warningsLbl.AutoSize = true; this.warningsLbl.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); this.warningsLbl.ForeColor = System.Drawing.Color.Firebrick; this.warningsLbl.Location = new System.Drawing.Point(346, 262); this.warningsLbl.Name = "warningsLbl"; - this.warningsLbl.Size = new System.Drawing.Size(14, 15); - this.warningsLbl.TabIndex = 100; - this.warningsLbl.Text = "6"; + this.warningsLbl.Size = new System.Drawing.Size(574, 77); + this.warningsLbl.TabIndex = 6; + this.warningsLbl.Text = "[warnings]"; // // exampleLbl // diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index ca183e11..822b4b2a 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -18,7 +18,7 @@ namespace LibationWinForms /// internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable { - #region implementation properties + #region implementation properties NOT exposed to the view // hide from public fields from Data Source GUI with [Browsable(false)] [Browsable(false)] @@ -28,10 +28,52 @@ namespace LibationWinForms #endregion + #region Model properties exposed to the view + private Image _cover; + public Image Cover + { + get => _cover; + private set + { + _cover = value; + NotifyPropertyChanged(); + } + } + + public string ProductRating { get; } + public string PurchaseDate { get; } + public string MyRating { get; } + public string Series { get; } + public string Title { get; } + public string Length { get; } + public string Authors { get; } + public string Narrators { get; } + public string Category { get; } + public string Misc { get; } + public string Description { get; } + public string DisplayTags + { + get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); + set => Book.UserDefinedItem.Tags = value; + } + + // these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint + public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate + { + get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book)); + + set + { + LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus; + LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus; + } + } + + #endregion + public event EventHandler Committed; private Book Book => LibraryBook.Book; - private Image _cover; public GridEntry(LibraryBook libraryBook) { @@ -145,53 +187,13 @@ namespace LibationWinForms Committed?.Invoke(this, null); } - #endregion - - #region Model properties exposed to the view - public Image Cover - { - get - { - return _cover; - } - private set - { - _cover = value; - NotifyPropertyChanged(); - } - } - - public string ProductRating { get; } - public string PurchaseDate { get; } - public string MyRating { get; } - public string Series { get; } - public string Title { get; } - public string Length { get; } - public string Authors { get; } - public string Narrators { get; } - public string Category { get; } - public string Misc { get; } - public string Description { get; } - public string DisplayTags - { - get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); - set => Book.UserDefinedItem.Tags = value; - } - // these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint - public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate - { - get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book)); - - set - { - LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus; - LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus; - } - } - #endregion #region Data Sorting + // These methods are implementation of Dinah.Core.DataBinding.IMemberComparable + // Used by Dinah.Core.DataBinding.SortableBindingList for all sorting + public virtual object GetMemberValue(string memberName) => _memberValues[memberName](); + public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType]; private Dictionary> _memberValues { get; } @@ -225,9 +227,6 @@ namespace LibationWinForms { typeof(LiberatedStatus), new ObjectComparer() }, }; - public virtual object GetMemberValue(string memberName) => _memberValues[memberName](); - public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType]; - private static readonly string[] _sortPrefixIgnores = { "the", "a", "an" }; private static string GetSortName(string unformattedName) { diff --git a/LibationWinForms/LibationWinForms.csproj b/LibationWinForms/LibationWinForms.csproj index ca0f987d..ab57fec8 100644 --- a/LibationWinForms/LibationWinForms.csproj +++ b/LibationWinForms/LibationWinForms.csproj @@ -29,7 +29,7 @@ - + diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 1154db8f..0fc13de5 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -71,7 +71,7 @@ namespace LibationWinForms } } - private async Task Liberate_Click(GridEntry liveGridEntry) + private static async Task Liberate_Click(GridEntry liveGridEntry) { var libraryBook = liveGridEntry.LibraryBook; @@ -91,7 +91,7 @@ namespace LibationWinForms await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook); } - private void Details_Click(GridEntry liveGridEntry) + private static void Details_Click(GridEntry liveGridEntry) { var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook); if (bookDetailsForm.ShowDialog() != DialogResult.OK) diff --git a/_Tests/LibationFileManager.Tests/TemplatesTests.cs b/_Tests/LibationFileManager.Tests/TemplatesTests.cs index 080cde1b..bd8d6db9 100644 --- a/_Tests/LibationFileManager.Tests/TemplatesTests.cs +++ b/_Tests/LibationFileManager.Tests/TemplatesTests.cs @@ -13,7 +13,7 @@ namespace TemplatesTests { public static class Shared { - public static LibraryBookDto GetLibraryBook(string asin) + public static LibraryBookDto GetLibraryBook(string asin, string seriesName = "Sherlock Holmes") => new() { Account = "my account", @@ -22,7 +22,7 @@ namespace TemplatesTests Locale = "us", Authors = new List { "Arthur Conan Doyle", "Stephen Fry - introductions" }, Narrators = new List { "Stephen Fry" }, - SeriesName = "Sherlock Holmes", + SeriesName = seriesName ?? "", SeriesNumber = "1" }; } @@ -75,6 +75,24 @@ namespace TemplatesTests => Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension) .GetFilePath() .Should().Be(expected); + + [TestMethod] + public void IfSeries_empty() + => Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<-if series>bar", @"C:\a\b", "ext") + .GetFilePath() + .Should().Be(@"C:\a\b\foobar.ext"); + + [TestMethod] + public void IfSeries_no_series() + => Templates.getFileNamingTemplate(GetLibraryBook("asin", ""), "foo---<-if series>bar", @"C:\a\b", "ext") + .GetFilePath() + .Should().Be(@"C:\a\b\foobar.ext"); + + [TestMethod] + public void IfSeries_with_series() + => Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo---<-if series>bar", @"C:\a\b", "ext") + .GetFilePath() + .Should().Be(@"C:\a\b\foo-Sherlock Holmes-asin-bar.ext"); } }