2013 Commits
Author SHA1 Message Date
Robert McRackan 24fc8200d7 typo 2026-09-12 16:05:48 -04:00
Robert McRackan f3fe4aa6d3 incr ver to 14.2.1 2026-09-12 16:05:11 -04:00
Robert McRackan 53d70d6c29 build mac 2026-09-12 15:56:37 -04:00
Robert McRackan d1477a76b0 Remove PrivateAssets, PublicAssets from protobuf ref 2026-09-12 13:52:54 -04:00
Robert McRackan fd459af012 update dependencies 2026-09-12 11:32:20 -04:00
Robert McRackan 331c07a0f3 incl CreativeMetrics contrib from #2039 2026-09-12 11:29:37 -04:00
Robert McRackan aa829df265 log accounts changes 2026-09-08 00:15:08 -04:00
Robert McRackan af7a1e8f69 Increment version to 14.2 2026-09-06 21:27:30 -04:00
Mbucari 8050e11a41 Revert SkiaSharp to 3.119.4, matching Avalonia 2026-09-06 01:11:20 -06:00
Mbucari bdf55009b5 Fix tests and warnings from new deps 2026-09-06 00:27:17 -06:00
Mbucari a5a28c9d62 Replace ImageSharp dependency with SkiaSharp 2026-09-05 23:52:14 -06:00
Mbucari 70909a40dc Update Dependencies
Replace obsolete Avalonia.Diagnostics package with AvaloniaUI.DiagnosticsSupport.

Update Avalonia to 12.1, applying necessary fixes.
2026-09-05 23:23:00 -06:00
Mbucari a922cfdf40 Add option to overwrite existing files in DownloadPdf 2026-09-05 16:37:57 -06:00
Robert McRackanandCursor f4dee94785 Add an experimental device-registration setting so License Denied users can re-register without changing the Android default.
EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 20:48:48 -04:00
Robert McRackanandCursor 67b476eaa0 Do not use Audible's sentinel episode numbers as podcast series order.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 16:18:39 -04:00
Robert McRackan 014beaf71f incr ver 2026-09-02 20:05:35 -04:00
Robert McRackanandCursor 74a028acaa Show a dialog when Audible throttles a download license.
CustomerThrottled was treated as a generic License Denied, so non-Plus titles got no explanation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 20:04:44 -04:00
Robert McRackanandCursor 635780a4cf Bump version to 14.0.1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:10:59 -04:00
Robert McRackanandCursor 82fb235de9 Drop the ApiExtendedFunc test hook. Pass the book's marketplace straight to CreateAsync.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:06:43 -04:00
Robert McRackanandCursor 336a01db01 Request download licenses from the marketplace the book was scanned from, not only the account's home store.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 14:52:44 -04:00
Robert McRackan 129486ce89 Warn if accounts dialog is dirty when user attempts to close it. This includes editing items from the Marketplaces child-dialog 2026-08-31 17:15:56 -04:00
Robert McRackan 4cd784787e avoid infinite retry loops 2026-08-30 21:40:38 -04:00
Robert McRackan 5503a9d768 Allow the download to resume from the last flushed position when TLS corruption occurs, just like it already does for premature connection closures. 2026-08-30 21:36:13 -04:00
Robert McRackan 09be09bd1b Cull dead code. Simplify 2026-08-30 14:16:27 -04:00
Robert McRackan 19a867488b Update dependencies 2026-08-30 14:15:35 -04:00
Cursor Agentandrmcrackan eca013e780 Bump version to 14.0.0
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 18:14:08 +00:00
rmcrackan 75c25fefe4 Merge branch 'master' into cursor/multi-marketplace-accounts-6d3e 2026-08-26 13:32:12 -04:00
Robert McRackan 7538491f32 update dependencies 2026-08-26 13:31:18 -04:00
Cursor Agentandrmcrackan 5268e0c375 Drop the registered marketplace from the extras on the way out too
Newtonsoft applies values in the order the document lists them, so a settings
file naming AdditionalLocaleNames before its IdentityTokens has no registered
marketplace to compare against yet, and the duplicate check on the way in does
nothing. That account would then scan one marketplace twice.

