diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index f15d3c6a..2cbc8580 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -1,11 +1,12 @@ -using AAXClean; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AAXClean; using Dinah.Core; using Dinah.Core.IO; using Dinah.Core.Net.Http; using Dinah.Core.StepRunner; -using System; -using System.IO; -using System.Linq; namespace AaxDecrypter { @@ -13,17 +14,19 @@ namespace AaxDecrypter { const int MAX_FILENAME_LENGTH = 255; private static readonly TimeSpan minChapterLength = TimeSpan.FromSeconds(3); - protected override StepSequence steps { get; } + protected override StepSequence Steps { get; } private AaxFile aaxFile; private OutputFormat OutputFormat { get; } + private List multiPartFilePaths { get; } = new List(); + public AaxcDownloadConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat, bool splitFileByChapters) - :base(outFileName, cacheDirectory, dlLic) + : base(outFileName, cacheDirectory, dlLic) { OutputFormat = outputFormat; - steps = new StepSequence + Steps = new StepSequence { Name = "Download and Convert Aaxc To " + OutputFormat, @@ -31,8 +34,8 @@ namespace AaxDecrypter ["Step 2: Download Decrypted Audiobook"] = splitFileByChapters ? Step2_DownloadAudiobookAsMultipleFilesPerChapter : Step2_DownloadAudiobookAsSingleFile, - ["Step 3: Create Cue"] = splitFileByChapters - ? () => true + ["Step 3: Create Cue"] = splitFileByChapters + ? () => true : Step3_CreateCue, ["Step 4: Cleanup"] = Step4_Cleanup, }; @@ -49,7 +52,7 @@ namespace AaxDecrypter } protected override bool Step1_GetMetadata() - { + { aaxFile = new AaxFile(InputFileStream); OnRetrievedTitle(aaxFile.AppleTags.TitleSansUnabridged); @@ -57,34 +60,38 @@ namespace AaxDecrypter OnRetrievedNarrators(aaxFile.AppleTags.Narrator ?? "[unknown]"); OnRetrievedCoverArt(aaxFile.AppleTags.Cover); - return !isCanceled; + return !IsCanceled; } protected override bool Step2_DownloadAudiobookAsSingleFile() { var zeroProgress = Step2_Start(); - - if (File.Exists(outputFileName)) - FileExt.SafeDelete(outputFileName); - var outputFile = File.Open(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); + if (File.Exists(OutputFileName)) + FileExt.SafeDelete(OutputFileName); + + var outputFile = File.Open(OutputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); aaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate; - var decryptionResult = OutputFormat == OutputFormat.M4b ? aaxFile.ConvertToMp4a(outputFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outputFile); + var decryptionResult = OutputFormat == OutputFormat.M4b ? aaxFile.ConvertToMp4a(outputFile, DownloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outputFile); aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; - downloadLicense.ChapterInfo = aaxFile.Chapters; - + DownloadLicense.ChapterInfo = aaxFile.Chapters; + Step2_End(zeroProgress); - return decryptionResult == ConversionResult.NoErrorsDetected && !isCanceled; + var success = decryptionResult == ConversionResult.NoErrorsDetected && !IsCanceled; + if (success) + base.OnFileCreated(OutputFileName); + + return success; } private bool Step2_DownloadAudiobookAsMultipleFilesPerChapter() { var zeroProgress = Step2_Start(); - var chapters = downloadLicense.ChapterInfo.Chapters.ToList(); + var chapters = DownloadLicense.ChapterInfo.Chapters.ToList(); //Ensure split files are at least minChapterLength in duration. var splitChapters = new ChapterInfo(); @@ -106,16 +113,25 @@ namespace AaxDecrypter } } + // reset, just in case + multiPartFilePaths.Clear(); + aaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate; - if(OutputFormat == OutputFormat.M4b) + if (OutputFormat == OutputFormat.M4b) ConvertToMultiMp4b(splitChapters); - else + else ConvertToMultiMp3(splitChapters); aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; Step2_End(zeroProgress); - return !isCanceled; + var success = !IsCanceled; + + if (success) + foreach (var path in multiPartFilePaths) + OnFileCreated(path); + + return success; } private DownloadProgress Step2_Start() @@ -129,10 +145,10 @@ namespace AaxDecrypter OnDecryptProgressUpdate(zeroProgress); - aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV); + aaxFile.SetDecryptionKey(DownloadLicense.AudibleKey, DownloadLicense.AudibleIV); return zeroProgress; } - + private void Step2_End(DownloadProgress zeroProgress) { aaxFile.Close(); @@ -147,19 +163,19 @@ namespace AaxDecrypter var chapterCount = 0; aaxFile.ConvertToMultiMp4a(splitChapters, newSplitCallback => { - var fileName = GetMultipartFileName(outputFileName, ++chapterCount, newSplitCallback.Chapter.Title); + var fileName = GetMultipartFileName(++chapterCount, newSplitCallback.Chapter.Title); if (File.Exists(fileName)) FileExt.SafeDelete(fileName); newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate); }); } - + private void ConvertToMultiMp3(ChapterInfo splitChapters) { var chapterCount = 0; aaxFile.ConvertToMultiMp3(splitChapters, newSplitCallback => { - var fileName = GetMultipartFileName(outputFileName, ++chapterCount, newSplitCallback.Chapter.Title); + var fileName = GetMultipartFileName(++chapterCount, newSplitCallback.Chapter.Title); if (File.Exists(fileName)) FileExt.SafeDelete(fileName); newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate); @@ -167,29 +183,21 @@ namespace AaxDecrypter }); } - private static string GetMultipartFileName(string baseFileName, int chapterCount, string chapterTitle) + // return. cache name for event call at end of processing + private string GetMultipartFileName(int chapterCount, string chapterTitle) { - string extension = Path.GetExtension(baseFileName); + string extension = Path.GetExtension(OutputFileName); - var fileNameChars = $"{Path.GetFileNameWithoutExtension(baseFileName)} - {chapterCount:D2} - {chapterTitle}".ToCharArray(); + var filenameBase = $"{Path.GetFileNameWithoutExtension(OutputFileName)} - {chapterCount:D2} - {chapterTitle}"; + // Replace illegal path characters with spaces + var filenameBaseSafe = string.Join(" ", filenameBase.Split(Path.GetInvalidFileNameChars())); + var fileName = filenameBaseSafe.Truncate(MAX_FILENAME_LENGTH - extension.Length); + var path = Path.Combine(Path.GetDirectoryName(OutputFileName), fileName + extension); - //Replace illegal path characters with spaces. - for (int i = 0; i (int)(aaxFile.Duration.TotalSeconds / (long)elapsed.TotalSeconds); - } + } } diff --git a/AaxDecrypter/AudiobookDownloadBase.cs b/AaxDecrypter/AudiobookDownloadBase.cs index a5436aab..bd89d4ca 100644 --- a/AaxDecrypter/AudiobookDownloadBase.cs +++ b/AaxDecrypter/AudiobookDownloadBase.cs @@ -21,44 +21,39 @@ namespace AaxDecrypter public event EventHandler RetrievedCoverArt; public event EventHandler DecryptProgressUpdate; public event EventHandler DecryptTimeRemaining; + public event EventHandler FileCreated; - public string AppName { get; set; } - - protected bool isCanceled { get; set; } - protected string outputFileName { get; } - protected string cacheDir { get; } - protected DownloadLicense downloadLicense { get; } + protected bool IsCanceled { get; set; } + protected string OutputFileName { get; } + protected string CacheDir { get; } + protected DownloadLicense DownloadLicense { get; } protected NetworkFileStream InputFileStream => (nfsPersister ??= OpenNetworkFileStream()).NetworkFileStream; - protected abstract StepSequence steps { get; } + protected abstract StepSequence Steps { get; } private NetworkFileStreamPersister nfsPersister; - private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json"); + private string jsonDownloadState => Path.Combine(CacheDir, Path.GetFileNameWithoutExtension(OutputFileName) + ".json"); private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".tmp"); public AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadLicense dlLic) { - AppName = GetType().Name; + OutputFileName = ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName)); - ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName)); - outputFileName = outFileName; - - var outDir = Path.GetDirectoryName(outputFileName); + var outDir = Path.GetDirectoryName(OutputFileName); if (!Directory.Exists(outDir)) throw new ArgumentNullException(nameof(outDir), "Directory does not exist"); - if (File.Exists(outputFileName)) - File.Delete(outputFileName); + if (File.Exists(OutputFileName)) + File.Delete(OutputFileName); if (!Directory.Exists(cacheDirectory)) throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist"); - cacheDir = cacheDirectory; + CacheDir = cacheDirectory; - downloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); + DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); } public abstract void Cancel(); - protected abstract int GetSpeedup(TimeSpan elapsed); protected abstract bool Step2_DownloadAudiobookAsSingleFile(); protected abstract bool Step1_GetMetadata(); @@ -72,7 +67,7 @@ namespace AaxDecrypter public bool Run() { - var (IsSuccess, Elapsed) = steps.Run(); + var (IsSuccess, Elapsed) = Steps.Run(); if (!IsSuccess) { @@ -80,7 +75,6 @@ namespace AaxDecrypter return false; } - //Serilog.Log.Logger.Information($"Speedup is {GetSpeedup(Elapsed)}x realtime."); return true; } @@ -94,8 +88,10 @@ namespace AaxDecrypter => RetrievedCoverArt?.Invoke(this, coverArt); protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress) => DecryptProgressUpdate?.Invoke(this, downloadProgress); - protected void OnDecryptTimeRemaining(TimeSpan timeRemaining) + protected void OnDecryptTimeRemaining(TimeSpan timeRemaining) => DecryptTimeRemaining?.Invoke(this, timeRemaining); + protected void OnFileCreated(string path) + => FileCreated?.Invoke(this, path); protected void CloseInputFileStream() { @@ -108,57 +104,51 @@ namespace AaxDecrypter // not a critical step. its failure should not prevent future steps from running try { - File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), downloadLicense.ChapterInfo)); + File.WriteAllText(PathLib.ReplaceExtension(OutputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadLicense.ChapterInfo)); } catch (Exception ex) { Serilog.Log.Logger.Error(ex, $"{nameof(Step3_CreateCue)}. FAILED"); } - return !isCanceled; + return !IsCanceled; } protected bool Step4_Cleanup() { FileExt.SafeDelete(jsonDownloadState); FileExt.SafeDelete(tempFile); - return !isCanceled; + return !IsCanceled; } private NetworkFileStreamPersister OpenNetworkFileStream() { - NetworkFileStreamPersister nfsp; + if (!File.Exists(jsonDownloadState)) + return NewNetworkFilePersister(); - if (File.Exists(jsonDownloadState)) + try { - try - { - nfsp = new NetworkFileStreamPersister(jsonDownloadState); - //If More than ~1 hour has elapsed since getting the download url, it will expire. - //The new url will be to the same file. - nfsp.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl)); - } - catch - { - FileExt.SafeDelete(jsonDownloadState); - FileExt.SafeDelete(tempFile); - nfsp = NewNetworkFilePersister(); - } + var nfsp = new NetworkFileStreamPersister(jsonDownloadState); + // If More than ~1 hour has elapsed since getting the download url, it will expire. + // The new url will be to the same file. + nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadLicense.DownloadUrl)); + return nfsp; } - else + catch { - nfsp = NewNetworkFilePersister(); + FileExt.SafeDelete(jsonDownloadState); + FileExt.SafeDelete(tempFile); + return NewNetworkFilePersister(); } - return nfsp; } private NetworkFileStreamPersister NewNetworkFilePersister() { var headers = new System.Net.WebHeaderCollection { - { "User-Agent", downloadLicense.UserAgent } + { "User-Agent", DownloadLicense.UserAgent } }; - var networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); + var networkFileStream = new NetworkFileStream(tempFile, new Uri(DownloadLicense.DownloadUrl), 0, headers); return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); } } diff --git a/AaxDecrypter/UnencryptedAudiobookDownloader.cs b/AaxDecrypter/UnencryptedAudiobookDownloader.cs index 31623e1d..dcbb98a3 100644 --- a/AaxDecrypter/UnencryptedAudiobookDownloader.cs +++ b/AaxDecrypter/UnencryptedAudiobookDownloader.cs @@ -10,13 +10,12 @@ namespace AaxDecrypter { public class UnencryptedAudiobookDownloader : AudiobookDownloadBase { - protected override StepSequence steps { get; } + protected override StepSequence Steps { get; } public UnencryptedAudiobookDownloader(string outFileName, string cacheDirectory, DownloadLicense dlLic) : base(outFileName, cacheDirectory, dlLic) { - - steps = new StepSequence + Steps = new StepSequence { Name = "Download Mp3 Audiobook", @@ -29,21 +28,15 @@ namespace AaxDecrypter public override void Cancel() { - isCanceled = true; + IsCanceled = true; CloseInputFileStream(); } - protected override int GetSpeedup(TimeSpan elapsed) - { - //Not implemented - return 0; - } - protected override bool Step1_GetMetadata() { OnRetrievedCoverArt(null); - return !isCanceled; + return !IsCanceled; } protected override bool Step2_DownloadAudiobookAsSingleFile() @@ -75,12 +68,13 @@ namespace AaxDecrypter CloseInputFileStream(); - if (File.Exists(outputFileName)) - FileExt.SafeDelete(outputFileName); + if (File.Exists(OutputFileName)) + FileExt.SafeDelete(OutputFileName); - FileExt.SafeMove(InputFileStream.SaveFilePath, outputFileName); + FileExt.SafeMove(InputFileStream.SaveFilePath, OutputFileName); + OnFileCreated(OutputFileName); - return !isCanceled; + return !IsCanceled; } } } diff --git a/AppScaffolding/AppScaffolding.csproj b/AppScaffolding/AppScaffolding.csproj index 9ee3175c..d8a29a10 100644 --- a/AppScaffolding/AppScaffolding.csproj +++ b/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net5.0 - 6.2.3.0 + 6.2.4.0 diff --git a/FileLiberator/ConvertToMp3.cs b/FileLiberator/ConvertToMp3.cs index 2d58b17f..abb1e6e2 100644 --- a/FileLiberator/ConvertToMp3.cs +++ b/FileLiberator/ConvertToMp3.cs @@ -55,6 +55,7 @@ namespace FileLiberator var mp3Path = Mp3FileName(m4bPath); FileExt.SafeMove(mp3File.Name, mp3Path); + OnFileCreated(libraryBook.Book.AudibleProductId, FileManager.FileType.Audio, mp3Path); var statusHandler = new StatusHandler(); diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 86403674..b794157b 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -25,16 +25,16 @@ namespace FileLiberator if (libraryBook.Book.Audio_Exists) return new StatusHandler { "Cannot find decrypt. Final audio file already exists" }; - var outputAudioFilename = await downloadAudiobookAsync(AudibleFileStorage.DownloadsInProgress, AudibleFileStorage.DecryptInProgress, libraryBook); + var outputAudioFilename = await downloadAudiobookAsync(libraryBook); // decrypt failed if (outputAudioFilename is null) return new StatusHandler { "Decrypt failed" }; - // moves files and returns dest dir - var moveResults = MoveFilesToBooksDir(libraryBook.Book, outputAudioFilename); + // moves new files from temp dir to final dest + var movedAudioFile = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename); - if (!moveResults.movedAudioFile) + if (!movedAudioFile) return new StatusHandler { "Cannot find final audio file after decryption" }; libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated; @@ -47,7 +47,7 @@ namespace FileLiberator } } - private async Task downloadAudiobookAsync(string cacheDir, string destinationDir, LibraryBook libraryBook) + private async Task downloadAudiobookAsync(LibraryBook libraryBook) { OnStreamingBegin($"Begin decrypting {libraryBook}"); @@ -82,18 +82,20 @@ namespace FileLiberator audiobookDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs)); } - var outFileName = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{outputFormat.ToString().ToLower()}"); + var outFileName = Path.Combine(AudibleFileStorage.DecryptInProgress, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{outputFormat.ToString().ToLower()}"); + + var cacheDir = AudibleFileStorage.DownloadsInProgress; abDownloader = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm ? new AaxcDownloadConverter(outFileName, cacheDir, audiobookDlLic, outputFormat, Configuration.Instance.SplitFilesByChapter) : new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic); - abDownloader.AppName = "Libation"; abDownloader.DecryptProgressUpdate += (_, progress) => OnStreamingProgressChanged(progress); abDownloader.DecryptTimeRemaining += (_, remaining) => OnStreamingTimeRemaining(remaining); abDownloader.RetrievedTitle += (_, title) => OnTitleDiscovered(title); abDownloader.RetrievedAuthors += (_, authors) => OnAuthorsDiscovered(authors); abDownloader.RetrievedNarrators += (_, narrators) => OnNarratorsDiscovered(narrators); abDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt; + abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook.Book.AudibleProductId, FileType.Audio, path); // REAL WORK DONE HERE var success = await Task.Run(abDownloader.Run); @@ -123,41 +125,46 @@ namespace FileLiberator } } - private static (string destinationDir, bool movedAudioFile) MoveFilesToBooksDir(Book product, string outputAudioFilename) + /// Move new files to 'Books' directory + /// True if audiobook file(s) were successfully created and can be located on disk. Else false. + private static bool moveFilesToBooksDir(Book book, string outputAudioFilename) { // create final directory. move each file into it. MOVE AUDIO FILE LAST // new dir: safetitle_limit50char + " [" + productId + "]" // TODO make this method handle multiple audio files or a single audio file. - var destinationDir = AudibleFileStorage.Audio.GetDestDir(product.Title, product.AudibleProductId); + var destinationDir = AudibleFileStorage.Audio.GetDestDir(book.Title, book.AudibleProductId); Directory.CreateDirectory(destinationDir); - var sortedFiles = getProductFilesSorted(product, outputAudioFilename); + var sortedFiles = getProductFilesSorted(book, outputAudioFilename); var musicFileExt = Path.GetExtension(outputAudioFilename).Trim('.'); // audio filename: safetitle_limit50char + " [" + productId + "]." + audio_ext - var audioFileName = FileUtility.GetValidFilename(destinationDir, product.Title, musicFileExt, product.AudibleProductId); + var audioFileName = FileUtility.GetValidFilename(destinationDir, book.Title, musicFileExt, book.AudibleProductId); bool movedAudioFile = false; foreach (var f in sortedFiles) { + var isAudio = AudibleFileStorage.Audio.IsFileTypeMatch(f); var dest - = AudibleFileStorage.Audio.IsFileTypeMatch(f)//f.Extension.Equals($".{musicFileExt}", StringComparison.OrdinalIgnoreCase) + = isAudio ? Path.Join(destinationDir, f.Name) // non-audio filename: safetitle_limit50char + " [" + productId + "][" + audio_ext + "]." + non_audio_ext - : FileUtility.GetValidFilename(destinationDir, product.Title, f.Extension, product.AudibleProductId, musicFileExt); + : FileUtility.GetValidFilename(destinationDir, book.Title, f.Extension, book.AudibleProductId, musicFileExt); if (Path.GetExtension(dest).Trim('.').ToLower() == "cue") Cue.UpdateFileName(f, audioFileName); File.Move(f.FullName, dest); + if (isAudio) + FilePathCache.Upsert(book.AudibleProductId, FileType.Audio, dest); movedAudioFile |= AudibleFileStorage.Audio.IsFileTypeMatch(f); } AudibleFileStorage.Audio.Refresh(); - return (destinationDir, movedAudioFile); + return movedAudioFile; } private static List getProductFilesSorted(Book product, string outputAudioFilename) diff --git a/FileLiberator/DownloadFile.cs b/FileLiberator/DownloadFile.cs index c41948e7..2decfd58 100644 --- a/FileLiberator/DownloadFile.cs +++ b/FileLiberator/DownloadFile.cs @@ -20,6 +20,7 @@ namespace FileLiberator try { var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress); + OnFileCreated("Upgrade", FileManager.FileType.Zip, actualDownloadedFilePath); return actualDownloadedFilePath; } finally diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs index 7a6fa039..2b4f4ff5 100644 --- a/FileLiberator/DownloadPdf.cs +++ b/FileLiberator/DownloadPdf.cs @@ -72,6 +72,8 @@ namespace FileLiberator var client = new HttpClient(); var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress); + OnFileCreated(libraryBook.Book.AudibleProductId, FileType.PDF, actualDownloadedFilePath); + OnStatusUpdate(actualDownloadedFilePath); return actualDownloadedFilePath; } diff --git a/FileLiberator/Streamable.cs b/FileLiberator/Streamable.cs index 6f130e9f..d86377d0 100644 --- a/FileLiberator/Streamable.cs +++ b/FileLiberator/Streamable.cs @@ -9,6 +9,8 @@ namespace FileLiberator public event EventHandler StreamingProgressChanged; public event EventHandler StreamingTimeRemaining; public event EventHandler StreamingCompleted; + /// Fired when a file is successfully saved to disk + public event EventHandler<(string id, FileManager.FileType type, string path)> FileCreated; protected void OnStreamingBegin(string filePath) { @@ -30,8 +32,13 @@ namespace FileLiberator { Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(StreamingCompleted), Message = filePath }); StreamingCompleted?.Invoke(this, filePath); + } - //TODO: Update file cache + protected void OnFileCreated(string productId, FileManager.FileType type, string path) + { + Serilog.Log.Logger.Information("File created {@DebugInfo}", new { Name = nameof(FileCreated), productId, TypeId = (int)type, TypeName = type.ToString(), path }); + FileManager.FilePathCache.Upsert(productId, type, path); + FileCreated?.Invoke(this, (productId, type, path)); } } } diff --git a/FileManager/AudibleFileStorage.cs b/FileManager/AudibleFileStorage.cs index 30d859d5..4f5457e8 100644 --- a/FileManager/AudibleFileStorage.cs +++ b/FileManager/AudibleFileStorage.cs @@ -8,7 +8,7 @@ using Dinah.Core.Collections.Generic; namespace FileManager { - public enum FileType { Unknown, Audio, AAXC, PDF } + public enum FileType { Unknown, Audio, AAXC, PDF, Zip } public abstract class AudibleFileStorage : Enumeration { @@ -66,16 +66,9 @@ namespace FileManager if (StorageDirectory == BooksDirectory) { //If user changed the BooksDirectory, reinitialize. - if (StorageDirectory != BookDirectoryFiles.RootDirectory) - { - lock (bookDirectoryFilesLocker) - { - if (StorageDirectory != BookDirectoryFiles.RootDirectory) - { - BookDirectoryFiles = new BackgroundFileSystem(StorageDirectory, "*.*", SearchOption.AllDirectories); - } - } - } + lock (bookDirectoryFilesLocker) + if (StorageDirectory != BookDirectoryFiles.RootDirectory) + BookDirectoryFiles = new BackgroundFileSystem(StorageDirectory, "*.*", SearchOption.AllDirectories); firstOrNull = BookDirectoryFiles.FindFile(regex); } @@ -87,10 +80,9 @@ namespace FileManager .FirstOrDefault(s => regex.IsMatch(s)); } - if (firstOrNull is null) - return null; + if (firstOrNull is not null) + FilePathCache.Upsert(productId, FileType, firstOrNull); - FilePathCache.Upsert(productId, FileType, firstOrNull); return firstOrNull; } #endregion diff --git a/FileManager/FilePathCache.cs b/FileManager/FilePathCache.cs index ea4070d5..c710740e 100644 --- a/FileManager/FilePathCache.cs +++ b/FileManager/FilePathCache.cs @@ -56,9 +56,15 @@ namespace FileManager } public static void Upsert(string id, FileType type, string path) - { - if (!File.Exists(path)) - throw new FileNotFoundException("Cannot add path to cache. File not found"); + { + if (!File.Exists(path)) + { + // file not found can happen after rapid move + System.Threading.Thread.Sleep(100); + + if (!File.Exists(path)) + throw new FileNotFoundException($"Cannot add path to cache. File not found. Id={id} FileType={type}", path); + } var entry = cache.SingleOrDefault(i => i.Id == id && i.FileType == type);