Skip to content

Add from LibriVox: admin search, the librivox.fetch Bridge job and its match - #378

Merged
KodeStar merged 17 commits into
mainfrom
feat/librivox-l6
Nov 13, 2026
Merged

KodeStar merged 17 commits into
mainfrom
feat/librivox-l6

Conversation

@KodeStar

Copy link
Copy Markdown
Owner

LibriVox L6-1 (v2.7.0 row 14, LIBRIVOX-PLAN.md "L6").

What:

  • internal/meta: lookup?librivox_id= and source-filtered work search (source=librivox), both also through the mirror's fallback transport.
  • Admin routes GET /admin/librivox/search, GET /admin/librivox/work, POST /admin/librivox/fetch. The fetch resolves the LibriVox project to its exact (work_id, recording_id) with meta's lookup at queue time and queues a librivox.fetch job (books library, writable on the Bridge, one open job per project and library). 409 in_library / kind_unavailable / not_book_library, 404 metadata_off.
  • catalog.FoldLibrivoxFetch (from OnEnded): a done fetch records a book_matches row (accepted, set by the requesting admin) at the placed path, so /meta answers the new book as its recording with no console match. Never from a first-recording fallback. It replaces only a stale row at that same path.
  • Console: Library > LibriVox (search, a work's LibriVox recordings, library picker, Add, job state, In library). The book card says "Matched to its LibriVox recording". Six languages.
  • Ungated (owner decision): admin + metadata on + a Bridge offering the librivox feature. No module, no new /server flag; additive wire.

Tests: allowed and denied cases on all three routes (listener, API key, bridge/cast/feed tokens, no token, ?token=), metadata off, in_library, dedupe, lapsed leases, stale-row overwrite, another path untouched, the fold's identity checks; console tests.

Reviews: /simplify + /code-review high (Fable 5.2) on the whole branch, fixes applied.

Verified live (scratch server + a real paired Bridge from KodeStar/audiosilo-bridge feat/librivox-l6 + live meta + Chrome): project 192 added in ~3 s, /meta matched with librivox.id 192 and no hand match, Health matched, In library + 409 on a second add, kill -9 of the Bridge mid-download resumed without re-fetching finished sections, a listener gets 403 and metadata off gives 404 on all three routes.

Not verified live: the exists / disk_full / archive_unavailable / cancel paths (Bridge tests only), mirror mode (tests only).

Pairs with KodeStar/audiosilo-bridge (librivox.fetch), the player (KodeStar/audiosilo-frontend, L6-2) and the docs (KodeStar/audiosilo-docs).

The console's Add from LibriVox needs the community metadata database's
LibriVox works and the exact recording a LibriVox project is. The client
gains lookup?librivox_id= (asked alone) and works/search's source and lang
filters; the Service gains LibrivoxSearch, LibrivoxWork (a work's LibriVox
recordings only, never a first-recording guess) and LibrivoxRecordingOf
(a lookup without a recording is not found). All three work in mirror mode
through pkg/query, tested over a real artifact.

Part of LibriVox L6 (v2.7.0 row 14).
An admin adds a LibriVox recording to a books library; the Bridge
downloads and places it, and the placed book is recorded as that
recording so /meta answers it with no console match.

- bridge: the librivox.fetch kind (internal, writes, a books library,
  server-built strict params, deduped per library and project), so a
  Bridge that runs it reports the librivox feature; its result and codes;
  the open fetches of a set of projects.
- catalog: FoldLibrivoxFetch records a done fetch's book in book_matches
  (accepted, set by the requesting admin), replacing a stale row at that
  path and never another path's, only when the result agrees with the job
  and names an indexed book; RecordingBooks finds a recording on this
  server by its exact, non-empty recording id.
- api: GET /admin/librivox/search, GET /admin/librivox/work and POST
  /admin/librivox/fetch (admin only, 404 metadata_off, 409 in_library),
  the fold from OnEnded, the done job named by its book in notifications.
- The scanner already skips .audiosilo/librivox.json; a test holds it.

Part of LibriVox L6 (v2.7.0 row 14).
A new Library section finds LibriVox works in the community metadata
database, lists a work's recordings (readers, length, sections, project,
its LibriVox page) and adds one to a books library through the Bridge's
librivox.fetch job, following the job until it lands. A recording already
on the server links to its book instead of offering Add. Add is off, with
a note, until a paired Bridge that runs librivox.fetch has reported; an
offline Bridge still takes the job. With community metadata off the page
says so and links to Metadata settings. The Bridge job list names the
project, the bell names a finished fetch, and in_library refusals have
their own words, in every console language.

Part of LibriVox L6 (v2.7.0 row 14).
…g it

A recording row now names a parked fetch's reason (a read-only library, a
full disk) instead of the bare state, and keeps following the last job it
queued or saw after the work stops naming it (the work lists only open
jobs), so a failed fetch's reason no longer vanishes on the next refresh.
The Portuguese strings say "o Bridge" like the rest of the console.

