116 lines
4.3 KiB
Markdown
116 lines
4.3 KiB
Markdown
# 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](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](../../lib/KOReaderSync/KOReaderSyncClient.cpp)
|
|
- HTTP calls and payload exchange.
|
|
|
|
- [lib/KOReaderSync/ProgressMapper.cpp](../../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.h)
|
|
- [lib/KOReaderSync/ChapterXPathIndexer.cpp](../../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](../../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](../../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.cpp](../../lib/KOReaderSync/ChapterXPathIndexerInternal.cpp)
|
|
- [lib/KOReaderSync/ChapterXPathIndexerInternal.h](../../lib/KOReaderSync/ChapterXPathIndexerInternal.h)
|
|
- UTF-8 helpers, XPath normalization, parse runner, and chapter text-byte counting.
|
|
|
|
- [lib/KOReaderSync/ChapterXPathIndexerState.h](../../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)
|
|
|
|
1. Decompress one spine XHTML to a temporary file.
|
|
2. Count total visible text bytes.
|
|
3. Cache that total per spine (cache-path + spine index + href) so repeated
|
|
mappings for the same chapter can skip the expensive counting pass.
|
|
4. Convert intra-spine progress to target visible-byte offset.
|
|
5. Stream parse and stop at target.
|
|
6. Emit anchor:
|
|
- element XPath, or
|
|
- /text()[N].M when in body-level text-node context.
|
|
|
|
### Reverse (KOReader -> CrossPoint)
|
|
|
|
1. Decompress one spine XHTML to a temporary file.
|
|
2. Stream parse chapter while evaluating candidate matches.
|
|
3. Resolve best tier in this order:
|
|
- exact
|
|
- exact-no-index
|
|
- ancestor
|
|
- ancestor-no-index
|
|
4. 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.
|