mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-05 21:41:15 +01:00
feat(library): mark search results already in a Calibre library (#1377)
Per discussion #1372, where you said you were fine with this specific implementation: check whether metadata.db exists, read it if so, and show a check mark saying the book is already there. Searching for a book you already own gives no hint that you own it, so the easiest way to end up with a second copy is to not remember you have the first. This reads a Calibre `metadata.db`, read only, and marks matching results with an **In library** badge in the card, list and compact views and in the details dialog. Off by default. It sits in Settings, General beside the existing Library URL, with a test button that reports how many books it indexed. No HTTP call, no token, nothing written back. Matching runs most to least confident: a shared external id, then an ISBN compared in both ISBN-10 and ISBN-13 form, then fuzzy title tokens plus the author surname. The check fails open, so an unreadable database degrades the badge and never blocks a search, and entries are cached for ten minutes with an early refresh when the file changes, so a large library costs one read rather than one per search. `text_match.py` is new and shared by the index and the provider, so title, author and ISBN matching stays consistent in one place. ## On the provider interface `library_index` talks only to a `LibraryProvider` protocol and knows nothing about Calibre. That is deliberate but it is not speculative generality, it is what let me send you the Calibre half on its own: I run an Audiobookshelf provider on the same interface in my fork, which is where the audiobook side of the badge comes from. I have left that out because it is a new service integration rather than something already in the codebase, which is the line your non-goals draw. Happy to send it separately if you ever want it, and equally happy for the answer to be no. Adding a library is a module with the `LibraryProvider` shape plus one line in `all_providers()`. ## Verification - `tests/core/test_library_index.py`: id, ISBN and fuzzy matching, per-content-type provider selection, fail-open on provider errors, stale-cache reuse, TTL and fingerprint refresh, per-provider cache isolation, and the test-connection path including unsaved form values. - `tests/core/test_text_match.py`: ISBN variants and token matching. - `src/frontend/src/tests/libraryBadge.test.ts` and the added cases in `bookTransformers.test.ts`. - Python suite (3269) and frontend suite (206) green, plus ruff, ruff format, basedpyright, vulture, tsc, oxlint, oxfmt and the production build.
This commit is contained in:
@@ -17,6 +17,7 @@ Use the guides below to set up the app, connect your library tools, and understa
|
||||
- [OIDC](oidc.md)
|
||||
- [API Access](api-access.md)
|
||||
- [URL Search Parameters](url-search-parameters.md)
|
||||
- [Library Check](library-check.md)
|
||||
- [Custom Scripts](custom-scripts.md)
|
||||
|
||||
## Help
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Library Check
|
||||
|
||||
Shelfmark can mark search results you already own, so you do not download a second copy
|
||||
of a book that is already on your shelf. The check is read only and off by default.
|
||||
|
||||
## Calibre
|
||||
|
||||
Point Shelfmark at the `metadata.db` of a Calibre library (Calibre, Calibre-Web,
|
||||
Calibre-Web-Automated, anything that keeps the standard Calibre format) and it reads the
|
||||
database directly. No HTTP call, no API token, and nothing is ever written back.
|
||||
|
||||
1. Mount the library folder into the container read only, for example
|
||||
`/path/to/calibre-library:/calibre-library:ro`. Mount the folder rather than the file
|
||||
so the `-wal` and `-shm` sidecars are visible, otherwise a library that is being
|
||||
written to can read as out of date.
|
||||
2. In **Settings, General**, turn on **Mark books already in your Calibre library**.
|
||||
3. Leave **Calibre metadata.db path** at `/calibre-library/metadata.db` unless you mounted
|
||||
it somewhere else.
|
||||
4. Press **Test Calibre library**. It reports how many books it indexed.
|
||||
|
||||
A result that matches the library then carries an **In library** badge in the card, list
|
||||
and compact views, and in the details dialog.
|
||||
|
||||
## How a match is decided
|
||||
|
||||
In order of confidence:
|
||||
|
||||
1. An external id the metadata provider and the library agree on.
|
||||
2. An ISBN, compared in both ISBN-10 and ISBN-13 form.
|
||||
3. Fuzzy title tokens plus the author surname, the same rule the rest of the app uses for
|
||||
book matching.
|
||||
|
||||
## Behaviour worth knowing
|
||||
|
||||
- **It fails open.** If the database cannot be read, Shelfmark logs a warning, reuses the
|
||||
last successful read if it has one, and otherwise treats the book as not owned. A broken
|
||||
path degrades the badge, it never blocks a search.
|
||||
- **Results are cached** for ten minutes, and refreshed early when the database file
|
||||
changes, so a large library costs one read rather than one per search.
|
||||
- **Ebooks only.** A Calibre library holds ebooks, so the badge answers for ebooks. The
|
||||
provider interface in `shelfmark/core/library_providers/` takes more libraries: add a
|
||||
module with the `LibraryProvider` shape and list it in `all_providers()`. Nothing above
|
||||
that function knows which libraries exist.
|
||||
Reference in New Issue
Block a user