Skip to content

refactor(epub): migrate from documentnode to grimmory epub4j library and update related code#334

Merged
zachyale merged 17 commits intogrimmory-tools:developfrom
balazs-szucs:epub4j
Apr 10, 2026
Merged

refactor(epub): migrate from documentnode to grimmory epub4j library and update related code#334
zachyale merged 17 commits intogrimmory-tools:developfrom
balazs-szucs:epub4j

Conversation

@balazs-szucs
Copy link
Copy Markdown
Member

@balazs-szucs balazs-szucs commented Apr 1, 2026

Description

Linked Issue: Fixes #

Changes

This pull request modernizes EPUB handling in the codebase by switching from the legacy io.documentnode.epub4j library to the new org.grimmory.epub4j fork, and by removing the dependency on zip4j in favor of standard Java ZIP utilities. It also updates JVM and build configurations to enable Java preview features and native access, and improves cover extraction logic for EPUB files.

EPUB Library Migration and Refactoring:

  • Replaced all usages of io.documentnode.epub4j with org.grimmory.epub4j throughout the codebase, including imports and EPUB processing logic in EpubMetadataExtractor.java and EpubMetadataWriter.java.
  • Refactored EPUB cover extraction to use EpubContainer and CoverDetector from the new library, improving robustness and removing custom XML/ZIP heuristics.
  • Removed all usage of zip4j for ZIP file handling, replacing it with standard Java ZIP utilities and new helper methods for extracting and creating EPUB archives.

Dependency and Build Configuration Updates:

  • Updated Gradle dependencies: replaced io.documentnode:epub4j-core with org.grimmory:epub4j-core and added org.grimmory:epub4j-native; switched OpenAPI dependency to springdoc-openapi-starter-webmvc-ui.
  • Enabled Java preview features and native access in the Dockerfile, Gradle build, test, and bootRun tasks to support new language/library features.

Summary by CodeRabbit

  • Dependencies

    • Updated EPUB handling and native EPUB library usage.
  • Refactor

    • Overhauled EPUB parsing, cover detection, streaming and repack logic for safer, more reliable metadata and cover handling.
    • Replaced legacy CFI/XPointer converter with integrated CFI support and added a Jsoup-backed document navigator.
  • Chores

    • Enabled Java preview features for compile and runtime.
  • Tests

    • Updated tests to use real EPUB fixtures and the new EPUB/CFI implementations.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

Replaces io.documentnode:epub4j + zip4j EPUB handling with org.grimmory:epub4j, migrating parsing, cover detection, CFI conversion, container streaming, and EPUB repackaging; adds Jsoup-based DocumentNavigator and enables Java preview flags in the Gradle build.

Changes

Cohort / File(s) Summary
Build Configuration
booklore-api/build.gradle.kts
Enabled Java preview for compilation and runtime JVM args; switched EPUB deps to org.grimmory:epub4j and added epub4j-native (local useLocalLibs behavior preserved).
Cover & Metadata Extraction
booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
Rewrote cover extraction to use epub4j lazy reading + CoverDetector with layered fallbacks using EpubContainers; removed legacy heuristic/zip helpers and adjusted media-type checks and logging.
EPUB Writing / ZIP Repack
booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
Replaced zip4j extraction/repack with EpubContainer streaming to temp dir and manual EPUB creation via JDK ZipOutputStream, ensuring mimetype is STORED first with proper CRC/size; removed recursive add helper.
EPUB Reader & Streaming
booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
Switched to org.grimmory.epub4j (readEpubLazy, NativeArchive) for manifest/spine/TOC/cover and streaming; removed charset-retry and Apache ZipFile logic; extended content-type map.
Content Utilities
booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java
Updated to call EpubReader.readEpubLazy(path, ...) and to obtain resource bytes via stream writes instead of manual ZipFile handling.
CFI Removal & Migration
booklore-api/src/main/java/org/booklore/util/koreader/CfiConvertor.java
Deleted legacy CfiConvertor and nested result type; functionality migrated to org.grimmory.epub4j.cfi.CfiConverter.
CFI Service & Navigator
booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java, .../JsoupDocumentNavigator.java
Updated EpubCfiService to call CfiConverter APIs and added an ApiError check for mismatched spine indices; added JsoupDocumentNavigator implementing epub4j DocumentNavigator for CFI traversal.
Unit Tests — Reader / Content / CFI
booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java, .../EpubContentReaderTest.java, .../CfiConverterTest.java, .../EpubCfiServiceTest.java
Replaced mocked ZIP fixtures with real EPUBs via EpubWriter, updated imports/constructors to org.grimmory types and JsoupDocumentNavigator, and adjusted assertions (some checks relaxed; non-existent-file test now expects exception).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant EpubMetadataExtractor
    participant EpubReader as "EpubReader (epub4j)"
    participant CoverDetector
    participant EpubContainer
    participant Filesystem

    Client->>EpubMetadataExtractor: request cover bytes
    EpubMetadataExtractor->>EpubReader: readEpubLazy(path, "UTF-8")
    EpubReader->>CoverDetector: detectCoverImageWithMethod(book)
    alt cover found
        CoverDetector-->>EpubMetadataExtractor: resource bytes
    else fallback
        EpubMetadataExtractor->>EpubContainer: open(path)
        EpubContainer->>EpubMetadataExtractor: parseOpf()
        EpubMetadataExtractor->>EpubContainer: readBytes(resolvedHref)
        EpubContainer-->>EpubMetadataExtractor: bytes (if exists)
    end
    EpubMetadataExtractor->>Client: return cover bytes or empty
Loading
sequenceDiagram
    participant WriterAPI
    participant EpubContainer
    participant TempDir
    participant ZipOut as "ZipOutputStream (JDK)"
    participant Filesystem

    WriterAPI->>EpubContainer: open(sourceEpub)
    EpubContainer->>TempDir: listAllFiles() -> stream bytes per entry
    TempDir-->>WriterAPI: files written to temp dir
    WriterAPI->>ZipOut: create target zip
    ZipOut->>Filesystem: write mimetype (STORED entry with CRC/size)
    ZipOut->>TempDir: walk files -> add entries (exclude mimetype), write directories
    ZipOut->>Filesystem: finalize EPUB zip
    WriterAPI->>Filesystem: return new epub path
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • imajes
  • zachyale

Poem

🐇 I hopped through bytes and OPF tonight,

