Commit Graph
181 Commits
Author SHA1 Message Date
rmcrackan a122a6917b Merge pull request #1951 from rmcrackan/cursor/heal-corrupt-lucene-search-index-3c5b
Heal a search index Lucene cannot open, and say which account needs a login
2026-08-16 14:44:17 -04:00
Cursor Agentandrmcrackan 9afcb09885 Keep account addresses out of the auto-scan pause log line
The dialog names the account in full because it is shown to whoever owns it, but
log files get attached to public issue reports, which is why the codebase has
MaskedLogEntry. Naming the account in the log the same way the dialog does would
have put real email addresses into every shared log.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 18:32:37 +00:00
rmcrackan 1999307dfa Merge pull request #1952 from rmcrackan/cursor/no-rights-retry-backoff-0844
Fix #1947: refused licenses re-requested every run, missing and misplaced PDFs, unbounded log
2026-08-16 14:28:32 -04:00
Cursor Agentandrmcrackan bb55fb40a3 Stop the filter box looping on dialogs when the search index is at fault
Both grids restored the last good filter by recursing into the filter handler, which
never terminated once the search index rather than the query was the problem: the
restore fails the same way, and the retry uses the same filter. The user got an
endless run of dialogs, each of them blaming a filter string that was fine. Only an
empty last-good filter broke the loop, because that short-circuits before reaching
the search engine.

The fallback is now a bounded sequence -- last good filter, then no filter -- and
the message distinguishes an index Libation cannot reach from a query it cannot
parse. Only the first failure is reported, so restoring is quiet. A malformed query
never surfaces as an IO-family exception, which QueryFailureShapeTests pins against
the real engine, so a typo is never mistaken for index trouble or made to trigger a
rebuild.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 18:06:40 +00:00
Cursor Agentandrmcrackan 0f4cfac3b0 Name the account and the real cause when auto-scan pauses for a login
Ported from #1949. The reporter's log paused auto-scan on a second account that
had never been logged in, while the dialog blamed an expired session and named no
account, so there was nothing to act on.

AccountCredentialStatus tells a never-registered account apart from one holding an
expired access token, by looking for a refresh token to renew from. AutoScanRunner
now hands the AuthenticationRequiredException to the notification so the prompt can
name the account, which means digging that exception back out of the wrappers the
scan adds on the way up. Same distinction in the log line and in the exception
message ApiExtended throws when interactive login is unavailable, which is what the
CLI and Docker users see.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:55:46 +00:00
Cursor Agentandrmcrackan 79bdbe6d76 Tell the user how to delete the search index when repair fails
Ported from #1949, which surfaces the manual recovery steps the maintainer had
been giving out by hand instead of leaving the user with a raw Lucene error.

Adapted to the failure now being contained: with the exception no longer escaping
into the library change, the scan-failure catch blocks #1949 hooked would never
see it, and hooking only those would still miss every other trigger -- removing
books is what crashed the GUI. So the guard moves from AppScaffolding into
SearchEngineCommands next to the update commands it protects, and raises
UpdateFailed from there. Both GUIs subscribe, so any trigger is covered, and the
event carries the exception rather than needing #1949's StackTrace string sniffing
to find it. The dialog is shown once per session: a damaged index fails on every
library change and these steps only need following once.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:44:58 +00:00
Cursor Agentandrmcrackan 824ff10dd9 fix(download): stop asking Audible for a license it just refused
A license denial left no trace: BookStatus stayed NotLiberated, so every
liberate run asked again. Only the GUI's bad-book dialog could mark a title
Error, and license denials take their own path and never reach that dialog, so
a headless install had no way at all to stop the retries. A cron schedule then
re-requested the same refused licenses every run and printed the same warning
block for each one, which is both wasted API traffic and the log noise reported
in issue #1947.

Record the refusal instead, with a wait that doubles per consecutive failure:
one day for an eligibility denial (up to 30), six hours for a title Audible has
no audio for yet such as an unreleased preorder (up to 7 days), one hour when
the denial names GenericError, which the GUI already reads as an outage
(up to 12). Nothing is permanent - every kind is attempted again on its own.

Only failures attributable to Audible are recorded. A dropped connection, a
decrypt error or a full disk keeps being retried on the next run as before.

Naming a title, --force, and setting a download status all clear the record:
asking for a title explicitly overrides the wait.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:13:17 +00:00
Cursor Agentandrmcrackan 4a200c6862 chore: add tippfehlr to the contributors list
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 16:03:12 +00:00
Cursor Agentandrmcrackan 2cceabb10e refactor(queue): trim the skip-reason breakdown
Drop the series-parent reason, which no caller can reach: parents are expanded
into their children before anything is queued. Replace the enum and its three
switches with a record carrying the label and the advice, which is all the
switches were mapping to.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 20:46:31 +00:00
Cursor Agentandrmcrackan 41de40e6cb fix(queue): put the count before the guidance in the skip breakdown
"Absent from your last library scan (run Scan, ...): 2" buries the number behind a
parenthetical. Lead with the label and count, then the advice.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 20:34:02 +00:00
Cursor Agentandrmcrackan f806af9632 fix(queue): explain a multi-book download that queues nothing
The multi-book branch of QueueDownloadDecryptAsync returned false with no log
entry and no message whenever UnLiberated() came back empty, so a request
Libation understood and declined looked exactly like a dead button. Callers that
pre-filter with UnLiberated() land here with an empty list, which is the common
way to hit it.

Classify each title that cannot be queued and report the breakdown: already
downloaded, previously failed, absent from the last scan, or a series parent with
no audio of its own. Log it either way; show it only when a person is waiting,
so the automatic post-scan download stays silent.