Filtering on read makes the order irrelevant.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 16:49:13 +00:00
Cursor Agentandrmcrackan e1449c9b1e Document marketplaces in the docs and the tour
The tour mentions marketplaces at the end rather than at the accounts step: that
step runs before any login, so the button is necessarily disabled while the user
is looking at it.

The FAQ entry is filed under the symptom - books missing from the scan - because
that is what someone in this position knows. Nothing tells them a marketplace
they have never heard of is holding titles.

The accounts screenshots in getting-started now predate the Marketplaces column
and need retaking on Windows.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 16:44:46 +00:00
Cursor Agentandrmcrackan c7f6815014 Add marketplaces to Classic and to list-accounts
WinForms gets the same per-row button and dialog Chardonnay has, and its scan
picker lists an account's marketplaces for the same reason.

list-accounts grows an 'Also scans' column, because printing one locale for an
account that reads three misreports what a scan will do. It appears only when
some account has more than one marketplace, so the ordinary table is unchanged;
--bare always emits the field, last, so a script reading the first five keeps
working.

Compile-checked only for WinForms: it builds on Linux via EnableWindowsTargeting
but cannot run there, so the new dialog's layout still needs a look on Windows.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 16:23:52 +00:00
Cursor Agentandrmcrackan 5b2c620bf5 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 <rmcrackan@gmail.com>
2026-08-26 16:17:47 +00:00
Cursor Agentandrmcrackan 72927943c2 Let one account scan more than one marketplace
A title bought while an Amazon address was briefly set to another country stays
in that country's library for good, and a scan of the account's own marketplace
never sees it - no error, no warning, the titles are simply absent. The only way
to reach them was to add the same login a second time under the other
marketplace, which meant knowing it was there and logging in again.

An account now records the further marketplaces it should read, and a scan reads
all of them as a unit. MarketplaceProbe finds them: one request per marketplace,
in sequence, and only when asked for.

