Fix temp file reuse/cleanup. Add retain aax option.

This commit is contained in:
Michael Bucari-Tovo 2022-05-08 11:08:23 -06:00
parent 8af60b56b6
commit 05f25a88c6
13 changed files with 90 additions and 55 deletions

View File

@ -10,7 +10,7 @@ namespace AaxDecrypter
protected AaxFile AaxFile;
protected AaxcDownloadConvertBase(string outFileName, string cacheDirectory, DownloadLicense dlLic)
protected AaxcDownloadConvertBase(string outFileName, string cacheDirectory, DownloadOptions dlLic)
: base(outFileName, cacheDirectory, dlLic) { }
/// <summary>Setting cover art by this method will insert the art into the audiobook metadata</summary>
@ -46,7 +46,7 @@ namespace AaxDecrypter
OnDecryptProgressUpdate(zeroProgress);
AaxFile.SetDecryptionKey(DownloadLicense.AudibleKey, DownloadLicense.AudibleIV);
AaxFile.SetDecryptionKey(DownloadOptions.AudibleKey, DownloadOptions.AudibleIV);
return zeroProgress;
}
@ -68,7 +68,7 @@ namespace AaxDecrypter
if (double.IsNormal(estTimeRemaining))
OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
var progressPercent = e.ProcessPosition.TotalSeconds / duration.TotalSeconds;
var progressPercent = (e.ProcessPosition / e.TotalDuration);
OnDecryptProgressUpdate(
new DownloadProgress

View File

@ -18,13 +18,13 @@ namespace AaxDecrypter
private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3);
private List<string> multiPartFilePaths { get; } = new List<string>();
public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic,
public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadOptions dlLic,
Func<MultiConvertFileProperties, string> multipartFileNameCallback = null)
: base(outFileName, cacheDirectory, dlLic)
{
Steps = new StepSequence
{
Name = "Download and Convert Aaxc To " + DownloadLicense.OutputFormat,
Name = "Download and Convert Aaxc To " + DownloadOptions.OutputFormat,
["Step 1: Get Aaxc Metadata"] = Step_GetMetadata,
["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter,
@ -61,10 +61,10 @@ That naming may not be desirable for everyone, but it's an easy change to instea
{
var zeroProgress = Step_DownloadAudiobook_Start();
var chapters = DownloadLicense.ChapterInfo.Chapters.ToList();
var chapters = DownloadOptions.ChapterInfo.Chapters.ToList();
// Ensure split files are at least minChapterLength in duration.
var splitChapters = new ChapterInfo(DownloadLicense.ChapterInfo.StartOffset);
var splitChapters = new ChapterInfo(DownloadOptions.ChapterInfo.StartOffset);
var runningTotal = TimeSpan.Zero;
string title = "";
@ -89,7 +89,7 @@ That naming may not be desirable for everyone, but it's an easy change to instea
ConversionResult result;
AaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate;
if (DownloadLicense.OutputFormat == OutputFormat.M4b)
if (DownloadOptions.OutputFormat == OutputFormat.M4b)
result = ConvertToMultiMp4a(splitChapters);
else
result = ConvertToMultiMp3(splitChapters);
@ -97,13 +97,7 @@ That naming may not be desirable for everyone, but it's an easy change to instea
Step_DownloadAudiobook_End(zeroProgress);
var success = result == ConversionResult.NoErrorsDetected;
if (success)
foreach (var path in multiPartFilePaths)
OnFileCreated(path);
return success;
return result == ConversionResult.NoErrorsDetected;
}
private ConversionResult ConvertToMultiMp4a(ChapterInfo splitChapters)
@ -111,7 +105,7 @@ That naming may not be desirable for everyone, but it's an easy change to instea
var chapterCount = 0;
return AaxFile.ConvertToMultiMp4a(splitChapters, newSplitCallback =>
createOutputFileStream(++chapterCount, splitChapters, newSplitCallback),
DownloadLicense.TrimOutputToChapterLength);
DownloadOptions.TrimOutputToChapterLength);
}
private ConversionResult ConvertToMultiMp3(ChapterInfo splitChapters)
@ -121,7 +115,7 @@ That naming may not be desirable for everyone, but it's an easy change to instea
{
createOutputFileStream(++chapterCount, splitChapters, newSplitCallback);
((NAudio.Lame.LameConfig)newSplitCallback.UserState).ID3.Track = chapterCount.ToString();
}, null, DownloadLicense.TrimOutputToChapterLength);
}, null, DownloadOptions.TrimOutputToChapterLength);
}
private void createOutputFileStream(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
@ -140,6 +134,8 @@ That naming may not be desirable for everyone, but it's an easy change to instea
FileUtility.SaferDelete(fileName);
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
OnFileCreated(fileName);
}
}
}

