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