Part of LibriVox L6 (v2.7.0 row 14).
The work route's lookup of open librivox.fetch jobs walked every fetch
ever kept, ended ones too, and sorted them in a temp b-tree: nothing
prunes this kind, so the set grows by one row per book added. An open job
never has a finished_at, so asking for that lets
idx_bridge_jobs_kind_finished reach only the open rows. The project ids
are bound as one JSON list, as the package's other IN reads are.

Part of LibriVox L6 (v2.7.0 row 14).
LibrivoxFetchCode, LibrivoxFetchCodes and the six code constants had no
caller outside their own test: the work route sends a job as {id, state},
so the server never states a fetch's code and the console reads it from
the job itself. A second copy of the code list with no reader could only
drift from the one that is shown.

Part of LibriVox L6 (v2.7.0 row 14).
Three places restated a rule the code already had a name for:

- the three LibriVox routes wrote the metadata service's 502 by hand
  instead of through writeLookupFailed;
- LibrivoxWork decided what a LibriVox recording is, and which page link
  is kept, beside librivoxOf, which /meta already uses for both;
- the fold admitted "." through bridgeBookPath and then refused it by
  hand, which is cleanLibPath; RecordingBooks filtered an empty recording
  in SQL after returning early for one.

No answer changes.

Part of LibriVox L6 (v2.7.0 row 14).
A match applied is one of the changes the chapter check, the upcoming
pass and the cover colours wake for, and every other match write reaches
them through EditBook. The fold wrote the book's match without telling
them, and the Bridge's rescan had already woken them before the match
existed, so a book added from LibriVox waited for the next unrelated
change (or the daily pass) before its work was looked at.

Part of LibriVox L6 (v2.7.0 row 14).
The work query asked again every 3 s while any recording had an open
job. Each ask is an uncached request to the metadata service (through one
of its four work-fetch slots) plus the job and match reads, and it never
stopped for a job queued behind an offline Bridge or parked on a full
disk. The work document does not change while a job runs: each row
already follows its own job by id and asks for the work again when it
ends, and Add asks for it too.

The search keeps its answers fresh for a minute, so a refocus or a
retyped query does not ask the metadata service again, and Add seeds the
new job after invalidating the job lists (its key sits under theirs), so
the answer it just got is not refetched at once.

Part of LibriVox L6 (v2.7.0 row 14).
The page restated things the console already has: the small external
link (ExternalTextLink), a person's id and name (MetaPersonRef), the
Bridge settings link's own label (its copy in six locales goes; es, fr,
it and pt now read as the rest of the console does) and the 3 s open-job
interval (JOBS_POLL_OPEN_MS).

Smaller forms of the same behaviour: a row's busy flag is a boolean (each
row owns its hook), the Add block is read off the Bridge note instead of
being derived twice, the row's status is early returns instead of a
three-deep ternary, the search text lives in the search card, and the
client's search method no longer takes a language and limit nothing
passes.

Part of LibriVox L6 (v2.7.0 row 14).
The Bridge counts every regular file in the placed folder outside
.audiosilo/, so the cover is in files and bytes too, not only the
sections.

Part of LibriVox L6 (v2.7.0 row 14).
OpenLibrivoxFetches was the one read of the open jobs that did not expire
lapsed leases first (Job, List, OpenJob and Blocker all do). A fetch whose
lease ran out on its last attempt, the Bridge having died, stayed claimed
in the table until something else expired it, so the work route went on
listing it as the recording's open job and the console kept Add off.

Part of LibriVox L6 (v2.7.0 row 14).
book_matches is keyed by library and path, with no index by work or
recording, so the work route scanned the whole table once per LibriVox
recording of the work. WorkRecordingBooks reads the work's matched books
once, grouped by recording; RecordingBooks (the fetch route's check) goes
through it, so the rule lives in one query. in_library is still an empty
list for a recording with no book.

Part of LibriVox L6 (v2.7.0 row 14).
Four ways a recording's row lost track of its fetch:

- The server records a done fetch's book just after the job reads done.
  A poll landing between the two asked the work once, was told the book
  is not in the library, and never asked again, so Add came back after a
  successful add. A done job now asks once more two seconds later.
- A failed ask for the work again (after an Add, or when a job ends)
  replaced the whole card with the error. Every row unmounted and forgot
  the job it followed, so after Retry a failed fetch no longer said why.
  The rows stay while there is an earlier answer to show.
- A followed job that is gone (its library deleted) answered 404, but the
  last good answer stayed as the job's state: the row read Queued or
  Running for ever, Add stayed off and the 404 was polled every 3 s. A
  404 now drops the job, stops the poll and asks the work again.
- Rows invalidated the work by the id in its answer, while the query is
  keyed by the id asked for. A retired work id answers with its new one,
  so neither ask reached the query.

Part of LibriVox L6 (v2.7.0 row 14).
A fetch parked on a read-only library read in the bell as "The job is
waiting for you (not_writable)", while the LibriVox page and the Bridge's
job list both word the code. The bell now uses the same words for a
librivox.fetch's known park codes; another kind's job with the same code
reads as before.

Part of LibriVox L6 (v2.7.0 row 14).
The link under "Adding a book needs a paired Bridge" took the shared
default label, which in Spanish, French, Italian and Portuguese says
"de la Bridge" where this page's notes say "un Bridge": a note and the
link beside it disagreed in one sentence. The page's own label, worded
like its notes, is back.

Part of LibriVox L6 (v2.7.0 row 14).
…x recording

The Community metadata card told a book with no ASIN or ISBN that it was
"matched to its store listing", which is wrong for a book added from
LibriVox: its recording has no store listing. The card now reads the
book's /meta answer (already fetched for the Story card), whose recording
carries a librivox block only for a LibriVox recording, and says "Matched
to its LibriVox recording" when that is the recorded match. Retail matches
keep the store listing line; until /meta answers the line stays neutral
("Matched ...") rather than guess. In all six console languages.

Part of LibriVox L6 (v2.7.0 row 14).
@KodeStar
KodeStar merged commit fe16122 into main Nov 13, 2026
4 checks passed
@KodeStar
KodeStar deleted the feat/librivox-l6 branch November 13, 2026 20:33
KodeStar added a commit to KodeStar/audiosilo-frontend that referenced this pull request Nov 13, 2026
…292)