View File

@ -11,12 +11,12 @@ namespace AaxDecrypter
{
protected override StepSequence Steps { get; }
public AaxcDownloadSingleConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic)
public AaxcDownloadSingleConverter(string outFileName, string cacheDirectory, DownloadOptions dlLic)
: base(outFileName, cacheDirectory, dlLic)
{
Steps = new StepSequence
{
Name = "Download and Convert Aaxc To " + DownloadLicense.OutputFormat,
Name = "Download and Convert Aaxc To " + DownloadOptions.OutputFormat,
["Step 1: Get Aaxc Metadata"] = Step_GetMetadata,
["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsSingleFile,
@ -32,15 +32,16 @@ namespace AaxDecrypter
FileUtility.SaferDelete(OutputFileName);
var outputFile = File.Open(OutputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
OnFileCreated(OutputFileName);
AaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate;
var decryptionResult
= DownloadLicense.OutputFormat == OutputFormat.M4b
? AaxFile.ConvertToMp4a(outputFile, DownloadLicense.ChapterInfo, DownloadLicense.TrimOutputToChapterLength)
: AaxFile.ConvertToMp3(outputFile, null, DownloadLicense.ChapterInfo, DownloadLicense.TrimOutputToChapterLength);
= DownloadOptions.OutputFormat == OutputFormat.M4b
? AaxFile.ConvertToMp4a(outputFile, DownloadOptions.ChapterInfo, DownloadOptions.TrimOutputToChapterLength)
: AaxFile.ConvertToMp3(outputFile, null, DownloadOptions.ChapterInfo, DownloadOptions.TrimOutputToChapterLength);
AaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate;
DownloadLicense.ChapterInfo = AaxFile.Chapters;
DownloadOptions.ChapterInfo = AaxFile.Chapters;
Step_DownloadAudiobook_End(zeroProgress);

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dinah.Core;
using Dinah.Core.Net.Http;
@ -20,8 +21,9 @@ namespace AaxDecrypter
public event EventHandler<string> FileCreated;
protected bool IsCanceled { get; set; }
protected string OutputFileName { get; private set; }
protected DownloadLicense DownloadLicense { get; }
protected DownloadOptions DownloadOptions { get; }
protected NetworkFileStream InputFileStream => (nfsPersister ??= OpenNetworkFileStream()).NetworkFileStream;
// Don't give the property a 'set'. This should have to be an obvious choice; not accidental
@ -31,9 +33,9 @@ namespace AaxDecrypter
private NetworkFileStreamPersister nfsPersister;
private string jsonDownloadState { get; }
private string tempFile => Path.ChangeExtension(jsonDownloadState, ".tmp");
public string TempFilePath { get; }
protected AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadLicense dlLic)
protected AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadOptions dlLic)
{
OutputFileName = ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName));
@ -43,9 +45,11 @@ namespace AaxDecrypter
if (!Directory.Exists(cacheDirectory))
throw new DirectoryNotFoundException($"Directory does not exist: {nameof(cacheDirectory)}");
jsonDownloadState = Path.Combine(cacheDirectory, Path.ChangeExtension(OutputFileName, ".json"));
DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
jsonDownloadState = Path.Combine(cacheDirectory, Path.GetFileName(Path.ChangeExtension(OutputFileName, ".json")));
TempFilePath = Path.ChangeExtension(jsonDownloadState, ".tmp");
DownloadOptions = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
// delete file after validation is complete
FileUtility.SaferDelete(OutputFileName);
@ -99,7 +103,7 @@ namespace AaxDecrypter
{
var path = Path.ChangeExtension(OutputFileName, ".cue");
path = FileUtility.GetValidFilename(path);
File.WriteAllText(path, Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadLicense.ChapterInfo));
File.WriteAllText(path, Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadOptions.ChapterInfo));
OnFileCreated(path);
}
catch (Exception ex)
@ -110,10 +114,28 @@ namespace AaxDecrypter
}
protected bool Step_Cleanup()
{
bool success = !IsCanceled;
if (success)
{
FileUtility.SaferDelete(jsonDownloadState);
FileUtility.SaferDelete(tempFile);
return !IsCanceled;
if (DownloadOptions.RetainEncryptedFile)
{
string aaxPath = Path.ChangeExtension(TempFilePath, ".aax");
FileUtility.SaferMove(TempFilePath, aaxPath);
OnFileCreated(aaxPath);
}
else
FileUtility.SaferDelete(TempFilePath);
}
else
{
FileUtility.SaferDelete(OutputFileName);
}
return success;
}
private NetworkFileStreamPersister OpenNetworkFileStream()
@ -126,13 +148,13 @@ namespace AaxDecrypter
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));
nfsp.NetworkFileStream.SetUriForSameFile(new Uri(DownloadOptions.DownloadUrl));
return nfsp;
}
catch
{
FileUtility.SaferDelete(jsonDownloadState);
FileUtility.SaferDelete(tempFile);
FileUtility.SaferDelete(TempFilePath);
return NewNetworkFilePersister();
}
}
@ -141,10 +163,10 @@ namespace AaxDecrypter
{
var headers = new System.Net.WebHeaderCollection
{
{ "User-Agent", DownloadLicense.UserAgent }
{ "User-Agent", DownloadOptions.UserAgent }
};
var networkFileStream = new NetworkFileStream(tempFile, new Uri(DownloadLicense.DownloadUrl), 0, headers);
var networkFileStream = new NetworkFileStream(TempFilePath, new Uri(DownloadOptions.DownloadUrl), 0, headers);
return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
}
}

