this updates the cron to avoid running the telemetry ping when
telemetry is disabled so that personally identifiable information
is not sent off to other parties (an IP address, an installation ID)
* Add BOOK_PER_FILE and BOOK_PER_FOLDER organization modes for library scanning
* Filter temp/partial files, support .ignore directories, and skip zero-byte files during scanning
* Replace event-based monitoring with WatchService, add pending deletion pool and move detection
* Fix folder-based audiobook grouping, improve series detection, and add tests
* Add unit tests for BookFileTransactionalHandler, BookFilePersistenceService, and LibraryFileEventProcessor
* overhaul OIDC: backend token exchange, security hardening, group mapping, session management
* add unit tests for OIDC overhaul (166 tests across 12 classes)
* rewrite unit tests for metadata and extractor packages (~450 tests)
* fix Kobo sync timeout with large magic shelves (#3172)
* fix: clean ISBN should allow ISBNs to have an X at the end
* hardcover mood filter overload for just list of tags
* When ISBN is given, fetch the book directly by the edition with that ISBN, and provide metadata from that edition
* chore: improved formatting
* PR improvements
* replaced CachedTag with GraphQL specific class
* moved hardcover cached tag to shared model
* fix(metadata): ensure EPUB version-aware metadata writing
EpubMetadataWriter unconditionally wrote EPUB3-only constructs into
all EPUB files regardless of version, producing invalid OPF documents
for EPUB3 files (e.g. opf:file-as/opf:role attributes on dc:creator)
and writing EPUB3-only elements into EPUB2 files.
Changes:
- createCreatorElement: EPUB3 uses <meta refines="#id"> for file-as/role;
EPUB2 uses opf: attributes on dc:creator
- addFolderContentsToZip: mimetype is now STORED (uncompressed) and
written as the first ZIP entry per EPUB spec
- replaceBelongsToCollection: EPUB3 uses belongs-to-collection with
refines; EPUB2 uses calibre:series/calibre:series_index convention
- addSubtitleToTitle: EPUB3 uses separate dc:title with title-type
refinement; EPUB2 stores subtitle via booklore:subtitle metadata only
- addBookloreMetadata/createBookloreMetaElement: EPUB3 uses property
attribute with prefix; EPUB2 uses name/content attribute form
- removeAllBookloreMetadata: now handles both EPUB3 property and EPUB2
name attributes
- cleanupCalibreArtifacts: preserves calibre:series and
calibre:series_index metas used for EPUB2 series
- organizeMetadataElements: correctly categorizes EPUB2-style series
and booklore metas into their respective buckets
- addBookloreMetadata: writes booklore:subtitle for round-trip fidelity
- Added isEpub3() helper method
Closes#2997
* fix(metadata): address review feedback for EPUB version-aware writing
- Only preserve calibre:series/calibre:series_index for EPUB2 in
cleanupCalibreArtifacts; EPUB3 files now properly remove stale entries
- Use isEpub3() helper in createCreatorElement instead of inline detection
- Add trim() to isEpub3() to handle whitespace in version attribute
- Log warning when mimetype file is missing from extracted EPUB
Restores and extends the EPUB metadata extraction work from PR #1879
with additional fixes for field mapping gaps found in the processor,
BookDrop finalization, and JSON sidecar.
EPUB Metadata Extractor (EpubMetadataExtractor.java)
- Restore Calibre user_metadata iteration that was regressed: now
iterates <calibre:user_metadata> elements and maps custom columns
via CALIBRE_FIELD_MAPPINGS
- Add #age_rating and #content_rating to CALIBRE_FIELD_MAPPINGS
so Calibre-managed rating/rating string fields are imported
- Add CALIBRE_IDENTIFIER_PREFIXES map for calibre: identifier
fallback (hardcover_book, lubimyczytac, ranobedb, etc.)
- Support both read paths: booklore: namespace (round-trip) and
Calibre user_metadata (first import from Calibre library)
- Validate age_rating values against VALID_AGE_RATINGS whitelist
EPUB Metadata Writer (EpubMetadataWriter.java)
- Write booklore:age_rating and booklore:content_rating to the
OPF so values survive a round-trip rescan
Metadata Change Detector (MetadataChangeDetector.java)
- Mark ageRating and contentRating as includedInFileWrite=true so
saving either field triggers a write-back to the EPUB file
EPUB Processor (EpubProcessor.java)
- Map extracted ageRating and contentRating from BookMetadata DTO
into BookMetadataEntity in setBookMetadata(); these two lines were
the silent reason values never reached the DB on library scan
BookDrop Service (BookDropService.java)
- Change processMovedFile() from REPLACE_ALL to REPLACE_WHEN_PROVIDED
when applying bookdrop review metadata after file extraction.
REPLACE_ALL unconditionally calls the setter even with null, wiping
ageRating/contentRating that EpubProcessor just extracted from the
file. REPLACE_WHEN_PROVIDED skips null fields and preserves the
file-extracted values when the bookdrop review form does not submit
those fields.
JSON Sidecar (SidecarIdentifiers.java, SidecarMetadataMapper.java)
- Add missing hardcoverBookId field to SidecarIdentifiers DTO
- Write hardcoverBookId in buildIdentifiers() (toSidecarMetadata path)
- Read hardcoverBookId in toBookMetadata() (sidecar import path)
All other identifiers were already present; hardcoverBookId was the
only one absent from all three locations simultaneously.
Tests (EpubMetadataExtractorTest.java)
- Add comprehensive test coverage for both extraction paths:
booklore: namespace (round-trip) and Calibre user_metadata/
identifier extraction including age_rating and content_rating
Expose coverUpdatedOn/audiobookCoverUpdatedOn from metadata for
cache busting, matching the webapp's approach. Replaces coverHash
fields which didn't change when thumbnails were regenerated.
Replace hasCover/hasAudiobookCover booleans with the actual
coverHash/audiobookCoverHash strings. The client uses these as
URL query params so cover updates bust the CachedNetworkImage cache.
Expose cover existence via bookCoverHash/audiobookCoverHash so the
mobile client can show generated placeholders instead of loading the
server's generic missing-cover image.