diff --git a/AaxDecrypter/AaxcDownloadMultiConverter.cs b/AaxDecrypter/AaxcDownloadMultiConverter.cs index cef5b258..494f35eb 100644 --- a/AaxDecrypter/AaxcDownloadMultiConverter.cs +++ b/AaxDecrypter/AaxcDownloadMultiConverter.cs @@ -12,10 +12,23 @@ namespace AaxDecrypter { protected override StepSequence Steps { get; } - private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3); + 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("chapter", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); + fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? ""); + + return fileTemplate.GetFilename(); + } + + 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) + public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat, + Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback = null) : base(outFileName, cacheDirectory, dlLic, outputFormat) { Steps = new StepSequence @@ -26,6 +39,7 @@ namespace AaxDecrypter ["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter, ["Step 3: Cleanup"] = Step_Cleanup, }; + this.multipartFileNameCallback = multipartFileNameCallback ?? DefaultMultipartFileName; } /* @@ -119,7 +133,9 @@ 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 = FileUtility.GetMultipartFileName(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback.Chapter.Title); + var fileName = multipartFileNameCallback(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback); + fileName = FileUtility.GetValidFilename(fileName); + multiPartFilePaths.Add(fileName); FileUtility.SaferDelete(fileName); diff --git a/AaxDecrypter/AudiobookDownloadBase.cs b/AaxDecrypter/AudiobookDownloadBase.cs index 5a7f0c8a..5d1b9b99 100644 --- a/AaxDecrypter/AudiobookDownloadBase.cs +++ b/AaxDecrypter/AudiobookDownloadBase.cs @@ -46,9 +46,10 @@ namespace AaxDecrypter throw new DirectoryNotFoundException($"Directory does not exist: {nameof(cacheDirectory)}"); cacheDir = cacheDirectory; + DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); + // delete file after validation is complete FileUtility.SaferDelete(OutputFileName); - DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); } public abstract void Cancel(); diff --git a/FileManager.Tests/FileUtilityTests.cs b/FileManager.Tests/FileUtilityTests.cs index b8472a58..637597f0 100644 --- a/FileManager.Tests/FileUtilityTests.cs +++ b/FileManager.Tests/FileUtilityTests.cs @@ -86,45 +86,39 @@ namespace FileUtilityTests } [TestClass] - public class GetMultipartFileName + public class GetSequenceFormatted { - [TestMethod] - public void null_path() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetMultipartFileName(null, 1, 1, "")); - - [TestMethod] - public void null_suffix() => Tests(@"C:\foo\bar\my file.txt", 2, 100, null, @"C:\foo\bar\my file - 002 - .txt"); - [TestMethod] public void negative_partsPosition() => Assert.ThrowsException<ArgumentException>(() - => FileUtility.GetMultipartFileName("foo", -1, 2, "") + => FileUtility.GetSequenceFormatted(-1, 2) ); [TestMethod] public void zero_partsPosition() => Assert.ThrowsException<ArgumentException>(() - => FileUtility.GetMultipartFileName("foo", 0, 2, "") + => FileUtility.GetSequenceFormatted(0, 2) ); [TestMethod] public void negative_partsTotal() => Assert.ThrowsException<ArgumentException>(() - => FileUtility.GetMultipartFileName("foo", 2, -1, "") + => FileUtility.GetSequenceFormatted(2, -1) ); [TestMethod] public void zero_partsTotal() => Assert.ThrowsException<ArgumentException>(() - => FileUtility.GetMultipartFileName("foo", 2, 0, "") + => FileUtility.GetSequenceFormatted(2, 0) ); [TestMethod] public void partsPosition_greater_than_partsTotal() => Assert.ThrowsException<ArgumentException>(() - => FileUtility.GetMultipartFileName("foo", 2, 1, "") + => FileUtility.GetSequenceFormatted(2, 1) ); [TestMethod] // only part - [DataRow(@"C:\foo\bar\my file.txt", 1, 1, "title", @"C:\foo\bar\my file - 1 - title.txt")] + [DataRow(1, 1, "1")] + // 2 digits + [DataRow(2, 90, "02")] // 3 digits - [DataRow(@"C:\foo\bar\my file.txt", 2, 100, "title", @"C:\foo\bar\my file - 002 - title.txt")] - // no suffix - [DataRow(@"C:\foo\bar\my file.txt", 2, 100, "", @"C:\foo\bar\my file - 002 - .txt")] - public void Tests(string originalPath, int partsPosition, int partsTotal, string suffix, string expected) - => FileUtility.GetMultipartFileName(originalPath, partsPosition, partsTotal, suffix).Should().Be(expected); + [DataRow(2, 900, "002")] + public void Tests(int partsPosition, int partsTotal, string expected) + => FileUtility.GetSequenceFormatted(partsPosition, partsTotal).Should().Be(expected); } } diff --git a/FileManager/FileUtility.cs b/FileManager/FileUtility.cs index c6cdbc88..8d57b447 100644 --- a/FileManager/FileUtility.cs +++ b/FileManager/FileUtility.cs @@ -35,26 +35,22 @@ namespace FileManager return fileTemplate.GetFilename(); } - public static string GetMultipartFileName(string originalPath, int partsPosition, int partsTotal, string suffix) + /// <summary> + /// Return position with correct number of leading zeros. + /// <br />- 2 of 9 => "2" + /// <br />- 2 of 90 => "02" + /// <br />- 2 of 900 => "002" + /// </summary> + /// <param name="position">position in sequence. The 'x' in 'x of y'</param> + /// <param name="total">total qty in sequence. The 'y' in 'x of y'</param> + public static string GetSequenceFormatted(int position, int total) { - ArgumentValidator.EnsureNotNull(originalPath, nameof(originalPath)); - ArgumentValidator.EnsureGreaterThan(partsPosition, nameof(partsPosition), 0); - ArgumentValidator.EnsureGreaterThan(partsTotal, nameof(partsTotal), 0); - if (partsPosition > partsTotal) - throw new ArgumentException($"{partsPosition} may not be greater than {partsTotal}"); + ArgumentValidator.EnsureGreaterThan(position, nameof(position), 0); + ArgumentValidator.EnsureGreaterThan(total, nameof(total), 0); + if (position > total) + throw new ArgumentException($"{position} may not be greater than {total}"); - // 1-9 => 1-9 - // 10-99 => 01-99 - // 100-999 => 001-999 - var chapterCountLeadingZeros = partsPosition.ToString().PadLeft(partsTotal.ToString().Length, '0'); - - var template = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + Path.GetExtension(originalPath); - - var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " }; - fileTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros); - fileTemplate.AddParameterReplacement("title", suffix); - - return fileTemplate.GetFilename(); + return position.ToString().PadLeft(total.ToString().Length, '0'); } private const int MAX_FILENAME_LENGTH = 255;