Files
Libation/Source/LibationCli/Options/LiberateOptions.cs
MBucari ce2b81036f Add license and settings overrides to LibationCli
- Add `LIBATION_FILES_DIR` environment variable to specify LibationFiles directory instead of appsettings.json
- OptionsBase supports overriding setting
  - Added `EphemeralSettings` which are loaded from Settings.json once and can be modified with the `--override` command parameter
- Added `get-setting` command
  - Prints (editable) settings and their values. Prints specified settings, or all settings if none specified
  - `--listEnumValues` option will list all names for a speficied enum-type settings. If no setting names are specified, prints all enum values for all enum settings.
  - Prints in a text-based table or bare with `-b` switch
- Added `get-license` command which requests a content license and prints it as a json to stdout
- Improved `liberate` command
  - Added `-force` option to force liberation without validation.
  - Added support to download with a license file supplied to stdin
  - Improve startup performance when downloading explicit ASIN(s)
  - Fix long-standing bug where cover art was not being downloading
2025-11-19 23:47:41 -07:00

110 lines
3.5 KiB
C#

using ApplicationServices;
using CommandLine;
using DataLayer;
using FileLiberator;
using LibationCli.Options;
using LibationFileManager;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.IO;
using System.Threading.Tasks;
#nullable enable
namespace LibationCli
{
[Verb("liberate", HelpText = "Liberate: book and pdf backups. Default: download and decrypt all un-liberated titles and download pdfs.\n"
+ "Optional: specify asin(s) of book(s) to liberate.\n"
+ "Optional: reads a license file from standard input.")]
public class LiberateOptions : ProcessableOptionsBase
{
[Option(shortName: 'p', longName: "pdf", Required = false, Default = false, HelpText = "Flag to only download pdfs")]
public bool PdfOnly { get; set; }
[Option(shortName: 'f', longName: "force", Required = false, Default = false, HelpText = "Force the book to re-download")]
public bool Force { get; set; }
protected override async Task ProcessAsync()
{
if (AudibleFileStorage.BooksDirectory is null)
{
Console.Error.WriteLine("Error: Books directory is not set. Please configure the 'Books' setting in Settings.json.");
return;
}
if (Console.IsInputRedirected)
{
Console.WriteLine("Reading license file from standard input.");
using var reader = new StreamReader(Console.OpenStandardInput());
var stdIn = await reader.ReadToEndAsync();
try
{
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Converters = [new StringEnumConverter(), new ByteArrayHexConverter()]
};
var licenseInfo = JsonConvert.DeserializeObject<DownloadOptions.LicenseInfo>(stdIn, jsonSettings);
if (licenseInfo?.ContentMetadata?.ContentReference?.Asin is not string asin)
{
Console.Error.WriteLine("Error: License file is missing ASIN information.");
return;
}
LibraryBook libraryBook;
using (var dbContext = DbContexts.GetContext())
{
if (dbContext.GetLibraryBook_Flat_NoTracking(asin) is not LibraryBook lb)
{
Console.Error.WriteLine($"Book not found with asin={asin}");
return;
}
libraryBook = lb;
}
SetDownloadedStatus(libraryBook);
await ProcessOneAsync(GetProcessable(licenseInfo), libraryBook, true);
}
catch
{
Console.Error.WriteLine("Error: Failed to read license file from standard input. Please ensure the input is a valid license file in JSON format.");
}
}
else
{
await RunAsync(GetProcessable(), SetDownloadedStatus);
}
}
private Processable GetProcessable(DownloadOptions.LicenseInfo? licenseInfo = null)
=> PdfOnly ? CreateProcessable<DownloadPdf>() : CreateBackupBook(licenseInfo);
private void SetDownloadedStatus(LibraryBook lb)
{
if (Force)
{
lb.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
lb.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
}
}
private static Processable CreateBackupBook(DownloadOptions.LicenseInfo? licenseInfo)
{
var downloadPdf = CreateProcessable<DownloadPdf>();
//Chain pdf download on DownloadDecryptBook.Completed
void onDownloadDecryptBookCompleted(object? sender, LibraryBook e)
{
// this is fast anyway. run as sync for easy exception catching
downloadPdf.TryProcessAsync(e).GetAwaiter().GetResult();
}
var downloadDecryptBook = CreateProcessable<DownloadDecryptBook>(onDownloadDecryptBookCompleted);
downloadDecryptBook.LicenseInfo = licenseInfo;
return downloadDecryptBook;
}
}
}