Commit Graph
307 Commits
Author SHA1 Message Date
Cursor Agentandrmcrackan 4ac94d1ff2 Say AsScalar instead of restating ToString
Same output, but it states the intent: leave a SecretString whole rather
than transform it. The comment now separates the two lines by what they
do, because they are not the same kind of thing - the policy is the
protection, and without it a masked object is written out property by
property, while this line only decides whether a secret reads as its
length or as {"HasValue":true}.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 02:08:57 +00:00
Cursor Agentandrmcrackan 742e58d2e8 Mask the account on the paths that do not go through an exception
Account.ToString() returned "id - locale", so interpolating an account or
logging a non-destructured {Account} published the address. It now returns
the masked entry, with a DebuggerDisplay keeping the real values visible
while debugging. Nothing in the UI relied on it: both scan dialogs build
their own labels.

For structured logging, an ILogMasked type is reduced to its masked entry
by a destructuring policy, which covers the {@DebugInfo} shape most of
Libation's logging uses. And DecryptKey - the activation bytes - is now a
SecretString, so it has no plaintext for a reflective dump to find at all.
Its JSON stays the bare string it always was, so existing settings files
load unchanged.

A registered policy that nobody notices is missing protects nothing, so
the tests write through a logger built by ConfigureLogging itself rather
than a hand-made one. Deleting either registration fails them: the masked
object comes out whole, and a destructured secret renders as
{"HasValue":true} instead of its length.

The contribute guide now states the rule, since the reason for all of
this is invisible from the code alone: log files get attached to public
issues, so treat what goes in them as published.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 02:04:09 +00:00
Cursor Agentandrmcrackan 337cb3169a Adopt Dinah.Core 10.2.2.1 and AudibleApi 11.0.0.1
AudibleApi 11 holds token, key, and cookie values in a SecretString
rather than a string, so nothing public exposes plaintext for a reflective
logger to find. Picking it up is a breaking upgrade: the seven package
references move, and the nine places that read a secret now call Reveal().

Two of those needed thought rather than a mechanical edit. Mkb79Auth
exports to and imports from audible-cli's JSON format, which is plaintext
by definition, so the cookie projections reveal explicitly in both
directions and the file format is unchanged. And the account's own
DecryptKey stays a plain string here: converting it is separate work.

This is the dependency bump only. The log leak it enables fixing - an
AuthenticationRequiredException carrying a live Account, whose address and
activation bytes Serilog.Exceptions writes into a shared log - is still
open, and none of the account-side masking has landed yet.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 01:49:21 +00:00
Cursor Agentandrmcrackan 4d8075927f fix(logging): roll the log on size, not only on the calendar month
The default Serilog config set rollingInterval only, so Serilog's own defaults
applied: no size-based roll and a 1 GB ceiling after which the sink silently
stops writing. A busy install (many accounts scanned several times an hour)
reaches tens of MB in a month, past the point where the log can be attached to
a bug report.

Add fileSizeLimitBytes, rollOnFileSizeLimit and retainedFileCountLimit to the
default File sink, and fill in whichever of the three an existing Settings.json
is missing so installs that already have a Serilog section benefit too. Only
absent keys are written, so a hand-tuned config is left alone.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:03:27 +00:00
Cursor Agentandrmcrackan 562b563477 feat(cli): stop a liberate run at a per-run download limit
Adds three mutually exclusive options to `liberate`: --limit-books,
--limit-mb and --limit-gb. Each stops the run once it has downloaded that
much, leaving the rest of the library un-liberated for the next run.

Requested in #1920: a scripted or scheduled run has no way to take only a
slice of a large library, so users resort to feeding the CLI a handful of
ASINs at a time. The GUI needs no equivalent, where selecting rows already
says exactly which titles to download.

Counting reuses the daily limit's history rows rather than a private tally,
so a book and a byte mean the same thing to both limits, and failed,
cancelled and pdf-only work is never counted. Only titles this run attempted
are counted, so a Libation window or a second container downloading at the
same time does not consume this run's allowance.

The limit is checked before each title rather than at the top of the run, so
a run whose books happen to end exactly at the limit reports nothing: nothing
was cut short. The daily download limit keeps applying on top, unchanged.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 19:46:47 +00:00
Cursor Agentandrmcrackan 3129221531 docs(limit): document the daily download limit and add a history seeding script
Adds a feature page covering the rolling window, what counts, the MB/GB
estimate and the Docker/CLI keys, plus a one-line pointer from getting started
where a new user with a large Plus library clicks Begin Book Backups.

Scripts/seed-download-history.cs seeds fake completed downloads so the limit
can be exercised without downloading, including dating rows just under 24 hours
old to turn the multi-day pause-and-resume behavior into a one minute test.

