From e5119357b261e04e2e9b7102b2d34b2b1119a2cb Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Fri, 22 Oct 2021 17:06:42 -0400 Subject: [PATCH] File naming is fully template driven --- AaxDecrypter/AaxcDownloadMultiConverter.cs | 4 +- AppScaffolding/AppScaffolding.csproj | 2 +- FileLiberator/AudioFileStorageExt.cs | 83 +++++++++---------- FileLiberator/DownloadDecryptBook.cs | 5 +- FileLiberator/DownloadPdf.cs | 10 +-- FileManager/FileUtility.cs | 5 ++ .../AudioFileStorageExtTests.cs | 60 ++++++++++---- _Tests/FileManager.Tests/FileUtilityTests.cs | 6 +- 8 files changed, 101 insertions(+), 74 deletions(-) diff --git a/AaxDecrypter/AaxcDownloadMultiConverter.cs b/AaxDecrypter/AaxcDownloadMultiConverter.cs index 3d297f1b..f1c676b1 100644 --- a/AaxDecrypter/AaxcDownloadMultiConverter.cs +++ b/AaxDecrypter/AaxcDownloadMultiConverter.cs @@ -15,10 +15,10 @@ namespace AaxDecrypter 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 template = Path.ChangeExtension(outputFileName, null) + " - <ch# 0> - <title>" + Path.GetExtension(outputFileName); var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " }; - fileTemplate.AddParameterReplacement("chapter", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); + fileTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? ""); return fileTemplate.GetFilePath(); diff --git a/AppScaffolding/AppScaffolding.csproj b/AppScaffolding/AppScaffolding.csproj index 0ee23e36..831d139f 100644 --- a/AppScaffolding/AppScaffolding.csproj +++ b/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ <PropertyGroup> <TargetFramework>net5.0</TargetFramework> - <Version>6.2.8.2</Version> + <Version>6.2.8.3</Version> </PropertyGroup> <ItemGroup> diff --git a/FileLiberator/AudioFileStorageExt.cs b/FileLiberator/AudioFileStorageExt.cs index e5f2043d..7d4f4ffd 100644 --- a/FileLiberator/AudioFileStorageExt.cs +++ b/FileLiberator/AudioFileStorageExt.cs @@ -9,72 +9,67 @@ using LibationFileManager; namespace FileLiberator { - public static class AudioFileStorageExt + public static class AudioFileStorageExt { - private class MultipartRenamer + private static string TEMP_SINGLE_TEMPLATE { get; } = "<title> [<id>]"; + private static string TEMP_DIR_TEMPLATE { get; } = "<title short> [<id>]"; + private static string TEMP_MULTI_TEMPLATE { get; } = "<title> [<id>] - <ch# 0> - <ch title>"; + + internal class MultipartRenamer { - public LibraryBook LibraryBook { get; init; } + public LibraryBook libraryBook { get; } - public string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback) + public MultipartRenamer(LibraryBook libraryBook) => this.libraryBook = libraryBook; + + internal string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback) + => MultipartFilename(TEMP_MULTI_TEMPLATE, AudibleFileStorage.DecryptInProgressDirectory, Path.GetExtension(outputFileName), partsPosition, partsTotal, newSplitCallback?.Chapter?.Title ?? ""); + + internal string MultipartFilename(string template, string fullDirPath, string extension, int partsPosition, int partsTotal, string chapterTitle) { - var extension = Path.GetExtension(outputFileName); - var baseFileName = GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, LibraryBook.Book.Title, extension, LibraryBook); + var fileTemplate = GetFileTemplateSingle(template, libraryBook, fullDirPath, extension); - var template = Path.ChangeExtension(baseFileName, null) + " - <chapter number> - <chapter title>" + extension; - - var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " }; - fileTemplate.AddParameterReplacement("chapter number", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); - fileTemplate.AddParameterReplacement("chapter title", newSplitCallback?.Chapter?.Title ?? ""); + fileTemplate.AddParameterReplacement("ch count", partsTotal.ToString()); + fileTemplate.AddParameterReplacement("ch#", partsPosition.ToString()); + fileTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); + fileTemplate.AddParameterReplacement("ch title", chapterTitle); return fileTemplate.GetFilePath(); } } public static Func<string, int, int, AAXClean.NewSplitCallback, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook) - => CreateMultipartRenamerFunc(libraryBook); - private static Func<string, int, int, AAXClean.NewSplitCallback, string> CreateMultipartRenamerFunc(LibraryBook libraryBook) - => new MultipartRenamer { LibraryBook = libraryBook }.MultipartFilename; + => new MultipartRenamer(libraryBook).MultipartFilename; public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) - => GetInProgressFilename(libraryBook, extension); - private static string GetInProgressFilename(LibraryBook libraryBook, string extension) - => GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, libraryBook.Book.Title, extension, libraryBook); + => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.DecryptInProgressDirectory, extension); public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) - => GetBooksDirectoryFilename(libraryBook, extension); - private static string GetBooksDirectoryFilename(LibraryBook libraryBook, string extension) - => GetValidFilename(AudibleFileStorage.BooksDirectory, libraryBook.Book.Title, extension, libraryBook); + => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.BooksDirectory, extension); - public static string CreateDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook) - => CreateDestinationDirectory(libraryBook); - private static string CreateDestinationDirectory(LibraryBook libraryBook) - { - var title = libraryBook.Book.Title; - - // to prevent the paths from getting too long, we don't need after the 1st ":" for the folder - var underscoreIndex = title.IndexOf(':'); - var titleDir - = underscoreIndex < 4 - ? title - : title.Substring(0, underscoreIndex); - var destinationDir = GetValidFilename(AudibleFileStorage.BooksDirectory, titleDir, null, libraryBook); - Directory.CreateDirectory(destinationDir); - return destinationDir; - } - - internal static string GetValidFilename(string dirFullPath, string filename, string extension, LibraryBook libraryBook) + public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook) + => GetFileTemplateSingle(TEMP_DIR_TEMPLATE, libraryBook, AudibleFileStorage.BooksDirectory, null) + .GetFilePath(); + + public static string GetCustomDirFilename(this AudioFileStorage _, LibraryBook libraryBook, string dirFullPath, string extension) + => GetFileTemplateSingle(TEMP_SINGLE_TEMPLATE, libraryBook, dirFullPath, extension) + .GetFilePath(); + + internal static FileTemplate GetFileTemplateSingle(string template, LibraryBook libraryBook, string dirFullPath, string extension) { + ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); + ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)); ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath)); - ArgumentValidator.EnsureNotNullOrWhiteSpace(filename, nameof(filename)); - - var template = $"<title> [<id>]"; var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension)); - var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" }; - fileTemplate.AddParameterReplacement("title", filename); + + var title = libraryBook.Book.Title ?? ""; + + fileTemplate.AddParameterReplacement("title", title); + fileTemplate.AddParameterReplacement("title short", title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'))); fileTemplate.AddParameterReplacement("id", libraryBook.Book.AudibleProductId); - return fileTemplate.GetFilePath(); + + return fileTemplate; } } } diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index ece04995..f169f80d 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -179,9 +179,10 @@ namespace FileLiberator private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries) { // create final directory. move each file into it - var destinationDir = AudibleFileStorage.Audio.CreateDestinationDirectory(libraryBook); + var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook); + Directory.CreateDirectory(destinationDir); - FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio); + FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio); if (getFirstAudio() == default) return false; diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs index 18d27114..88f8587e 100644 --- a/FileLiberator/DownloadPdf.cs +++ b/FileLiberator/DownloadPdf.cs @@ -40,14 +40,14 @@ namespace FileLiberator private static string getProposedDownloadFilePath(LibraryBook libraryBook) { + var extension = Path.GetExtension(getdownloadUrl(libraryBook)); + // if audio file exists, get it's dir. else return base Book dir var existingPath = Path.GetDirectoryName(AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId)); - var file = getdownloadUrl(libraryBook); + if (existingPath is not null) + return AudibleFileStorage.Audio.GetCustomDirFilename(libraryBook, existingPath, extension); - if (existingPath != null) - return Path.Combine(existingPath, Path.GetFileName(file)); - - return AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, Path.GetExtension(file)); + return AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, extension); } private static string getdownloadUrl(LibraryBook libraryBook) diff --git a/FileManager/FileUtility.cs b/FileManager/FileUtility.cs index d2d88e12..dadf40a3 100644 --- a/FileManager/FileUtility.cs +++ b/FileManager/FileUtility.cs @@ -103,6 +103,11 @@ namespace FileManager builder.Append(c); } fixedPath = builder.ToString(); + + var dblSeparator = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}"; + while (fixedPath.Contains(dblSeparator)) + fixedPath = fixedPath.Replace(dblSeparator, $"{Path.DirectorySeparatorChar}"); + return fixedPath; } diff --git a/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs b/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs index 7b8b4efe..7062079c 100644 --- a/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs +++ b/_Tests/FileLiberator.Tests/AudioFileStorageExtTests.cs @@ -6,40 +6,64 @@ using FileLiberator; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using static AudioFileStorageExtTests.Shared; + namespace AudioFileStorageExtTests { - [TestClass] - public class GetValidFilename + public static class Shared { - private DataLayer.LibraryBook GetLibraryBook(string asin) + 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 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; } + } + [TestClass] + public class MultipartRenamer_MultipartFilename + { [TestMethod] - [DataRow(null, "name", "ext", "suffix")] - [DataRow(@"C:\", null, "ext", "suffix")] + [DataRow("asin", "[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt")] + [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) + .Should().Be(expected); + } + + [TestClass] + public class GetFileTemplateSingle + { + [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 dirFullPath, string filename, string extension, string metadataSuffix) - => AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)); + public void arg_null_exception(string template, string asin, string dirFullPath, string extension) + => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension); [TestMethod] - [DataRow("", "name", "ext", "suffix")] - [DataRow(" ", "name", "ext", "suffix")] - [DataRow(@"C:\", "", "ext", "suffix")] - [DataRow(@"C:\", " ", "ext", "suffix")] + [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 dirFullPath, string filename, string extension, string metadataSuffix) - => AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)); + public void arg_exception(string template, string asin, string dirFullPath, string extension) + => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension); [TestMethod] - public void null_extension() => Tests(@"C:\foo\bar", "my file", null, "meta", @"C:\foo\bar\my file [meta]"); + public void null_extension() => Tests("f.txt", "asin", @"C:\foo\bar", null, @"C:\foo\bar\f.txt"); [TestMethod] - [DataRow(@"C:\foo\bar", "my file", "txt", "my id", @"C:\foo\bar\my file [my id].txt")] - public void Tests(string dirFullPath, string filename, string extension, string metadataSuffix, string expected) - => AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)).Should().Be(expected); + [DataRow("f.txt", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\f.txt.ext")] + [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) + .GetFilePath() + .Should().Be(expected); } } diff --git a/_Tests/FileManager.Tests/FileUtilityTests.cs b/_Tests/FileManager.Tests/FileUtilityTests.cs index 51e4e9fc..d9371c15 100644 --- a/_Tests/FileManager.Tests/FileUtilityTests.cs +++ b/_Tests/FileManager.Tests/FileUtilityTests.cs @@ -17,7 +17,7 @@ namespace FileUtilityTests // needs separate method. middle null param not running correctly in TestExplorer when used in DataRow() [TestMethod] - [DataRow("http://test.com/a/b/c", @"http\\test.com\a\b\c")] + [DataRow("http://test.com/a/b/c", @"http\test.com\a\b\c")] public void null_replacement(string inStr, string outStr) => Tests(inStr, null, outStr); [TestMethod] @@ -31,8 +31,10 @@ namespace FileUtilityTests [DataRow("a*?:z.txt", "Z", "aZZZz.txt")] // retain drive letter path colon [DataRow(@"C:\az.txt", "Z", @"C:\az.txt")] - // replace all other colongs + // replace all other colons [DataRow(@"a\b:c\d.txt", "ZZZ", @"a\bZZZc\d.txt")] + // remove empty directories + [DataRow(@"C:\a\\\b\c\\\d.txt", "ZZZ", @"C:\a\b\c\d.txt")] public void Tests(string inStr, string replacement, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, replacement)); }