Nothing about stored credentials moves. Encrypted tokens are bound to their
marketplace by AES-GCM associated data built from Identity.LocaleName, and the
identity is located in the file by that same name, so both keep resolving to the
registration that already exists. The extra marketplaces are names alone, and are
left out of the file entirely when there are none - so an account with one
marketplace, which is nearly all of them, writes the file it always did.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 15:59:48 +00:00
rmcrackan b02aedb579 Merge pull request #2007 from rmcrackan/cursor/serilog-startup-resilience-3b83
Survive an install folder that cannot load Serilog (#2001)
2026-08-26 10:51:46 -04:00
Cursor Agentandrmcrackan 3ab22a9796 Report the crash log path without the Windows long-path prefix
PreLoggingCrashLog returned its LongPath straight out, so on Windows the crash
dialog named \\?\C:\Users\...\Log202608.log. The prefix is for the Win32 API,
not for a person being asked to find the file and attach it to a bug report.

Return PathWithoutPrefix instead, and use it for the LibationFiles line inside
the record for the same reason. A test now pins that the reported path carries
no prefix and is usable as-is; on Linux the two forms are identical, which is
why local runs could not see this and the Windows CI job could.

While in there: ReplaceInstallFile moved the old file aside and then wrote the
new one, so a copy that failed after the move left nothing at all where a file
used to be. Put the old one back before reporting the failure.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 14:42:27 +00:00
Cursor Agentandrmcrackan 47980e1f86 Check the rollback's own work, and offer a restart when it went well
The rollback restored files, announced 'Libation restored your previous install
files', and deleted the pending marker without ever reading back what it had
written. It also could not tell the difference between an overlay that had not
started, where putting the backup back returns the install to one version, and
an overlay that got partway, where the backup covers a dozen names out of the
few hundred in the folder and cannot.

Grade the outcome instead. Verify each restored file against its backup, and
use the manifest files that did match the upgrade package as the signal that
the overlay had begun. Report which files could not be restored, and keep
restoring the rest rather than abandoning them at the first failure.

Libation has to close either way, since the files underneath it just changed,
so the wording and the offer now follow the grade: back on the previous version
asks to restart, a mixed install asks but says a fresh install would be better,
and a restore that could not finish only offers OK. A relaunched process never
offers again, so a pending marker we failed to delete cannot become a loop.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 14:23:48 +00:00
rmcrackan 673f8f3b06 Merge pull request #2005 from rmcrackan/cursor/adopt-dinah-core-10-2-6-1-b4c7
Adopt Dinah.* 10.2.6.1 and AudibleApi 11.0.5.1
2026-08-26 09:58:17 -04:00
rmcrackan 52325e7ae8 Merge pull request #2003 from rmcrackan/cursor/fix-widevine-metadata-tags-2f5b
Fix metadata tags on the Widevine download path (#2002)
2026-08-26 09:52:25 -04:00
Cursor Agentandrmcrackan 0c0b72cd87 Update AudibleApi to 11.0.5.1 and Dinah.Core.WindowsDesktop to 10.2.6.1
Completes the adoption of this Dinah.Core release across the
dependency tree: AudibleApi 11.0.5.1 carries the Dinah.Core 10.2.6.1
floor, and Dinah.Core.WindowsDesktop 10.2.6.1 is versioned in step.
No code change in any of them.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 13:42:43 +00:00
Cursor Agentandrmcrackan bfaab98eff Fit a longer startup message in the crash dialog
The dialog was pinned to 450px tall with the description in an Auto row, which
was fine for the two-line generic text but not for a message that names the
file, the version it has, the version it needs and the recovery steps: the
description pushed the exception box, the report instructions and the OK button
out of the window.

Cap and scroll the description, give the window room to grow, and top-align the
icon so it stops overlapping the text.

The Classic equivalent, MessageBoxLib.ShowAdminAlert, may need the same and
cannot be checked here.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 13:15:53 +00:00
Cursor Agentandrmcrackan bc72aad30c refactor(metadata): flatten Audible's HTML at the API boundary, not on read
Parsing HTML belongs before the data reaches the data layer, not in it. Move the
flattening to AudibleUtilities, where the rest of the Item translation lives, so
Book.Description holds plain text and DataLayer no longer references
HtmlAgilityPack.

Import now updates a description as well as creating one, which it never did
before: books imported while descriptions were stored as raw HTML are still
holding markup, and a re-scan is what clears it. A scan that reports no summary
leaves the stored one alone.

This also cleans up the CSV/Excel export, which was emitting raw HTML too.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 13:15:02 +00:00
Cursor Agentandrmcrackan 31ad21f69d Update Dinah.EntityFrameworkCore to 10.2.6.1
Versioned in step with Dinah.Core for this release; no code change in
the package.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 13:12:31 +00:00
Cursor Agentandrmcrackan 1ab18f5f60 Cover the startup paths that issue #2001 showed were untested
The rollback test holds the file it restores open with the sharing mode .NET
uses for a loaded assembly. Against the old File.Copy(overwrite: true) it fails
with 'the process cannot access the file because it is being used by another
process', which is the same refusal Windows gives for a loaded assembly and the
reason the rollback could never finish.

The rest pin the behaviour that was missing: recovery survives a log sink that
throws on every call, a displaced file is swept up later, a stale or absent
install assembly is recognised and both versions are read, a plain file path is
not mistaken for an assembly reference so the EF Core message keeps its own
wording, and the crash record appends to the newest Log*.log and reports the
path it used.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 13:01:23 +00:00
Cursor Agentandrmcrackan 77cb6bf78e chore: keep the two Dinah package references adjacent
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:59:18 +00:00
Cursor Agentandrmcrackan 1c32149c2b Update Dinah.Core to 10.2.6.1
No code change needed. 10.2.6.1 replaces the Pluralize.NET dependency
with an internal pluralizer inside Dinah.Core; the Pluralize and
PluralizeWithCount extensions this repo uses behave the same, which the
existing tests that assert on their output confirm.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:58:28 +00:00
Cursor Agentandrmcrackan 06356f249a Share one crash log between both UIs, and stop after a rollback
Chardonnay's Serilog-free crash record was private to its entry point, so
Classic wrote nothing at all when startup failed before logging. Move it to
LibationFileManager as PreLoggingCrashLog, where both UIs can call it and where
the existing test project can cover it. It needs nothing from AppScaffolding:
Configuration.LibationVersion already holds the version, and ReleaseIdentifier
is not populated this early, so callers pass what they know.

Guard every field, since an unguarded InteropFactory.InteropFunctionsType,
whose static constructor logged through Serilog, was enough for the bare catch
to swallow the whole record and leave no trace of the crash. Return the file
written so the dialog names it: both UIs pointed at LibationCrash.log, which is
not where the record goes when a Log*.log already exists.

RecoverFromIncompleteUpgradeIfNeeded now reports a completed rollback, and both
entry points show it and quit. Continuing meant running the assemblies already
loaded against the older files just restored underneath them. Consuming the
alert at startup also retires the two late call sites that could never fire.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:56:44 +00:00
Cursor Agentandrmcrackan c4b4fa6971 Recognise any install-folder assembly failure, and let the rollback finish
Three separate reasons the recovery added for #1878 could not help the install
in issue #2001:

Classification was by assembly name, covering only EntityFrameworkCore,
Microsoft.Data.Sqlite and LibationUiBase, so a Serilog bind failure reached
neither the emergency rollback nor an actionable message. Match on the assembly
reference instead and compare the requested version against the file on disk,
so the dialog can say which file is stale and what version it should be. A
stale file and an absent one report identically through the loader, which is
why the version has to be read to tell them apart.

RestoreFromBackup wrote over assemblies this process had already loaded and
mapped. That segfaulted the process on Linux and Windows denies the write, so
the rollback never completed either way. Move the loaded file aside first and
sweep the leavings on a later startup.

Serilog.dll and three more assemblies Libation cannot start without were not in
the upgrade manifest, so an overlay could lose one and still verify clean.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:49:32 +00:00
Cursor Agentandrmcrackan 5c5d1bd946 Replace CsvHelper with a minimal internal CSV writer
CsvHelper was only used to write headers and flat DTO rows in the csv
exports. The new internal writer reproduces its output byte-for-byte:
the delimiter is the culture's list separator, records end with CRLF,
fields containing the delimiter, quotes, newlines, or leading/trailing
spaces are quoted per RFC 4180, and values are formatted with the
culture. The [Name]/[Ignore] attributes it replaced are now two tiny
local attributes.

Verified byte-identical output against CsvHelper 33.1.0 for both
exporters in en-US, de-DE (';' delimiter), and invariant cultures, and
for a seeded 30-book demo library export before/after.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:48:13 +00:00
Cursor Agentandrmcrackan 23ecdf56ee test(metadata): cover both shapes of file Audible delivers
FillMissingTags is driven against a real MetadataItems built from a hand-rolled
ilst box: empty, as a Widevine download arrives, and pre-populated, as an .aaxc
download arrives. Reverting any of the three fixes fails a test with the exact
value from the issue report.

MockLibraryBook.CreateBook gains a copyright knob to match UpdateBookDetails.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:46:56 +00:00
Cursor Agentandrmcrackan 1729f84dbb Log startup recovery without Serilog so a broken install cannot silence it
Libation's releases are ReadyToRun, so a Serilog reference resolves lazily
when the line holding it runs. An install folder whose Serilog.dll is missing
or older than the build therefore threw inside the very catch blocks that were
meant to report the problem, which destroyed the original exception and aborted
the in-app upgrade recovery before it could roll anything back.

Add StartupLog: a Serilog-free, never-throwing buffer for the window before
ConfigureLogging runs, replayed into Serilog once logging exists. Move the
pre-logging call sites onto it, including the crash-message and crash-dialog
paths that run precisely when the install is broken.

Those messages were silent before this change: Serilog.Log.Logger is still
Serilog's silent logger until App.RunMigrations. They now reach the log file.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 12:44:36 +00:00