Also shortens the queue status text: the process queue column clips rather than
wrapping, so the resume time was being cut off.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 18:03:23 +00:00
Cursor Agentandrmcrackan 818510d1b0 feat(limit): add opt-in daily download limit core, history store and enforcement
Records every successful audiobook download in the library database (a new
DownloadHistory table) and, when the user opts in, stops downloading once the
rolling 24 hour window is full.

The history lives in the database rather than a file under LibationFiles
because in Docker only the database is on a volume; a file there is discarded
on every container restart.

The limit is checked immediately before each book downloads, never at queueing
time, so a full queue stays full and the user can raise or disable the limit
mid-run. When nothing in the queue can proceed the queue pauses and re-checks
every 15 seconds, recomputing settings, history and clock from scratch, so a
queue left running for days drip-feeds itself as downloads age out. The CLI
never waits: it skips covered titles and reports a count.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 17:16:47 +00:00
Cursor Agentandrmcrackan a5b1df0d29 Centralize best-effort file deletion in FileUtility
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-12 18:55:42 +00:00
Cursor Agentandrmcrackan 90856b29a6 Add single-instance guard and make SQLite WAL/SHM cleanup data-safe
Concurrent Libation instances against one LibationFiles folder raced on the
SQLite database, Lucene search index, and log file, and the startup routine
unconditionally deleted LibationContext.db-wal/-shm - discarding committed
transactions when a prior run died abruptly (issue #1931).

- Add SingleInstance (named mutex keyed on the LibationFiles folder). Wire it
  into the Avalonia and WinForms startup so a second launch shows a message and
  exits before any database access, and holds the lock for the process lifetime.
- DeleteOpenSqliteFiles now skips cleanup when the DB is held by another process
  and preserves a non-empty (unrecovered) WAL so SQLite can recover it on open.

Also compare AccountId case-insensitively in AccountsSettings.GetAccount so a
capitalization difference no longer causes spurious 'No account found' errors.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-12 18:13:58 +00:00
Cursor Agentandrmcrackan da23a7ddb0 fix: use '/' in Unix mount prefix matching (Windows CI)
FindLongestMountPointPrefix is exercised on Windows CI with Unix-style
paths. Path.DirectorySeparatorChar is '\\' there, so '/var/home' never
matched '/var/home/...'. Hardcode '/' for Unix mount identity.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-09 17:55:38 +00:00
Cursor Agentandrmcrackan 16e6a6f659 bugfix #1919: resolve Unix mount points for disk-space preflight
Path.GetPathRoot always returns "/" for absolute Unix paths, so bulk
backup preflight queried composefs root free space (often 0 on Bazzite)
instead of the filesystem containing Books/In progress.

On Unix, symlink-canonicalize paths (so /home -> /var/home) and pick the
longest DriveInfo.GetDrives() mount prefix. Route GetBackupDriveSpaces
through the shared helper so grouping and free-space queries agree.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-09 17:51:36 +00:00
Robert McRackan 065118cf6c Fail fast on invalid Settings.json enums and broken Serilog structure: reject unknown enum values at startup with clear errors, accept case-insensitive names, migrate ZipFile sinks, and validate Serilog shape without blocking hand-edited custom sinks 2026-08-07 11:10:43 -04:00
Robert McRackan e0d49b57e1 Encrypt-fail plaintext fallback 2026-08-05 10:02:53 -04:00
Robert McRackan 4513a8007b Quiet Docker ConfigApp.dll noise and document login-external via /config 2026-08-03 09:09:59 -04:00
Robert McRackan 712568b188 Surface real CLI/Docker auth failures and fix login-external locales 2026-08-02 11:55:48 -04:00
Robert McRackan 5f4a75aade Detect identity decrypt failures by exception type 2026-08-01 11:25:56 -04:00
Robert McRackan a6d637c718 Harden Audiobookshelf Server URL handling and connection errors 2026-07-31 14:05:36 -04:00
John Doe b5e87847ff fix: Address review feedback - UI parity, soft-fail, token encryption, pagination, CLI wiring 2026-07-26 19:12:31 +02:00
John Doe ad61618cc9 Add Audiobookshelf auto-upload integration
- Persist settings: enabled, server URL, API token, library/folder IDs

- Add AudiobookshelfApiService for login, library listing, and multipart upload

- Add UploadToAudiobookshelf post-download processable

- Add settings tab to WinForms and Avalonia with library/folder dropdowns

- Match Avalonia layout to WinForms with aligned columns
2026-07-26 09:58:12 +02:00
Robert McRackan 20150cc4ed update dependencies 2026-07-25 22:03:23 -04:00
Robert McRackan abea874db8 Add Important settings UI for encrypted token storage 2026-07-25 21:32:42 -04:00
Robert McRackan bb18e9adc4 Add TokenStorageMethod preference and AudibleApi token-storage wiring 2026-07-25 21:17:11 -04:00
Robert McRackan a9e6f75443 update dependencies 2026-07-25 20:57:52 -04:00
rmcrackan d30086e53a bugfix 2026-06-17 10:38:24 -04:00
rmcrackan 82d4001f0a #1878 : Detect incomplete in-app upgrades, roll back install files, and show clear recovery messages 2026-06-17 10:18:52 -04:00
rmcrackan d955cb7605 #1873 , #1876 - Handle Windows Application Control blocking after in-app upgrades 2026-06-16 17:25:18 -04:00
rmcrackan b654a8b551 #1867 - Fix duplicate-ASIN crashes. New Hangover recovery 2026-06-12 11:48:32 -04:00
rmcrackan b7ce96e91d Finish windows installers, phase 1 2026-06-08 14:03:59 -04:00
rmcrackan 2c2ae2c8b0 #1845 - improve error message for common VPN blocking errors. improve troubleshooting docs 2026-06-02 12:53:20 -04:00
rmcrackan e6c17053f7 #1844 - add lock to avoid race/NRE 2026-06-02 10:54:14 -04:00
rmcrackan 3cf5d0b3fa unit test bug fix: culture info 2026-05-25 10:46:34 -04:00
rmcrackan 6bd1ea7ca4 fix unit tests for windows-only logic 2026-05-25 10:41:20 -04:00
rmcrackan f8c5f0da68 #1836 - Fix misleading "Not enough disk space" warnings when Books and In progress are on different drives 2026-05-25 10:25:46 -04:00
rmcrackan b9490966bb * Multi-step backup: PDF and later steps now run in the same awaited chain, so disk full on PDF stops the queue instead of counting as success.
* Detection: Quota / NAS “out of space” messages are treated like disk full, not generic retries.
2026-05-18 10:00:32 -04:00
rmcrackan 77f518f8d3 #1823 - Address full disk better. Fail clearly and early when possible, fail safely when space cannot be queried, and do not take down the UI or spam the user when the disk is full. 2026-05-18 09:20:51 -04:00
rmcrackan 74b18c170a #1822 - Fix the startup crash after auto-upgrade by loading the library only after InteropFactory assembly resolution is ready, checking that Microsoft.EntityFrameworkCore.Sqlite.dll is present in the install folder, and showing a clear reinstall message instead of a generic "Unexpected error" when that still fails. 2026-05-18 08:54:40 -04:00
Jo-Be-Co 4be0361e42 fixing typos and naming of test method 2026-05-14 16:51:51 +02:00
Jo-Be-Co c2c7b04acd minor fixes and comment corrections 2026-05-14 02:12:25 +02:00
Jo-Be-Co f194032139 #1762 add filter() to list properties 2026-05-14 02:10:38 +02:00
Jo-Be-Co a21bb8174d #1762 introduce CommonFormatters.TryGetLiteral 2026-05-14 02:04:26 +02:00
rmcrackan 597c8f7b3d Merge pull request #1795 from Jo-Be-Co/1762_unique_and_count
#1762 add unique() and count() to list properties
2026-05-09 23:21:29 -04:00
Jo-Be-Co 6996fb1b81 When formatting ContributorDto, retain the IFormatProvider even if the format is empty 2026-05-10 00:36:49 +02:00
Jo-Be-Co 7ef10b1e7d #1762 add unique() and count() to list properties 2026-05-09 21:15:24 +02:00
rmcrackan 727c869ba9 #1711 : Linux/Docker: the default in-progress download/decrypt folder is now per-user (/tmp/Libation-<username>) and is created with 0700 permissions, fixing failures caused by leftover /tmp/Libation directories from previous root installs and hardening against another local user reading partial download artifacts. Auto-upgrade downloads now land in a randomly-named per-run temp directory. 2026-05-08 09:04:39 -04:00
MBucari 505c614210 Code Cleanup
Remove unused parameters
Remove unnecessary casts
Make fields readonly
Order modifiers
Format document
Sort usings
Remove unnecessary nullable directive
Apply namespace preferences (file-level)
2026-05-04 21:10:28 -06:00
Michael Bucari-Tovo df531de255 Update Avalonia to v12
Avalonia 12 uses compiled bindings by default. Converted all remaining reflection bindings into compiled bindings,

Fixed binding errors
2026-05-04 21:08:26 -06:00
rmcrackan dd059a0ce9 #1776 - Make Linux Snap SQLite startup failures easier to understand and fix (stale LibationFiles in appsettings.json after a revision change), improve crash and log detail (inner exceptions + paths), and document that in the docs and FAQ. Tests lock in exception formatting behavior. 2026-05-01 10:01:03 -04:00
rmcrackan 849f5f508e trying again to fix the intermittent cover image issue 2026-05-01 09:38:29 -04:00
rmcrackan f610ccdf58 #1744 Make Explorer folder icons reliable by running them after other post-download work, retrying briefly when the 300×300 cover or ICO write fails, and logging clear, actionable messages when it still cannot succeed. 2026-04-27 10:30:27 -04:00
Jo-Be-Co 30809fb38d fixed quoting errors in format-strings 2026-04-20 21:38:45 +02:00