Removed unused classes.
This commit is contained in:
parent
9550aac788
commit
81d0f87b8a
@ -1,207 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AaxDecrypter;
|
|
||||||
using AudibleApi;
|
|
||||||
using DataLayer;
|
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.ErrorHandling;
|
|
||||||
using FileManager;
|
|
||||||
using InternalUtilities;
|
|
||||||
|
|
||||||
namespace FileLiberator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Decrypt audiobook files
|
|
||||||
///
|
|
||||||
/// Processes:
|
|
||||||
/// Download: download aax file: the DRM encrypted audiobook
|
|
||||||
/// Decrypt: remove DRM encryption from audiobook. Store final book
|
|
||||||
/// Backup: perform all steps (downloaded, decrypt) still needed to get final book
|
|
||||||
/// </summary>
|
|
||||||
public class DecryptBook : IDecryptable
|
|
||||||
{
|
|
||||||
public event EventHandler<LibraryBook> Begin;
|
|
||||||
public event EventHandler<string> StatusUpdate;
|
|
||||||
public event EventHandler<string> DecryptBegin;
|
|
||||||
|
|
||||||
public event EventHandler<string> TitleDiscovered;
|
|
||||||
public event EventHandler<string> AuthorsDiscovered;
|
|
||||||
public event EventHandler<string> NarratorsDiscovered;
|
|
||||||
public event EventHandler<byte[]> CoverImageFilepathDiscovered;
|
|
||||||
public event EventHandler<int> UpdateProgress;
|
|
||||||
|
|
||||||
public event EventHandler<string> DecryptCompleted;
|
|
||||||
public event EventHandler<LibraryBook> Completed;
|
|
||||||
|
|
||||||
public bool Validate(LibraryBook libraryBook)
|
|
||||||
=> AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId)
|
|
||||||
&& !AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId);
|
|
||||||
|
|
||||||
// do NOT use ConfigureAwait(false) on ProcessAsync()
|
|
||||||
// often calls events which prints to forms in the UI context
|
|
||||||
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
|
||||||
{
|
|
||||||
Begin?.Invoke(this, libraryBook);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var aaxFilename = AudibleFileStorage.AAX.GetPath(libraryBook.Book.AudibleProductId);
|
|
||||||
|
|
||||||
if (aaxFilename == null)
|
|
||||||
return new StatusHandler { "aaxFilename parameter is null" };
|
|
||||||
if (!File.Exists(aaxFilename))
|
|
||||||
return new StatusHandler { $"Cannot find AAX file: {aaxFilename}" };
|
|
||||||
if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId))
|
|
||||||
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
|
||||||
|
|
||||||
var chapters = await downloadChapterNamesAsync(libraryBook);
|
|
||||||
|
|
||||||
var outputAudioFilename = await aaxToM4bConverterDecryptAsync(aaxFilename, libraryBook, chapters);
|
|
||||||
|
|
||||||
// decrypt failed
|
|
||||||
if (outputAudioFilename == null)
|
|
||||||
return new StatusHandler { "Decrypt failed" };
|
|
||||||
|
|
||||||
// moves files and returns dest dir. Do not put inside of if(RetainAaxFiles)
|
|
||||||
var destinationDir = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
|
|
||||||
|
|
||||||
var jsonFilename = PathLib.ReplaceExtension(aaxFilename, "json");
|
|
||||||
if (Configuration.Instance.RetainAaxFiles)
|
|
||||||
{
|
|
||||||
var newAaxFilename = FileUtility.GetValidFilename(
|
|
||||||
destinationDir,
|
|
||||||
Path.GetFileNameWithoutExtension(aaxFilename),
|
|
||||||
"aax");
|
|
||||||
File.Move(aaxFilename, newAaxFilename);
|
|
||||||
|
|
||||||
var newJsonFilename = PathLib.ReplaceExtension(newAaxFilename, "json");
|
|
||||||
File.Move(jsonFilename, newJsonFilename);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Dinah.Core.IO.FileExt.SafeDelete(aaxFilename);
|
|
||||||
Dinah.Core.IO.FileExt.SafeDelete(jsonFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
var finalAudioExists = AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId);
|
|
||||||
if (!finalAudioExists)
|
|
||||||
return new StatusHandler { "Cannot find final audio file after decryption" };
|
|
||||||
|
|
||||||
return new StatusHandler();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Completed?.Invoke(this, libraryBook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Chapters> downloadChapterNamesAsync(LibraryBook libraryBook)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var api = await AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
|
|
||||||
var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId);
|
|
||||||
if (contentMetadata?.ChapterInfo is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new DownloadedChapters(contentMetadata.ChapterInfo);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> aaxToM4bConverterDecryptAsync(string aaxFilename, LibraryBook libraryBook, Chapters chapters)
|
|
||||||
{
|
|
||||||
DecryptBegin?.Invoke(this, $"Begin decrypting {aaxFilename}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jsonPath = PathLib.ReplaceExtension(aaxFilename, "json");
|
|
||||||
var jsonContents = File.ReadAllText(jsonPath);
|
|
||||||
var dlLic = Newtonsoft.Json.JsonConvert.DeserializeObject<AudibleApiDTOs.DownloadLicense>(jsonContents);
|
|
||||||
|
|
||||||
var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, dlLic.AudibleKey, dlLic.AudibleIV, chapters);
|
|
||||||
converter.AppName = "Libation";
|
|
||||||
|
|
||||||
TitleDiscovered?.Invoke(this, converter.tags.title);
|
|
||||||
AuthorsDiscovered?.Invoke(this, converter.tags.author);
|
|
||||||
NarratorsDiscovered?.Invoke(this, converter.tags.narrator);
|
|
||||||
CoverImageFilepathDiscovered?.Invoke(this, converter.coverBytes);
|
|
||||||
|
|
||||||
// override default which was set in CreateAsync
|
|
||||||
var proposedOutputFile = Path.Combine(AudibleFileStorage.DecryptInProgress, $"[{libraryBook.Book.AudibleProductId}].m4b");
|
|
||||||
converter.SetOutputFilename(proposedOutputFile);
|
|
||||||
converter.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress);
|
|
||||||
|
|
||||||
// REAL WORK DONE HERE
|
|
||||||
var success = await Task.Run(() => converter.Run());
|
|
||||||
|
|
||||||
// decrypt failed
|
|
||||||
if (!success)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return converter.outputFileName;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DecryptCompleted?.Invoke(this, $"Completed decrypting {aaxFilename}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
|
|
||||||
{
|
|
||||||
// create final directory. move each file into it. MOVE AUDIO FILE LAST
|
|
||||||
// new dir: safetitle_limit50char + " [" + productId + "]"
|
|
||||||
|
|
||||||
var destinationDir = AudibleFileStorage.Audio.GetDestDir(product.Title, product.AudibleProductId);
|
|
||||||
Directory.CreateDirectory(destinationDir);
|
|
||||||
|
|
||||||
var sortedFiles = getProductFilesSorted(product, outputAudioFilename);
|
|
||||||
|
|
||||||
var musicFileExt = Path.GetExtension(outputAudioFilename).Trim('.');
|
|
||||||
|
|
||||||
// audio filename: safetitle_limit50char + " [" + productId + "]." + audio_ext
|
|
||||||
var audioFileName = FileUtility.GetValidFilename(destinationDir, product.Title, musicFileExt, product.AudibleProductId);
|
|
||||||
|
|
||||||
foreach (var f in sortedFiles)
|
|
||||||
{
|
|
||||||
var dest
|
|
||||||
= AudibleFileStorage.Audio.IsFileTypeMatch(f)
|
|
||||||
? audioFileName
|
|
||||||
// non-audio filename: safetitle_limit50char + " [" + productId + "][" + audio_ext +"]." + non_audio_ext
|
|
||||||
: FileUtility.GetValidFilename(destinationDir, product.Title, f.Extension, product.AudibleProductId, musicFileExt);
|
|
||||||
|
|
||||||
if (Path.GetExtension(dest).Trim('.').ToLower() == "cue")
|
|
||||||
Cue.UpdateFileName(f, audioFileName);
|
|
||||||
|
|
||||||
File.Move(f.FullName, dest);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destinationDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<FileInfo> getProductFilesSorted(Book product, string outputAudioFilename)
|
|
||||||
{
|
|
||||||
// files are: temp path\author\[asin].ext
|
|
||||||
var m4bDir = new FileInfo(outputAudioFilename).Directory;
|
|
||||||
var files = m4bDir
|
|
||||||
.EnumerateFiles()
|
|
||||||
.Where(f => f.Name.ContainsInsensitive(product.AudibleProductId))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// move audio files to the end of the collection so these files are moved last
|
|
||||||
var musicFiles = files.Where(f => AudibleFileStorage.Audio.IsFileTypeMatch(f));
|
|
||||||
var sortedFiles = files
|
|
||||||
.Except(musicFiles)
|
|
||||||
.Concat(musicFiles)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return sortedFiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AudibleApi;
|
|
||||||
using DataLayer;
|
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.ErrorHandling;
|
|
||||||
using FileManager;
|
|
||||||
using System.Net.Http;
|
|
||||||
using Dinah.Core.Net.Http;
|
|
||||||
|
|
||||||
namespace FileLiberator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Download DRM book
|
|
||||||
///
|
|
||||||
/// Processes:
|
|
||||||
/// Download: download aax file: the DRM encrypted audiobook
|
|
||||||
/// Decrypt: remove DRM encryption from audiobook. Store final book
|
|
||||||
/// Backup: perform all steps (downloaded, decrypt) still needed to get final book
|
|
||||||
/// </summary>
|
|
||||||
public class DownloadBook : DownloadableBase
|
|
||||||
{
|
|
||||||
private const string SERVICE_UNAVAILABLE = "Content Delivery Companion Service is not available.";
|
|
||||||
|
|
||||||
public override bool Validate(LibraryBook libraryBook)
|
|
||||||
=> !AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId)
|
|
||||||
&& !AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId);
|
|
||||||
|
|
||||||
public override async Task<StatusHandler> ProcessItemAsync(LibraryBook libraryBook)
|
|
||||||
{
|
|
||||||
var tempAaxFilename = getDownloadPath(libraryBook);
|
|
||||||
var actualFilePath = await downloadAaxcBookAsync(libraryBook, tempAaxFilename);
|
|
||||||
moveBook(libraryBook, actualFilePath);
|
|
||||||
return verifyDownload(libraryBook);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string getDownloadPath(LibraryBook libraryBook)
|
|
||||||
=> FileUtility.GetValidFilename(
|
|
||||||
AudibleFileStorage.DownloadsInProgress,
|
|
||||||
libraryBook.Book.Title,
|
|
||||||
"aaxc",
|
|
||||||
libraryBook.Book.AudibleProductId);
|
|
||||||
|
|
||||||
private async Task<string> downloadAaxcBookAsync(LibraryBook libraryBook, string tempAaxFilename)
|
|
||||||
{
|
|
||||||
validate(libraryBook);
|
|
||||||
|
|
||||||
var api = await GetApiAsync(libraryBook);
|
|
||||||
|
|
||||||
var dlLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
|
|
||||||
|
|
||||||
var client = new HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("User-Agent", Resources.UserAgent);
|
|
||||||
|
|
||||||
var actualFilePath = await PerformDownloadAsync(
|
|
||||||
tempAaxFilename,
|
|
||||||
(p) => client.DownloadFileAsync(new Uri(dlLic.DownloadUrl).AbsoluteUri, tempAaxFilename, p));
|
|
||||||
|
|
||||||
System.Threading.Thread.Sleep(100);
|
|
||||||
// if bad file download, a 0-33 byte file will be created
|
|
||||||
// if service unavailable, a 52 byte string will be saved as file
|
|
||||||
var length = new FileInfo(actualFilePath).Length;
|
|
||||||
|
|
||||||
// success. save json and return
|
|
||||||
if (length > 100)
|
|
||||||
{
|
|
||||||
// save along side book
|
|
||||||
var jsonPath = PathLib.ReplaceExtension(actualFilePath, "json");
|
|
||||||
var jsonContents = Newtonsoft.Json.JsonConvert.SerializeObject(dlLic, Newtonsoft.Json.Formatting.Indented);
|
|
||||||
File.WriteAllText(jsonPath, jsonContents);
|
|
||||||
|
|
||||||
return actualFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// else: failure. clean up and throw
|
|
||||||
var contents = File.ReadAllText(actualFilePath);
|
|
||||||
File.Delete(actualFilePath);
|
|
||||||
|
|
||||||
var exMsg = contents.StartsWithInsensitive(SERVICE_UNAVAILABLE)
|
|
||||||
? SERVICE_UNAVAILABLE
|
|
||||||
: "Error downloading file";
|
|
||||||
|
|
||||||
var ex = new Exception(exMsg);
|
|
||||||
Serilog.Log.Logger.Error(ex, "Download error {@DebugInfo}", new
|
|
||||||
{
|
|
||||||
libraryBook.Book.Title,
|
|
||||||
libraryBook.Book.AudibleProductId,
|
|
||||||
libraryBook.Book.Locale,
|
|
||||||
Account = libraryBook.Account?.ToMask() ?? "[empty]",
|
|
||||||
tempAaxFilename,
|
|
||||||
actualFilePath,
|
|
||||||
length,
|
|
||||||
contents
|
|
||||||
});
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void validate(LibraryBook libraryBook)
|
|
||||||
{
|
|
||||||
string errorString(string field)
|
|
||||||
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
|
|
||||||
|
|
||||||
string errorTitle()
|
|
||||||
{
|
|
||||||
var title
|
|
||||||
= (libraryBook.Book.Title.Length > 53)
|
|
||||||
? $"{libraryBook.Book.Title.Truncate(50)}..."
|
|
||||||
: libraryBook.Book.Title;
|
|
||||||
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
|
|
||||||
return errorBookTitle;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(libraryBook.Account))
|
|
||||||
throw new Exception(errorString("Account"));
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
|
|
||||||
throw new Exception(errorString("Locale"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveBook(LibraryBook libraryBook, string actualFilePath)
|
|
||||||
{
|
|
||||||
var newAaxFilename = FileUtility.GetValidFilename(
|
|
||||||
AudibleFileStorage.DownloadsFinal,
|
|
||||||
libraryBook.Book.Title,
|
|
||||||
"aax",
|
|
||||||
libraryBook.Book.AudibleProductId);
|
|
||||||
File.Move(actualFilePath, newAaxFilename);
|
|
||||||
|
|
||||||
// also move DownloadLicense json file
|
|
||||||
var jsonPathOld = PathLib.ReplaceExtension(actualFilePath, "json");
|
|
||||||
var jsonPathNew = PathLib.ReplaceExtension(newAaxFilename, "json");
|
|
||||||
File.Move(jsonPathOld, jsonPathNew);
|
|
||||||
|
|
||||||
Invoke_StatusUpdate($"Successfully downloaded. Moved to: {newAaxFilename}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StatusHandler verifyDownload(LibraryBook libraryBook)
|
|
||||||
=> !AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId)
|
|
||||||
? new StatusHandler { "Downloaded AAX file cannot be found" }
|
|
||||||
: new StatusHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using AaxDecrypter;
|
|
||||||
using AudibleApiDTOs;
|
|
||||||
using Dinah.Core.Diagnostics;
|
|
||||||
|
|
||||||
|
|
||||||
namespace FileLiberator
|
|
||||||
{
|
|
||||||
public class DownloadedChapters : Chapters
|
|
||||||
{
|
|
||||||
public DownloadedChapters(ChapterInfo chapterInfo)
|
|
||||||
{
|
|
||||||
AddChapters(chapterInfo.Chapters
|
|
||||||
.Select(c => new AaxDecrypter.Chapter(c.StartOffsetMs / 1000d, (c.StartOffsetMs + c.LengthMs) / 1000d, c.Title)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user