LibriVox L6-2 (v2.7.0 row 14, LIBRIVOX-PLAN.md "L6"), the player side of
Add from LibriVox.

What:
- A books library's Books mode gets a "..." menu with "Find on LibriVox"
(admins only). It opens `/find-librivox?connection=&library=`, a
Library-tab page beside Find shows.
- Search LibriVox works (community metadata, never librivox.org), open a
work, see its LibriVox recordings (reader, length, sections, project
link). "Add to <library>" queues the server's `librivox.fetch` Bridge
job. The row follows the job by id (steps, sections, park and failure
reasons), offers Cancel, then "In your library" with Open. Library lists
are re-read when a book lands.
- Gate `librivoxAccess`: admin + the `bridge` flag + `bridge.features`
has `librivox` + `metadata`. Older servers and non-admins never see the
menu, and a pasted URL sends no request. Offline state follows the app's
offline rule.
- Wire mirrored by hand: the three `/admin/librivox` routes, `GET
/admin/bridge/jobs/{id}`, error codes `metadata_off`, `in_library`,
`not_book_library`. Additive; no change for older servers.
- Six locales; CONVENTIONS and STYLEGUIDE entries.

Tests: gating, client shapes and error codes, hooks against a fake
client (polling, Add, Cancel), the page's states (RNTL), the menu, the
route tree.

Reviews: /simplify + /code-review high (Fable 5.2) on the whole branch,
fixes applied.

Verified live (web export under the server's /web CSP, server + paired
Bridge from the paired branches, live meta, Chrome at 1440 and 390 px):
menu opens the page; Johnny Crow's Garden added and followed to "In your
library", Open lands on the book, the book shows in Books mode; Cancel
mid-download on a 23-section project leaves no staging folder and offers
Add again; a listener, metadata off and a stopped Bridge all hide the
menu and send no admin request; no console errors or CSP violations on
the page.

Needs a device run: the native "..." dropdown, the library Select
popover (only shown with two or more books libraries, so not seen live),
the LibriVox link on iOS and Android.

Pairs with KodeStar/audiosilo-server#378 (routes, job, fold, console)
and KodeStar/audiosilo-bridge#68 (librivox.fetch).
KodeStar added a commit to KodeStar/audiosilo-docs that referenced this pull request Nov 13, 2026
…ots (#303)

Docs for LibriVox L6 (v2.7.0 row 14): Add from LibriVox in the console
and the player, run by the Bridge.

- New User Guide page `admin/librivox` ("Add free books from LibriVox"):
what is needed (admin, community metadata on, a paired Bridge with the
library mapped writable), console and player steps, what lands on disk,
the book arriving matched, cancel and resume, failure reasons, Clear
community matches.
- New Developer Docs page `bridge/librivox`: the gate, the three
`/admin/librivox` routes, the `librivox.fetch` job (params, progress,
result, failure codes), the identity rule (a `book_matches` row from the
fold, never a first-recording fallback), archive.org-only fetching,
placement, staging and resume, `.audiosilo/librivox.json`.
- Updated: features, running a Bridge, Bridge jobs, console tour, books,
browsing, FAQ, API reference and index, Bridge pairing/jobs,
configuration, attachments, scanner, data model, web UI, notifications,
player library/routes/state, the cross-repo contract, the documentation
mapping table. Also adds `audible.sync` and `audible.import` where two
kind lists had missed them.
- Screenshots: `admin/librivox.png` and `web-player/find-librivox.png`,
captured from the real UI with live community metadata.

Pairs with KodeStar/audiosilo-server#378, KodeStar/audiosilo-bridge#68,
KodeStar/audiosilo-frontend#292.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant