File naming is fully template driven

This commit is contained in:
Robert McRackan 2021-10-22 17:06:42 -04:00
parent b42ff827d5
commit e5119357b2
8 changed files with 101 additions and 74 deletions

View File

@ -15,10 +15,10 @@ namespace AaxDecrypter
private Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback { get; } private Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback { get; }
private static string DefaultMultipartFilename(string outputFileName, int partsPosition, int partsTotal, NewSplitCallback newSplitCallback) private static string DefaultMultipartFilename(string outputFileName, int partsPosition, int partsTotal, NewSplitCallback newSplitCallback)
{ {
var template = Path.ChangeExtension(outputFileName, null) + " - <chapter> - <title>" + Path.GetExtension(outputFileName); var template = Path.ChangeExtension(outputFileName, null) + " - <ch# 0> - <title>" + Path.GetExtension(outputFileName);
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " }; 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 ?? ""); fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? "");
return fileTemplate.GetFilePath(); return fileTemplate.GetFilePath();

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<Version>6.2.8.2</Version> <Version>6.2.8.3</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -11,70 +11,65 @@ 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 fileTemplate = GetFileTemplateSingle(template, libraryBook, fullDirPath, extension);
var baseFileName = GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, LibraryBook.Book.Title, extension, LibraryBook);
var template = Path.ChangeExtension(baseFileName, null) + " - <chapter number> - <chapter title>" + extension; fileTemplate.AddParameterReplacement("ch count", partsTotal.ToString());
fileTemplate.AddParameterReplacement("ch#", partsPosition.ToString());
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " }; fileTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
fileTemplate.AddParameterReplacement("chapter number", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); fileTemplate.AddParameterReplacement("ch title", chapterTitle);
fileTemplate.AddParameterReplacement("chapter title", newSplitCallback?.Chapter?.Title ?? "");
return fileTemplate.GetFilePath(); return fileTemplate.GetFilePath();
} }
} }
public static Func<string, int, int, AAXClean.NewSplitCallback, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook) public static Func<string, int, int, AAXClean.NewSplitCallback, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook)
=> CreateMultipartRenamerFunc(libraryBook); => new MultipartRenamer(libraryBook).MultipartFilename;
private static Func<string, int, int, AAXClean.NewSplitCallback, string> CreateMultipartRenamerFunc(LibraryBook libraryBook)
=> new MultipartRenamer { LibraryBook = libraryBook }.MultipartFilename;
public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
=> GetInProgressFilename(libraryBook, extension); => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.DecryptInProgressDirectory, extension);
private static string GetInProgressFilename(LibraryBook libraryBook, string extension)
=> GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, libraryBook.Book.Title, extension, libraryBook);
public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension) public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
=> GetBooksDirectoryFilename(libraryBook, extension); => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.BooksDirectory, extension);
private static string GetBooksDirectoryFilename(LibraryBook libraryBook, string extension)
=> GetValidFilename(AudibleFileStorage.BooksDirectory, libraryBook.Book.Title, extension, libraryBook);
public static string CreateDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook) public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook)
=> CreateDestinationDirectory(libraryBook); => GetFileTemplateSingle(TEMP_DIR_TEMPLATE, libraryBook, AudibleFileStorage.BooksDirectory, null)
private static string CreateDestinationDirectory(LibraryBook libraryBook) .GetFilePath();
{
var title = libraryBook.Book.Title; public static string GetCustomDirFilename(this AudioFileStorage _, LibraryBook libraryBook, string dirFullPath, string extension)
=> GetFileTemplateSingle(TEMP_SINGLE_TEMPLATE, libraryBook, dirFullPath, extension)
// to prevent the paths from getting too long, we don't need after the 1st ":" for the folder .GetFilePath();
var underscoreIndex = title.IndexOf(':');
var titleDir internal static FileTemplate GetFileTemplateSingle(string template, LibraryBook libraryBook, string dirFullPath, string extension)
= 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)
{ {
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath)); ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath));
ArgumentValidator.EnsureNotNullOrWhiteSpace(filename, nameof(filename));
var template = $"<title> [<id>]";
var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension)); var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension));
var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" }; 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); fileTemplate.AddParameterReplacement("id", libraryBook.Book.AudibleProductId);
return fileTemplate.GetFilePath();
return fileTemplate;
} }
} }
} }

