From c837fefbdd20972a3b8f58f715703e19cd102290 Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Tue, 2 Nov 2021 14:26:11 -0400 Subject: [PATCH] template file naming: code complete. Clean up and testing remain --- AaxDecrypter/AaxcDownloadMultiConverter.cs | 24 ++- AaxDecrypter/MultiConvertFileProperties.cs | 25 +++ AppScaffolding/AppScaffolding.csproj | 2 +- FileLiberator/AudioFileStorageExt.cs | 100 +++++++----- FileLiberator/UtilityExtensions.cs | 17 ++ ...{FileTemplate.cs => FileNamingTemplate.cs} | 4 +- LibationFileManager/Configuration.cs | 8 +- LibationFileManager/LibraryBookDto.cs | 29 ++++ LibationFileManager/TemplateTags.cs | 13 +- LibationFileManager/Templates.cs | 55 +++++-- LibationFileManager/UtilityExtensions.cs | 13 ++ .../Dialogs/EditTemplateDialog.Designer.cs | 57 ++++--- .../Dialogs/EditTemplateDialog.cs | 145 +++++++++++------- .../AudioFileStorageExtTests.cs | 34 ++-- ...ateTests.cs => FileNamingTemplateTests.cs} | 33 ++-- __README - COLLABORATORS.txt | 2 +- 16 files changed, 369 insertions(+), 192 deletions(-) create mode 100644 AaxDecrypter/MultiConvertFileProperties.cs rename FileManager/{FileTemplate.cs => FileNamingTemplate.cs} (93%) create mode 100644 LibationFileManager/LibraryBookDto.cs create mode 100644 LibationFileManager/UtilityExtensions.cs rename _Tests/FileManager.Tests/{FileTemplateTests.cs => FileNamingTemplateTests.cs} (69%) diff --git a/AaxDecrypter/AaxcDownloadMultiConverter.cs b/AaxDecrypter/AaxcDownloadMultiConverter.cs index f1c676b1..79560158 100644 --- a/AaxDecrypter/AaxcDownloadMultiConverter.cs +++ b/AaxDecrypter/AaxcDownloadMultiConverter.cs @@ -12,23 +12,13 @@ namespace AaxDecrypter { protected override StepSequence Steps { get; } - private Func multipartFileNameCallback { get; } - private static string DefaultMultipartFilename(string outputFileName, int partsPosition, int partsTotal, NewSplitCallback newSplitCallback) - { - var template = Path.ChangeExtension(outputFileName, null) + " - - " + Path.GetExtension(outputFileName); - - var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " }; - fileTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); - fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? ""); - - return fileTemplate.GetFilePath(); - } + private Func<MultiConvertFileProperties, string> multipartFileNameCallback { get; } private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3); private List<string> multiPartFilePaths { get; } = new List<string>(); public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat, - Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback = null) + Func<MultiConvertFileProperties, string> multipartFileNameCallback = null) : base(outFileName, cacheDirectory, dlLic, outputFormat) { Steps = new StepSequence @@ -39,7 +29,7 @@ namespace AaxDecrypter ["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter, ["Step 3: Cleanup"] = Step_Cleanup, }; - this.multipartFileNameCallback = multipartFileNameCallback ?? DefaultMultipartFilename; + this.multipartFileNameCallback = multipartFileNameCallback ?? MultiConvertFileProperties.DefaultMultipartFilename; } /* @@ -133,7 +123,13 @@ That naming may not be desirable for everyone, but it's an easy change to instea private void createOutputFileStream(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback) { - var fileName = multipartFileNameCallback(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback); + var fileName = multipartFileNameCallback(new() + { + OutputFileName = OutputFileName, + PartsPosition = currentChapter, + PartsTotal = splitChapters.Count, + Title = newSplitCallback?.Chapter?.Title + }); fileName = FileUtility.GetValidFilename(fileName); multiPartFilePaths.Add(fileName); diff --git a/AaxDecrypter/MultiConvertFileProperties.cs b/AaxDecrypter/MultiConvertFileProperties.cs new file mode 100644 index 00000000..febc2a49 --- /dev/null +++ b/AaxDecrypter/MultiConvertFileProperties.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using FileManager; + +namespace AaxDecrypter +{ + public class MultiConvertFileProperties + { + public string OutputFileName { get; set; } + public int PartsPosition { get; set; } + public int PartsTotal { get; set; } + public string Title { get; set; } + + public static string DefaultMultipartFilename(MultiConvertFileProperties multiConvertFileProperties) + { + var template = Path.ChangeExtension(multiConvertFileProperties.OutputFileName, null) + " - <ch# 0> - <title>" + Path.GetExtension(multiConvertFileProperties.OutputFileName); + + var fileNamingTemplate = new FileNamingTemplate(template) { IllegalCharacterReplacements = " " }; + fileNamingTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(multiConvertFileProperties.PartsPosition, multiConvertFileProperties.PartsTotal)); + fileNamingTemplate.AddParameterReplacement("title", multiConvertFileProperties.Title ?? ""); + + return fileNamingTemplate.GetFilePath(); + } + } +} diff --git a/AppScaffolding/AppScaffolding.csproj b/AppScaffolding/AppScaffolding.csproj index 444aabf4..cfabee5d 100644 --- a/AppScaffolding/AppScaffolding.csproj +++ b/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ <PropertyGroup> <TargetFramework>net5.0</TargetFramework> - <Version>6.3.4.1</Version> + <Version>6.3.4.19</Version> </PropertyGroup> <ItemGroup> diff --git a/FileLiberator/AudioFileStorageExt.cs b/FileLiberator/AudioFileStorageExt.cs index 080eba90..ed6b7d0f 100644 --- a/FileLiberator/AudioFileStorageExt.cs +++ b/FileLiberator/AudioFileStorageExt.cs @@ -11,72 +11,94 @@ namespace FileLiberator { public static class AudioFileStorageExt { - private static void AddParameterReplacement(this FileTemplate fileTemplate, TemplateTags templateTags, object value) - => fileTemplate.AddParameterReplacement(templateTags.TagName, value); - - internal class MultipartRenamer + public class MultipartRenamer { - public LibraryBook libraryBook { get; } + private LibraryBookDto libraryBookDto { get; } - public MultipartRenamer(LibraryBook libraryBook) => this.libraryBook = libraryBook; + public MultipartRenamer(LibraryBook libraryBook) : this(libraryBook.ToDto()) { } + public MultipartRenamer(LibraryBookDto libraryBookDto) => this.libraryBookDto = libraryBookDto; - internal string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback) - => MultipartFilename(Configuration.Instance.ChapterFileTemplate, AudibleFileStorage.DecryptInProgressDirectory, Path.GetExtension(outputFileName), partsPosition, partsTotal, newSplitCallback?.Chapter?.Title ?? ""); + internal string MultipartFilename(AaxDecrypter.MultiConvertFileProperties props) + => MultipartFilename(props, Configuration.Instance.ChapterFileTemplate, AudibleFileStorage.DecryptInProgressDirectory); - internal string MultipartFilename(string template, string fullDirPath, string extension, int partsPosition, int partsTotal, string chapterTitle) + public string MultipartFilename(AaxDecrypter.MultiConvertFileProperties props, string template, string fullDirPath) { - var fileTemplate = GetFileTemplateSingle(template, libraryBook, fullDirPath, extension); + var fileNamingTemplate = GetFileNamingTemplate(template, libraryBookDto, fullDirPath, Path.GetExtension(props.OutputFileName)); - fileTemplate.AddParameterReplacement(TemplateTags.ChCount, partsTotal); - fileTemplate.AddParameterReplacement(TemplateTags.ChNumber, partsPosition); - fileTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); - fileTemplate.AddParameterReplacement(TemplateTags.ChTitle, chapterTitle); + fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal); + fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber, props.PartsPosition); + fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(props.PartsPosition, props.PartsTotal)); + fileNamingTemplate.AddParameterReplacement(TemplateTags.ChTitle, props.Title ?? ""); - return fileTemplate.GetFilePath(); + return fileNamingTemplate.GetFilePath(); } } - public static Func<string, int, int, AAXClean.NewSplitCallback, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook) + public static Func<AaxDecrypter.MultiConvertFileProperties, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook) => new MultipartRenamer(libraryBook).MultipartFilename; + public static Func<AaxDecrypter.MultiConvertFileProperties, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBookDto libraryBookDto) + => new MultipartRenamer(libraryBookDto).MultipartFilename; + /// <summary> + /// DownloadDecryptBook: + /// Path: in progress directory. + /// File name: final file name. + /// </summary> public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) - => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.DecryptInProgressDirectory, extension); - - public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) - => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.BooksDirectory, extension); + => GetFileNamingTemplate(Configuration.Instance.FileTemplate, libraryBook.ToDto(), AudibleFileStorage.DecryptInProgressDirectory, extension) + .GetFilePath(); + /// <summary> + /// DownloadDecryptBook: + /// File path for where to move files into. + /// Path: directory nested inside of Books directory + /// File name: n/a + /// </summary> public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook) - => GetFileTemplateSingle(Configuration.Instance.FolderTemplate, libraryBook, AudibleFileStorage.BooksDirectory, null) + => GetFileNamingTemplate(Configuration.Instance.FolderTemplate, libraryBook.ToDto(), AudibleFileStorage.BooksDirectory, null) .GetFilePath(); + /// <summary> + /// PDF: audio file does not exist + /// </summary> + public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) + => GetFileNamingTemplate(Configuration.Instance.FileTemplate, libraryBook.ToDto(), AudibleFileStorage.BooksDirectory, extension) + .GetFilePath(); + + /// <summary> + /// PDF: audio file already exists + /// </summary> public static string GetCustomDirFilename(this AudioFileStorage _, LibraryBook libraryBook, string dirFullPath, string extension) - => GetFileTemplateSingle(Configuration.Instance.FileTemplate, libraryBook, dirFullPath, extension) + => GetFileNamingTemplate(Configuration.Instance.FileTemplate, libraryBook.ToDto(), dirFullPath, extension) .GetFilePath(); - internal static FileTemplate GetFileTemplateSingle(string template, LibraryBook libraryBook, string dirFullPath, string extension) + public static FileNamingTemplate GetFileNamingTemplate(string template, LibraryBookDto libraryBookDto, string dirFullPath, string extension) { ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); - ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)); - ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath)); + ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto)); - var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension)); - var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" }; + dirFullPath = dirFullPath?.Trim() ?? ""; + var t = template + FileUtility.GetStandardizedExtension(extension); + var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t); - var title = libraryBook.Book.Title ?? ""; + var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" }; - fileTemplate.AddParameterReplacement(TemplateTags.Id, libraryBook.Book.AudibleProductId); - fileTemplate.AddParameterReplacement(TemplateTags.Title, title); - fileTemplate.AddParameterReplacement(TemplateTags.TitleShort, title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'))); - fileTemplate.AddParameterReplacement(TemplateTags.Author, libraryBook.Book.AuthorNames); - fileTemplate.AddParameterReplacement(TemplateTags.FirstAuthor, libraryBook.Book.Authors.FirstOrDefault()?.Name); - fileTemplate.AddParameterReplacement(TemplateTags.Narrator, libraryBook.Book.NarratorNames); - fileTemplate.AddParameterReplacement(TemplateTags.FirstNarrator, libraryBook.Book.Narrators.FirstOrDefault()?.Name); + var title = libraryBookDto.Title ?? ""; + var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':')); - var seriesLink = libraryBook.Book.SeriesLink.FirstOrDefault(); - fileTemplate.AddParameterReplacement(TemplateTags.Series, seriesLink?.Series.Name); - fileTemplate.AddParameterReplacement(TemplateTags.SeriesNumber, seriesLink?.Order); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Id, libraryBookDto.AudibleProductId); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Title, title); + fileNamingTemplate.AddParameterReplacement(TemplateTags.TitleShort, titleShort); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Author, libraryBookDto.AuthorNames); + fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstAuthor, libraryBookDto.FirstAuthor); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Narrator, libraryBookDto.NarratorNames); + fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstNarrator, libraryBookDto.FirstNarrator); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Series, libraryBookDto.SeriesName); + fileNamingTemplate.AddParameterReplacement(TemplateTags.SeriesNumber, libraryBookDto.SeriesNumber); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Account, libraryBookDto.Account); + fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale); - return fileTemplate; + return fileNamingTemplate; } } } diff --git a/FileLiberator/UtilityExtensions.cs b/FileLiberator/UtilityExtensions.cs index 08bf8c6c..b6516ba9 100644 --- a/FileLiberator/UtilityExtensions.cs +++ b/FileLiberator/UtilityExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using DataLayer; using Dinah.Core; +using LibationFileManager; namespace FileLiberator { @@ -22,5 +23,21 @@ namespace FileLiberator var apiExtended = await AudibleUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale); return apiExtended.Api; } + + public static LibraryBookDto ToDto(this LibraryBook libraryBook) => new() + { + Account = libraryBook.Account, + + AudibleProductId = libraryBook.Book.AudibleProductId, + Title = libraryBook.Book.Title ?? "", + Locale = libraryBook.Book.Locale, + + Authors = libraryBook.Book.Authors.Select(c => c.Name).ToList(), + + Narrators = libraryBook.Book.Narrators.Select(c => c.Name).ToList(), + + SeriesName = libraryBook.Book.SeriesLink.FirstOrDefault()?.Series.Name, + SeriesNumber = libraryBook.Book.SeriesLink.FirstOrDefault()?.Order + }; } } diff --git a/FileManager/FileTemplate.cs b/FileManager/FileNamingTemplate.cs similarity index 93% rename from FileManager/FileTemplate.cs rename to FileManager/FileNamingTemplate.cs index 6848aba0..0b4dca26 100644 --- a/FileManager/FileTemplate.cs +++ b/FileManager/FileNamingTemplate.cs @@ -6,13 +6,13 @@ using Dinah.Core; namespace FileManager { /// <summary>Get valid filename. Advanced features incl. parameterized template</summary> - public class FileTemplate + public class FileNamingTemplate { /// <summary>Proposed full file path. May contain optional html-styled template tags. Eg: <name></summary> public string Template { get; } /// <param name="template">Proposed file name with optional html-styled template tags.</param> - public FileTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); + public FileNamingTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); /// <summary>Optional step 1: Replace html-styled template tags with parameters. Eg {"name", "Bill Gates"} => /<name>/ => /Bill Gates/</summary> public Dictionary<string, object> ParameterReplacements { get; } = new Dictionary<string, object>(); diff --git a/LibationFileManager/Configuration.cs b/LibationFileManager/Configuration.cs index c56a5576..77ae9a32 100644 --- a/LibationFileManager/Configuration.cs +++ b/LibationFileManager/Configuration.cs @@ -170,14 +170,10 @@ namespace LibationFileManager set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value); } - private string getTemplate(string settingName, Templates templ) - { - var value = persistentDictionary.GetString(settingName)?.Trim(); - return templ.IsValid(value) ? value : templ.DefaultTemplate; - } + private string getTemplate(string settingName, Templates templ) => templ.GetValid(persistentDictionary.GetString(settingName)); private void setTemplate(string settingName, Templates templ, string newValue) { - var template = newValue.Trim(); + var template = newValue?.Trim(); if (templ.IsValid(template)) persistentDictionary.SetString(settingName, template); } diff --git a/LibationFileManager/LibraryBookDto.cs b/LibationFileManager/LibraryBookDto.cs new file mode 100644 index 00000000..a3d0c690 --- /dev/null +++ b/LibationFileManager/LibraryBookDto.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LibationFileManager +{ + public class BookDto + { + public string AudibleProductId { get; set; } + public string Title { get; set; } + public string Locale { get; set; } + + public IEnumerable<string> Authors { get; set; } + public string AuthorNames => string.Join(", ", Authors); + public string FirstAuthor => Authors.FirstOrDefault(); + + public IEnumerable<string> Narrators { get; set; } + public string NarratorNames => string.Join(", ", Narrators); + public string FirstNarrator => Narrators.FirstOrDefault(); + + public string SeriesName { get; set; } + public string SeriesNumber { get; set; } + } + + public class LibraryBookDto : BookDto + { + public string Account { get; set; } + } +} diff --git a/LibationFileManager/TemplateTags.cs b/LibationFileManager/TemplateTags.cs index 5b16e316..c9dfdda8 100644 --- a/LibationFileManager/TemplateTags.cs +++ b/LibationFileManager/TemplateTags.cs @@ -18,6 +18,12 @@ namespace LibationFileManager IsChapterOnly = isChapterOnly; } + // putting these first is the incredibly lazy way to make them show up first in the settings dialog + 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 Id { get; } = new TemplateTags("id", "Audible ID"); public static TemplateTags Title { get; } = new TemplateTags("title", "Full title"); public static TemplateTags TitleShort { get; } = new TemplateTags("title short", "Title. Stop at first colon"); @@ -28,10 +34,7 @@ namespace LibationFileManager public static TemplateTags Series { get; } = new TemplateTags("series", "Name of series"); // can't also have a leading zeros version. Too many weird edge cases. Eg: "1-4" public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series"); - - 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 Account { get; } = new TemplateTags("account", "Audible account of this book"); + public static TemplateTags Locale { get; } = new TemplateTags("locale", "Region/country"); } } diff --git a/LibationFileManager/Templates.cs b/LibationFileManager/Templates.cs index 934a7d08..ff3d2b61 100644 --- a/LibationFileManager/Templates.cs +++ b/LibationFileManager/Templates.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; namespace LibationFileManager @@ -8,7 +9,7 @@ namespace LibationFileManager { protected static string[] Valid => Array.Empty<string>(); public const string ERROR_NULL_IS_INVALID = "Null template is invalid."; - public const string ERROR_FULL_PATH_IS_INVALID = @"No full paths allowed. Eg: should not start with C:\"; + public const string ERROR_FULL_PATH_IS_INVALID = @"No colons or full paths allowed. Eg: should not start with C:\"; public const string ERROR_INVALID_FILE_NAME_CHAR = @"Only file name friendly characters allowed. Eg: no colons or slashes"; public const string WARNING_EMPTY = "Template is empty."; @@ -26,6 +27,40 @@ namespace LibationFileManager public abstract string DefaultTemplate { get; } protected abstract bool IsChapterized { get; } + internal string GetValid(string configValue) + { + var value = configValue?.Trim(); + return IsValid(value) ? value : DefaultTemplate; + } + + public static string Sanitize(string template) + { + var value = template ?? ""; + + // don't use alt slash + value = value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + + // don't allow double slashes + var sing = $"{Path.DirectorySeparatorChar}"; + var dbl = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}"; + while (value.Contains(dbl)) + value = value.Replace(dbl, sing); + + // trim. don't start or end with slash + while (true) + { + var start = value.Length; + value = value + .Trim() + .Trim(Path.DirectorySeparatorChar); + var end = value.Length; + if (start == end) + break; + } + + return value; + } + public abstract IEnumerable<string> GetErrors(string template); public bool IsValid(string template) => !GetErrors(template).Any(); @@ -51,7 +86,7 @@ namespace LibationFileManager public static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>"); - protected static string[] getFileErrors(string template) + protected static string[] GetFileErrors(string template) { // File name only; not path. all other path chars are valid enough to pass this check and will be handled on final save. @@ -60,15 +95,15 @@ namespace LibationFileManager return new[] { ERROR_NULL_IS_INVALID }; if (template.Contains(':') - || template.Contains(System.IO.Path.DirectorySeparatorChar) - || template.Contains(System.IO.Path.AltDirectorySeparatorChar) + || template.Contains(Path.DirectorySeparatorChar) + || template.Contains(Path.AltDirectorySeparatorChar) ) return new[] { ERROR_INVALID_FILE_NAME_CHAR }; return Valid; } - protected IEnumerable<string> getWarnings(string template) + protected IEnumerable<string> GetStandardWarnings(string template) { var warnings = GetErrors(template).ToList(); if (template is null) @@ -108,7 +143,7 @@ namespace LibationFileManager return Valid; } - public override IEnumerable<string> GetWarnings(string template) => getWarnings(template); + public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template); } private class FileTemplate : Templates @@ -118,9 +153,9 @@ namespace LibationFileManager public override string DefaultTemplate { get; } = "<title> [<id>]"; protected override bool IsChapterized { get; } = false; - public override IEnumerable<string> GetErrors(string template) => getFileErrors(template); + public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template); - public override IEnumerable<string> GetWarnings(string template) => getWarnings(template); + public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template); } private class ChapterFileTemplate : Templates @@ -130,11 +165,11 @@ namespace LibationFileManager public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>"; protected override bool IsChapterized { get; } = true; - public override IEnumerable<string> GetErrors(string template) => getFileErrors(template); + public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template); public override IEnumerable<string> GetWarnings(string template) { - var warnings = getWarnings(template).ToList(); + var warnings = GetStandardWarnings(template).ToList(); if (template is null) return warnings; diff --git a/LibationFileManager/UtilityExtensions.cs b/LibationFileManager/UtilityExtensions.cs new file mode 100644 index 00000000..c731c6b8 --- /dev/null +++ b/LibationFileManager/UtilityExtensions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FileManager; + +namespace LibationFileManager +{ + public static class UtilityExtensions + { + public static void AddParameterReplacement(this FileNamingTemplate fileNamingTemplate, TemplateTags templateTags, object value) + => fileNamingTemplate.AddParameterReplacement(templateTags.TagName, value); + } +} diff --git a/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs b/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs index 7bf11cce..790751cc 100644 --- a/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs +++ b/LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs @@ -33,16 +33,17 @@ this.templateTb = new System.Windows.Forms.TextBox(); this.templateLbl = new System.Windows.Forms.Label(); this.resetToDefaultBtn = new System.Windows.Forms.Button(); - this.outputTb = new System.Windows.Forms.TextBox(); this.listView1 = new System.Windows.Forms.ListView(); this.columnHeader1 = new System.Windows.Forms.ColumnHeader(); this.columnHeader2 = new System.Windows.Forms.ColumnHeader(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.warningsLbl = new System.Windows.Forms.Label(); this.SuspendLayout(); // // saveBtn // this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.saveBtn.Location = new System.Drawing.Point(714, 496); + this.saveBtn.Location = new System.Drawing.Point(714, 345); this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.saveBtn.Name = "saveBtn"; this.saveBtn.Size = new System.Drawing.Size(88, 27); @@ -55,7 +56,7 @@ // this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.cancelBtn.Location = new System.Drawing.Point(832, 496); + this.cancelBtn.Location = new System.Drawing.Point(832, 345); this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.cancelBtn.Name = "cancelBtn"; this.cancelBtn.Size = new System.Drawing.Size(88, 27); @@ -94,19 +95,6 @@ this.resetToDefaultBtn.UseVisualStyleBackColor = true; this.resetToDefaultBtn.Click += new System.EventHandler(this.resetToDefaultBtn_Click); // - // outputTb - // - this.outputTb.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.outputTb.Location = new System.Drawing.Point(346, 56); - this.outputTb.Multiline = true; - this.outputTb.Name = "outputTb"; - this.outputTb.ReadOnly = true; - this.outputTb.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.outputTb.Size = new System.Drawing.Size(574, 434); - this.outputTb.TabIndex = 4; - // // listView1 // this.listView1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -117,8 +105,8 @@ this.listView1.HideSelection = false; this.listView1.Location = new System.Drawing.Point(12, 56); this.listView1.Name = "listView1"; - this.listView1.Size = new System.Drawing.Size(328, 434); - this.listView1.TabIndex = 100; + this.listView1.Size = new System.Drawing.Size(328, 283); + this.listView1.TabIndex = 3; this.listView1.UseCompatibleStateImageBehavior = false; this.listView1.View = System.Windows.Forms.View.Details; // @@ -132,15 +120,41 @@ this.columnHeader2.Text = "Description"; this.columnHeader2.Width = 230; // + // richTextBox1 + // + this.richTextBox1.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.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.richTextBox1.Location = new System.Drawing.Point(346, 56); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.ReadOnly = true; + this.richTextBox1.Size = new System.Drawing.Size(574, 203); + this.richTextBox1.TabIndex = 4; + this.richTextBox1.Text = ""; + // + // 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(40, 15); + this.warningsLbl.TabIndex = 100; + this.warningsLbl.Text = "label1"; + // // EditTemplateDialog // this.AcceptButton = this.saveBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.cancelBtn; - this.ClientSize = new System.Drawing.Size(933, 539); + this.ClientSize = new System.Drawing.Size(933, 388); + this.Controls.Add(this.warningsLbl); + this.Controls.Add(this.richTextBox1); this.Controls.Add(this.listView1); - this.Controls.Add(this.outputTb); this.Controls.Add(this.resetToDefaultBtn); this.Controls.Add(this.templateLbl); this.Controls.Add(this.templateTb); @@ -163,9 +177,10 @@ private System.Windows.Forms.TextBox templateTb; private System.Windows.Forms.Label templateLbl; private System.Windows.Forms.Button resetToDefaultBtn; - private System.Windows.Forms.TextBox outputTb; private System.Windows.Forms.ListView listView1; private System.Windows.Forms.ColumnHeader columnHeader1; private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.Label warningsLbl; } } \ No newline at end of file diff --git a/LibationWinForms/Dialogs/EditTemplateDialog.cs b/LibationWinForms/Dialogs/EditTemplateDialog.cs index 03c668a6..b1e339ca 100644 --- a/LibationWinForms/Dialogs/EditTemplateDialog.cs +++ b/LibationWinForms/Dialogs/EditTemplateDialog.cs @@ -10,8 +10,19 @@ namespace LibationWinForms.Dialogs { public partial class EditTemplateDialog : Form { + // final valid value public string TemplateText { get; private set; } + // work-in-progress. not guaranteed to be valid + private string _workingTemplateText; + private string workingTemplateText + { + get => _workingTemplateText; + set => _workingTemplateText = Templates.Sanitize(value); + } + + private void resetTextBox(string value) => this.templateTb.Text = workingTemplateText = value; + private Configuration config { get; } = Configuration.Instance; private Templates template { get; } @@ -35,108 +46,124 @@ namespace LibationWinForms.Dialogs return; } + warningsLbl.Text = ""; + this.Text = $"Edit {template.Name}"; this.templateLbl.Text = template.Description; - this.templateTb.Text = inputTemplateText; + resetTextBox(inputTemplateText); // populate list view foreach (var tag in template.GetTemplateTags()) listView1.Items.Add(new ListViewItem(new[] { $"<{tag.TagName}>", tag.Description })); } - private void resetToDefaultBtn_Click(object sender, EventArgs e) => templateTb.Text = template.DefaultTemplate; + private void resetToDefaultBtn_Click(object sender, EventArgs e) => resetTextBox(template.DefaultTemplate); private void templateTb_TextChanged(object sender, EventArgs e) { - var t = templateTb.Text; + workingTemplateText = templateTb.Text; - var warnings - = !template.HasWarnings(t) - ? "" - : "Warnings:\r\n" + - template - .GetWarnings(t) - .Select(err => $"- {err}") - .Aggregate((a, b) => $"{a}\r\n{b}"); + var isFolder = template == Templates.Folder; + var libraryBookDto = new LibraryBookDto + { + Account = "my account", + AudibleProductId = "123456789", + Title = "A Study in Scarlet: A Sherlock Holmes Novel", + Locale = "us", + Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" }, + Narrators = new List<string> { "Stephen Fry" }, + SeriesName = "Sherlock Holmes", + SeriesNumber = "1" + }; + var chapterName = "A Flight for Life"; + var chapterNumber = 4; + var chaptersTotal = 10; var books = config.Books; - var folderTemplate = template == Templates.Folder ? t : config.FolderTemplate; - folderTemplate = folderTemplate.Trim().Trim(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Trim(); - var fileTemplate = template == Templates.Folder ? config.FileTemplate : t; - fileTemplate = fileTemplate.Trim(); + var folder = FileLiberator.AudioFileStorageExt.GetFileNamingTemplate( + isFolder ? workingTemplateText : config.FolderTemplate, + libraryBookDto, + null, + null) + .GetFilePath(); + var file + = (template == Templates.ChapterFile) + ? new FileLiberator.AudioFileStorageExt.MultipartRenamer(libraryBookDto).MultipartFilename( + new() { OutputFileName = "", PartsPosition = chapterNumber, PartsTotal = chaptersTotal, Title = chapterName }, + workingTemplateText, + "") + : FileLiberator.AudioFileStorageExt.GetFileNamingTemplate( + isFolder ? config.FileTemplate : workingTemplateText, + libraryBookDto, + null, + null) + .GetFilePath(); var ext = config.DecryptToLossy ? "mp3" : "m4b"; - var path = Path.Combine(books, folderTemplate, $"{fileTemplate}.{ext}"); - - // this logic should be external - path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - var sing = $"{Path.DirectorySeparatorChar}"; - var dbl = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}"; - while (path.Contains(dbl)) - path = path.Replace(dbl, sing); - - // once path is finalized const char ZERO_WIDTH_SPACE = '\u200B'; - path = path.Replace(sing, $"{ZERO_WIDTH_SPACE}{sing}"); + var sing = $"{Path.DirectorySeparatorChar}"; + // result: can wrap long paths. eg: // |-- LINE WRAP BOUNDARIES --| // \books\author with a very <= normal line break on space between words // long name\narrator narrator // \title <= line break on the zero-with space we added before slashes + string slashWrap(string val) => val.Replace(sing, $"{ZERO_WIDTH_SPACE}{sing}"); - var book = new DataLayer.Book( - new DataLayer.AudibleProductId("123456789"), - "A Study in Scarlet: A Sherlock Holmes Novel", - "Fake description", - 1234, - DataLayer.ContentType.Product, - new List<DataLayer.Contributor> - { - new("Arthur Conan Doyle"), - new("Stephen Fry - introductions") - }, - new List<DataLayer.Contributor> { new("Stephen Fry") }, - new DataLayer.Category(new DataLayer.AudibleCategoryId("cat12345"), "Mystery"), - "us" - ); - var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my account"); + warningsLbl.Text + = !template.HasWarnings(workingTemplateText) + ? "" + : "Warning:\r\n" + + template + .GetWarnings(workingTemplateText) + .Select(err => $"- {err}") + .Aggregate((a, b) => $"{a}\r\n{b}"); - outputTb.Text = @$" + var bold = new System.Drawing.Font(richTextBox1.Font, System.Drawing.FontStyle.Bold); + var reg = new System.Drawing.Font(richTextBox1.Font, System.Drawing.FontStyle.Regular); -Example: + richTextBox1.Clear(); + richTextBox1.SelectionFont = reg; -{books} -{folderTemplate} -{fileTemplate} -{ext} -{path} + richTextBox1.AppendText(slashWrap(books)); + richTextBox1.AppendText(sing); -{book.AudibleProductId} -{book.Title} -{book.AuthorNames} -{book.NarratorNames} -series: {"Sherlock Holmes"} + if (isFolder) + richTextBox1.SelectionFont = bold; -{warnings} + richTextBox1.AppendText(slashWrap(folder)); -".Trim(); + if (isFolder) + richTextBox1.SelectionFont = reg; + + richTextBox1.AppendText(sing); + + if (!isFolder) + richTextBox1.SelectionFont = bold; + + richTextBox1.AppendText(file); + + if (!isFolder) + richTextBox1.SelectionFont = reg; + + richTextBox1.AppendText($".{ext}"); } private void saveBtn_Click(object sender, EventArgs e) { - if (!template.IsValid(templateTb.Text)) + if (!template.IsValid(workingTemplateText)) { var errors = template - .GetErrors(templateTb.Text) + .GetErrors(workingTemplateText) .Select(err => $"- {err}") .Aggregate((a, b) => $"{a}\r\n{b}"); MessageBox.Show($"This template text is not valid. Errors:\r\n{errors}", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } - TemplateText = templateTb.Text; + TemplateText = workingTemplateText; this.DialogResult = DialogResult.OK; this.Close(); diff --git a/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs b/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs index 7062079c..2d523606 100644 --- a/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs +++ b/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs @@ -12,12 +12,18 @@ namespace AudioFileStorageExtTests { public static class Shared { - public static DataLayer.LibraryBook GetLibraryBook(string asin) - { - var book = new DataLayer.Book(new DataLayer.AudibleProductId(asin), "title", "desc", 1, DataLayer.ContentType.Product, new List<DataLayer.Contributor> { new DataLayer.Contributor("author") }, new List<DataLayer.Contributor> { new DataLayer.Contributor("narrator") }, new DataLayer.Category(new DataLayer.AudibleCategoryId("seriesId"), "name"), "us"); - var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my us"); - return libraryBook; - } + public static LibationFileManager.LibraryBookDto GetLibraryBook(string asin) + => new() + { + Account = "my account", + AudibleProductId = asin, + Title = "A Study in Scarlet: A Sherlock Holmes Novel", + Locale = "us", + Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" }, + Narrators = new List<string> { "Stephen Fry" }, + SeriesName = "Sherlock Holmes", + SeriesNumber = "1" + }; } [TestClass] @@ -28,31 +34,25 @@ namespace AudioFileStorageExtTests [DataRow("asin", "<ch#>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\6.txt")] public void Tests(string asin, string template, string dir, string ext, int pos, int total, string chapter, string expected) => new AudioFileStorageExt.MultipartRenamer(GetLibraryBook(asin)) - .MultipartFilename(template, dir, ext, pos, total, chapter) + .MultipartFilename(new() { OutputFileName = $"xyz.{ext}", PartsPosition = pos, PartsTotal = total, Title = chapter }, template, dir) .Should().Be(expected); } [TestClass] - public class GetFileTemplateSingle + public class GetFileNamingTemplate { [TestMethod] [DataRow(null, "asin", @"C:\", "ext")] - [DataRow("f.txt", null, @"C:\", "ext")] - [DataRow("f.txt", "asin", null, "ext")] [ExpectedException(typeof(ArgumentNullException))] public void arg_null_exception(string template, string asin, string dirFullPath, string extension) - => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension); + => AudioFileStorageExt.GetFileNamingTemplate(template, GetLibraryBook(asin), dirFullPath, extension); [TestMethod] [DataRow("", "asin", @"C:\foo\bar", "ext")] [DataRow(" ", "asin", @"C:\foo\bar", "ext")] - [DataRow("f.txt", "", @"C:\foo\bar", "ext")] - [DataRow("f.txt", " ", @"C:\foo\bar", "ext")] - [DataRow("f.txt", "asin", "", "ext")] - [DataRow("f.txt", "asin", " ", "ext")] [ExpectedException(typeof(ArgumentException))] public void arg_exception(string template, string asin, string dirFullPath, string extension) - => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension); + => AudioFileStorageExt.GetFileNamingTemplate(template, GetLibraryBook(asin), dirFullPath, extension); [TestMethod] public void null_extension() => Tests("f.txt", "asin", @"C:\foo\bar", null, @"C:\foo\bar\f.txt"); @@ -62,7 +62,7 @@ namespace AudioFileStorageExtTests [DataRow("f", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\f.ext")] [DataRow("<id>", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\asin.ext")] public void Tests(string template, string asin, string dirFullPath, string extension, string expected) - => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension) + => AudioFileStorageExt.GetFileNamingTemplate(template, GetLibraryBook(asin), dirFullPath, extension) .GetFilePath() .Should().Be(expected); } diff --git a/_Tests/FileManager.Tests/FileTemplateTests.cs b/_Tests/FileManager.Tests/FileNamingTemplateTests.cs similarity index 69% rename from _Tests/FileManager.Tests/FileTemplateTests.cs rename to _Tests/FileManager.Tests/FileNamingTemplateTests.cs index c958e9b1..2fb9c59e 100644 --- a/_Tests/FileManager.Tests/FileTemplateTests.cs +++ b/_Tests/FileManager.Tests/FileNamingTemplateTests.cs @@ -7,7 +7,7 @@ using FileManager; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace FileTemplateTests +namespace FileNamingTemplateTests { [TestClass] public class GetFilePath @@ -17,7 +17,7 @@ namespace FileTemplateTests { var expected = @"C:\foo\bar\my_ book LONG_1234567890_1234567890_1234567890_123 [ID123456].txt"; var f1 = OLD_GetValidFilename(@"C:\foo\bar", "my: book LONG_1234567890_1234567890_1234567890_12345", "txt", "ID123456"); - var f2 = NEW_GetValidFilename_FileTemplate(@"C:\foo\bar", "my: book LONG_1234567890_1234567890_1234567890_12345", "txt", "ID123456"); + var f2 = NEW_GetValidFilename_FileNamingTemplate(@"C:\foo\bar", "my: book LONG_1234567890_1234567890_1234567890_12345", "txt", "ID123456"); f1.Should().Be(expected); f1.Should().Be(f2); @@ -49,16 +49,16 @@ namespace FileTemplateTests return fullfilename; } - private static string NEW_GetValidFilename_FileTemplate(string dirFullPath, string filename, string extension, string metadataSuffix) + private static string NEW_GetValidFilename_FileNamingTemplate(string dirFullPath, string filename, string extension, string metadataSuffix) { var template = $"<title> [<id>]"; var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension)); - var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" }; - fileTemplate.AddParameterReplacement("title", filename); - fileTemplate.AddParameterReplacement("id", metadataSuffix); - return fileTemplate.GetFilePath(); + var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" }; + fileNamingTemplate.AddParameterReplacement("title", filename); + fileNamingTemplate.AddParameterReplacement("id", metadataSuffix); + return fileNamingTemplate.GetFilePath(); } [TestMethod] @@ -66,7 +66,7 @@ namespace FileTemplateTests { var expected = @"C:\foo\bar\my file - 002 - title.txt"; var f1 = OLD_GetMultipartFileName(@"C:\foo\bar\my file.txt", 2, 100, "title"); - var f2 = NEW_GetMultipartFileName_FileTemplate(@"C:\foo\bar\my file.txt", 2, 100, "title"); + var f2 = NEW_GetMultipartFileName_FileNamingTemplate(@"C:\foo\bar\my file.txt", 2, 100, "title"); f1.Should().Be(expected); f1.Should().Be(f2); @@ -89,7 +89,7 @@ namespace FileTemplateTests var path = Path.Combine(Path.GetDirectoryName(originalPath), fileName + extension); return path; } - private static string NEW_GetMultipartFileName_FileTemplate(string originalPath, int partsPosition, int partsTotal, string suffix) + private static string NEW_GetMultipartFileName_FileNamingTemplate(string originalPath, int partsPosition, int partsTotal, string suffix) { // 1-9 => 1-9 // 10-99 => 01-99 @@ -98,19 +98,18 @@ namespace FileTemplateTests var t = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + Path.GetExtension(originalPath); - var fileTemplate = new FileTemplate(t) { IllegalCharacterReplacements = " " }; - fileTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros); - fileTemplate.AddParameterReplacement("title", suffix); - - return fileTemplate.GetFilePath(); + var fileNamingTemplate = new FileNamingTemplate(t) { IllegalCharacterReplacements = " " }; + fileNamingTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros); + fileNamingTemplate.AddParameterReplacement("title", suffix); + return fileNamingTemplate.GetFilePath(); } [TestMethod] public void remove_slashes() { - var fileTemplate = new FileTemplate(@"\foo\<title>.txt"); - fileTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s"); - fileTemplate.GetFilePath().Should().Be(@"\foo\slashes.txt"); + var fileNamingTemplate = new FileNamingTemplate(@"\foo\<title>.txt"); + fileNamingTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s"); + fileNamingTemplate.GetFilePath().Should().Be(@"\foo\slashes.txt"); } } } diff --git a/__README - COLLABORATORS.txt b/__README - COLLABORATORS.txt index 1d4204d6..5c131701 100644 --- a/__README - COLLABORATORS.txt +++ b/__README - COLLABORATORS.txt @@ -24,7 +24,7 @@ STRUCTURE * 2 Utilities (domain ignorant) Stand-alone libraries with no knowledge of anything having to do with Libation or other programs. In theory any of these should be able to one day be converted to a nuget pkg * 3 Domain Internal Utilities (db ignorant) - Can have knowledge of Libation concepts. Cannot access the database. + Cannot access the database. Can have knowledge of Libation concepts. Can even have knowledge of some db concepts, but no actual db access. * 4 Domain (db) All database access * 5 Domain Utilities (db aware)