Count only primary and content files when profiling a book's media, so a
side image, cover or .nfo in an audiobook folder no longer adds it to the
Books group on the series page.
Fixes#1862
First volume, Latest volume and First unread series cards drew their cover
with a cropping img, so a square audiobook cover in a 2:3 grid lost a third
of its width. They now render through BookCoverArtwork like single tiles and
follow the cover display setting, while the grid keeps one tile shape.
Fixes#1863
Give users without stored appearance choices the Nostalgia style and teal accent while preserving saved selections and BookOrbit styling on standalone pages.
A device that had not weighed its open book against BookOrbit since
opening dated its place by its last page turn. One turn from a fresh
download's cover made another device's further place look older, so
Sync now pushed the cover over it without asking, and the phone then
reopened the book at its title page.
Until the open book's place has been compared with BookOrbit's (a pull
or push went through, or Jump or Stay was answered), a further BookOrbit
place now also counts as newer, so Sync now and pulls offer Jump instead
of overwriting it. Once compared, page-turn times decide as before, and a
reset stamped after the last page turn still prompts.
Ref #1806
Continue Listening listed only books with audiobook progress, so a book
whose only audio is a read-along EPUB never appeared there, on the web,
on the phone or in the car, however much of its narration was heard.
The shelf now also takes read-along EPUBs whose narration progress was
saved by playing it, which is when its seconds are set. Reading on any
device carries the narration marker along and clears the seconds, so a
book only read stays in Continue Reading. A book with both an audiobook
and narration under way is listed once, at its latest listening.
The web and the iOS Home shelf show these as ordinary book cards. iOS
CarPlay builds before this change leave them out of Continue, which
there lists only audiobooks; the current build plays their narration.
Ref #1847
Squash of feat/koreader-widget-board. KOReader plugin 2.0.0 gets the widget-board dashboard, redesigned
book details, menus, lists and dialogs, Decor page and reading companions. Highlights, notes and bookmarks
now stay in step across web, Kobo, KOReader and the phones (defects D-00 to D-20, migration 0121), and
reading progress and narration hand off cleanly between devices.
A page load during a server restart read the failed session refresh as
signed out and stranded the reader on /login. Boot now holds the first
navigation behind a reconnect screen and retries until the server
answers, and api() keeps the session when a refresh gets a 5xx or no
connection.
A Kobo sends Host without a port, so cover, reading state and download
links got the port the server listens on. Behind a published mapping
such as 3010:3000 that is the internal port, which the Kobo cannot
reach. Use APP_URL's origin when it names the same host; other hosts,
explicit ports and forwarded headers behave as before.
Fixes#1281
kepubify looks a manifest href up exactly as written, so a chapter stored
as "chapter one.xhtml" but listed as "chapter%20one.xhtml" shipped with no
KoboSpans and every synced position snapped back to the chapter start.
Convert a copy with those hrefs decoded and restore the book's spelling
afterwards; only affected books get a new cache key. Position conversion
now matches and loads such chapters too.
Fixes#1136
Kobos on firmware 4.45 were reported failing every sync on BookOrbit's
prefixed PX tokens and passing on Calibre-Web's. Send Calibre-Web's base64
JSON token, or Kobo's own cursor verbatim when store titles carry one. The
token never located the snapshot, and PX and PX2 tokens still decode.
Fixes#1175Fixes#435
A Kobo downloads a cover only for a new entitlement, so a changed cover
sent as changed metadata left the book with a text-only cover until the
home screen fetched a small tile. Record the cover id on each device
snapshot row and re-announce the book when it moves; rows from before
the change learn it silently. Serve tile-size requests at a full-screen
size so the sleep screen never stretches a tile.
A Kobo asks for each cover at its screen size but always got the
400x600 grid thumbnail, so sleep screens and full-screen covers were
blurry. Scale the full cover to fit the requested box instead, behind a
new Full-size covers setting that is on by default. Drop the stale
UserStorage entries from the authorization matrix gap baseline.
Fixes#924
A Kobo syncs its notebooks through Kobo's reading services, which
BookOrbit answered with an empty listing, so notebooks never synced.
When Include Kobo store titles is on, relay every UserStorage call to
readingservices.kobo.com; otherwise keep the empty listing.
Fixes#1787
When a Kobo syncs after finishing a book, its finished state closes the
reading attempt before the offline sessions arrive, and those sessions
opened a second attempt, marking the book as reread or read twice.
Activity dated within the finished read now joins it, activity from
before it began joins nothing, and an undated start never widens the
range. Update the reading-attempts E2E Kobo test to use a real device
that holds the book.
Fixes#1224Fixes#1182
A Kobo dogear was stored as an empty yellow highlight. Land it as a
BookOrbit bookmark linked to the device's id, serve it back as a dogear,
sync deletions both ways, and move dogears already stored as highlights.
Report only books on that Kobo from checkforchanges: a re-registered
device was told about books it never had, so real changes were fetched
outside its sync and never applied. Serve a known annotation at its last
location instead of failing the whole pull when this copy cannot place it.
Fixes#1374
A book's first announcement went to the Kobo before it was downloaded,
when no Kobo location could be computed, so it carried a bare percentage
and the book opened at its start. Compute the location from the copy
being offered for bookmarks sent to the device; positions it reports
still wait for the transfer.
When a book's primary file changed, the Kobo kept the copy built from the
old file but its positions no longer converted, so it was sent a bare
percentage, reset to the start, and that reset was saved over real
progress. Sync each device against its copy's source file instead, which
BookOrbit's sibling sync carries to the other EPUBs, and serve the new
primary when the device downloads the book again.
Fixes#1786
A replacement stayed ambiguous after the user confirmed it, so every
position the Kobo sent was discarded and annotation pulls failed with 409.
Confirming now makes it the device's copy and moves the stored bookmark
onto it. It still cannot drag saved progress backwards, and that is
judged by position, since its percentages run on a different scale.
Ref #1786
Every position conversion resolved the delivered artifact by hashing the
whole source file, so a multi-gigabyte read-along outlasted the Kobo's 30 s
request timeout. Remember each source's hash by its file signature and
hash again only after the file changes. Reusing the stamped Kobo location
on device syncs (defect 3) is left out: that stamp is not per device.
Ref #1786
A replacement byte-identical to the device's copy was never downloaded, so
it stayed offered and every bookmark the device pushed was discarded.
Keep the current delivery instead, and tell the user in the replacement
panel that the device already has this copy.
Ref #1786
A read-along save can hold the sentence being narrated without a text
position, and the Kobo was then sent a bare percentage, so it opened the
book at its start. Turn the narration marker into a text position and a
Kobo location when the saved progress has no cfi.
Ref #1786
A replacement prepared before the device ever received the book had no
earlier delivery to fall back to, so every sync failed the book. It now
stops standing for the book and the book gets an ordinary first offer.
Cancelling a replacement the device has not fetched now clears it, hands
the book back to the earlier delivery, and re-announces it. Update the
download E2E suite for the stale-offer replacement from #1816.
Ref #1786
A store cursor over the carry limit was dropped while the device was still
told to continue, so the store restarted at its first page forever. Stop
store paging for that sync instead, and carry Kobo's cursor verbatim in a
compact PX2 token so the limit can rise to 7,000 characters. Tokens in the
old format still decode.
Fixes#1679
When a book's file changed after it was offered to a Kobo, every download
returned 409 because the offer could no longer be rebuilt. A device download
now retires that offer and serves a fresh one from the current file, and the
device bookmark adopts the copy it downloaded. Log the conflict code when a
sync defers a delivery. Also drop a dev-only relocate log from the reader.
Fixes#1816
fix(kobo): reconcile counter estimates on their counter interval (#1796)
Stop counting offline Kobo reading twice. Record when each counter
estimate's interval began, split measured sessions across intervals by
wall-clock overlap, and subtract measured seconds instead of dropping the
estimate, so both arrival orders converge.
Closes#1667
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
feat(metadata): extract metadata from file paths with regex rules (#1669)
Let libraries define ordered regex rules that read title, authors, series,
series number and dates from each book's path relative to its library folder.
Run rules in a bounded worker pool, honour source precedence and field locks,
and apply them on scans, uploads and watcher moves. Also retry unprobed M4B
tag writes with the mp4 muxer.
Closes#1351
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Clip the hidden genre measuring strip so its full single-line width no
longer widens the scrollable middle column, and let the rating row and
reading activity stats wrap in narrow columns.
Put every auth page on a shared Ex Libris plate with signature-line fields, a show-password toggle and a Caps Lock hint.
Show sign-in messages outside the password form, with Retry, a reset link on lockout and a reset confirmation.
Add language and endpaper pickers (eight repeats) to the auth toolbar, replacing the background and corner controls.
Show the global rule and each library's own rule on Field Rules-style tabs, with live counts per
condition, the score histogram and the books a run would take. Store only what a library changes,
run every library with its own rule, and record each library's run. Also fix the DjVu queue test
that assumed which request waits, and inventory the DjVu reader routes.
Reader › Fonts now uses the Server Fonts ledger, family sheet and upload tray for a reader's own
fonts, and lists the server's fonts with a show/hide switch and a preview sheet. The shared pieces
move to features/settings/fonts and the ledger columns narrow to give the specimen room.
Server: additive GET /fonts/usage and PATCH /fonts/families, upload error codes, and a 409 instead
of a 500 when a reader uploads a second file for the same face.
Changes, Sign-ins, Security and All views replace the filter form. Repeats fold into one row,
sign-ins fold per person, day and network, and rows name their targets; events now snapshot
the target's name so deleted ones keep it. The drawer adds the event's facts, the target's
history, what the same person did around it, and one-click scoping. New additive routes:
/audit-log/summary, /sign-ins and /export; retention is editable from the page.
Each family is a row with its specimen, a weight grid of the styles readers get and how readers
use it; its sheet previews the face on reader pages and inside the reader's own font list. Uploads
answer per file in one tray, removing a family confirms with its cost, and a rename is one request.
Server: additive GET /server-fonts/usage (counts only), PATCH /server-fonts/families, upload error codes.
Show each custom field on one row with its type, libraries and how many
books are filled, and edit, create, archive and delete it in the settings
sheet. Order Edit Metadata and card values by the order set on this page,
and add usageByLibrary to the admin field list, an additive field.
Replace the card grid with medals, a showcase, category shelves, a record book and a
detail sheet. Add GET /achievements/hints, which explains how to unlock what is still
locked from the user's own library and reading history, backed by a new page-count
index. Year-Round Reader now reports its months read as progress.
Remove AppHeader. The sidebar gains a Search row that opens a Cmd+K palette (books, authors,
series, places, settings pages and actions), a utility dock above the footer, and a Reading
group below the lists. Every page opens with a shared PageTitleRow; phones without one get a
slim bar. Settings headers gain a full description, and the top glow defaults to 50% with one
reach and strength for every style. The Appearance panel opens past the sidebar, reorders its
controls and names the current pick, and keeps each pastel accent under its vivid one.
Describe the six entity sections once in useSidebarSections and render them with one v-for in
both the expanded list and the rail, instead of twelve hand-written copies. Move data loading to
useSidebarDataSync, scan and import progress to useSidebarLibraryActivity, and the footer to
SidebarFooterBar. The rendered sidebar is unchanged.
Library, smart scope, collection, authors and series pages share a new
BrowseHeader: the place and its actions on one row, then search, sort,
filters and facets in one bar that folds groups instead of wrapping.
Format chips count from the new POST /libraries/:id/books/facets, built
on the listing's own query; scopes and collections gain filter and sort.
Three defects, found while planning the Android client:
- The email module's providers, preferences, templates, recipient
groups, tests and send requests had no types in packages/types, so
every client mirrored them from server code. They are typed now, from
the JSON the routes return and accept, and the services and DTOs
declare them. No response changes.
- Narration audio paths were decoded twice: the router decodes the
wildcard, and the route decoded it again, so a name holding a literal
% resolved to the wrong entry or none. A path is now decoded once and
matched to the playlist's hrefs exactly, with an exact entry name
preferred. Each request also read the whole audio entry into memory;
it is now streamed, read from its offset when stored and inflated and
skipped when deflated, with the same headers and Range handling, and
an open range at or past the end is a 416. Both narration audio
routes skip response compression, which dropped Content-Length.
- A playlist's section starts left out any clip of unknown length.
They now use the media:duration each overlay declares when a clip's
own length is unknown. A seq that interleaves par and seq children
was reordered by the XML parser's grouping; SMIL is now read in
document order.
Three book request defects, found while planning the Android client:
- The sweep that cancels a self-serve request nobody picked a release
for timed it from createdAt. A self-fulfiller who took on an older
request somebody else made inherited that clock, so a request taken
on more than six hours after it was made was cancelled at the next
sweep and dismissed for the person who asked for it. It is now timed
from when the request became self-serve, the decidedAt that creating
it self-serve and taking it on both set.
- The grab routes kept Fastify's 1 MiB body limit while their DTO takes
a base64 .torrent of up to 4 Mi characters and the service 2 MiB
decoded, so a .torrent over about 768 KiB was refused with a 413
before the DTO was read. Both grab routes now declare a body limit
that fits the DTO.
- Approving with a new library and no folder kept the request's stored
folder, which belongs to its old library, and was refused with
SUBMIT_FOLDER_NOT_IN_LIBRARY; nothing the approver could send cleared
it. Rerouting without a folder now leaves the old one behind.
Five defects in resumable uploads, found while planning the Android
client's transfers:
- A create under a key whose session had failed, been cancelled or
expired handed that dead session back, and the unique index refused a
new one, so a client that keeps its key across Try again could never
succeed. A dead session now releases its key, keeping its row and
outcome, and a new session is made under it.
- A failed import was stored as UPLOAD_IMPORT_FAILED whatever the error,
with the raw exception message, which can carry server paths. A coded
upload error now keeps its own code and message; anything else is
stored with a fixed message and the raw error goes to the log.
- Upload capabilities listed every reachable library whatever its type,
and the session routes had no type check. Only book libraries are
offered or accepted, as on the older library upload route.
- Contents that do not match the format were refused only at complete,
after every byte was sent, and the session stayed receiving. The first
chunk is now checked against the format's signature, and refused
contents end the session and free its staged bytes.
- A chunk's body was read while the session's lock was held, with no
timeout, so a connection gone half-open blocked every later chunk,
complete and cancel for that session until the server restarted. The
body is now read before the lock is taken, and a chunk that sends
nothing for two minutes is given up.
The response shapes and request fields are unchanged.
Replace the lattice placeholder with a typesetting engine that measures real glyphs, balances line
breaks and parses series, subtitles and sort-form authors before drawing.
Add a placeholderCoverStyle display preference, Bauhaus by default, with a style switcher and sample
shelf in Book Covers settings. Colours follow the series, author or title, so a cover stays the same
when genres arrive later; only Paperback's bands use genre.
Scanned books are commonly distributed as DjVu, which the scanner only stored as a supplement.
Decode pages on the server with DjVuLibre so DjVu imports with a cover and page count, opens in
the image reader with its embedded OCR text, and can be the preferred format of an ordinary email
recipient. Migration 0113 widens the recipient format check to allow it.
Decoding runs on validated private snapshots behind a bounded, reader-first queue, and
revalidation and cached pages are answered without a decoder. Released iOS builds cannot open
DjVu, so series continuation offers it only to callers sending includeDjvu=true, and the
dashboard and notebook read file never name it. The first full scan after upgrading promotes
stored .djvu supplements to content.
Use the source finish day when importing completed KOReader books so
completion timelines reflect reading history instead of the sync date.
Preserve newer manual edits and retain fallback behavior for unusable dates.
Closes#1783
Copy unchanged ZIP records without inflating and recompressing book content
so metadata saves avoid processing every image, chapter, and font.
Use bounded file I/O and validate archive structure while preserving atomic
replacement and original entry compression. Cover ZIP64, descriptor variants,
byte preservation, repeated saves, and filesystem failures in regression tests.
Closes#1798
Resolve OPF and Dublin Core namespaces so valid prefixed metadata retains
book language and avoids incorrect English dictionary lookups.
Preserve element order and identifiers, and cover reader metadata and
narration with unit and HTTP regression tests.
Closes#1789
Library access roles had no effect on metadata editing: only the global Edit metadata permission
was checked, so a Viewer could edit every library they could see and an Editor could not be
limited to one library. Require Editor or Owner on the book's library for every metadata write,
including bulk edits, locks, covers, file renames, entity manager changes and author edits, and
hide the matching actions in the web client.
Keep existing access levels when an admin saves a user's library list. The old replace reset every
row to Viewer, which would now silently revoke editing on each user save.
Existing grants are Viewer, so after upgrading, admins must set Editor on each library where a
non-superuser should keep editing metadata. The released iOS app already gates editing on Editor.
Closes#1778