Files
Libation/Source/LibationCli/Options/_ProcessableOptionsBase.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

137 lines
3.8 KiB
C#

using ApplicationServices;
using CommandLine;
using DataLayer;
using FileLiberator;
using LibationFileManager;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#nullable enable
namespace LibationCli
{
public abstract class ProcessableOptionsBase : OptionsBase
{
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books to process.")]
public IEnumerable<string>? Asins { get; set; }
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook>? completedAction = null)
where TProcessable : Processable, new()
{
var progressBar = new ConsoleProgressBar(Console.Out);
var strProc = new TProcessable();
LibraryBook? currentLibraryBook = null;
strProc.Begin += (o, e) =>
{
currentLibraryBook = e;
Console.WriteLine($"{typeof(TProcessable).Name} Begin: {e}");
};
strProc.Completed += (o, e) =>
{
progressBar.Clear();
Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
};
strProc.Completed += (s, e) =>
{
try
{
completedAction?.Invoke(s, e);
}
catch (Exception ex)
{
Console.Error.WriteLine("CLI error. See log for more details.");
Serilog.Log.Logger.Error(ex, "CLI error");
}
};
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
if (strProc is AudioDecodable audDec)
{
audDec.RequestCoverArt += (_,_) =>
{
if (currentLibraryBook is null)
return null;
var quality
= Configuration.Instance.FileDownloadQuality == Configuration.DownloadQuality.High && currentLibraryBook.Book.PictureLarge is not null
? new PictureDefinition(currentLibraryBook.Book.PictureLarge, PictureSize.Native)
: new PictureDefinition(currentLibraryBook.Book.PictureId, PictureSize._500x500);
return PictureStorage.GetPictureSynchronously(quality);
};
}
return strProc;
}
protected async Task RunAsync(Processable Processable, Action<LibraryBook>? config = null)
{
if (Asins?.Any() is true)
{
foreach (var asin in Asins.Select(a => a.TrimStart('[').TrimEnd(']')))
{
LibraryBook? lb = null;
using (var dbContext = DbContexts.GetContext())
{
lb = dbContext.GetLibraryBook_Flat_NoTracking(asin, caseSensative: false);
}
if (lb is not null)
{
config?.Invoke(lb);
await ProcessOneAsync(Processable, lb, true);
}
else
{
var msg = $"Book with ASIN '{asin}' not found in library. Skipping.";
Console.Error.WriteLine(msg);
Serilog.Log.Logger.Error(msg);
}
}
}
else
{
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
foreach (var lb in Processable.GetValidLibraryBooks(libraryBooks))
{
config?.Invoke(lb);
await ProcessOneAsync(Processable, lb, false);
}
}
var done = "Done. All books have been processed";
Console.WriteLine(done);
Serilog.Log.Logger.Information(done);
}
protected async Task ProcessOneAsync(Processable Processable, LibraryBook libraryBook, bool validate)
{
try
{
var statusHandler = await Processable.ProcessSingleAsync(libraryBook, validate);
if (statusHandler.IsSuccess)
return;
foreach (var errorMessage in statusHandler.Errors)
{
Console.Error.WriteLine(errorMessage);
Serilog.Log.Logger.Error(errorMessage);
}
}
catch (Exception ex)
{
var msg = "Error processing book. Skipping. This book will be tried again on next attempt. For options of skipping or marking as error, retry with main Libation app.";
Console.Error.WriteLine(msg + ". See log for more details.");
Serilog.Log.Logger.Error(ex, $"{msg} {{@DebugInfo}}", new { Book = libraryBook.LogFriendly() });
}
}
}
}