View File

@ -179,7 +179,8 @@ namespace FileLiberator
private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries) private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries)
{ {
// create final directory. move each file into it // 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);

View File

@ -40,14 +40,14 @@ namespace FileLiberator
private static string getProposedDownloadFilePath(LibraryBook libraryBook) 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 // if audio file exists, get it's dir. else return base Book dir
var existingPath = Path.GetDirectoryName(AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId)); 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 AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, extension);
return Path.Combine(existingPath, Path.GetFileName(file));
return AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, Path.GetExtension(file));
} }
private static string getdownloadUrl(LibraryBook libraryBook) private static string getdownloadUrl(LibraryBook libraryBook)

View File

@ -103,6 +103,11 @@ namespace FileManager
builder.Append(c); builder.Append(c);
} }
fixedPath = builder.ToString(); fixedPath = builder.ToString();
var dblSeparator = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
while (fixedPath.Contains(dblSeparator))
fixedPath = fixedPath.Replace(dblSeparator, $"{Path.DirectorySeparatorChar}");
return fixedPath; return fixedPath;
} }

View File

@ -6,40 +6,64 @@ using FileLiberator;
using FluentAssertions; using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using static AudioFileStorageExtTests.Shared;
namespace AudioFileStorageExtTests namespace AudioFileStorageExtTests
{ {
[TestClass] public static class Shared
public class GetValidFilename
{ {
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"); var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my us");
return libraryBook; return libraryBook;
} }
}
[TestClass]
public class MultipartRenamer_MultipartFilename
{
[TestMethod] [TestMethod]
[DataRow(null, "name", "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(@"C:\", null, "ext", "suffix")] [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))] [ExpectedException(typeof(ArgumentNullException))]
public void arg_null_exception(string dirFullPath, string filename, string extension, string metadataSuffix) public void arg_null_exception(string template, string asin, string dirFullPath, string extension)
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)); => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension);
[TestMethod] [TestMethod]
[DataRow("", "name", "ext", "suffix")] [DataRow("", "asin", @"C:\foo\bar", "ext")]
[DataRow(" ", "name", "ext", "suffix")] [DataRow(" ", "asin", @"C:\foo\bar", "ext")]
[DataRow(@"C:\", "", "ext", "suffix")] [DataRow("f.txt", "", @"C:\foo\bar", "ext")]
[DataRow(@"C:\", " ", "ext", "suffix")] [DataRow("f.txt", " ", @"C:\foo\bar", "ext")]
[DataRow("f.txt", "asin", "", "ext")]
[DataRow("f.txt", "asin", " ", "ext")]
[ExpectedException(typeof(ArgumentException))] [ExpectedException(typeof(ArgumentException))]
public void arg_exception(string dirFullPath, string filename, string extension, string metadataSuffix) public void arg_exception(string template, string asin, string dirFullPath, string extension)
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)); => AudioFileStorageExt.GetFileTemplateSingle(template, GetLibraryBook(asin), dirFullPath, extension);
[TestMethod] [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] [TestMethod]
[DataRow(@"C:\foo\bar", "my file", "txt", "my id", @"C:\foo\bar\my file [my id].txt")] [DataRow("f.txt", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\f.txt.ext")]
public void Tests(string dirFullPath, string filename, string extension, string metadataSuffix, string expected) [DataRow("f", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\f.ext")]
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)).Should().Be(expected); [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);
} }
} }

View File

@ -17,7 +17,7 @@ namespace FileUtilityTests
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow() // needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
[TestMethod] [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); public void null_replacement(string inStr, string outStr) => Tests(inStr, null, outStr);
[TestMethod] [TestMethod]
@ -31,8 +31,10 @@ namespace FileUtilityTests
[DataRow("a*?:z.txt", "Z", "aZZZz.txt")] [DataRow("a*?:z.txt", "Z", "aZZZz.txt")]
// retain drive letter path colon // retain drive letter path colon
[DataRow(@"C:\az.txt", "Z", @"C:\az.txt")] [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")] [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)); public void Tests(string inStr, string replacement, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, replacement));
} }