View File

@ -3,7 +3,7 @@ using Dinah.Core;
namespace AaxDecrypter
{
public class DownloadLicense
public class DownloadOptions
{
public string DownloadUrl { get; }
public string UserAgent { get; }
@ -11,9 +11,10 @@ namespace AaxDecrypter
public string AudibleIV { get; init; }
public OutputFormat OutputFormat { get; init; }
public bool TrimOutputToChapterLength { get; init; }
public bool RetainEncryptedFile { get; init; }
public ChapterInfo ChapterInfo { get; set; }
public DownloadLicense(string downloadUrl, string userAgent)
public DownloadOptions(string downloadUrl, string userAgent)
{
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
UserAgent = ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));

View File

@ -10,7 +10,7 @@ namespace AaxDecrypter
{
protected override StepSequence Steps { get; }
public UnencryptedAudiobookDownloader(string outFileName, string cacheDirectory, DownloadLicense dlLic)
public UnencryptedAudiobookDownloader(string outFileName, string cacheDirectory, DownloadOptions dlLic)
: base(outFileName, cacheDirectory, dlLic)
{
Steps = new StepSequence

View File

@ -84,6 +84,9 @@ namespace AppScaffolding
if (!config.Exists(nameof(config.StripAudibleBrandAudio)))
config.StripAudibleBrandAudio = false;
if (!config.Exists(nameof(config.RetainAaxFile)))
config.RetainAaxFile = false;
if (!config.Exists(nameof(config.FolderTemplate)))
config.FolderTemplate = Templates.Folder.DefaultTemplate;

View File

@ -39,7 +39,7 @@ namespace FileLiberator
/// File name: final file name.
/// </summary>
public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
=> Templates.File.GetFilename(libraryBook.ToDto(), AudibleFileStorage.DecryptInProgressDirectory, extension);
=> Templates.File.GetFilename(libraryBook.ToDto(), AudibleFileStorage.DecryptInProgressDirectory, extension, returnFirstExisting: true);
/// <summary>
/// PDF: audio file does not exist

View File

@ -61,7 +61,12 @@ namespace FileLiberator
// decrypt failed
if (!success)
{
foreach (var tmpFile in entries)
FileUtility.SaferDelete(tmpFile.Path);
return new StatusHandler { "Decrypt failed" };
}
// moves new files from temp dir to final dest
var movedAudioFile = moveFilesToBooksDir(libraryBook, entries);
@ -92,7 +97,7 @@ namespace FileLiberator
var api = await libraryBook.GetApiAsync();
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
var audiobookDlLic = BuildDownloadLicense(config, contentLic);
var audiobookDlLic = BuildDownloadOptions(config, contentLic);
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, audiobookDlLic.OutputFormat.ToString().ToLower());
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
@ -132,28 +137,28 @@ namespace FileLiberator
}
}
private static DownloadLicense BuildDownloadLicense(Configuration config, AudibleApi.Common.ContentLicense contentLic)
private static DownloadOptions BuildDownloadOptions(Configuration config, AudibleApi.Common.ContentLicense contentLic)
{
//I assume if ContentFormat == "MPEG" that the delivered file is an unencrypted mp3.
//I also assume that if DrmType != Adrm, the file will be an mp3.
//These assumptions may be wrong, and only time and bug reports will tell.
var outputFormat =
contentLic?.ContentMetadata?.ContentReference?.ContentFormat == "MPEG" ||
(config.AllowLibationFixup && config.DecryptToLossy) ?
bool encrypted = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm;
var outputFormat = !encrypted || (config.AllowLibationFixup && config.DecryptToLossy) ?
OutputFormat.Mp3 : OutputFormat.M4b;
var audiobookDlLic = new DownloadLicense
var audiobookDlLic = new DownloadOptions
(
contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl,
Resources.USER_AGENT
)
{
AudibleKey = contentLic?.Voucher?.Key,
AudibleIV = contentLic?.Voucher?.Iv,
OutputFormat = outputFormat,
TrimOutputToChapterLength = config.StripAudibleBrandAudio
TrimOutputToChapterLength = config.StripAudibleBrandAudio,
RetainEncryptedFile = config.RetainAaxFile && encrypted
};
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)

View File

@ -29,14 +29,14 @@ namespace FileManager
public string IllegalCharacterReplacements { get; set; }
/// <summary>Generate a valid path for this file or directory</summary>
public string GetFilePath()
public string GetFilePath(bool returnFirstExisting = false)
{
var filename = Template;
foreach (var r in ParameterReplacements)
filename = filename.Replace($"<{formatKey(r.Key)}>", formatValue(r.Value));
return FileUtility.GetValidFilename(filename, IllegalCharacterReplacements);
return FileUtility.GetValidFilename(filename, IllegalCharacterReplacements, returnFirstExisting);
}
private static string formatKey(string key)

View File

@ -48,7 +48,7 @@ namespace FileManager
/// <br/>- ensure uniqueness
/// <br/>- enforce max file length
/// </summary>
public static string GetValidFilename(string path, string illegalCharacterReplacements = "")
public static string GetValidFilename(string path, string illegalCharacterReplacements = "", bool returnFirstExisting = false)
{
ArgumentValidator.EnsureNotNull(path, nameof(path));
@ -69,7 +69,7 @@ namespace FileManager
fullfilename = removeInvalidWhitespace(fullfilename);
var i = 0;
while (File.Exists(fullfilename))
while (File.Exists(fullfilename) && !returnFirstExisting)
{
var increm = $" ({++i})";
fullfilename = fileStem.Truncate(MAX_FILENAME_LENGTH - increm.Length - extension.Length) + increm + extension;

View File

@ -117,6 +117,13 @@ namespace LibationFileManager
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
}
[Description("Retain the Aax file after successfully decrypting")]
public bool RetainAaxFile
{
get => persistentDictionary.GetNonString<bool>(nameof(RetainAaxFile));
set => persistentDictionary.SetNonString(nameof(RetainAaxFile), value);
}
public enum BadBookAction
{
[Description("Ask each time what action to take.")]

View File

@ -230,9 +230,9 @@ namespace LibationFileManager
#region to file name
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension)
public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension, bool returnFirstExisting = false)
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FileTemplate, dirFullPath, extension)
.GetFilePath();
.GetFilePath(returnFirstExisting);
#endregion
}