4.3 KiB
KOReader Synchronization Architecture
This document explains the intent and internal structure of the KOReader synchronization code in CrossPoint.
Scope:
- Synchronization logic that maps between CrossPoint reading position and KOReader sync payloads.
- Module boundaries and responsibilities.
- Matching rules, fallback strategy, and expected behavior.
For XPath-specific details and examples, see koreader-sync-xpath-mapping.md.
Goals
The synchronization layer is designed to:
- Be robust on constrained devices (ESP32-C3 memory constraints).
- Be deterministic and debuggable when mapping positions.
- Keep transport/client logic separated from parsing/mapping logic.
- Prefer precise anchors when available, but degrade gracefully.
Data Model Mismatch
CrossPoint stores position as chapter/page-centric state. KOReader sync payload stores position as XPath-like anchor plus percentage.
Because layout engines differ, page equality cannot be guaranteed across devices. The synchronization strategy therefore combines:
- Structural anchor mapping (XPath).
- Percent-based fallback.
- Paragraph LUT refinement when available.
Module Responsibilities
Client / orchestration
-
lib/KOReaderSync/KOReaderSyncClient.cpp
- HTTP calls and payload exchange.
-
lib/KOReaderSync/ProgressMapper.cpp
- High-level mapping from app state to KOReader payload and back.
- Chooses XPath path or percentage fallback.
XPath indexing facade
- lib/KOReaderSync/ChapterXPathIndexer.h
- lib/KOReaderSync/ChapterXPathIndexer.cpp
- Public API consumed by ProgressMapper.
- Thin facade over forward/reverse mapper internals.
- Utility extraction helpers (DocFragment index, paragraph index).
Forward mapping engine
- lib/KOReaderSync/ChapterXPathForwardMapper.cpp
- Maps intra-spine progress to XPath.
- Emits /text()[N].M for body-level text-node locations.
Reverse mapping engine
- lib/KOReaderSync/ChapterXPathReverseMapper.cpp
- Maps XPath to intra-spine progress.
- Supports exact and tolerant matching tiers.
- Handles /text()[N].M codepoint offsets.
Shared parser/state/utilities
-
lib/KOReaderSync/ChapterXPathIndexerInternal.h
- UTF-8 helpers, XPath normalization, parse runner, and chapter text-byte counting.
-
lib/KOReaderSync/ChapterXPathIndexerState.h
- Shared stack model and generic Expat callback adapters.
- Common parser code pattern used by both forward/reverse engines.
Core Logic
Forward (CrossPoint -> KOReader)
- Decompress one spine XHTML to a temporary file.
- Count total visible text bytes.
- Cache that total per spine (cache-path + spine index + href) so repeated mappings for the same chapter can skip the expensive counting pass.
- Convert intra-spine progress to target visible-byte offset.
- Stream parse and stop at target.
- Emit anchor:
- element XPath, or
- /text()[N].M when in body-level text-node context.
Reverse (KOReader -> CrossPoint)
- Decompress one spine XHTML to a temporary file.
- Stream parse chapter while evaluating candidate matches.
- Resolve best tier in this order:
- exact
- exact-no-index
- ancestor
- ancestor-no-index
- Convert resolved byte offset to intra-spine progress.
For text-node anchors /text()[N].M:
- N is treated as 1-based text node index.
- M is treated as 0-based codepoint offset.
Fallback Strategy
When XPath mapping fails or is ambiguous:
- Fall back to percentage-driven chapter/page estimation.
- Use paragraph LUT refinement where available.
This guarantees user progress continuity even for malformed or sparse content.
Constraints and Non-Goals
- No full DOM materialization for entire books.
- Parse only one spine item on demand.
- Keep memory usage bounded and transient.
- Do not attempt pixel-perfect page parity with KOReader.