🔗Session Tracking
Session tracking is the core feature of the plugin. Every time you read a book in KOReader, the plugin records a reading session and uploads it to your BookLore server.
🔗What is a reading session?
A reading session is a single continuous reading period - from when you open (or resume) a book to when you close it (or the device sleeps). Each session contains:
| Field | Description |
|---|---|
start_time | ISO 8601 timestamp when reading began |
end_time | ISO 8601 timestamp when reading ended |
duration_seconds | Total seconds elapsed |
start_progress | Progress percentage at the start |
end_progress | Progress percentage at the end |
progress_delta | Difference between end and start progress |
start_location | Page number or position at the start |
end_location | Page number or position at the end |
duration_formatted | duration_seconds formatted to eg. 5m 4s |
🔗Supported formats
| Format | Progress method |
|---|---|
| EPUB | Fractional document position from KOReader's rendering engine |
(current_page / total_pages) × 100 | |
| CBZ | (current_page / total_pages) × 100 |
| CBR | (current_page / total_pages) × 100 |
🔗Session events
The plugin hooks into four KOReader events:
| Event | Trigger | Action |
|---|---|---|
onReaderReady | Book opens | Start new session |
onCloseDocument | Book closes | End session, validate, queue |
onSuspend | Device sleeps | End session, validate, queue |
onResume | Device wakes | Start new session, sync pending |
🔗Session validation
Before a session is saved, it is validated against the thresholds you configure.
Detection mode: duration (default)
The session is kept if:
duration_seconds ≥ min_duration(default: 30 seconds)pages_read > 0(at least one page was turned)
Detection mode: pages
The session is kept if:
pages_read ≥ min_pages(default: 5 pages)
Sessions that fail validation are discarded immediately and never written to the database.
In duration mode, a session where no pages were turned (e.g., you opened the book but did not scroll or turn a page) is always discarded, regardless of how long the book was open.
🔗Suspend and resume behaviour
flowchart LR
subgraph sleep ["Device sleeps - onSuspend"]
S1["End session at suspend timestamp"]
S2["Validate and save to queue"]
S3["Sync attempt (if enabled)"]
S1 --> S2 --> S3
end
subgraph wake ["Device wakes - onResume"]
W1["Start new session from current position"]
W2["Deferred wake sync scheduled (15s delay)"]
W3["Silent background sync of pending sessions"]
W1 --> W2 --> W3
end
sleep -->|"Two separate sessions recorded"| wakeThis means a reading period that spans a sleep event is split into two sessions: one before sleep, one after. Both sessions are uploaded to BookLore separately.
🔗Resume cooldown
To avoid flooding the server when a device suspends and resumes rapidly (e.g., repeated short sleep events), the plugin enforces a 5-minute cooldown between auto-syncs triggered by resume. If a resume event occurs within 5 minutes of the last auto-sync, the sync is deferred rather than run immediately.
🔗Deferred wake sync
When the device wakes, the plugin schedules a sync to run 15 seconds after resume. This gives the network time to reconnect before the upload is attempted. If the network is not yet available after 15 seconds, the session stays queued and will be picked up when connectivity is detected.
🔗Network-connected sync
If the plugin is waiting for network and the device reports a new connection (via the onNetworkConnected event), any deferred wake sync is triggered immediately without waiting for the full 15-second timer.
🔗Duration formatting
Session durations are displayed in human-readable format in notifications:
45s
2m 30s
1h 5m 9s
🔗Book fingerprinting
Each book is identified by an MD5 fingerprint computed from strategic byte samples of the file. This is the same algorithm used by BookLore's KOReader integration endpoint.
The fingerprint is:
- Computed once per file and cached in the local database.
- Used to look up the BookLore book ID from the server.
- Used to resolve the book ID for sessions that were saved offline.
If a book file is renamed or moved, the plugin detects the new path and updates the cache entry.
If the hash lookup returns no match, the plugin automatically attempts a second lookup using the ISBN embedded in the book file's metadata. Only an exact match is accepted. See Book ID Resolution for how this works and how to prepare your files.
🔗Progress precision
Progress is always stored locally at full precision. The decimal places setting (default: 2, range: 0–5) is applied at sync time - it controls how many decimal places are sent to the BookLore server, not how many are kept in the local database.
Increase this value if your BookLore server reports noticeably rounded progress values for very long books.
See Configuration → Session Tracking to change this setting.
🔗Per-book tracking toggle
You can disable session tracking for individual books without turning off the plugin globally. This is useful for reference books, cookbooks, or any book where you do not want reading history recorded.
To toggle tracking for a book:
- Long-press the book in the file manager.
- Select Enable tracking or Disable tracking from the context menu.
When tracking is disabled for a book:
- No session is saved when you close or suspend the device while reading it.
- No annotations or ratings are synced.
- The book is silently skipped - no notification is shown.
The tracking state is stored per-book in the local database (tracking_enabled column in book_cache) and persists across restarts.
🔗Session feedback
After a session ends, the plugin may show a brief notification:
| Result | Message shown | Condition |
|---|---|---|
| Session saved / queued | "Session saved" with annotation count | Manual Sync Only mode; not in silent mode |
| Criteria not met | "Session not saved: criteria not met" | Session failed validation (too short or no progress); not in silent mode |
| Tracking disabled | (no message) | Book has tracking disabled |
All session notifications are suppressed when Silent Mode is enabled in preferences.
🔗Book deletion notification
When you delete a book from the file manager (via the long-press menu), the plugin sends a deletion event to BookLore:
DELETE /api/v1/books/{id}
If the book's server ID is not yet known (e.g., it was never synced), or if the device is offline, the deletion is stored in the pending_deletions table and retried on the next sync.
This keeps your BookLore library in sync with your device - books you remove locally are also removed from the server.
🔗SDR detection
KOReader stores per-book metadata (reading progress, highlights, bookmarks) in a sidecar directory with the .sdr extension. The plugin needs to know where this directory is so that it can read annotations from disk when live in-memory objects are unavailable (for example, on a deferred retry after a sync failure).
🔗Detection on initial open
When a book is opened for the first time, the plugin queries DocSettings.getSidecarDir() - the same function KOReader itself uses - to obtain the exact sidecar path for the current document_metadata_folder setting (doc, dir, or hash mode). This call is authoritative: it never does a disk scan or guesses a fallback path; it computes the correct location based on KOReader's current configuration.
The resolved path is written to the sdr_path column of the book_metadata table. Any subsequent open of the same book skips the detection call entirely.
🔗Database-backed lookup (skip on subsequent opens)
Before attempting any sidecar detection, the plugin checks the database for a cached sdr_path:
- If a non-empty
sdr_pathis already stored, it is used immediately. No disk access orDocSettingscall is made. - If no path is cached, the detection runs once and the result is persisted.
This means that after the first open, sidecar lookups are effectively free - a single SQL read rather than a filesystem call.
🔗Why this matters for annotations and ratings
When the plugin reads highlights or the KOReader star rating at sync time, it falls back to the on-disk sidecar only when live in-memory objects (the ReaderAnnotation module and doc_settings) are unavailable. Having the sidecar path pre-cached in the database ensures that fallback reads are reliable and fast, without re-running the detection logic each time.