Fixes #1940

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 19:52:04 +00:00
Cursor Agentandrmcrackan b35276c7a1 fix(limit): let newly queued books withdraw a pending Cancel All
Cancel All sets a flag the daily-limit wait loop watches. If books were queued
while the cancelled book was still settling, the queue was still running, so
those new books inherited the cancellation at the gate. Queueing work now
clears the flag.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-14 18:11:03 +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
rmcrackan 5af29b6bed Merge pull request #1918 from caiowilson/caiowilson/cli-audiobookshelf-upload
Add CLI abs upload for Audiobookshelf backfill
2026-08-13 15:36:25 -04:00
Cursor Agentandrmcrackan 2eb12a26de Let the Plus badge overlap the PDF instead of displacing it
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 18:09:35 +00:00
Cursor Agentandrmcrackan ee94c6f3d6 Separate the Plus badge from the stoplight with a knocked-out rim
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 16:03:20 +00:00
Cursor Agentandrmcrackan 69ab7f1c3f Mention Plus in the icon descriptor's cache-key remarks
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 15:27:02 +00:00
Cursor Agentandrmcrackan c09a08b7b6 Test the Audible Plus badge and widen the WinForms Liberate column
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 15:14:35 +00:00
Cursor Agentandrmcrackan 99b52103a7 Badge Audible Plus titles on the Liberate stoplight icon
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 15:09:29 +00:00
Cursor Agentandrmcrackan ac4186ac25 Show a plus on collapsed series rows, not on expanded ones
The Liberate icon offers an action rather than reporting a state, so an expanded
series shows a minus to collapse it and a collapsed one shows a plus to expand
it. Naming the geometry after the state it belonged to had these swapped.

Name it after its shape instead, and pin the direction with a test: the plus is
the minus plus a bar, so it is strictly the inkier of the two.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 06:08:59 +00:00
Cursor Agentandrmcrackan c04b24f722 Add shared Liberate icon generator to LibationUiBase
Both UIs need the same stoplight/PDF/error/series icons. WinForms selected
pre-baked PNGs by name while Avalonia composed vector layers in XAML, so the
two drifted and every new combination needed another pair of PNGs.

Port the Avalonia artwork and palette into LibationUiBase and rasterize it with
SkiaSharp, keyed by a descriptor of the finite combinations and cached in memory.
EntryStatus.ButtonImage now serves that shared rendering to both UIs.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-13 05:46:55 +00:00
Cursor Agentandrmcrackan 44db619c0c Add single-book download to context menu and clarify stoplight tooltip
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-11 20:44:25 +00:00
Caio Wilson 9ac1f64056 Fix Audiobookshelf CLI upload follow-ups 2026-08-10 18:47:21 -03:00
Cursor Agentandrmcrackan 4e6bba54b5 bugfix #1925: show orphaned episodes as standalone rows
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-10 03:20:07 +00:00
Robert McRackan c144c20c53 bugfix #1914 : fix race when linking podcast episodes under series parents 2026-08-06 16:08:57 -04:00
Robert McRackan 00fdd903aa Add @oxivanisher to contributors 2026-08-02 11:14:06 -04:00
Robert McRackan 636b85f8e4 UI: add Settings export for the portable encryption master key 2026-08-01 12:09:13 -04:00
Robert McRackan a079752c71 incr ver. add contributor @Demoniskk 2026-07-28 14:22:35 -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 abea874db8 Add Important settings UI for encrypted token storage 2026-07-25 21:32:42 -04:00
Robert McRackan b4f7cf4288 Shared walkthrough helpers and constants 2026-07-25 12:35:22 -04:00
Robert McRackan e1bede5a6a Centralize walkthrough copy/text 2026-07-25 12:28:43 -04:00
Robert McRackan 353a5b8800 Mark macOS app bundle as APPL so Gatekeeper and Homebrew cask audits accept it. Thanks, @m-j-r ! 2026-07-17 14:46:38 -04:00
Robert McRackan 3cdaa09aff Pause auto-scan on auth failure instead of spamming login dialogs 2026-07-07 09:40:14 -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 4bd7796f70 #1872 : Suggest Widevine when ADRM licenserequest fails with Sable acr:null 2026-06-16 11:12:58 -04:00
rmcrackan 158ee3d5ba Merge pull request #1869 from rmcrackan/rmcrackan/1867-duplicate-asin
#1867 - Fix duplicate-ASIN crashes. New Hangover recovery
2026-06-12 11:52:42 -04:00
rmcrackan b654a8b551 #1867 - Fix duplicate-ASIN crashes. New Hangover recovery 2026-06-12 11:48:32 -04:00
rmcrackan 4d1b78f7e4 #1865 - Add in-dialog "Apply to all" and "Remember in Settings" for bad-book errors. Also a testing section when run in debug (ie: from visual studio) 2026-06-12 09:53:57 -04:00
rmcrackan c285522a0c Add contributor from PR #1854 2026-06-07 12:56:13 -04:00
rmcrackan 869917e0ce #1851 - threading bug 2026-06-04 09:59:12 -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 d63fabd73d Download works but queue goes red with Download PDF 2026-05-21 10:33:13 -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 d1114b410e sync/async bug fixes 2026-05-18 09:53:05 -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 eb4ab59737 Add @CryptoJones to contributor list 2026-05-15 09:41:33 -04: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
rmcrackan 0cc2ef773d * Default Scan library to on for new accounts from Upsert / Mkb79 import (matches GUI)
* CLI liberate: print short license-denial reasons to stderr
* GUI: message when stoplight can’t queue (e.g. absent from last scan)
2026-05-05 13:32:28 -04:00