From 0cc2ef773d8f390d58d73a8973b539d3d727ef4c Mon Sep 17 00:00:00 2001 From: rmcrackan Date: Tue, 5 May 2026 13:32:28 -0400 Subject: [PATCH] =?UTF-8?q?*=20Default=20Scan=20library=20to=20on=20for=20?= =?UTF-8?q?new=20accounts=20from=20Upsert=20/=20Mkb79=20import=20(matches?= =?UTF-8?q?=20GUI)=20*=20CLI=20liberate:=20print=20short=20license-denial?= =?UTF-8?q?=20reasons=20to=20stderr=20*=20GUI:=20message=20when=20stopligh?= =?UTF-8?q?t=20can=E2=80=99t=20queue=20(e.g.=20absent=20from=20last=20scan?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/AudibleUtilities/AccountsSettings.cs | 3 ++- Source/AudibleUtilities/Mkb79Auth.cs | 3 ++- .../ContentLicenseDeniedCliSummary.cs | 26 ++++++++++++++++++ .../Options/ListAccountsOptions.cs | 2 +- .../Options/_ProcessableOptionsBase.cs | 7 +++++ .../ProcessQueue/ProcessQueueViewModel.cs | 27 +++++++++++++++++-- .../AudibleUtilities.Tests/AccountTests.cs | 1 + docs/advanced/command-line-interface.md | 2 ++ 8 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 Source/LibationCli/ContentLicenseDeniedCliSummary.cs diff --git a/Source/AudibleUtilities/AccountsSettings.cs b/Source/AudibleUtilities/AccountsSettings.cs index 5fdaef58..2b606f5e 100644 --- a/Source/AudibleUtilities/AccountsSettings.cs +++ b/Source/AudibleUtilities/AccountsSettings.cs @@ -88,7 +88,8 @@ public class AccountsSettings : IUpdatable var l = Localization.Get(locale); var id = new Identity(l); - var account = new Account(accountId) { IdentityTokens = id }; + // Match GUI default for new rows (WinForms/Avalonia): include account in library scans. + var account = new Account(accountId) { IdentityTokens = id, LibraryScan = true }; Add(account); return account; } diff --git a/Source/AudibleUtilities/Mkb79Auth.cs b/Source/AudibleUtilities/Mkb79Auth.cs index 709f8c47..87df41f8 100644 --- a/Source/AudibleUtilities/Mkb79Auth.cs +++ b/Source/AudibleUtilities/Mkb79Auth.cs @@ -217,7 +217,8 @@ public partial class Mkb79Auth { DecryptKey = ActivationBytes, AccountName = $"{email} - {Locale.Name}", - IdentityTokens = new Identity(Locale) + IdentityTokens = new Identity(Locale), + LibraryScan = true, }; account.IdentityTokens.Update( diff --git a/Source/LibationCli/ContentLicenseDeniedCliSummary.cs b/Source/LibationCli/ContentLicenseDeniedCliSummary.cs new file mode 100644 index 00000000..8b56e636 --- /dev/null +++ b/Source/LibationCli/ContentLicenseDeniedCliSummary.cs @@ -0,0 +1,26 @@ +using AudibleApi; +using System; +using System.Collections.Generic; + +namespace LibationCli; + +internal static class ContentLicenseDeniedCliSummary +{ + /// Short lines for stderr when Audible denies a download license; mirrors log detail without dumping the full JSON. + public static IEnumerable Lines(ContentLicenseDeniedException ex) + { + ArgumentNullException.ThrowIfNull(ex); + + yield return "Audible denied a content license (download not allowed for this account/title)."; + yield return ex.Message; + + if (ex.Ownership?.Message is { } own && !string.IsNullOrWhiteSpace(own)) + yield return $"Ownership: {own}"; + if (ex.Client?.Message is { } cli && !string.IsNullOrWhiteSpace(cli)) + yield return $"Client: {cli}"; + if (ex.Membership?.Message is { } mem && !string.IsNullOrWhiteSpace(mem)) + yield return $"Membership: {mem}"; + if (ex.AYCL?.Message is { } aycl && !string.IsNullOrWhiteSpace(aycl)) + yield return $"AYCL (aka: Plus catalog): {aycl}"; + } +} diff --git a/Source/LibationCli/Options/ListAccountsOptions.cs b/Source/LibationCli/Options/ListAccountsOptions.cs index 742cd7b8..8685cc24 100644 --- a/Source/LibationCli/Options/ListAccountsOptions.cs +++ b/Source/LibationCli/Options/ListAccountsOptions.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace LibationCli; -[Verb("list-accounts", HelpText = "List configured Audible accounts, locale, scan flag, and whether stored credentials are valid.")] +[Verb("list-accounts", HelpText = "List configured Audible accounts: locale, whether the account is included in automatic GUI scans ('Scan library'), and whether stored credentials are valid.")] internal class ListAccountsOptions : OptionsBase { [Option('b', "bare", HelpText = "Print tab-separated values without table borders (account id, name, locale, scan library, authenticated).")] diff --git a/Source/LibationCli/Options/_ProcessableOptionsBase.cs b/Source/LibationCli/Options/_ProcessableOptionsBase.cs index 2463ecb9..c38edbae 100644 --- a/Source/LibationCli/Options/_ProcessableOptionsBase.cs +++ b/Source/LibationCli/Options/_ProcessableOptionsBase.cs @@ -1,4 +1,5 @@ using ApplicationServices; +using AudibleApi; using CommandLine; using DataLayer; using FileLiberator; @@ -121,6 +122,12 @@ public abstract class ProcessableOptionsBase : OptionsBase Serilog.Log.Logger.Error(errorMessage); } } + catch (ContentLicenseDeniedException clEx) + { + foreach (var line in ContentLicenseDeniedCliSummary.Lines(clEx)) + Console.Error.WriteLine(line); + Serilog.Log.Logger.Error(clEx, "Content license denied {@DebugInfo}", new { Book = libraryBook.LogFriendly() }); + } 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."; diff --git a/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs b/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs index 958d9c81..a317db4b 100644 --- a/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs +++ b/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs @@ -1,5 +1,6 @@ using ApplicationServices; using DataLayer; +using FileLiberator; using LibationFileManager; using LibationUiBase.Forms; using LibationUiBase; @@ -137,21 +138,43 @@ public class ProcessQueueViewModel : ReactiveObject var item = libraryBooks[0]; if (item.AbsentFromLastScan) + { + Serilog.Log.Logger.Warning("Download not queued: {libraryBook} is absent from the last library scan.", item.LogFriendly()); + MessageBoxBase.Show( + "This title is marked absent from your last library scan.\n\nRun Scan (or `libationcli scan`) so Libation can refresh your library, then try again.", + "Library scan required", + MessageBoxButtons.OK, + MessageBoxIcon.Information); return false; - else if (item.NeedsBookDownload) + } + if (item.NeedsBookDownload) { RemoveCompleted(item); Serilog.Log.Logger.Information("Begin single library book backup of {libraryBook}", item); AddDownloadDecrypt([item], config); return true; } - else if (item.NeedsPdfDownload) + if (item.NeedsPdfDownload) { RemoveCompleted(item); Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", item); AddDownloadPdf([item], config); return true; } + + Serilog.Log.Logger.Warning( + "Download not queued: single-item backup not applicable for {libraryBook} (book status or type does not request download).", + item.LogFriendly()); + if (!item.Book.AudioExists) + { + MessageBoxBase.Show( + "Libation could not queue a download for this title.\n\n" + + "If it should be downloadable: confirm it is not already liberated, try \"Set download status\" to Not downloaded, or check whether a library scan is required.", + "Download not queued", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + return false; } else { diff --git a/Source/_Tests/AudibleUtilities.Tests/AccountTests.cs b/Source/_Tests/AudibleUtilities.Tests/AccountTests.cs index e389a5be..3ee6fb8e 100644 --- a/Source/_Tests/AudibleUtilities.Tests/AccountTests.cs +++ b/Source/_Tests/AudibleUtilities.Tests/AccountTests.cs @@ -425,6 +425,7 @@ public class upsert : AccountsTestBase var acct = accountsSettings.GetAccount("cng", "us"); acct.BeNotNull(); acct.AccountId.Should().Be("cng"); + acct.LibraryScan.Should().BeTrue(); } [TestMethod] diff --git a/docs/advanced/command-line-interface.md b/docs/advanced/command-line-interface.md index 91fcc423..53db935d 100644 --- a/docs/advanced/command-line-interface.md +++ b/docs/advanced/command-line-interface.md @@ -102,6 +102,8 @@ libationcli list-accounts --bare `--bare` (`-b`) prints tab-separated values with no table: account id, name, locale, scan library (`yes` / `no`), authenticated (`yes` / `no`), for scripts and `cut` / `awk`. +**Scan library** (`yes` / `no`) is the same checkbox as "Include in library scan?" in Accounts: it controls whether the main Libation app includes that account in automatic scans (startup / periodic scan behavior). It does **not** restrict `libationcli scan` with no arguments, which still imports from every configured account unless you pass specific account nicknames or ids. + If no accounts exist yet, the CLI prints `No accounts configured.` and exits successfully. ## Scan All Libraries