New and moved files Upsert themselves in FileManager.FilePathCache
This commit is contained in:
parent
18cca53968
commit
d0b78cc501
11 changed files with 149 additions and 144 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue