From 5b2c620bf5b05ea755bdbd89c00a39334ebe1754 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 26 Aug 2026 16:17:47 +0000 Subject: [PATCH] Add the marketplaces dialog to the accounts grid The accounts grid gains a Marketplaces button per row, enabled once the account has credentials to check with, and a dialog that asks each marketplace what it holds and lets the user tick the ones to scan. The scan picker now lists every marketplace an account reads, since one checkbox there can scan several. A marketplace that could not be reached is reported as unchecked rather than empty. Calling it empty would recreate the exact silence this feature exists to break: titles present, and nothing anywhere in the app to suggest it. Co-authored-by: rmcrackan --- Source/AudibleUtilities/Mkb79Auth.cs | 6 + Source/AudibleUtilities/Mkb79AuthImporter.cs | 42 ++++- .../Dialogs/AccountsDialog.axaml | 17 ++ .../Dialogs/AccountsDialog.axaml.cs | 73 ++++++++- .../Dialogs/MarketplacesDialog.axaml | 81 ++++++++++ .../Dialogs/MarketplacesDialog.axaml.cs | 148 ++++++++++++++++++ .../Dialogs/ScanAccountsDialog.axaml.cs | 4 +- .../Options/ImportAccountOptions.cs | 6 +- Source/LibationUiBase/MarketplacesUi.cs | 107 +++++++++++++ .../Dialogs/AccountsDialog.cs | 4 +- .../Mkb79AuthExportTests.cs | 26 +++ .../MarketplacesUiTests.cs | 123 +++++++++++++++ 12 files changed, 619 insertions(+), 18 deletions(-) create mode 100644 Source/LibationAvalonia/Dialogs/MarketplacesDialog.axaml create mode 100644 Source/LibationAvalonia/Dialogs/MarketplacesDialog.axaml.cs create mode 100644 Source/LibationUiBase/MarketplacesUi.cs create mode 100644 Source/_Tests/LibationUiBase.Tests/MarketplacesUiTests.cs diff --git a/Source/AudibleUtilities/Mkb79Auth.cs b/Source/AudibleUtilities/Mkb79Auth.cs index 4731a806..e58e6041 100644 --- a/Source/AudibleUtilities/Mkb79Auth.cs +++ b/Source/AudibleUtilities/Mkb79Auth.cs @@ -237,6 +237,12 @@ public partial class Mkb79Auth return account; } + /// + /// The exported file names one marketplace - the one this account is registered with - because that is all + /// the format holds: a single locale_code alongside a single device registration. audible-cli switches + /// marketplaces on its own from those same tokens, so nothing is lost to it. Any additional marketplaces + /// Libation reads for this account are its own bookkeeping and have no slot here. + /// public static Mkb79Auth FromAccount(Account account) => new() { diff --git a/Source/AudibleUtilities/Mkb79AuthImporter.cs b/Source/AudibleUtilities/Mkb79AuthImporter.cs index 82b21ccb..a4227dba 100644 --- a/Source/AudibleUtilities/Mkb79AuthImporter.cs +++ b/Source/AudibleUtilities/Mkb79AuthImporter.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System; using System.Threading.Tasks; namespace AudibleUtilities; @@ -10,10 +10,37 @@ public enum Mkb79ImportOutcome InvalidFile, } -public sealed record Mkb79ImportResult(Mkb79ImportOutcome Outcome, Account? Account = null, string? Message = null); +/// +/// For , the account already scanning that marketplace. It may +/// be one registered with it, or one carrying it as an additional marketplace. +/// +public sealed record Mkb79ImportResult( + Mkb79ImportOutcome Outcome, + Account? Account = null, + string? Message = null, + Account? ClaimedBy = null); public static class Mkb79AuthImporter { + /// + /// Why a duplicate import was refused, in the same words everywhere it is refused. Naming the account that + /// already reads the marketplace matters now that it need not be a row registered with it - it may be one + /// reading it as an additional marketplace, which is not obvious from the accounts grid. + /// + public static string DuplicateMessage(Mkb79ImportResult result) + { + var locale = result.Account?.Locale?.Name ?? "[unknown]"; + + if (result.ClaimedBy is { } claimedBy && claimedBy.Locale?.Name != locale) + return $"The '{locale}' marketplace is already scanned by the account " + + $"{AccountCredentialStatus.FormatAccountLabel(claimedBy)}, as an additional marketplace. " + + "Nothing was imported."; + + return "An account with that account id and country already exists." + + $"{Environment.NewLine}Account ID: {result.Account?.AccountId}" + + $"{Environment.NewLine}Country: {locale}"; + } + /// /// Deserialize mkb79/audible-cli JSON, refresh tokens, and add the account if not already present. /// @@ -32,11 +59,12 @@ public static class Mkb79AuthImporter using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - if (persister.AccountsSettings.Accounts.Any(a => - a.AccountId == account.AccountId && a.IdentityTokens?.Locale.Name == account.Locale?.Name)) - { - return new Mkb79ImportResult(Mkb79ImportOutcome.DuplicateAccount, account); - } + // An mkb79 file names one marketplace, and a marketplace can only be scanned by one account. Ask about + // every claim on it, not just registrations: an existing account may already be reading this marketplace + // as an additional one, in which case importing would scan it twice. + var claimedBy = persister.AccountsSettings.GetAccountClaimingMarketplace(account.AccountId, account.Locale?.Name); + if (claimedBy is not null) + return new Mkb79ImportResult(Mkb79ImportOutcome.DuplicateAccount, account, ClaimedBy: claimedBy); persister.AccountsSettings.Add(account); return new Mkb79ImportResult(Mkb79ImportOutcome.Success, account); diff --git a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml index c0cf27b4..1dc81128 100644 --- a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml @@ -87,6 +87,23 @@ + + + + +