New and moved files Upsert themselves in FileManager.FilePathCache

This commit is contained in:
Robert McRackan 2021-10-08 11:47:54 -04:00
parent 18cca53968
commit d0b78cc501
11 changed files with 149 additions and 144 deletions

View file

@ -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<string> multiPartFilePaths { get; } = new List<string>();
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 <fileNameChars.Length; i++)
{
foreach (var illegal in Path.GetInvalidFileNameChars())
{
if (fileNameChars[i] == illegal)
{
fileNameChars[i] = ' ';
break;
}
}
}
multiPartFilePaths.Add(path);
var fileName = new string(fileNameChars).Truncate(MAX_FILENAME_LENGTH - extension.Length);
return Path.Combine(Path.GetDirectoryName(baseFileName), fileName + extension);
}
return path;
}
private void AaxFile_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)
{
@ -213,13 +221,10 @@ namespace AaxDecrypter
public override void Cancel()
{
isCanceled = true;
IsCanceled = true;
aaxFile?.Cancel();
aaxFile?.Dispose();
CloseInputFileStream();
}
protected override int GetSpeedup(TimeSpan elapsed)
=> (int)(aaxFile.Duration.TotalSeconds / (long)elapsed.TotalSeconds);
}
}
}

View file

@ -21,44 +21,39 @@ namespace AaxDecrypter
public event EventHandler<byte[]> RetrievedCoverArt;
public event EventHandler<DownloadProgress> DecryptProgressUpdate;
public event EventHandler<TimeSpan> DecryptTimeRemaining;
public event EventHandler<string> 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);
}
}

View file

@ -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;
}
}
}