found covers hidden just out of sight.
Grimmory led me, ZipStream in paw,
I stitched up EPUBs without a flaw.
A tiny rabbit's cheer for code made bright 🥕📚

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the conventional commit format with type 'refactor' and a clear, descriptive subject about migrating from documentnode to grimmory epub4j library.
Description check ✅ Passed The PR description is complete with a detailed explanation of changes, covering EPUB library migration, dependency updates, and build configuration changes. The template structure is followed with sections for Description and Changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java (1)

247-254: Redundant condition in navHref detection.

Line 250 checks resource.getMediaType() != MediaTypes.NCX, but this is always true when resource.getMediaType() == MediaTypes.XHTML (line 248) since they are mutually exclusive media types.

         for (Resource resource : book.getResources().getAll()) {
             if (resource.getMediaType() == MediaTypes.XHTML
-                    && !spineHrefs.contains(resource.getHref())
-                    && resource.getMediaType() != MediaTypes.NCX) {
+                    && !spineHrefs.contains(resource.getHref())) {
                 navHref = rootPath + resource.getHref();
                 break;
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java`
around lines 247 - 254, The loop in EpubReaderService that sets navHref contains
a redundant check: when iterating resources from book.getResources().getAll()
you already test resource.getMediaType() == MediaTypes.XHTML, so the additional
resource.getMediaType() != MediaTypes.NCX is unnecessary; remove the redundant
NCX comparison and simplify the if to only check MediaTypes.XHTML and
!spineHrefs.contains(resource.getHref()), keeping assignment to navHref and the
break as-is. Ensure you update the conditional in the method where navHref is
computed (reference symbols: navHref, book.getResources().getAll(), Resource,
MediaTypes.XHTML, MediaTypes.NCX, spineHrefs) and run tests to confirm behavior
unchanged.
booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java (1)

22-26: Verify the null entry in MEDIA_TYPES is intentional.

Adding null to the MEDIA_TYPES list is unusual. If this is required by the epub4j library to handle resources with unknown/null media types, consider adding a brief comment explaining the purpose.

     private static final List<MediaType> MEDIA_TYPES = new ArrayList<>();
     static {
         MEDIA_TYPES.addAll(Arrays.asList(MediaTypes.mediaTypes));
-        MEDIA_TYPES.add(null);
+        MEDIA_TYPES.add(null); // Include null to handle resources with unspecified media types
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java`
around lines 22 - 26, The MEDIA_TYPES list in EpubContentReader currently
appends a null entry (MEDIA_TYPES and MediaTypes.mediaTypes); verify whether
epub4j requires a null media type sentinel — if it is required keep the null but
add a clear inline comment explaining why (e.g., "null used as sentinel for
unknown/unspecified media types required by epub4j resource lookup") and ensure
any code that iterates MEDIA_TYPES handles null safely; if it is not required,
remove the MEDIA_TYPES.add(null) line and update any callers/tests that relied
on the null to use explicit handling for unknown media types instead.
Dockerfile (1)

64-71: Consider removing redundant JVM flag from CMD.

The --enable-native-access=ALL-UNNAMED flag in CMD (line 107) is now redundant since it's already set in JAVA_TOOL_OPTIONS (line 70). The --enable-preview flag is also missing from CMD but is covered by JAVA_TOOL_OPTIONS.

You could simplify the CMD to:

CMD ["java", "-jar", "/app/app.jar"]

Or, for consistency/explicitness, keep CMD with both flags. The current configuration works but has asymmetric redundancy.

Also applies to: 107-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 64 - 71, The Dockerfile sets JVM flags in the ENV
variable JAVA_TOOL_OPTIONS (including --enable-native-access=ALL-UNNAMED and
--enable-preview) but the CMD also redundantly repeats --enable-native-access
and omits --enable-preview; update the CMD invocation (the java -jar startup
command) to either remove redundant flags and simply use CMD
["java","-jar","/app/app.jar"] or make it explicit and consistent by including
both --enable-native-access=ALL-UNNAMED and --enable-preview in CMD to match
ENV, ensuring no asymmetric duplication between ENV JAVA_TOOL_OPTIONS and the
CMD startup command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@booklore-api/build.gradle.kts`:
- Around line 94-95: The build fails because ArchiveService.java still imports
and uses nightcompress APIs (imports com.github.gotson.nightcompress.Archive,
ArchiveEntry, LibArchiveException and calls Archive.isAvailable(),
Archive.getEntries(), Archive.getInputStream()) while the dependency was
removed; fix by either restoring the nightcompress dependency in
build.gradle.kts or migrating ArchiveService.java to junrar: replace
nightcompress imports/usages with equivalent junrar classes and APIs (e.g., open
archive streams, iterate entries, extract InputStreams and handle exceptions)
and update all call sites in ArchiveService.java (the imports at the top and
calls around lines where Archive.isAvailable(), Archive.getEntries(),
Archive.getInputStream() are used) to the junrar API ensuring exception handling
and resource closing match junrar patterns.

In
`@booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java`:
- Around line 402-407: streamEntryFromZip currently calls NativeArchive.open and
archive.readEntry which buffers the entire entry into a byte[]; change it to
stream the entry directly to the provided OutputStream (similar to
ArchiveService.transferEntryTo) by opening an InputStream for the entry from
NativeArchive (e.g., archive.openEntryStream / getInputStream for entryName) and
calling inputStream.transferTo(outputStream) or using a buffered copy loop,
ensuring the entry InputStream is closed in a try-with-resources and preserving
the existing exception signature of streamEntryFromZip.

---

Nitpick comments:
In
`@booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java`:
- Around line 247-254: The loop in EpubReaderService that sets navHref contains
a redundant check: when iterating resources from book.getResources().getAll()
you already test resource.getMediaType() == MediaTypes.XHTML, so the additional
resource.getMediaType() != MediaTypes.NCX is unnecessary; remove the redundant
NCX comparison and simplify the if to only check MediaTypes.XHTML and
!spineHrefs.contains(resource.getHref()), keeping assignment to navHref and the
break as-is. Ensure you update the conditional in the method where navHref is
computed (reference symbols: navHref, book.getResources().getAll(), Resource,
MediaTypes.XHTML, MediaTypes.NCX, spineHrefs) and run tests to confirm behavior
unchanged.

In `@booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java`:
- Around line 22-26: The MEDIA_TYPES list in EpubContentReader currently appends
a null entry (MEDIA_TYPES and MediaTypes.mediaTypes); verify whether epub4j
requires a null media type sentinel — if it is required keep the null but add a
clear inline comment explaining why (e.g., "null used as sentinel for
unknown/unspecified media types required by epub4j resource lookup") and ensure
any code that iterates MEDIA_TYPES handles null safely; if it is not required,
remove the MEDIA_TYPES.add(null) line and update any callers/tests that relied
on the null to use explicit handling for unknown media types instead.

In `@Dockerfile`:
- Around line 64-71: The Dockerfile sets JVM flags in the ENV variable
JAVA_TOOL_OPTIONS (including --enable-native-access=ALL-UNNAMED and
--enable-preview) but the CMD also redundantly repeats --enable-native-access
and omits --enable-preview; update the CMD invocation (the java -jar startup
command) to either remove redundant flags and simply use CMD
["java","-jar","/app/app.jar"] or make it explicit and consistent by including
both --enable-native-access=ALL-UNNAMED and --enable-preview in CMD to match
ENV, ensuring no asymmetric duplication between ENV JAVA_TOOL_OPTIONS and the
CMD startup command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5c79d929-e00e-4e1d-b1d3-179188e15325

📥 Commits

Reviewing files that changed from the base of the PR and between bb73dc9 and 0470efa.

📒 Files selected for processing (9)
  • Dockerfile
  • booklore-api/build.gradle.kts
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
  • booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java
  • booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java
  • booklore-api/src/test/java/org/booklore/util/epub/EpubContentReaderTest.java
  • booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java
  • booklore-api/src/test/java/org/booklore/util/epub/EpubContentReaderTest.java
  • booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java
  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
booklore-api/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required

Files:

  • booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java
  • booklore-api/src/test/java/org/booklore/util/epub/EpubContentReaderTest.java
  • booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java
🧠 Learnings (6)
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/test/**/*.java : Prefer focused unit tests; use `SpringBootTest` only when the Spring context is required

Applied to files:

  • booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java
  • booklore-api/src/test/java/org/booklore/util/epub/EpubContentReaderTest.java
  • booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java
📚 Learning: 2026-03-24T18:46:47.249Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:188-199
Timestamp: 2026-03-24T18:46:47.249Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a `hardcoverBookId` is stored in book metadata but is malformed (non-numeric), the preferred behavior is to return null and skip the sync rather than falling back to ISBN-based lookup. The reasoning is that an explicitly set bookId represents deliberate user intent, and silently resolving via ISBN could sync progress to a different book than intended.

Applied to files:

  • booklore-api/src/test/java/org/booklore/util/epub/EpubContentReaderTest.java
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/**/*Entity.java : Keep JPA entities on the `*Entity` suffix

Applied to files:

  • booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/**/*.java : Use MapStruct for entity/DTO mapping

Applied to files:

  • booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java
  • booklore-api/build.gradle.kts
📚 Learning: 2026-03-31T06:22:25.311Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 113
File: booklore-api/build.gradle.kts:89-90
Timestamp: 2026-03-31T06:22:25.311Z
Learning: When reviewing build logic or CI checks that call the JitPack build status API, do not treat a response like {"status":"none"} as evidence that a dependency is unavailable or that a build has failed. JitPack typically builds packages on-demand the first time they’re requested (e.g., via Gradle); "none" means the package isn’t pre-built/cached yet. Only raise a build failure concern when the status explicitly indicates an error/failure (e.g., failed/cancelled), or when dependent resolution actually fails.

Applied to files:

  • booklore-api/build.gradle.kts
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/**/*.java : Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce `Autowired` field injection

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
🔇 Additional comments (13)
booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java (1)

31-56: LGTM!

The migration to org.grimmory.epub4j is clean. The method correctly validates spine bounds and resource existence before accessing data.

booklore-api/src/test/java/org/booklore/util/epub/EpubContentReaderTest.java (1)

1-332: LGTM!

The test migration is well done:

  • Uses real EPUB generation via EpubWriter instead of mocks
  • Properly tests the behavior change where getSpineSize now throws EpubReadException for non-existent files
  • Follows the coding guideline of using focused unit tests without @SpringBootTest

Based on learnings: "Prefer focused unit tests; use SpringBootTest only when the Spring context is required"

booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java (1)

3-6: LGTM!

Import-only change to align with the epub4j library migration. No test logic was modified.

booklore-api/build.gradle.kts (2)

29-31: LGTM on preview feature configuration.

The --enable-preview flag is consistently applied to:

  • All JavaCompile tasks (line 30)
  • Test execution (line 147)
  • bootRun (line 175)

This aligns with the Dockerfile changes and supports Java preview features like guarded pattern matching (as shown in AppSettingService.java).

Also applies to: 147-147, 175-175


88-89: The code already uses org.grimmory.epub4j extensively; verify the build succeeds to confirm dependency availability.

The codebase imports from org.grimmory.epub4j packages across multiple files (EpubContentReader, EpubReaderService, EpubMetadataWriter, EpubMetadataExtractor, and test files). Since the project uses these classes, the dependencies org.grimmory:epub4j-core:0.4.0 and org.grimmory:epub4j-native:0.4.0 must be resolvable for the build to succeed. The org.grimmory group format (rather than the typical com.github.* format for JitPack) suggests a non-standard repository setup. Ensure the build succeeds to confirm these versions are available.

booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java (2)

524-535: LGTM! Good security implementation for ZIP extraction.

The path traversal check at lines 528-530 correctly prevents zip-slip attacks by validating that extracted paths stay within the target directory using normalize() and startsWith().


537-579: LGTM! EPUB ZIP creation follows spec requirements.

The implementation correctly:

  • Places mimetype as the first uncompressed (STORED) entry per EPUB specification
  • Computes and sets CRC32, size, and compressedSize for the STORED entry
  • Handles directory entries with trailing slashes
  • Converts path separators to forward slashes for cross-platform ZIP compatibility
booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java (1)

223-231: LGTM on the core parsing migration.

The migration to EpubReader.readEpubLazy() and the new mapping methods (mapBookToInfo, mapManifest, mapSpine, mapMetadata, mapToc) are well-structured and correctly extract EPUB metadata using the new library.

Also applies to: 233-271

booklore-api/src/test/java/org/booklore/service/reader/EpubReaderServiceTest.java (2)

60-90: LGTM! Excellent test fixture generation.

The writeTestEpub() method creates a realistic EPUB with:

  • Proper metadata (title, author, language)
  • Multiple chapters in the spine
  • A stylesheet resource
  • A cover image

This approach is superior to mocking ZIP structures as it validates the actual integration with the epub4j library.

Based on learnings: "Prefer focused unit tests; use SpringBootTest only when the Spring context is required" - this test correctly uses Mockito without requiring Spring context.


92-341: LGTM!

The test suite comprehensively covers:

  • Book info retrieval and caching
  • File streaming with path validation
  • Content type detection
  • File size retrieval
  • Spine ordering
  • TOC parsing
  • Manifest properties including cover detection
  • Security (path traversal blocking)
booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java (3)

119-120: Unable to verify review comment — persistent infrastructure errors prevent access to the repository. Additionally, the file path (booklore-api/src/...) does not match the stated repository under review (grimmory-tools/grimmory). Manual verification is required to confirm:

  • The actual code at lines 119-120 and 172-173
  • Whether EpubContainers.open() truly bypasses EpubReader/ResourcesLoader protections
  • The specific behavior of ZipEpubContainer and its eager-loading implications
  • Whether the major issue classification is justified

129-130: Replace URLDecoder with UriUtils.decode() for proper URI component decoding.

URLDecoder is designed for HTML form decoding (application/x-www-form-urlencoded) and converts + to space. This breaks archive entry lookups for valid assets like Images/front+01.jpg, which would be resolved as Images/front 01.jpg. Use UriUtils.decode() instead to properly decode URI components per RFC 3986 without the + conversion.

🩹 Suggested fix
-                    String href = URLDecoder.decode(item.getAttribute("href"), StandardCharsets.UTF_8);
+                    String href = UriUtils.decode(item.getAttribute("href"), StandardCharsets.UTF_8.name());
-                        String decodedHref = URLDecoder.decode(href, StandardCharsets.UTF_8);
+                        String decodedHref = UriUtils.decode(href, StandardCharsets.UTF_8.name());
import org.springframework.web.util.UriUtils;

Also applies to lines 146-147.


123-125: Use a namespace-aware manifest lookup here.

parseOpf() is backed by a namespace-aware DOM builder, and later in this class you already query metadata with getElementsByTagNameNS("*", "metadata"). Using getElementsByTagName("item") here makes the cover fallback depend on the OPF manifest using an unprefixed tag name; getElementsByTagNameNS("*", "item") is the safer match. This is an inference from the DOM contract plus epub4j's parser configuration.

🩹 Suggested change
-            NodeList items = opf.getElementsByTagName("item");
+            NodeList items = opf.getElementsByTagNameNS("*", "item");

@balazs-szucs balazs-szucs linked an issue Apr 2, 2026 that may be closed by this pull request
1 task
@balazs-szucs balazs-szucs marked this pull request as ready for review April 2, 2026 21:48
@coderabbitai coderabbitai bot requested a review from imajes April 2, 2026 21:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java (1)

136-470: Optional: extract converter factory helper to reduce repeated setup.

new CfiConverter(new JsoupDocumentNavigator(...), spineIndex) is repeated many times; a tiny helper would improve readability and reduce churn if constructor wiring changes again.

♻️ Proposed test refactor
 class CfiConverterTest {

     private Document simpleDocument;
     private Document complexDocument;
+
+    private CfiConverter converter(Document doc, int spineIndex) {
+        return new CfiConverter(new JsoupDocumentNavigator(doc), spineIndex);
+    }

@@
-            CfiConverter converter = new CfiConverter(new JsoupDocumentNavigator(simpleDocument), 0);
+            CfiConverter converter = converter(simpleDocument, 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java`
around lines 136 - 470, Tests repeat constructing converters via new
CfiConverter(new JsoupDocumentNavigator(...), spineIndex); extract a small
private factory helper in the test class (e.g., a method named
createConverter(Document doc, int spineIndex) or createConverter(int spineIndex)
that returns CfiConverter) and update all tests to call that helper; reference
CfiConverter and JsoupDocumentNavigator when locating the repeated constructor
calls and ensure the helper centralizes creation to simplify future changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java`:
- Around line 52-55: convertXPointerRangeToCfi currently only extracts the spine
index from startXPointer (via CfiConverter.extractSpineIndex) and uses that to
build a converter, which allows silently creating an invalid range if
endXPointer references a different spine; update convertXPointerRangeToCfi to
also extract the spine index from endXPointer, compare the two indices, and if
they differ throw an appropriate runtime exception (e.g.,
IllegalArgumentException) with a clear message; only proceed to call
createConverter(epubFile, spineIndex) and converter.xPointerToCfi(...) when the
two extracted spine indices match.

---

Nitpick comments:
In `@booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java`:
- Around line 136-470: Tests repeat constructing converters via new
CfiConverter(new JsoupDocumentNavigator(...), spineIndex); extract a small
private factory helper in the test class (e.g., a method named
createConverter(Document doc, int spineIndex) or createConverter(int spineIndex)
that returns CfiConverter) and update all tests to call that helper; reference
CfiConverter and JsoupDocumentNavigator when locating the repeated constructor
calls and ensure the helper centralizes creation to simplify future changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f5446f7-9ee1-493f-8f60-4dc86ff9a787

📥 Commits

Reviewing files that changed from the base of the PR and between 0470efa and 6deb4e1.

📒 Files selected for processing (7)
  • booklore-api/build.gradle.kts
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
  • booklore-api/src/main/java/org/booklore/util/koreader/CfiConvertor.java
  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
  • booklore-api/src/main/java/org/booklore/util/koreader/JsoupDocumentNavigator.java
  • booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java
  • booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java
💤 Files with no reviewable changes (1)
  • booklore-api/src/main/java/org/booklore/util/koreader/CfiConvertor.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • booklore-api/src/test/java/org/booklore/util/koreader/EpubCfiServiceTest.java
  • booklore-api/build.gradle.kts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (actions)
🧰 Additional context used
📓 Path-based instructions (2)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java
  • booklore-api/src/main/java/org/booklore/util/koreader/JsoupDocumentNavigator.java
  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
booklore-api/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required

Files:

  • booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, epub processing uses `org.grimmory.epub4j` from https://github.com/grimmory-tools/epub4j. This is a grimmory-maintained fork and is fundamentally different from `io.documentnode.epub4j`. Do NOT generalize API knowledge, behavior, or limitations of `io.documentnode.epub4j` to the grimmory fork. Before raising review comments on epub-related file processing code, verify the current API and capabilities in the grimmory-tools/epub4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/build.gradle.kts:0-0
Timestamp: 2026-04-02T09:12:48.158Z
Learning: In `grimmory-tools/grimmory`, nightcompress (`com.github.gotson.nightcompress:nightcompress`) is the preferred library for archive/unarchiving operations (including RAR and other formats). Flag any PR changes that remove or replace nightcompress with an alternative (e.g., junrar, commons-compress, JNI-based libs, or standard Java ZipInputStream/ZipFile) for unarchiving purposes. Nightcompress is considered better than JNI Java alternatives and generally better than standard Java unzip. Exceptions are acceptable when nightcompress cannot handle a specific format, but such trade-offs should be made explicit and confirmed by the maintainer.
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, epub processing uses `org.grimmory.epub4j` from https://github.com/grimmory-tools/epub4j. This is a grimmory-maintained fork and is fundamentally different from `io.documentnode.epub4j`. Do NOT generalize API knowledge, behavior, or limitations of `io.documentnode.epub4j` to the grimmory fork. Before raising review comments on epub-related file processing code, verify the current API and capabilities in the grimmory-tools/epub4j repository.

Applied to files:

  • booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java
  • booklore-api/src/main/java/org/booklore/util/koreader/JsoupDocumentNavigator.java
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.

Applied to files:

  • booklore-api/src/test/java/org/booklore/util/koreader/CfiConverterTest.java
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: When reviewing epub-related file processing code in this project, remember that grimmory-tools/grimmory uses the `org.grimmory.epub4j` library (from grimmory-tools/epub4j) and that it is a grimmory-maintained fork fundamentally different from `io.documentnode.epub4j`. Do not assume the same API behavior, limitations, or semantics as `io.documentnode.epub4j`. Before flagging issues or proposing changes, verify the current API/capabilities in the grimmory-tools/epub4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
📚 Learning: 2026-03-24T18:46:47.249Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:188-199
Timestamp: 2026-03-24T18:46:47.249Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a `hardcoverBookId` is stored in book metadata but is malformed (non-numeric), the preferred behavior is to return null and skip the sync rather than falling back to ISBN-based lookup. The reasoning is that an explicitly set bookId represents deliberate user intent, and silently resolving via ISBN could sync progress to a different book than intended.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
🔇 Additional comments (7)
booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java (5)

3-11: LGTM!

Clean import migration to the grimmory epub4j library with appropriate types for the new cover detection and container APIs.


107-154: LGTM!

Well-structured fallback logic with proper resource management via try-with-resources. Good defensive programming with container.exists() checks before reading, and comprehensive heuristics (OPF properties → id/href matching → filename scanning).


161-162: LGTM!

Proper use of try-with-resources ensures the EpubContainer is closed after metadata extraction.


561-583: LGTM!

Correct path resolution logic that properly handles absolute paths, normalizes .. and . components, and avoids filesystem dependencies by operating purely on strings. The use of LinkedList is appropriate for the removeLast() operation.


549-549: No action needed. The MediaType.name() method is the correct API for the grimmory epub4j fork (it's a Java record accessor) and correctly returns a MIME type string starting with "image" for image resources.

booklore-api/src/main/java/org/booklore/util/koreader/JsoupDocumentNavigator.java (1)

25-72: Good adapter implementation for epub4j CFI navigation.

DocumentNavigator methods consistently return Jsoup-backed nodes/elements and keep traversal behavior coherent for CFI conversion paths.

booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java (1)

32-108: Migration to grimmory CFI types is clean and consistent.

The service-level API/type migration to CfiConverter / XPointerResult is consistently applied across conversion and validation methods.

@coderabbitai coderabbitai bot requested a review from zachyale April 3, 2026 19:19
# Conflicts:
#	booklore-api/build.gradle.kts
#	booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java (1)

33-35: Route all spine-index parsing through the extractSpineIndex(...) wrapper method.

Lines 34, 44, 80, and 91 directly call CfiConverter.extractSpineIndex(...), while the local wrapper method at line 100 already exists. Using the wrapper everywhere would centralize this dependency and make it easy to add consistent error handling across all entry points, aligning with the project's preference for ApiError-based exceptions.

Suggested refactor
 public XPointerResult convertCfiToXPointer(File epubFile, String cfi) {
-    int spineIndex = CfiConverter.extractSpineIndex(cfi);
+    int spineIndex = extractSpineIndex(cfi);
     CfiConverter converter = createConverter(epubFile, spineIndex);

 public String convertXPointerToCfi(File epubFile, String xpointer) {
-    int spineIndex = CfiConverter.extractSpineIndex(xpointer);
+    int spineIndex = extractSpineIndex(xpointer);
     CfiConverter converter = createConverter(epubFile, spineIndex);

 public boolean validateCfi(File epubFile, String cfi) {
     try {
-        int spineIndex = CfiConverter.extractSpineIndex(cfi);
+        int spineIndex = extractSpineIndex(cfi);
         CfiConverter converter = createConverter(epubFile, spineIndex);

 public boolean validateXPointer(File epubFile, String xpointer) {
     try {
-        int spineIndex = CfiConverter.extractSpineIndex(xpointer);
+        int spineIndex = extractSpineIndex(xpointer);
         CfiConverter converter = createConverter(epubFile, spineIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java`
around lines 33 - 35, Replace every direct call to
CfiConverter.extractSpineIndex(...) in this class with the class's existing
local wrapper method (the extractSpineIndex wrapper defined near line 100) so
that convertCfiToXPointer and the other methods currently calling
CfiConverter.extractSpineIndex (the calls at lines you flagged) route through
that wrapper; update createConverter and any other places using the static call
to use the wrapper and ensure the wrapper throws/returns ApiError-style
exceptions consistently for invalid input.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java`:
- Around line 33-35: Replace every direct call to
CfiConverter.extractSpineIndex(...) in this class with the class's existing
local wrapper method (the extractSpineIndex wrapper defined near line 100) so
that convertCfiToXPointer and the other methods currently calling
CfiConverter.extractSpineIndex (the calls at lines you flagged) route through
that wrapper; update createConverter and any other places using the static call
to use the wrapper and ensure the wrapper throws/returns ApiError-style
exceptions consistently for invalid input.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a9440ab7-7c27-4f61-b8ea-6f49e6aeb659

📥 Commits

Reviewing files that changed from the base of the PR and between 0896c7c and b2169d8.

📒 Files selected for processing (2)
  • booklore-api/build.gradle.kts
  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • booklore-api/build.gradle.kts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
🧠 Learnings (7)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, epub processing uses `org.grimmory.epub4j` from https://github.com/grimmory-tools/epub4j. This is a grimmory-maintained fork and is fundamentally different from `io.documentnode.epub4j`. Do NOT generalize API knowledge, behavior, or limitations of `io.documentnode.epub4j` to the grimmory fork. Before raising review comments on epub-related file processing code, verify the current API and capabilities in the grimmory-tools/epub4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/build.gradle.kts:0-0
Timestamp: 2026-04-02T09:12:48.158Z
Learning: In `grimmory-tools/grimmory`, nightcompress (`com.github.gotson.nightcompress:nightcompress`) is the preferred library for archive/unarchiving operations (including RAR and other formats). Flag any PR changes that remove or replace nightcompress with an alternative (e.g., junrar, commons-compress, JNI-based libs, or standard Java ZipInputStream/ZipFile) for unarchiving purposes. Nightcompress is considered better than JNI Java alternatives and generally better than standard Java unzip. Exceptions are acceptable when nightcompress cannot handle a specific format, but such trade-offs should be made explicit and confirmed by the maintainer.
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: When reviewing epub-related file processing code in this project, remember that grimmory-tools/grimmory uses the `org.grimmory.epub4j` library (from grimmory-tools/epub4j) and that it is a grimmory-maintained fork fundamentally different from `io.documentnode.epub4j`. Do not assume the same API behavior, limitations, or semantics as `io.documentnode.epub4j`. Before flagging issues or proposing changes, verify the current API/capabilities in the grimmory-tools/epub4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-04-03T09:45:33.392Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java:53-62
Timestamp: 2026-04-03T09:45:33.392Z
Learning: In grimmory-tools/grimmory, always prefer throwing errors using `ApiError` (e.g., `ApiError.INVALID_INPUT.createException("...")`) instead of generic Java exceptions like `IllegalArgumentException` or `RuntimeException`. When suggesting error handling in code reviews for this project, always recommend `ApiError`-based exceptions over raw Java exceptions.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-03-24T18:46:47.249Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:188-199
Timestamp: 2026-03-24T18:46:47.249Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a `hardcoverBookId` is stored in book metadata but is malformed (non-numeric), the preferred behavior is to return null and skip the sync rather than falling back to ISBN-based lookup. The reasoning is that an explicitly set bookId represents deliberate user intent, and silently resolving via ISBN could sync progress to a different book than intended.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-03-24T18:58:08.199Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:291-294
Timestamp: 2026-03-24T18:58:08.199Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a Hardcover edition has no page count, falling back to the book-level page count (book.pages) is the preferred behavior. Skipping the sync entirely in this case is considered worse than using a slightly approximate total, because the page count is only a proxy for converting progress % to page numbers for the API. The Hardcover API allows setting page-based progress even when the edition lacks a page count, and progress displays in at least some UI views (e.g., the book detail dropdown).

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
🔇 Additional comments (1)
booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java (1)

53-62: Nice early rejection of mixed-spine ranges.

Failing fast before xPointerToCfi(...) sees a cross-spine range is the right safeguard here, and the ApiError.INVALID_INPUT path matches the project's preferred input-error handling.

Based on learnings, "In grimmory-tools/grimmory, always prefer throwing errors using ApiError (e.g., ApiError.INVALID_INPUT.createException("...")) instead of generic Java exceptions like IllegalArgumentException or RuntimeException."

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java (1)

75-80: ⚠️ Potential issue | 🟠 Major

Use the container-reported OPF path instead of scanning for the first .opf.

findOpfFile() selects the first .opf file encountered during directory traversal rather than using the canonical package path from container.xml. In archives with multiple .opf files, this can cause metadata edits to be applied to the wrong package while applyCoverImageToEpub() later resolves cover paths relative to the true rootfile, creating internal EPUB inconsistencies. EpubContainer exposes the authoritative OPF path via getOpfName(), which is already used elsewhere in the codebase.

Refactor extractZipToDirectory() to return the OPF path by calling container.getOpfName() before the container closes, eliminating the need for findOpfFile() at both call sites (lines 75–80 and 364–369).

Suggested implementation
-    private void extractZipToDirectory(File zipSource, Path targetDir) throws IOException {
+    private Path extractZipToDirectory(File zipSource, Path targetDir) throws IOException {
         try (EpubContainer container = EpubContainers.open(zipSource.toPath())) {
             for (String name : container.listAllFiles()) {
                 Path entryPath = targetDir.resolve(name).normalize();
                 if (!entryPath.startsWith(targetDir)) {
                     throw new IOException("ZIP entry outside target directory: " + name);
                 }
                 Files.createDirectories(entryPath.getParent());
                 try (OutputStream out = Files.newOutputStream(entryPath)) {
                     container.streamTo(name, out);
                 }
             }
+
+            Path opfPath = targetDir.resolve(container.getOpfName()).normalize();
+            if (!opfPath.startsWith(targetDir) || !Files.isRegularFile(opfPath)) {
+                throw new IOException("Invalid OPF path: " + container.getOpfName());
+            }
+            return opfPath;
         }
     }

Update call sites:

-            extractZipToDirectory(epubFile, tempDir);
-            File opfFile = findOpfFile(tempDir.toFile());
-            if (opfFile == null) {
-                log.warn("Could not locate OPF file in EPUB");
-                return;
-            }
+            File opfFile = extractZipToDirectory(epubFile, tempDir).toFile();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java`
around lines 75 - 80, Refactor extractZipToDirectory to return the authoritative
OPF path (String) by opening the EPUB container inside extractZipToDirectory,
calling container.getOpfName() before closing the container, and returning that
path along with extracting files; then update callers (the two sites that
currently call extractZipToDirectory and then findOpfFile) to accept the
returned opfPath and use it instead of calling findOpfFile(), and remove the
now-unnecessary findOpfFile usage; ensure applyCoverImageToEpub and any
metadata-writing logic resolve paths relative to the returned opfPath/rootfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java`:
- Around line 543-558: The code currently logs a warning when the EPUB
`mimetype` file is missing; instead, in EpubMetadataWriter (the method that
opens the ZipOutputStream `zos` and reads `Path mimetypeFile`) make this a hard
failure by throwing an IOException with a clear message if
Files.exists(mimetypeFile) is false, and perform that existence check before
writing any entries so the archive is not created; preserve the existing logic
that writes the mimetype as an uncompressed STORED ZipEntry with correct size,
crc and name "mimetype" when present, but abort by throwing the IOException (no
warning-only) if the file is absent.

---

Outside diff comments:
In
`@booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java`:
- Around line 75-80: Refactor extractZipToDirectory to return the authoritative
OPF path (String) by opening the EPUB container inside extractZipToDirectory,
calling container.getOpfName() before closing the container, and returning that
path along with extracting files; then update callers (the two sites that
currently call extractZipToDirectory and then findOpfFile) to accept the
returned opfPath and use it instead of calling findOpfFile(), and remove the
now-unnecessary findOpfFile usage; ensure applyCoverImageToEpub and any
metadata-writing logic resolve paths relative to the returned opfPath/rootfile.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9beb927-c4f1-49ce-ac8a-77fbfde64107

📥 Commits

Reviewing files that changed from the base of the PR and between 60634cb and 835a922.

📒 Files selected for processing (5)
  • booklore-api/build.gradle.kts
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
  • booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • booklore-api/src/main/java/org/booklore/util/epub/EpubContentReader.java
  • booklore-api/build.gradle.kts
  • booklore-api/src/main/java/org/booklore/service/metadata/extractor/EpubMetadataExtractor.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
🧠 Learnings (13)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, epub processing uses `org.grimmory.epub4j` from https://github.com/grimmory-tools/epub4j. This is a grimmory-maintained fork and is fundamentally different from `io.documentnode.epub4j`. Do NOT generalize API knowledge, behavior, or limitations of `io.documentnode.epub4j` to the grimmory fork. Before raising review comments on epub-related file processing code, verify the current API and capabilities in the grimmory-tools/epub4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/build.gradle.kts:0-0
Timestamp: 2026-04-02T09:12:50.790Z
Learning: In `grimmory-tools/grimmory`, nightcompress (`com.github.gotson.nightcompress:nightcompress`) is the preferred library for archive/unarchiving operations (including RAR and other formats). Flag any PR changes that remove or replace nightcompress with an alternative (e.g., junrar, commons-compress, JNI-based libs, or standard Java ZipInputStream/ZipFile) for unarchiving purposes. Nightcompress is considered better than JNI Java alternatives and generally better than standard Java unzip. Exceptions are acceptable when nightcompress cannot handle a specific format, but such trade-offs should be made explicit and confirmed by the maintainer.
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: When reviewing epub-related file processing code in this project, remember that grimmory-tools/grimmory uses the `org.grimmory.epub4j` library (from grimmory-tools/epub4j) and that it is a grimmory-maintained fork fundamentally different from `io.documentnode.epub4j`. Do not assume the same API behavior, limitations, or semantics as `io.documentnode.epub4j`. Before flagging issues or proposing changes, verify the current API/capabilities in the grimmory-tools/epub4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-04-02T09:25:48.330Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
📚 Learning: 2026-04-02T09:06:44.150Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/build.gradle.kts:94-95
Timestamp: 2026-04-02T09:06:44.150Z
Learning: In `grimmory-tools/grimmory`, the project is committed to `com.github.gotson.nightcompress:nightcompress` for archive/RAR support. Do not suggest replacing it with junrar or any other archive library. `ArchiveService.java` uses nightcompress APIs (`Archive`, `ArchiveEntry`, `LibArchiveException`).

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-04-02T09:12:50.790Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/build.gradle.kts:0-0
Timestamp: 2026-04-02T09:12:50.790Z
Learning: In `grimmory-tools/grimmory`, nightcompress (`com.github.gotson.nightcompress:nightcompress`) is the preferred library for archive/unarchiving operations (including RAR and other formats). Flag any PR changes that remove or replace nightcompress with an alternative (e.g., junrar, commons-compress, JNI-based libs, or standard Java ZipInputStream/ZipFile) for unarchiving purposes. Nightcompress is considered better than JNI Java alternatives and generally better than standard Java unzip. Exceptions are acceptable when nightcompress cannot handle a specific format, but such trade-offs should be made explicit and confirmed by the maintainer.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/metadata/writer/EpubMetadataWriter.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: When reviewing code in grimmory-tools/grimmory that processes EPUB or PDF files (e.g., readers/services), first verify the current status and APIs of the corresponding upstream grimmory replacement libraries: `grimmory-tools/epub4j` for EPUB and `grimmory-tools/PDFium4j` for PDF. If an issue relates to streaming/buffering behavior or mismatched API capabilities/surface, prefer filing or fixing it in the relevant upstream repo (epub4j/PDFium4j) rather than changing grimmory itself, unless the upstream integration is confirmed to be correct and the defect is in grimmory.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-04-02T09:25:48.330Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-03-24T18:46:47.249Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:188-199
Timestamp: 2026-03-24T18:46:47.249Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a `hardcoverBookId` is stored in book metadata but is malformed (non-numeric), the preferred behavior is to return null and skip the sync rather than falling back to ISBN-based lookup. The reasoning is that an explicitly set bookId represents deliberate user intent, and silently resolving via ISBN could sync progress to a different book than intended.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-03-24T18:58:08.199Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:291-294
Timestamp: 2026-03-24T18:58:08.199Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a Hardcover edition has no page count, falling back to the book-level page count (book.pages) is the preferred behavior. Skipping the sync entirely in this case is considered worse than using a slightly approximate total, because the page count is only a proxy for converting progress % to page numbers for the API. The Hardcover API allows setting page-based progress even when the edition lacks a page count, and progress displays in at least some UI views (e.g., the book detail dropdown).

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-04-01T17:50:06.817Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 247
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:156-159
Timestamp: 2026-04-01T17:50:06.817Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), the Hardcover API already returns `user_book_reads` in a consistent, ordered fashion (ascending by recency), so `getLast()` reliably retrieves the most recent read entry. Adding an explicit `order_by` to the `user_book_reads` query is unnecessary.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-04-04T15:36:56.558Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: booklore-api/src/main/java/org/booklore/app/service/AppBookService.java:357-359
Timestamp: 2026-04-04T15:36:56.558Z
Learning: In `booklore-api/src/main/java/org/booklore/app/service/AppBookService.java`, `shelfId` and `magicShelfId` are mutually exclusive navigation contexts in `getFilterOptions`. A request will never supply both at the same time, so the `else if (shelfId != null)` branch after the `magicBookIds != null` check is intentional and correct — there is no missing shelf-∩-magicShelf intersection case. Do not flag this pattern as a bug.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-03-25T19:09:09.638Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 189
File: booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java:113-116
Timestamp: 2026-03-25T19:09:09.638Z
Learning: In `booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java`, when deciding whether to forward the `Authorization` header to the upstream Kobo server proxy on the `/v1/library/sync` endpoint, a simple null check (`request.getHeader(HttpHeaders.AUTHORIZATION) != null`) is the correct guard. The header is either absent or present — if present it may or may not be a valid token, but validity is determined by the upstream Kobo server, not by Grimmory. Blank/whitespace intermediate states do not occur in practice, and pre-validating the token value before forwarding is not the responsibility of this service.

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/**/*.java : Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce `Autowired` field injection

Applied to files:

  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java (1)

52-62: Consider using consistent API call style throughout the class.

Lines 53-54 call the instance wrapper extractSpineIndex(), while lines 33, 43, 79, and 90 call CfiConverter.extractSpineIndex() directly. Both work identically, but for consistency within the class, consider using one style throughout.

♻️ Suggested change for consistency
 public String convertXPointerRangeToCfi(File epubFile, String startXPointer, String endXPointer) {
-    int startSpineIndex = extractSpineIndex(startXPointer);
-    int endSpineIndex = extractSpineIndex(endXPointer);
+    int startSpineIndex = CfiConverter.extractSpineIndex(startXPointer);
+    int endSpineIndex = CfiConverter.extractSpineIndex(endXPointer);

     if (startSpineIndex != endSpineIndex) {
         throw ApiError.INVALID_INPUT.createException("Start and end XPointers must reference the same spine index");
     }

     CfiConverter converter = createConverter(epubFile, startSpineIndex);
     return converter.xPointerToCfi(startXPointer, endXPointer);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java`
around lines 52 - 62, convertXPointerRangeToCfi uses the instance method
extractSpineIndex() while other places call the static/class method
CfiConverter.extractSpineIndex(); change the two calls in
convertXPointerRangeToCfi to use CfiConverter.extractSpineIndex(startXPointer)
and CfiConverter.extractSpineIndex(endXPointer) so the class consistently uses
the static/class-style call alongside other usages (leave createConverter(...)
and converter.xPointerToCfi(...) unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java`:
- Around line 52-62: convertXPointerRangeToCfi uses the instance method
extractSpineIndex() while other places call the static/class method
CfiConverter.extractSpineIndex(); change the two calls in
convertXPointerRangeToCfi to use CfiConverter.extractSpineIndex(startXPointer)
and CfiConverter.extractSpineIndex(endXPointer) so the class consistently uses
the static/class-style call alongside other usages (leave createConverter(...)
and converter.xPointerToCfi(...) unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b3193ef1-b929-4bff-92ec-c674e7b628b9

📥 Commits

Reviewing files that changed from the base of the PR and between 835a922 and 6c84d06.

📒 Files selected for processing (1)
  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
🧠 Learnings (7)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, epub processing uses `org.grimmory.epub4j` from https://github.com/grimmory-tools/epub4j. This is a grimmory-maintained fork and is fundamentally different from `io.documentnode.epub4j`. Do NOT generalize API knowledge, behavior, or limitations of `io.documentnode.epub4j` to the grimmory fork. Before raising review comments on epub-related file processing code, verify the current API and capabilities in the grimmory-tools/epub4j repository.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: When reviewing epub-related file processing code in this project, remember that grimmory-tools/grimmory uses the `org.grimmory.epub4j` library (from grimmory-tools/epub4j) and that it is a grimmory-maintained fork fundamentally different from `io.documentnode.epub4j`. Do not assume the same API behavior, limitations, or semantics as `io.documentnode.epub4j`. Before flagging issues or proposing changes, verify the current API/capabilities in the grimmory-tools/epub4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-04-02T09:25:48.330Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-04-03T09:45:37.512Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java:53-62
Timestamp: 2026-04-03T09:45:37.512Z
Learning: In grimmory-tools/grimmory, always prefer throwing errors using `ApiError` (e.g., `ApiError.INVALID_INPUT.createException("...")`) instead of generic Java exceptions like `IllegalArgumentException` or `RuntimeException`. When suggesting error handling in code reviews for this project, always recommend `ApiError`-based exceptions over raw Java exceptions.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-03-24T18:46:47.249Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:188-199
Timestamp: 2026-03-24T18:46:47.249Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a `hardcoverBookId` is stored in book metadata but is malformed (non-numeric), the preferred behavior is to return null and skip the sync rather than falling back to ISBN-based lookup. The reasoning is that an explicitly set bookId represents deliberate user intent, and silently resolving via ISBN could sync progress to a different book than intended.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-04-02T09:25:48.330Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, PDF processing uses `grimmory PDFium4j` from https://github.com/grimmory-tools/PDFium4j. This is a grimmory-maintained library and is fundamentally different from Apache PDFBox. Do NOT generalize API knowledge, behavior, or limitations of PDFBox to grimmory PDFium4j. Before raising review comments on PDF-related file processing code, verify the current API and capabilities in the grimmory-tools/PDFium4j repository.

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
📚 Learning: 2026-03-24T18:58:08.199Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:291-294
Timestamp: 2026-03-24T18:58:08.199Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a Hardcover edition has no page count, falling back to the book-level page count (book.pages) is the preferred behavior. Skipping the sync entirely in this case is considered worse than using a slightly approximate total, because the page count is only a proxy for converting progress % to page numbers for the API. The Hardcover API allows setting page-based progress even when the edition lacks a page count, and progress displays in at least some UI views (e.g., the book detail dropdown).

Applied to files:

  • booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java
🔇 Additional comments (7)
booklore-api/src/main/java/org/booklore/util/koreader/EpubCfiService.java (7)

3-12: LGTM!

The imports are correctly updated to use the new org.grimmory.epub4j.cfi package for CfiConverter and XPointerResult, and the ApiError import supports the improved error handling.


32-40: LGTM!

The migration to the new XPointerResult return type and usage of CfiConverter.extractSpineIndex() is clean. The Path overload correctly delegates to the File version.


42-50: LGTM!

Method correctly uses the new library's static CfiConverter.extractSpineIndex() and instance xPointerToCfi() methods.


68-75: LGTM!

The method correctly chains the new library's XPointerResult.getXpointer() with CfiConverter.normalizeProgressXPointer() for progress normalization.


77-97: LGTM!

Both validation methods follow a clean pattern: extract spine index, create converter, validate, and gracefully return false on any exception. The DEBUG-level logging for validation failures is appropriate.


99-101: LGTM!

The wrapper provides a clean public API for external callers to extract spine indices without coupling to CfiConverter directly.


107-114: LGTM!

The converter creation correctly uses the new library's constructor with JsoupDocumentNavigator, bridging Jsoup document parsing with epub4j's CFI navigation capabilities.

Copy link
Copy Markdown
Member

@zachyale zachyale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for engaging in such a constructive debate about this- looks good to me!

😄 🚢 🚀

@zachyale zachyale merged commit 307bdf8 into grimmory-tools:develop Apr 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unable to write metadata to some.files due to ownership

2 participants