diff --git a/Source/FileLiberator/AudioFileStorageExt.cs b/Source/FileLiberator/AudioFileStorageExt.cs index 913bc18a..dc96fa27 100644 --- a/Source/FileLiberator/AudioFileStorageExt.cs +++ b/Source/FileLiberator/AudioFileStorageExt.cs @@ -25,12 +25,12 @@ namespace FileLiberator if (seriesParent is not null) { - var baseDir = Templates.Folder.GetFilename(seriesParent.ToDto(), "", ""); + var baseDir = Templates.Folder.GetFilename(seriesParent.ToDto(), AudibleFileStorage.BooksDirectory, ""); return Templates.Folder.GetFilename(libraryBook.ToDto(), baseDir, ""); } } } - return Templates.Folder.GetFilename(libraryBook.ToDto(), "", ""); + return Templates.Folder.GetFilename(libraryBook.ToDto(), AudibleFileStorage.BooksDirectory, ""); } /// diff --git a/Source/FileManager/NamingTemplate/NamingTemplate.cs b/Source/FileManager/NamingTemplate/NamingTemplate.cs index dc6fbc71..4d5016da 100644 --- a/Source/FileManager/NamingTemplate/NamingTemplate.cs +++ b/Source/FileManager/NamingTemplate/NamingTemplate.cs @@ -106,7 +106,7 @@ public class NamingTemplate while (templateString.Length > 0) { - if (StartsWith(Classes, templateString, out string exactPropertyName, out var propertyTag, out var valueExpression)) + if (StartsWith(templateString, out string exactPropertyName, out var propertyTag, out var valueExpression)) { checkAndAddLiterals(); @@ -120,7 +120,7 @@ public class NamingTemplate templateString = templateString[exactPropertyName.Length..]; } - else if (StartsWithClosing(Classes, templateString, out exactPropertyName, out var closingPropertyTag)) + else if (StartsWithClosing(templateString, out exactPropertyName, out var closingPropertyTag)) { checkAndAddLiterals(); @@ -176,9 +176,9 @@ public class NamingTemplate } } - private static bool StartsWith(IEnumerable propertyClasses, string template, out string exactName, out IPropertyTag propertyTag, out Expression valueExpression) + private bool StartsWith(string template, out string exactName, out IPropertyTag propertyTag, out Expression valueExpression) { - foreach (var pc in propertyClasses) + foreach (var pc in Classes) { if (pc.StartsWith(template, out exactName, out propertyTag, out valueExpression)) return true; @@ -189,9 +189,9 @@ public class NamingTemplate return false; } - private static bool StartsWithClosing(IEnumerable conditionalGroups, string template, out string exactName, out IClosingPropertyTag closingPropertyTag) + private bool StartsWithClosing(string template, out string exactName, out IClosingPropertyTag closingPropertyTag) { - foreach (var pc in conditionalGroups) + foreach (var pc in Classes) { if (pc.StartsWithClosing(template, out exactName, out closingPropertyTag)) return true; diff --git a/Source/LibationFileManager/Templates.cs b/Source/LibationFileManager/Templates.cs index a0f8afbc..d42e70a0 100644 --- a/Source/LibationFileManager/Templates.cs +++ b/Source/LibationFileManager/Templates.cs @@ -50,33 +50,22 @@ namespace LibationFileManager static Templates() { - Configuration.Instance.PropertyChanged += FolderTemplate_PropertyChanged; - Configuration.Instance.PropertyChanged += FileTemplate_PropertyChanged; - Configuration.Instance.PropertyChanged += ChapterFileTemplate_PropertyChanged; - Configuration.Instance.PropertyChanged += ChapterTitleTemplate_PropertyChanged; - } + Configuration.Instance.PropertyChanged += + [PropertyChangeFilter(nameof(Configuration.FolderTemplate))] + (_,e) => _folder = GetTemplate((string)e.NewValue); - [PropertyChangeFilter(nameof(Configuration.FolderTemplate))] - private static void FolderTemplate_PropertyChanged(object sender, PropertyChangedEventArgsEx e) - { - _folder = GetTemplate((string)e.NewValue); - } - [PropertyChangeFilter(nameof(Configuration.FileTemplate))] - private static void FileTemplate_PropertyChanged(object sender, PropertyChangedEventArgsEx e) - { - _file = GetTemplate((string)e.NewValue); - } - [PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))] - private static void ChapterFileTemplate_PropertyChanged(object sender, PropertyChangedEventArgsEx e) - { - _chapterFile = GetTemplate((string)e.NewValue); - } - [PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))] - private static void ChapterTitleTemplate_PropertyChanged(object sender, PropertyChangedEventArgsEx e) - { - _chapterTitle = GetTemplate((string)e.NewValue); - } + Configuration.Instance.PropertyChanged + += [PropertyChangeFilter(nameof(Configuration.FileTemplate))] + (_, e) => _file = GetTemplate((string)e.NewValue); + Configuration.Instance.PropertyChanged + += [PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))] + (_, e) => _chapterFile = GetTemplate((string)e.NewValue); + + Configuration.Instance.PropertyChanged + += [PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))] + (_, e) => _chapterTitle = GetTemplate((string)e.NewValue); + } #endregion #region Template Properties @@ -87,6 +76,7 @@ namespace LibationFileManager public string TemplateText => Template.TemplateText; protected NamingTemplate Template { get; private set; } + #endregion #region validation @@ -134,15 +124,24 @@ namespace LibationFileManager private LongPath GetFilename(string baseDir, string fileExtension, bool returnFirstExisting, ReplacementCharacters replacements, params object[] dtos) { - var parts = Template.Evaluate(dtos).ToList(); + fileExtension = FileUtility.GetStandardizedExtension(fileExtension); + var parts = Template.Evaluate(dtos).ToList(); var pathParts = GetPathParts(GetTemplatePartsStrings(parts, replacements)); //Remove 1 character from the end of the longest filename part until //the total filename is less than max filename length - foreach (var part in pathParts) + for (int i = 0; i < pathParts.Count; i++) { - while (part.Sum(LongPath.GetFilesystemStringLength) > LongPath.MaxFilenameLength) + var part = pathParts[i]; + + //If file already exists, GetValidFilename will append " (n)" to the filename. + //This could cause the filename length to exceed MaxFilenameLength, so reduce + //allowable filename length by 5 chars, allowing for up to 99 duplicates. + var maxFilenameLength = LongPath.MaxFilenameLength - + (i < pathParts.Count - 1 || string.IsNullOrEmpty(fileExtension) ? 0 : fileExtension.Length + 5); + + while (part.Sum(LongPath.GetFilesystemStringLength) > maxFilenameLength) { int maxLength = part.Max(p => p.Length); var maxEntry = part.First(p => p.Length == maxLength); diff --git a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs index 89c2bacb..46398993 100644 --- a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs +++ b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs @@ -297,9 +297,15 @@ namespace Templates_Other static ReplacementCharacters Replacements = ReplacementCharacters.Default; [TestMethod] - [DataRow(@"C:\foo\bar", @"C:\foo\bar\Folder\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)] - [DataRow(@"/foo/bar", @"/foo/bar/Folder/my: book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/[ID123456].txt", PlatformID.Unix)] - public void equiv_GetValidFilename(string dirFullPath, string expected, PlatformID platformID) + [DataRow(@"C:\foo\bar", @"\Folder\\[<id>]\", @"C:\foo\bar\Folder\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)] + [DataRow("/foo/bar", "/Folder/<title>/[<id>]/", @" / foo/bar/Folder/my: book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/[ID123456].txt", PlatformID.Unix)] + [DataRow(@"C:\foo\bar", @"\Folder\<title> [<id>]", @"C:\foo\bar\Folder\my꞉ book 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt", PlatformID.Win32NT)] + [DataRow("/foo/bar", "/Folder/<title> [<id>]", @"/foo/bar/Folder/my: book 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt", PlatformID.Unix)] + [DataRow(@"C:\foo\bar", @"\Folder\<title> <title> <title> <title> <title> <title> <title> <title> <title> [<id>]", @"C:\foo\bar\Folder\my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 00000000000000000 my꞉ book 00000000000000000 [ID123456].txt", PlatformID.Win32NT)] + [DataRow("/foo/bar", "/Folder/<title> <title> <title> <title> <title> <title> <title> <title> <title> [<id>]", @"/foo/bar/Folder/my: book 0000000000000000 my: book 0000000000000000 my: book 0000000000000000 my: book 0000000000000000 my: book 0000000000000000 my: book 0000000000000000 my: book 0000000000000000 my: book 00000000000000000 my: book 00000000000000000 [ID123456].txt", PlatformID.Unix)] + [DataRow(@"C:\foo\bar", @"\<title>\<title> [<id>]", @"C:\foo\bar\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\my꞉ book 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt", PlatformID.Win32NT)] + [DataRow("/foo/bar", @"/<title>/<title> [<id>]", "/foo/bar/my: book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/my: book 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt", PlatformID.Unix)] + public void Test_trim_to_max_path(string dirFullPath, string template, string expected, PlatformID platformID) { if (Environment.OSVersion.Platform != platformID) return; @@ -308,7 +314,21 @@ namespace Templates_Other sb.Append('0', 300); var longText = sb.ToString(); - NEW_GetValidFilename_FileNamingTemplate(dirFullPath, "my: book " + longText, "txt", "ID123456").Should().Be(expected); + NEW_GetValidFilename_FileNamingTemplate(dirFullPath, template, "my: book " + longText, "txt").Should().Be(expected); + } + + [TestMethod] + [DataRow(@"\foo\bar", @"<title>\<title>")] + [DataRow(@"\foooo\barrrr", "<title>")] + public void Test_windows_relative_path_too_long(string baseDir, string template) + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + return; + + var sb = new System.Text.StringBuilder(); + sb.Append('0', 300); + var longText = sb.ToString(); + Assert.ThrowsException<PathTooLongException>(() => NEW_GetValidFilename_FileNamingTemplate(baseDir, template, "my: book " + longText, "txt")); } private class TemplateTag : ITemplateTag @@ -318,17 +338,13 @@ namespace Templates_Other public string Description { get; } public string Display { get; } } - private static string NEW_GetValidFilename_FileNamingTemplate(string dirFullPath, string filename, string extension, string metadataSuffix) + private static string NEW_GetValidFilename_FileNamingTemplate(string dirFullPath, string template, string title, string extension) { - char slash = Path.DirectorySeparatorChar; - - var template = $"{slash}Folder{slash}<title>{slash}[<id>]{slash}"; - extension = FileUtility.GetStandardizedExtension(extension); var lbDto = GetLibraryBook(); - lbDto.Title = filename; - lbDto.AudibleProductId = metadataSuffix; + lbDto.Title = title; + lbDto.AudibleProductId = "ID123456"; Templates.TryGetTemplate<Templates.FolderTemplate>(template, out var fileNamingTemplate).Should().BeTrue();