diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj index b27b02b1..9ef266c7 100644 --- a/Source/AaxDecrypter/AaxDecrypter.csproj +++ b/Source/AaxDecrypter/AaxDecrypter.csproj @@ -5,7 +5,7 @@ - + diff --git a/Source/AaxDecrypter/AudiobookDownloadBase.cs b/Source/AaxDecrypter/AudiobookDownloadBase.cs index 34e10579..5980fb93 100644 --- a/Source/AaxDecrypter/AudiobookDownloadBase.cs +++ b/Source/AaxDecrypter/AudiobookDownloadBase.cs @@ -42,10 +42,10 @@ namespace AaxDecrypter var outDir = Path.GetDirectoryName(OutputFileName); if (!Directory.Exists(outDir)) - throw new DirectoryNotFoundException($"Directory does not exist: {nameof(outDir)}"); + Directory.CreateDirectory(outDir); if (!Directory.Exists(cacheDirectory)) - throw new DirectoryNotFoundException($"Directory does not exist: {nameof(cacheDirectory)}"); + Directory.CreateDirectory(cacheDirectory); jsonDownloadState = Path.Combine(cacheDirectory, Path.GetFileName(Path.ChangeExtension(OutputFileName, ".json"))); TempFilePath = Path.ChangeExtension(jsonDownloadState, ".aaxc"); diff --git a/Source/FileLiberator/ConvertToMp3.cs b/Source/FileLiberator/ConvertToMp3.cs index 58f54a0b..40077c6c 100644 --- a/Source/FileLiberator/ConvertToMp3.cs +++ b/Source/FileLiberator/ConvertToMp3.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Threading.Tasks; using AAXClean; using AAXClean.Codecs; @@ -19,12 +20,12 @@ namespace FileLiberator private long fileSize; private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3"); - public override Task CancelAsync() => m4bBook?.CancelAsync(); + public override Task CancelAsync() => m4bBook?.CancelAsync() ?? Task.CompletedTask; public static bool ValidateMp3(LibraryBook libraryBook) { - var path = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); - return path?.ToString()?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path)); + var paths = AudibleFileStorage.Audio.GetPaths(libraryBook.Book.AudibleProductId); + return paths.Any(path => path?.ToString()?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path))); } public override bool Validate(LibraryBook libraryBook) => ValidateMp3(libraryBook); @@ -35,33 +36,38 @@ namespace FileLiberator try { - var m4bPath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); - m4bBook = new Mp4File(m4bPath, FileAccess.Read); - m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate; + var m4bPaths = AudibleFileStorage.Audio.GetPaths(libraryBook.Book.AudibleProductId); - fileSize = m4bBook.InputStream.Length; + foreach (var m4bPath in m4bPaths) + { + var proposedMp3Path = Mp3FileName(m4bPath); + if (File.Exists(proposedMp3Path) || !File.Exists(m4bPath)) continue; - OnTitleDiscovered(m4bBook.AppleTags.Title); - OnAuthorsDiscovered(m4bBook.AppleTags.FirstAuthor); - OnNarratorsDiscovered(m4bBook.AppleTags.Narrator); - OnCoverImageDiscovered(m4bBook.AppleTags.Cover); + m4bBook = new Mp4File(m4bPath, FileAccess.Read); + m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate; - using var mp3File = File.OpenWrite(Path.GetTempFileName()); - var lameConfig = GetLameOptions(Configuration.Instance); - var result = await Task.Run(() => m4bBook.ConvertToMp3(mp3File, lameConfig)); - m4bBook.InputStream.Close(); - mp3File.Close(); + fileSize = m4bBook.InputStream.Length; - var proposedMp3Path = Mp3FileName(m4bPath); - var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path); - OnFileCreated(libraryBook, realMp3Path); + OnTitleDiscovered(m4bBook.AppleTags.Title); + OnAuthorsDiscovered(m4bBook.AppleTags.FirstAuthor); + OnNarratorsDiscovered(m4bBook.AppleTags.Narrator); + OnCoverImageDiscovered(m4bBook.AppleTags.Cover); - if (result == ConversionResult.Failed) - return new StatusHandler { "Conversion failed" }; - else if (result == ConversionResult.Cancelled) - return new StatusHandler { "Cancelled" }; - else - return new StatusHandler(); + using var mp3File = File.OpenWrite(Path.GetTempFileName()); + var lameConfig = GetLameOptions(Configuration.Instance); + var result = await Task.Run(() => m4bBook.ConvertToMp3(mp3File, lameConfig)); + m4bBook.InputStream.Close(); + mp3File.Close(); + + var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path); + OnFileCreated(libraryBook, realMp3Path); + + if (result == ConversionResult.Failed) + return new StatusHandler { "Conversion failed" }; + else if (result == ConversionResult.Cancelled) + return new StatusHandler { "Cancelled" }; + } + return new StatusHandler(); } finally { diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs index 49da0437..435e3fad 100644 --- a/Source/FileLiberator/DownloadDecryptBook.cs +++ b/Source/FileLiberator/DownloadDecryptBook.cs @@ -21,7 +21,7 @@ namespace FileLiberator public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists(); - public override Task CancelAsync() => abDownloader?.CancelAsync(); + public override Task CancelAsync() => abDownloader?.CancelAsync() ?? Task.CompletedTask; public override async Task ProcessAsync(LibraryBook libraryBook) { diff --git a/Source/FileManager/BackgroundFileSystem.cs b/Source/FileManager/BackgroundFileSystem.cs index c3503e2d..4f68003d 100644 --- a/Source/FileManager/BackgroundFileSystem.cs +++ b/Source/FileManager/BackgroundFileSystem.cs @@ -32,12 +32,18 @@ namespace FileManager Init(); } - public string FindFile(System.Text.RegularExpressions.Regex regex) + public LongPath FindFile(System.Text.RegularExpressions.Regex regex) { lock (fsCacheLocker) return fsCache.FirstOrDefault(s => regex.IsMatch(s)); } + public List FindFiles(System.Text.RegularExpressions.Regex regex) + { + lock (fsCacheLocker) + return fsCache.Where(s => regex.IsMatch(s)).ToList(); + } + public void RefreshFiles() { lock (fsCacheLocker) diff --git a/Source/FileManager/FileUtility.cs b/Source/FileManager/FileUtility.cs index 978613d9..f7435c91 100644 --- a/Source/FileManager/FileUtility.cs +++ b/Source/FileManager/FileUtility.cs @@ -90,9 +90,9 @@ namespace FileManager var pathNoPrefix = path.PathWithoutPrefix; + pathNoPrefix = pathNoPrefix?.Replace(':', '꞉')?.Replace('?', '︖')?.Replace('*', '⁎'); + pathNoPrefix = replaceInvalidChars(pathNoPrefix, illegalCharacterReplacements); - pathNoPrefix = standardizeSlashes(pathNoPrefix); - pathNoPrefix = replaceColons(pathNoPrefix, illegalCharacterReplacements); pathNoPrefix = removeDoubleSlashes(pathNoPrefix); return pathNoPrefix; @@ -107,24 +107,6 @@ namespace FileManager private static string replaceInvalidChars(string path, string illegalCharacterReplacements) => string.Join(illegalCharacterReplacements ?? "", path.Split(invalidChars)); - private static string standardizeSlashes(string path) - => path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - - private static string replaceColons(string path, string illegalCharacterReplacements) - { - // replace all colons except within the first 2 chars - var builder = new System.Text.StringBuilder(); - for (var i = 0; i < path.Length; i++) - { - var c = path[i]; - if (i >= 2 && c == ':') - builder.Append(illegalCharacterReplacements); - else - builder.Append(c); - } - return builder.ToString(); - } - private static string removeDoubleSlashes(string path) { if (path.Length < 2) diff --git a/Source/FileManager/LongPath.cs b/Source/FileManager/LongPath.cs index 18f59e8b..535da6d0 100644 --- a/Source/FileManager/LongPath.cs +++ b/Source/FileManager/LongPath.cs @@ -27,7 +27,7 @@ namespace FileManager //File I/O functions in the Windows API convert "/" to "\" as part of converting //the name to an NT-style name, except when using the "\\?\" prefix - path = path.Replace('/', '\\'); + path = path.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar); if (path.StartsWith(LONG_PATH_PREFIX)) return new LongPath { Path = path }; diff --git a/Source/LibationFileManager/AudibleFileStorage.cs b/Source/LibationFileManager/AudibleFileStorage.cs index d5c6a344..eae2ca31 100644 --- a/Source/LibationFileManager/AudibleFileStorage.cs +++ b/Source/LibationFileManager/AudibleFileStorage.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -9,6 +10,7 @@ namespace LibationFileManager public abstract class AudibleFileStorage { protected abstract LongPath GetFilePathCustom(string productId); + protected abstract List GetFilePathsCustom(string productId); #region static public static LongPath DownloadsInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName; @@ -57,6 +59,9 @@ namespace LibationFileManager return firstOrNull; } + public List GetPaths(string productId) + => GetFilePathsCustom(productId); + protected Regex GetBookSearchRegex(string productId) { var pattern = string.Format(regexTemplate, productId); @@ -70,11 +75,14 @@ namespace LibationFileManager internal AaxcFileStorage() : base(FileType.AAXC) { } protected override LongPath GetFilePathCustom(string productId) + => GetFilePathsCustom(productId).FirstOrDefault(); + + protected override List GetFilePathsCustom(string productId) { var regex = GetBookSearchRegex(productId); return FileUtility .SaferEnumerateFiles(DownloadsInProgressDirectory, "*.*", SearchOption.AllDirectories) - .FirstOrDefault(s => regex.IsMatch(s)); + .Where(s => regex.IsMatch(s)).ToList(); } public bool Exists(string productId) => GetFilePath(productId) is not null; @@ -87,7 +95,11 @@ namespace LibationFileManager private static BackgroundFileSystem BookDirectoryFiles { get; set; } private static object bookDirectoryFilesLocker { get; } = new(); + protected override LongPath GetFilePathCustom(string productId) + => GetFilePathsCustom(productId).FirstOrDefault(); + + protected override List GetFilePathsCustom(string productId) { // If user changed the BooksDirectory: reinitialize lock (bookDirectoryFilesLocker) @@ -95,11 +107,12 @@ namespace LibationFileManager BookDirectoryFiles = new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories); var regex = GetBookSearchRegex(productId); - return BookDirectoryFiles.FindFile(regex); + return BookDirectoryFiles.FindFiles(regex); } public void Refresh() => BookDirectoryFiles.RefreshFiles(); public LongPath GetPath(string productId) => GetFilePath(productId); - } + + } } diff --git a/Source/LibationWinForms/Form1.Liberate.cs b/Source/LibationWinForms/Form1.Liberate.cs index 619fc69a..d9ab6667 100644 --- a/Source/LibationWinForms/Form1.Liberate.cs +++ b/Source/LibationWinForms/Form1.Liberate.cs @@ -38,7 +38,7 @@ namespace LibationWinForms { SetQueueCollapseState(false); await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() - .Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated))); + .Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product))); } //Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing. } diff --git a/Source/LibationWinForms/LibationWinForms.csproj b/Source/LibationWinForms/LibationWinForms.csproj index 7ca31c26..7fab1293 100644 --- a/Source/LibationWinForms/LibationWinForms.csproj +++ b/Source/LibationWinForms/LibationWinForms.csproj @@ -43,6 +43,12 @@ Form1.cs + + + + Dialogs\SettingsDialog.cs + + diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index d4666e2e..0b5655f5 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -138,7 +138,7 @@ namespace LibationWinForms.ProcessQueue return Result; } - public async Task Cancel() + public async Task CancelAsync() { try { diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index f698e29b..ce073ea0 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -212,7 +212,7 @@ namespace LibationWinForms.ProcessQueue private void cancelAllBtn_Click(object sender, EventArgs e) { Queue.ClearQueue(); - Queue.Current?.Cancel(); + Queue.Current?.CancelAsync(); virtualFlowControl2.VirtualControlCount = Queue.Count; UpdateAllControls(); } @@ -331,7 +331,7 @@ namespace LibationWinForms.ProcessQueue ProcessBook item = Queue[queueIndex]; if (buttonName == nameof(panelClicked.cancelBtn)) { - await item.Cancel(); + await item.CancelAsync(); Queue.RemoveQueued(item); virtualFlowControl2.VirtualControlCount = Queue.Count; }