Introduction
The Kobo Plugin for KOReader enables seamless integration with your Kobo device's native library. This plugin creates a virtual library that allows you to browse and read kepub books from your Kobo's Nickel OS directly within KOReader, while maintaining synchronization of reading progress between both applications.
What is this plugin?
This plugin bridges the gap between KOReader and Kobo's native reading experience by:
- Creating a virtual library from your Kobo's kepub collection
- Syncing reading progress bidirectionally between KOReader and Kobo
- Providing seamless access to kepub files without manual file management
- Maintaining compatibility with Kobo's native reading features
Key Benefits
Komga / Calibre Web Users
- Streamline your workflow by syncing books only through Kobo's native system
- Read Komga or Calibre Web synced books directly in KOReader without duplicate setup
- Let Kobo handle progress synchronization while enjoying KOReader's enhanced reading features
Requirements
- Kobo eReader device running KOReader
- Kepub books in your Kobo library (unencrypted)
- KOReader version with plugin support
Supported Platforms
This plugin is specifically designed for and tested on Kobo eReader devices. It requires access to Kobo's internal database and file structure.
Installation
Prerequisites
Before installing the Kobo Plugin, ensure you have:
- A Kobo eReader device (Clara HD, Libra, Sage, etc.)
- KOReader installed on your Kobo device
- Access to the Kobo filesystem (usually via USB or file manager)
Installation Method
TODO
Getting Started
First Steps with Kobo Kepub Library
After installation, follow these steps to start using your virtual Kobo library in KOReader.
Accessing Your Kobo Library
1. Open the Virtual Library
- In KOReader, tap the File browser icon
- Look for "Kobo Library" in the directory list from the home folder
- Tap "Kobo Library" to enter your virtual kepub collection
2. Browse Your Books
- Books are listed directly in the Kobo Library folder, sorted by file name
- Book covers and metadata are automatically loaded from Kobo's database
3. Open a Book
- Tap any book to open it in KOReader
- The book will open at your last reading position (if sync is enabled)
- Use all standard KOReader features (annotations, fonts, themes, etc.)
Understanding the Interface
Virtual Library Navigation
📁 Kobo Library/
├── 📖 Book Title 1 by Author Name (25%)
├── 📖 Book Title 2 by Author Name (Complete)
├── 📖 Another Book by Another Author (New)
└── 📖 [More books...]
Menu Options
- Sync reading state now: Manual sync trigger
- Refresh library: Update library with new books
- About: Plugin version and information
Features Overview
The Kobo Plugin provides comprehensive integration between KOReader and your Kobo device's native library system.
Core Features
🗃️ Virtual Library Access
- Browse your entire Kobo kepub collection from within KOReader
- Automatic book discovery from Kobo's database
- Rich metadata display (covers, titles, authors, progress)
🔄 Bidirectional Reading Sync
- Sync reading progress between KOReader and Kobo
- Automatic detection of the most recent reading position
- Support for multiple sync strategies and user preferences
⚙️ Flexible Sync Configuration
- Granular control over sync behavior
- Direction-specific settings (FROM Kobo, TO Kobo)
- Scenario-based sync rules (newer vs. older progress)
- Manual and automatic sync modes
Virtual Library
The Virtual Library is the core feature that creates a seamless bridge between your Kobo's kepub collection and KOReader's file system.
How It Works
The plugin creates a virtual filesystem layer that presents your Kobo books as if they were regular files in KOReader's file browser. This virtual representation is built by:
- Reading Kobo's database (
KoboReader.sqlite) for book metadata - Creating virtual paths in the format
KOBO_VIRTUAL://BOOKID/filename - Mapping virtual paths to actual kepub file locations
- Providing file system operations for seamless KOReader integration
Virtual Path Structure
Kobo Library/
├── Harry Potter and the Philosopher's Stone.kepub.epub
├── Harry Potter and the Chamber of Secrets.kepub.epub
├── 1984.kepub.epub
├── Animal Farm.kepub.epub
└── [More books...]
Path Translation
| Virtual Path | Actual Path |
|---|---|
KOBO_VIRTUAL://ABC123/book.kepub.epub | /mnt/onboard/.kobo/kepub/ABC123 |
Library Organization
Flat Structure
Books are presented in a single flat directory without subfolders:
- Book files appear with their titles from Kobo's database
- File extensions are preserved (
.kepub.epub) - No subdirectories: All books in one folder for simple browsing
Metadata Integration
Each virtual book entry includes:
- Title: From Kobo's database or filename fallback
- Author: Primary author from book metadata
- Cover: Extracted from Kobo's cover cache
- Series: Extracted from Kobo's database
Troubleshooting Virtual Library
Missing Books
- DRM protection: Encrypted books cannot be accessed
Reading State Sync
The Reading State Sync feature maintains consistent reading progress between KOReader and Kobo's native reader by synchronizing position, completion status, and reading statistics.
Note: When syncing to Kobo, reading position is updated only at chapter boundaries. Fine-grained position within a chapter is not preserved.
Overview
Reading State Sync works by:
- Monitoring reading progress in both KOReader and Kobo
- Comparing timestamps to determine the most recent reading session
- Syncing progress and status in the appropriate direction
How Sync Works
Bidirectional Synchronization
The plugin supports sync in both directions:
FROM Kobo TO KOReader (Pull)
- Retrieves progress from Kobo's database
- Updates KOReader's document settings
- Preserves reading position and completion status
FROM KOReader TO Kobo (Push)
- Reads KOReader's progress from sidecar files
- Writes to Kobo's SQLite database
- Updates reading statistics in Kobo
Timestamp-Based Sync Decision
graph TD
A[Read KOReader Progress] --> B[Read Kobo Progress]
B --> C{Compare Timestamps}
C -->|Kobo Newer| D[Sync FROM Kobo]
C -->|KOReader Newer| E[Sync TO Kobo]
C -->|Same/No Data| F[No Sync Needed]
D --> G[Update KOReader Data]
E --> H[Update Kobo Database]
F --> I[Done]
G --> I
H --> I
Sync Decision Flowchart
flowchart TD
A[Start Sync] --> B{Progress Data Exists?}
B -- "Neither" --> C[No Sync Needed]
B -- "Both" --> D[Compare Timestamps]
B -- "Only Kobo" --> E[Sync FROM Kobo]
B -- "Only KOReader" --> F[Sync TO Kobo]
D -- "Kobo Newer" --> E
D -- "KOReader Newer" --> F
D -- "Same/No Change" --> C
E --> G[Update KOReader Data]
F --> H[Update Kobo Database]
G --> I[Done]
H --> I
C --> I
Data Sources
KOReader Data
Reading progress is stored in .sdr sidecar files managed by KOReader.
Kobo Data
Progress is read from the KoboReader.sqlite database used by the Kobo system.
Automatic Sync Triggers
Library Access
graph TD
A[Open Virtual Library] --> B{Auto-sync Enabled?}
B -->|No| D[Show Library]
B -->|Yes| C{Already Synced Since KOReader Started?}
C -->|Yes| D
C -->|No| E[Sync All Books]
E --> D
Document Close
graph TD
A[Close Document] --> B{Is Virtual Kepub?}
B -->|No| D[Normal Close]
B -->|Yes| C[Extract Book ID]
C --> E{Auto-sync Enabled?}
E -->|No| F[Skip Sync]
E -->|Yes| G[Sync TO Kobo]
G --> H["Update Position<br/>(Chapter Boundary Only)"]
H --> I[Return to Virtual Library]
F --> I
Important Limitation: When syncing progress to Kobo, the position is updated only at chapter boundaries. Fine-grained position within a chapter is not preserved by Kobo's native reader.
However, when Kobo syncs progress back to KOReader, KOReader opens the book at the exact percentage received from Kobo, providing fine-grained positioning.
Settings
The Kobo Plugin provides comprehensive settings to control how the plugin operates, particularly around reading state synchronization between KOReader and Kobo.
Settings Categories
Sync Settings
Control how and when reading progress is synchronized between KOReader and Kobo's native reader:
- Sync Settings Overview - Core settings for enabling and configuring sync
- Sync Direction Settings - Detailed control over sync behavior in each direction
- Sync Configuration Examples - Common configuration patterns for different use cases
Accessing Settings
- Open KOReader file browser
- Open the menu (top-left corner)
- Select "Kobo Library" → Settings
All settings are stored in KOReader's configuration and persist across restarts.
Sync Settings Overview
The plugin provides granular control over reading state synchronization behavior through comprehensive settings that let you customize how and when sync operations occur.
Documentation
- Core Settings: Main controls, direction toggles, and behavior options with defaults table
- Settings Menu Navigation: How to access settings and menu hierarchy reference
- Manual Sync: Triggering manual sync operations and sync behavior
- Sync Decision Dialog: Understanding the dialog shown when sync requires user input
Core Settings
All sync settings are stored in KOReader's configuration and persist across restarts. Here are the core settings:
Main Controls
sync_reading_state (Default: false)
Global enable/disable for all sync functionality. When disabled, no synchronization occurs between KOReader and Kobo.
Menu: Kobo Library → Sync reading state with Kobo
enable_auto_sync (Default: false)
Auto-sync reading progress when opening the virtual library. The sync only happens once per KOReader startup. Books are always displayed in the virtual library regardless of this setting.
Menu: Kobo Library → Enable automatic sync on virtual library
Direction Controls
enable_sync_from_kobo (Default: false)
Allow pulling progress from Kobo to KOReader (FROM Kobo sync).
Menu: Kobo Library → Sync behavior → Enable sync FROM Kobo TO KOReader
enable_sync_to_kobo (Default: true)
Allow pushing progress from KOReader to Kobo (TO Kobo sync).
Menu: Kobo Library → Sync behavior → Enable sync FROM KOReader TO Kobo
Behavior Controls
Granular settings for different sync scenarios with three options each:
PROMPT: Ask user before syncingSILENT: Sync automatically without confirmationNEVER: Skip sync in this scenario
See Sync Direction Settings for details on the four behavior settings.
Settings Menu Navigation
Accessing Settings
- Open KOReader file browser
- Open the menu (top-left corner)
- Select "Kobo Library" → Settings
Settings Hierarchy
Kobo Library
├── Sync reading state with Kobo [Toggle]
├── Enable automatic sync on virtual library [Toggle]
├── Sync reading state now [Action]
├── Sync behavior [Submenu]
│ ├── Enable sync FROM Kobo TO KOReader [Toggle]
│ ├── Enable sync FROM KOReader TO Kobo [Toggle]
│ ├── From Kobo to KOReader [Submenu]
│ │ ├── Sync from newer state (Current: Prompt)
│ │ └── Sync from older state (Current: Never)
│ └── From KOReader to Kobo [Submenu]
│ ├── Sync to newer state (Current: Silent)
│ └── Sync to older state (Current: Never)
├── Refresh library [Action]
└── About [Info]
Menu Item Reference
| Menu Item | Type | Function |
|---|---|---|
| Sync reading state with Kobo | Toggle | Enable/disable all sync functionality |
| Enable automatic sync on virtual library | Toggle | Enable/disable automatic progress sync on library access (books are always displayed) |
| Sync reading state now | Action | Manually trigger progress sync for all books |
| Sync behavior | Submenu | Configure sync direction and behavior |
| Refresh library | Action | Refresh virtual library metadata |
| About | Info | Display plugin information |
Manual Sync
You can trigger a manual sync at any time via the menu: Kobo Library → Sync reading state now
This respects all the sync behavior settings configured in Sync Direction Settings.
Manual Sync Process
- Open the file browser in KOReader
- Open the menu (top-left corner)
- Select "Kobo Library" → "Sync reading state now"
- Wait for sync to complete
- See confirmation when done
Sync Behavior with Manual Sync
Manual sync respects all configured settings:
- sync_from_kobo_newer and sync_from_kobo_older: Control pull behavior
- sync_to_kobo_newer and sync_to_kobo_older: Control push behavior
- PROMPT settings will show dialogs for each decision
- SILENT settings will sync automatically
- NEVER settings will skip those scenarios
Sync Decision Dialog
When Sync Direction Settings are set to PROMPT, the plugin shows a dialog whenever a sync decision needs to be made.
Dialog Format
Book: The Great Gatsby
Kobo: 45% (2024-01-15 14:30)
KOReader: 38% (2024-01-14 22:15)
Sync newer reading progress from Kobo?
When Dialogs Appear
Dialogs appear when:
- A book has different reading positions in KOReader and Kobo
- Sync Direction Settings are set to PROMPT for that scenario
- The sync decision is about to be made (newer or older state)
Dialog Behavior
When you see a dialog, you have two options:
| Button | Effect |
|---|---|
| Yes | Proceed with the sync operation |
| No | Skip this sync decision, no changes are made |
Example Scenarios
Scenario 1: Pull newer from Kobo
Book: The Great Gatsby
Kobo: 60% (2024-01-15 14:30)
KOReader: 40% (2024-01-14 22:15)
Sync newer reading progress from Kobo?
- Kobo has newer timestamp (newer progress)
- Appears when
sync_from_kobo_newer = PROMPT - Click Yes to update KOReader to 60%, or No to keep 40%
Scenario 2: Push older to Kobo
Book: The Great Gatsby
KOReader: 35% (2024-01-13 10:00)
Kobo: 50% (2024-01-14 22:15)
Sync older reading progress to Kobo?
- KOReader has older progress (less complete)
- Appears when
sync_to_kobo_older = PROMPT - Click Yes to revert Kobo to 35%, or No to keep 50%
Sync Direction Settings
These settings control sync behavior based on which side (Kobo or KOReader) has newer progress. Each setting has three options: PROMPT (ask user), SILENT (automatic), or NEVER (skip).
FROM Kobo (Pull) Sync Settings
sync_from_kobo_newer (Default: PROMPT)
When to use: Kobo has more recent reading progress than KOReader
Options:
- Prompt: Show a dialog and ask whether to pull Kobo's newer progress
- Silent: Automatically pull newer progress from Kobo without asking
- Never: Keep KOReader progress, ignore newer Kobo data
Example: Kobo shows 45% (read yesterday), KOReader shows 30% (read 2 days ago). This setting decides what happens.
Menu: Kobo Library → Sync behavior → From Kobo to KOReader → Sync from newer state
sync_from_kobo_older (Default: NEVER)
When to use: Kobo has a newer timestamp but lower progress percentage than KOReader
Options:
- Prompt: Show a dialog and ask whether to pull Kobo's progress despite it being less complete
- Silent: Automatically pull Kobo's progress, overwriting KOReader's higher progress
- Never: Keep KOReader's higher progress, don't regress to lower Kobo percentage (recommended)
Example: Kobo shows 40% (read today - newer timestamp), KOReader shows 80% (read 2 days ago - older timestamp). This setting decides what happens.
Menu: Kobo Library → Sync behavior → From Kobo to KOReader → Sync from older state
FROM KOReader (Push) Sync Settings
sync_to_kobo_newer (Default: SILENT)
When to use: KOReader has more recent reading progress than Kobo
Options:
- Prompt: Show a dialog and ask whether to push KOReader's newer progress
- Silent: Automatically push newer progress to Kobo without asking (recommended)
- Never: Keep Kobo progress, don't update with newer KOReader data
Example: KOReader shows 65% (read today), Kobo shows 50% (read yesterday). This setting decides what happens.
Menu: Kobo Library → Sync behavior → From KOReader to Kobo → Sync to newer state
sync_to_kobo_older (Default: NEVER)
When to use: KOReader has a newer timestamp but lower progress percentage than Kobo
Options:
- Prompt: Show a dialog and ask whether to push KOReader's progress despite it being less complete (unusual)
- Silent: Automatically push KOReader's progress, overwriting Kobo's higher progress (unusual)
- Never: Keep Kobo's higher progress, don't regress to lower KOReader percentage (recommended)
Example: KOReader shows 40% (read today - newer timestamp), Kobo shows 80% (read 2 days ago - older timestamp). This setting decides what happens.
Menu: Kobo Library → Sync behavior → From KOReader to Kobo → Sync to older state
Understanding "Newer" vs "Older"
The plugin compares the timestamps of when progress was last updated:
- Newer: The more recent progress update (more recently modified timestamp)
- Older: The less recent progress update (older modification timestamp)
However, "older" in the setting names refers to scenarios where the source has a newer timestamp but lower progress percentage. This can happen when someone re-reads earlier parts of a book.
These settings let you decide whether to keep progress based on timestamps or based on completion percentage in each direction.
Sync Configuration Examples
Common configuration patterns for different use cases. Choose the pattern that best matches your reading habits.
Conservative Sync
Use case: You want full control and visibility over every sync decision.
Settings:
sync_reading_state = true
enable_auto_sync = false
enable_sync_from_kobo = true
enable_sync_to_kobo = true
sync_from_kobo_newer = PROMPT -- Ask before pulling newer
sync_from_kobo_older = NEVER -- Never regress progress
sync_to_kobo_newer = PROMPT -- Ask before pushing newer
sync_to_kobo_older = NEVER -- Never regress progress
Automatic Sync (Both directions, prefer newer)
Use case: You read on both systems and want seamless synchronization.
Settings:
sync_reading_state = true
enable_auto_sync = true
enable_sync_from_kobo = true
enable_sync_to_kobo = true
sync_from_kobo_newer = SILENT -- Auto-pull newer
sync_from_kobo_older = NEVER -- Keep higher KOReader progress
sync_to_kobo_newer = SILENT -- Auto-push newer
sync_to_kobo_older = NEVER -- Keep higher Kobo progress
KOReader Primary
Use case: You primarily read in KOReader and want Kobo to follow.
Settings:
sync_reading_state = true
enable_auto_sync = false
enable_sync_from_kobo = false -- Don't pull from Kobo
enable_sync_to_kobo = true
sync_to_kobo_newer = SILENT -- Auto-push newer to Kobo
sync_to_kobo_older = NEVER -- Don't regress Kobo
Kobo Primary
Use case: You primarily read in Kobo native reader and want KOReader to follow.
Settings:
sync_reading_state = true
enable_auto_sync = true
enable_sync_from_kobo = true
enable_sync_to_kobo = false -- Don't push to Kobo
sync_from_kobo_newer = SILENT -- Auto-pull newer from Kobo
sync_from_kobo_older = NEVER -- Don't regress KOReader
Manual Sync Only
Use case: You want complete control and prefer to trigger sync manually.
Settings:
sync_reading_state = true
enable_auto_sync = false
enable_sync_from_kobo = true
enable_sync_to_kobo = true
sync_from_kobo_newer = PROMPT -- Always ask
sync_from_kobo_older = PROMPT -- Always ask
sync_to_kobo_newer = PROMPT -- Always ask
sync_to_kobo_older = PROMPT -- Always ask
Usage Scenarios
This section provides detailed walkthroughs for common use cases of the Kobo Plugin.
Scenarios
- Komga / Calibre Web Integration: Seamless book syncing between Komga/Calibre Web through Kobo
Komga / Calibre Web Integration
Goal
Streamline your workflow by syncing books through Kobo's native system. Read Komga or Calibre Web synced books directly in KOReader while maintaining seamless reading progress synchronization.
Use Case
You use Komga or Calibre Web to manage your digital library and sync books to your Kobo device. You want to:
- Keep Kobo as your single source of truth for books
- Enjoy KOReader's superior reading features and customization
- Have reading progress automatically sync between Kobo and KOReader
- Avoid managing duplicate book copies or complex sync setups
- Kobo syncs progress back to Komga/Calibre Web, completing the loop
Benefits
Simplified Setup
- One book source (Komga/Calibre Web → Kobo)
- No need to manually manage books in multiple locations
- KOReader automatically sees all books from your sync service
- Reading progress syncs back to Komga/Calibre Web through Kobo's sync mechanism
Best of Both Worlds
- Use KOReader's superior reader
- Leverage Komga/Calibre Web's library management
- Use Kobo's native sync capabilities
Setup
Prerequisites
- Komga or Calibre Web configured and syncing books to Kobo
- Kobo Plugin installed and enabled in KOReader
- Sync enabled in plugin settings
Important Limitation
When syncing reading progress to Kobo, the position is rounded to chapter boundaries. This means Kobo's native reader will open at the nearest chapter rather than the exact position where you stopped in KOReader. However, when KOReader receives progress from Kobo, it opens at the exact percentage, providing fine-grained positioning.
Configuration
Use these recommended settings:
✅ Sync reading state with Kobo: ON
✅ Enable automatic sync on virtual library: ON
✅ Enable sync FROM Kobo TO KOReader: ON
✅ Enable sync FROM KOReader TO Kobo: ON
Sync from Kobo (newer): SILENT
Sync to Kobo (newer): SILENT
Sync from Kobo (older): NEVER
Sync to Kobo (older): NEVER
These settings mean:
- Sync is enabled globally
- Auto-sync is enabled (syncs automatically when accessing the virtual library)
- Progress always syncs when you close books or access the library
- Never sync older/less complete progress (prevents losing progress)
Workflows
Detailed step-by-step workflows for common reading scenarios:
- Reading a New Book: Setting up and opening a book for the first time
- Continuing in Kobo Native: Switching between KOReader and Kobo's native reader
- Return to KOReader: Coming back to KOReader after reading in Kobo
See the Workflows page for detailed instructions.
Sync Flow Diagram
The Sync Flow Diagram visualizes how books and reading progress move through the system.
Next Steps
- Review Sync Settings for advanced configuration
- See Settings Menu Navigation for how to access settings
Workflows
This section covers the detailed workflows for using the Kobo Plugin with Komga/Calibre Web.
Available Workflows
- Reading a New Book: Setting up and opening a book for the first time
- Continuing in Kobo Native: Switching between KOReader and Kobo's native reader
- Return to KOReader: Coming back to KOReader after reading in Kobo
Reading a New Book
-
Book arrives from Komga/Calibre Web
- Your book sync service pushes new books to Kobo
- Books appear in both Kobo's native library and the Kobo Plugin's virtual library
-
Open KOReader and browse the virtual library
- Navigate to the Kobo Library in KOReader
- All books are displayed (books are always synced)
- Plugin automatically syncs reading progress for all books (auto-sync is enabled)
-
Select and open a book
- Book automatically opens at the correct position based on synced progress
- Latest progress from Kobo is already synced
-
Read with KOReader's features
- Enjoy KOReader's customizable fonts, margins, and reading features
- Progress is tracked normally
-
Close the book
- Reading progress is automatically synced back to Kobo
- No manual action needed
Continuing in Kobo Native
-
Switch to Kobo native reader
- Open the book in Kobo's native reading app
- Book automatically opens at your last read position from KOReader
- Note: Kobo rounds to chapter boundaries, so the exact position may vary slightly
- Continue reading with Kobo's features if desired
-
Make progress
- Read in Kobo's native reader
- Close when done
Return to KOReader
-
Open KOReader and browse the virtual library
- Navigate back to the Kobo Library
- All books are displayed (books are always synced)
- Plugin automatically syncs reading progress for all books again (auto-sync enabled)
-
Open the same book
- Book automatically opens at the updated position based on latest synced progress
- KOReader uses fine-grained percentage positioning from Kobo
- Latest progress from Kobo is already synced
-
Keep reading
- Make additional progress in KOReader
- Close when done (progress syncs automatically back to Kobo)
Sync Flow Diagram
Note: This diagram assumes the recommended settings are enabled (auto-sync ON).
Important Limitation: When syncing to Kobo, position is rounded to chapter boundaries. When Kobo syncs back to KOReader, KOReader opens at the exact percentage from Kobo.
sequenceDiagram
participant Komga as Komga/<br/>Calibre Web
participant Kobo as Kobo<br/>Database
participant VLib as Virtual<br/>Library
participant KOR as KOReader
Komga->>Kobo: New book or update
Note over VLib: User opens KOReader
KOR->>VLib: Browse library
VLib->>Kobo: Read book list
Note over VLib: Auto-sync triggered (first open per session)
VLib->>Kobo: Sync all books progress
Kobo->>VLib: Return latest progress
Note over KOR: User selects and opens book
KOR->>KOR: Open book at last synced position (fine-grained %)
Note over KOR: User reads to 45%
Note over KOR: User closes book
Note over KOR,Kobo: Sync TO Kobo (rounded to chapter boundary)
KOR->>Kobo: Write 45% progress (as chapter position)
Kobo->>Kobo: Update book status
Note over Kobo: User opens Kobo native
Kobo->>Kobo: Read progress (at chapter boundary)
Kobo->>Kobo: Open book at chapter boundary
Note over Kobo: User reads to 60%
Note over Kobo: User closes book
Kobo->>Kobo: Update to 60%
Note over VLib: User opens KOReader again (new session)
KOR->>VLib: Browse library
VLib->>Kobo: Read book list
Note over VLib: Auto-sync triggered (first open in new session)
VLib->>Kobo: Sync all books progress
Kobo->>VLib: Return 60% progress
Note over KOR: User selects and opens book
Note over KOR: Sync FROM Kobo (fine-grained %)
KOR->>KOR: Open book at 60% (fine-grained position from Kobo)
This diagram shows the complete flow of how books and reading progress move through the system when using Komga or Calibre Web with the Kobo Plugin. The key points are:
- Books are always displayed in the virtual library (independent of sync setting)
- Reading progress syncs automatically when:
- Accessing the virtual library (if auto-sync enabled) - syncs all books once per session
- Closing a book - always syncs that book's progress to Kobo (rounded to chapter boundary)
- KOReader uses fine-grained percentages when opening books (from either app)
- Kobo uses chapter-based positioning when opening books
Architecture Overview
The Kobo Plugin bridges two different reading progress tracking systems: Kobo's centralized SQLite database and KOReader's distributed sidecar files.
Core Concept
graph LR
K[Kobo Database<br/>Centralized<br/>Chapter-based] <-->|Plugin<br/>Sync| R[KOReader Sidecars<br/>Distributed<br/>Percentage-based]
style K fill:#fff3e0
style R fill:#e1f5ff
The plugin acts as a translator and synchronizer between these two fundamentally different systems.
Key Topics
High-Level Architecture
Visual overview of components and their relationships. Start here to understand the overall system design.
Database & Data Storage
How both systems store reading progress, why they're different, and how the plugin bridges the gap. This is the most important section for understanding the plugin's core functionality.
High-Level Architecture
This chapter contains the visual overviews and high-level component relationships.
Architecture
architecture-beta
group koreader(mdi:laptop)[KOReader Environment]
service ui(mdi:palette)[User Interface] in koreader
service fm(mdi:folder-open)[File Manager] in koreader
service dr(mdi:book-open-page-variant)[Document Reader] in koreader
service ps(mdi:puzzle)[Plugin System] in koreader
group plugin_core(mdi:cog)[Plugin Core]
service mp(mdi:play-circle)[Main Plugin] in plugin_core
service vl(mdi:library)[Virtual Library] in plugin_core
service rss(mdi:sync)[Reading State Sync] in plugin_core
service meta(mdi:tag-multiple)[Metadata Parser] in plugin_core
group extensions(mdi:sitemap)[Extensions]
service uie(mdi:palette-advanced)[UI Extensions] in extensions
service fse(mdi:folder-network)[Filesystem Extensions] in extensions
service dce(mdi:file-document)[Document Extensions] in extensions
service dse(mdi:cog-box)[DocSettings Extensions] in extensions
group kobo_system(mdi:harddisk)[Kobo System]
service db(mdi:database)[SQLite Database] in kobo_system
service kf(mdi:book-open-blank-variant)[Kepub Files] in kobo_system
junction toDB
junction toRSS
junction extA
junction extB
junction extC
junction extD
junction extE
ui:R --> L:fm
fm:R --> L:vl
vl:T --> B:meta
meta:R -- L:toDB
toDB:B --> T:db
vl:B --> T:kf
dr:T -- B:toRSS
toRSS:R --> L:rss
rss:R -- T:toDB
mp:R -- L:extA
extA:R -- L:extB
extB:R -- L:extC
extC:R -- L:extD
extD:R -- L:extE
extB:B --> T:uie
extC:B --> T:fse
extD:B --> T:dce
extE:B --> T:dse
Database & Data Storage Overview
This section explains how the plugin interacts with both Kobo's database and KOReader's storage system to synchronize reading progress.
The Fundamental Difference
graph TD
subgraph "Kobo's Approach"
A[Single SQLite Database] --> B[All books in one place]
B --> C[Chapter-based positioning]
C --> D[Unknown coordinate format]
end
subgraph "KOReader's Approach"
E[Sidecar files per book] --> F[Distributed storage]
F --> G[Percentage-based positioning]
G --> H[Precise decimal positioning]
end
style A fill:#fff3e0
style E fill:#e1f5ff
Why This Matters
These architectural differences create the core challenge the plugin solves:
- Storage location: Kobo uses one central database, KOReader uses individual files
- Position format: Kobo uses chapter+coordinate system (format unknown), KOReader uses percentages
- Precision: Kobo can point to specific positions within chapters, KOReader tracks overall progress
- Timestamps: Kobo stores ISO 8601 strings in database, KOReader uses file modification times
Quick Reference
Data Flow Summary
sequenceDiagram
participant K as Kobo DB
participant P as Plugin
participant R as KOReader
Note over K,R: Reading State Sync
P->>K: Read: percent, timestamp, status
P->>R: Read: percent_finished, file mtime
P->>P: Compare timestamps
alt Kobo is newer
P->>R: Update percent_finished
Note over R: KOReader gets Kobo's progress
else KOReader is newer
P->>K: Update at chapter boundary
Note over K: Kobo gets KOReader's progress
else Equal
Note over P: No sync needed
end
Key Files in Codebase
| File | Purpose |
|---|---|
src/lib/kobo_state_reader.lua | Reads progress from Kobo database |
src/lib/kobo_state_writer.lua | Writes progress to Kobo database |
src/lib/sync_decision_maker.lua | Decides when/how to sync |
src/reading_state_sync.lua | Coordinates sync operations |
src/metadata_parser.lua | Queries Kobo database for book info |
See the individual topics above for detailed explanations of how each system works and how the plugin bridges them.
Kobo Database
This section covers the Kobo SQLite database and how the plugin interacts with it.
Contents
- Schema - Database structure and field definitions
- Progress Storage - How reading progress is calculated and stored
- Queries - SQL queries used by the plugin
Overview
The Kobo database is a SQLite database located at /mnt/onboard/.kobo/KoboReader.sqlite. It
contains all book metadata, reading progress, and user annotations for books purchased from the Kobo
store or synced through Kobo's ecosystem.
The plugin reads from this database to pull reading progress into KOReader, and writes to it to push KOReader's progress back to Kobo.
Kobo Database Schema
This document describes the Kobo SQLite database schema and the key tables used by the plugin.
Database Location
The Kobo database is located at:
/mnt/onboard/.kobo/KoboReader.sqlite
Key Tables
Content Table
The primary table containing book and chapter information.
Relevant Fields
| Field | Type | Purpose | Example |
|---|---|---|---|
ContentID | TEXT | Unique identifier (PRIMARY KEY) | "0N3773Z7HFPXB" |
ContentType | INTEGER | 6 = Book entry, 9 = Chapter entry | 6 |
BookTitle | TEXT | Book title | "The Great Gatsby" |
Attribution | TEXT | Author information | "F. Scott Fitzgerald" |
___PercentRead | INTEGER | Reading progress (0-100) | 67 |
___FileOffset | INTEGER | Cumulative percentage where chapter starts | 50 (chapter starts at 50%) |
___FileSize | INTEGER | Percentage size of this chapter | 10 (chapter is 10% of book) |
DateLastRead | TEXT | Last reading timestamp (ISO 8601) | "2024-01-15 14:30:00.000+00:00" |
ReadStatus | INTEGER | Reading status code (see below) | 1 |
ChapterIDBookmarked | TEXT | Current chapter bookmark | "chapter1.html#kobo.1.1" |
ReadStatus Codes
0 = Unread/Unopened -- Book never opened
1 = Reading -- Currently reading
2 = Finished -- Book completed
3 = Reading (alt) -- Alternative reading status
ContentType Values
6 = Book entry -- Main book record
9 = Chapter entry -- Individual chapter records
Timestamp Format
Kobo uses ISO 8601 format:
2024-01-15 14:30:00.000+00:00
The plugin converts between Unix timestamps and this format:
-- Parse Kobo timestamp to Unix timestamp
function parseKoboTimestamp(date_string)
local year, month, day, hour, min, sec =
date_string:match("(%d+)-(%d+)-(%d+)[T ](%d+):(%d+):(%d+)")
return os.time({
year = tonumber(year),
month = tonumber(month),
day = tonumber(day),
hour = tonumber(hour),
min = tonumber(min),
sec = tonumber(sec),
})
end
-- Format Unix timestamp to Kobo format
function formatKoboTimestamp(timestamp)
return os.date("!%Y-%m-%d %H:%M:%S.000+00:00", timestamp)
end
Progress Storage in Kobo Database
This document explains how reading progress is stored and calculated in the Kobo database.
Book vs Chapter Entries
Each book has:
- One ContentType=6 entry: The main book record with overall metadata
- Multiple ContentType=9 entries: One for each chapter
graph TD
B[Book Entry<br/>ContentType=6<br/>ContentID: 0N3773Z7HFPXB] --> C1[Chapter 1<br/>ContentType=9<br/>ContentID: 0N3773Z7HFPXB!!chapter1.html]
B --> C2[Chapter 2<br/>ContentType=9<br/>ContentID: 0N3773Z7HFPXB!!chapter2.html]
B --> C3[Chapter 3<br/>ContentType=9<br/>ContentID: 0N3773Z7HFPXB!!chapter3.html]
style B fill:#e1f5ff
style C1 fill:#fff3e0
style C2 fill:#fff3e0
style C3 fill:#fff3e0
Progress Calculation
Progress is calculated based on percentage ranges of chapters:
-- ___FileOffset = cumulative percentage where chapter starts (stored by Kobo)
-- ___FileSize = percentage size of the chapter
-- ___PercentRead (in chapter entry) = progress within this chapter (0-100)
-- To calculate overall progress, the plugin uses ___FileOffset directly:
-- 1. Looks up the current chapter entry
-- 2. Gets ___FileOffset (where chapter starts)
-- 3. Adds current chapter contribution: (chapter_size * chapter_percent / 100)
-- Example calculation:
chapter_offset = current_chapter.___FileOffset -- Where chapter starts
current_chapter_contribution = (current_chapter.___FileSize * current_chapter.___PercentRead) / 100
overall_percent = chapter_offset + current_chapter_contribution
Example:
Book: 3 chapters (ordered by ___FileOffset)
Chapter 1: ___FileOffset=0, ___FileSize=30 (spans 0-30%) [completed]
Chapter 2: ___FileOffset=30, ___FileSize=40 (spans 30-70%) [reading at 50%]
Chapter 3: ___FileOffset=70, ___FileSize=30 (spans 70-100%) [not started]
Calculation:
- Completed chapters: 30% (Chapter 1)
- Current chapter: 40 * 0.5 = 20%
- Overall: 30 + 20 = 50%
Important:
___FileOffsetand___FileSizeare percentage values, not byte counts- All chapter
___FileSizevalues sum to 100
Why Chapter Boundaries?
The plugin can only write progress at chapter boundaries because:
- Kobo's chapter structure: Each chapter is a separate database row (ContentType=9)
- Bookmark format limitation: The
ChapterIDBookmarkedformat is"chapter.html#kobo.x.y"wherex.yare some form of coordinates (assumed to be in the HTML document, but exact format is unknown) - Coordinate mapping unknown: There is no known way to convert KOReader's position data into
the
x.yformat that Kobo uses - Chapter-level precision: The plugin defaults to
#kobo.1.1(start of chapter) for all bookmarks
When syncing to Kobo, the plugin:
- Finds the chapter that contains the target percentage
- Sets
ChapterIDBookmarkedto that chapter's ID - Updates that chapter's
___PercentReadto reflect position within the chapter - Updates the main book entry's
___PercentReadto the overall percentage
graph LR
A[KOReader Position<br/>67.3%] --> B[Find Chapter<br/>Chapter with ___FileOffset ≤ 67]
B --> C[Found: Chapter 3<br/>___FileOffset=60, ___FileSize=20]
C --> D[Calculate within-chapter %<br/>67 - 60 / 20 = 35%]
D --> E[Update Chapter 3<br/>___PercentRead = 35]
E --> F[Update Book Entry<br/>___PercentRead = 67]
F --> G[Set Bookmark<br/>ChapterIDBookmarked = chapter3.html#kobo.1.1]
Database Queries
This document lists all SQL queries used by the plugin to interact with the Kobo database.
Reading Progress (Pull from Kobo)
-- Main book query
SELECT DateLastRead, ReadStatus, ChapterIDBookmarked, ___PercentRead
FROM content
WHERE ContentID = ? AND ContentType = 6
LIMIT 1
-- Chapter lookup (to calculate exact progress using ___FileOffset directly)
SELECT ContentID, ___FileOffset, ___FileSize, ___PercentRead
FROM content
WHERE ContentID LIKE '?%' AND ContentType = 9
AND (ContentID LIKE '%?' OR ContentID LIKE '%?#%')
LIMIT 1
Writing Progress (Push to Kobo)
-- Find target chapter using ___FileOffset
SELECT ContentID, ___FileOffset, ___FileSize
FROM content
WHERE ContentID LIKE '?%' AND ContentType = 9
AND ___FileOffset <= ?
ORDER BY ___FileOffset DESC
LIMIT 1
-- Fallback: Get last chapter (if position is beyond all chapters)
SELECT ContentID
FROM content
WHERE ContentID LIKE '?%' AND ContentType = 9
ORDER BY ___FileOffset DESC
LIMIT 1
-- Update main book entry
UPDATE content
SET ___PercentRead = ?,
DateLastRead = ?,
ReadStatus = ?,
ChapterIDBookmarked = ?
WHERE ContentID = ? AND ContentType = 6
-- Update current chapter entry
UPDATE content
SET ___PercentRead = ?
WHERE ContentID = ? AND ContentType = 9
Data Flow Diagram
sequenceDiagram
participant P as Plugin
participant DB as Kobo Database
participant BE as Book Entry<br/>(ContentType=6)
participant CE as Chapter Entries<br/>(ContentType=9)
Note over P: Reading from Kobo
P->>DB: Query book entry
DB->>BE: SELECT DateLastRead, ReadStatus, ChapterIDBookmarked, ___PercentRead
BE-->>P: Return book data
P->>DB: Query chapters
DB->>CE: SELECT ContentID, ___FileOffset, ___FileSize, ___PercentRead
CE-->>P: Return chapter data
P->>P: Calculate total progress<br/>from chapter offsets/sizes
Note over P: Writing to Kobo
P->>P: Find target chapter<br/>for percentage
P->>DB: Update chapter entry
DB->>CE: UPDATE ___PercentRead
P->>DB: Update book entry
DB->>BE: UPDATE ___PercentRead, DateLastRead,<br/>ReadStatus, ChapterIDBookmarked
KOReader Data Storage
This section covers how KOReader stores reading progress and metadata.
Contents
- DocSettings - Sidecar file structure and percent calculation
- ReadHistory - Timestamp tracking and challenges
- Data Flow - How data moves between systems
Overview
KOReader stores reading progress in "sidecar" files alongside each book. These are Lua table files that contain the reading position, status, and other metadata.
Unlike Kobo's centralized database, KOReader uses a distributed approach where each book has its own metadata file. The plugin reads from these files to push progress to Kobo, and writes to them when pulling progress from Kobo.
KOReader DocSettings
This document describes how KOReader stores reading progress in sidecar files.
DocSettings (Sidecar Files)
KOReader stores reading progress in "sidecar" files alongside the book files. These are Lua tables serialized to disk.
File Location
For a book at /mnt/onboard/.kobo/kepub/book.epub, the sidecar is at:
/mnt/onboard/.kobo/kepub/book.sdr/metadata.epub.lua
Key Fields
{
-- Core progress data
percent_finished = 0.673, -- 0.0 to 1.0 (67.3% read)
last_percent = 0.673, -- Last known percent
-- Status and metadata
summary = {
status = "reading", -- "reading", "complete", or "finished"
modified = "2024-01-15", -- Last modification date
},
-- Page/position data (depends on document type)
last_xpointer = "/body/div[2]/p[15]", -- Position in EPUB
page = 42, -- Current page number (PDFs)
-- Timestamps (stored by ReadHistory, not in sidecar directly)
-- See ReadHistory section below
}
How KOReader Calculates Percent
The percent_finished field is calculated differently based on document type:
EPUB (Reflowable)
-- Position is tracked by XPointer (path in DOM tree)
-- Percentage = (current_position_bytes / total_document_bytes)
-- Example:
percent_finished = 0.673 -- 67.3% through the document
PDF (Fixed Layout)
-- Position is tracked by page number
-- Percentage = (current_page / total_pages)
-- Example:
-- Page 42 of 100 pages
percent_finished = 0.42 -- 42%
ReadHistory and Timestamps
This document explains how KOReader tracks reading history and the challenges of determining accurate timestamps.
ReadHistory
KOReader maintains a separate ReadHistory object that tracks:
- When books were last opened
- Reading duration
- Final reading position
Location
-- Global ReadHistory module
require("readhistory")
Data Structure
ReadHistory.hist = {
["/path/to/book.epub"] = {
file = "/path/to/book.epub",
time = 1705330200, -- Unix timestamp of last access
},
}
Timestamp Challenges
The plugin needs to determine "when was this book last read" to compare with Kobo's timestamp.
Source of Timestamp
The plugin uses ReadHistory only as the source of truth for KOReader timestamps:
ReadHistory.hist = {
["/path/to/book.epub"] = {
file = "/path/to/book.epub",
time = 1705330200, -- Unix timestamp from ReadHistory
},
}
Critical Validation: Sidecar File Check
A ReadHistory timestamp is only valid if the sidecar file exists:
function getValidatedKOReaderTimestamp(doc_path)
-- 1. Get timestamp from ReadHistory
local kr_timestamp = getKOReaderTimestampFromHistory(doc_path)
if kr_timestamp == 0 then
return 0
end
-- 2. Validate that sidecar file exists
local has_sidecar = DocSettings:hasSidecarFile(doc_path)
-- 3. Return 0 if no sidecar (no actual reading progress)
if not has_sidecar then
-- This ensures PULL from Kobo when KOReader has no valid data
return 0
end
return kr_timestamp
end
Why Sidecar Validation Matters
Without a sidecar (.sdr) file, there's no actual reading progress in KOReader. A ReadHistory entry
without a sidecar is unreliable:
- Could be from after a reset that deleted the
.sdrfile - Could be a stale entry from a deleted book
By returning 0 when no sidecar exists, the plugin ensures:
- PULL from Kobo: Kobo's timestamp will always be newer than
0
Data Flow and Status Mapping
This document shows how data flows between the plugin and KOReader, and how status values are converted.
Data Flow: Reading from KOReader
sequenceDiagram
participant P as Plugin
participant DS as DocSettings
participant RH as ReadHistory
participant FS as File System
P->>DS: readSetting("percent_finished")
DS-->>P: 0.673
P->>DS: readSetting("summary")
DS-->>P: {status: "reading"}
P->>RH: Get timestamp for book
RH-->>P: 1705330200 or 0
P->>FS: Check if sidecar exists
FS-->>P: true/false
alt Sidecar exists
P->>P: Use ReadHistory timestamp<br/>= 1705330200
else No sidecar
P->>P: Return 0<br/>(ensures PULL from Kobo)
end
Data Flow: Writing to KOReader
sequenceDiagram
participant P as Plugin
participant DS as DocSettings
participant BL as BookList Cache
Note over P: Syncing 67% from Kobo
P->>DS: saveSetting("percent_finished", 0.67)
P->>DS: saveSetting("last_percent", 0.67)
P->>DS: Read current summary
DS-->>P: {status: "reading", modified: "2024-01-14"}
P->>P: Update status if needed<br/>(67% -> still "reading")
P->>DS: saveSetting("summary", updated_summary)
P->>DS: flush() (write to disk)
P->>BL: Update UI cache
Note over BL: BookList widget will show 67%
Status Mapping
KOReader and Kobo use different status values:
| KOReader | Kobo ReadStatus | Meaning |
|---|---|---|
"reading" | 1 | In progress |
"complete" | 2 | Finished |
"finished" | 2 | Finished (alternative) |
| (not set) | 0 | Unopened |
-- Converting Kobo -> KOReader
function StatusConverter.koboToKoreader(kobo_status)
if kobo_status == 0 then return nil end -- Unopened
if kobo_status == 2 then return "complete" end -- Finished
return "reading" -- 1 or 3
end
-- Converting KOReader -> Kobo
function StatusConverter.koreaderToKobo(kr_status)
if kr_status == "complete" or kr_status == "finished" then
return 2 -- Finished
end
return 1 -- Reading
end
Example: Complete Sync Flow
sequenceDiagram
participant K as Kobo DB
participant P as Plugin
participant DS as DocSettings
participant RH as ReadHistory
Note over P: User opens book in virtual library
P->>K: Read Kobo state
K-->>P: 45%, timestamp: 1705300000
P->>DS: Read KOReader state
DS-->>P: percent_finished: 0.67
P->>RH: Get last read time
RH-->>P: 1705330000
P->>P: Compare timestamps<br/>KOReader newer (1705330000 > 1705300000)
Note over P: Decision: Push KOReader -> Kobo
P->>K: Write 67% to Kobo<br/>at chapter boundary
K-->>P: Success
Note over K: Kobo now shows 67%<br/>at start of chapter 2
Sync Decision Logic
This document explains how the plugin decides when to sync, in which direction, and how conflicts are resolved.
Sync Triggers
The plugin performs sync operations at specific times:
1. Virtual Library Access (Auto-sync)
graph TD
A[Open Virtual Library] --> B{Auto-sync enabled?}
B -->|Yes| C[For each book]
B -->|No| D[Just show library]
C --> E[Compare timestamps]
E --> F{Which is newer?}
F -->|Kobo| G[PULL: Kobo -> KOReader]
F -->|KOReader| H[PUSH: KOReader -> Kobo]
F -->|Equal| I[Skip sync]
2. Document Close
graph TD
A[Close Document] --> B{Is Kepub?}
B -->|No| C[Normal close]
B -->|Yes| D{Auto-sync enabled?}
D -->|No| E[Skip sync]
D -->|Yes| F[PUSH: KOReader -> Kobo]
F --> G[Update Kobo with current time]
3. Manual Sync
graph TD
A[User triggers manual sync] --> B[Read both states]
B --> C[Compare timestamps]
C --> D{Which is newer?}
D -->|Kobo| E[PULL: Kobo -> KOReader]
D -->|KOReader| F[PUSH: KOReader -> Kobo]
D -->|Equal| G[No sync needed]
Timestamp Comparison
The core of sync decision-making is comparing timestamps:
function syncBidirectional(book_id, doc_settings)
-- Read both states
local kobo_state = readKoboState(book_id)
local kr_percent = doc_settings:readSetting("percent_finished") or 0
local kr_timestamp = getValidatedKOReaderTimestamp(doc_path)
-- Check if both sides are complete
if areBothSidesComplete(kobo_state, kr_percent, kr_status) then
return false -- Skip sync
end
-- Compare timestamps
if kobo_state.timestamp > kr_timestamp then
-- Kobo is newer -> PULL
return executePullFromKobo(book_id, doc_settings, kobo_state, kr_percent, kr_timestamp)
end
-- KOReader is newer (or equal) -> PUSH
return executePushToKobo(book_id, doc_settings, kobo_state, kr_percent, kr_timestamp)
end
Special Cases
Both Sides Complete
If both systems show the book as finished, skip sync to avoid unnecessary writes:
function areBothSidesComplete(kobo_state, kr_percent, kr_status)
local kobo_complete = (kobo_state.status == "complete") or
(kobo_state.percent_read >= 100)
local kr_complete = (kr_status == "complete") or
(kr_status == "finished") or
(kr_percent >= 1.0)
return kobo_complete and kr_complete
end
-- Usage in sync logic
if areBothSidesComplete(kobo_state, kr_percent, kr_status) then
logger.info("Both sides complete, skipping sync")
return false
end
Unopened Books (ReadStatus=0)
Books that have never been opened in Kobo's native reader need special handling:
-- When PULLING from Kobo
if kobo_state.kobo_status == 0 and kobo_state.percent_read == 0 then
-- Book is unopened in Kobo, don't sync TO KOReader
-- (Would overwrite KOReader progress with 0%)
logger.info("Skipping sync for unopened book")
return false
end
-- When PUSHING to Kobo
-- Always allow! This is how users start reading in KOReader first
This asymmetry is important:
- PULL: Don't overwrite KOReader progress with Kobo's 0%
- PUSH: Always allow KOReader to update Kobo (user may have started in KOReader)
Missing Sidecar File
If a book has ReadHistory but no sidecar file, the ReadHistory timestamp is considered unreliable:
function getValidatedKOReaderTimestamp(doc_path)
local kr_timestamp = getKOReaderTimestampFromHistory(doc_path)
if kr_timestamp == 0 then
return 0
end
-- Check if sidecar file exists
local has_sidecar = DocSettings:hasSidecarFile(doc_path)
if not has_sidecar then
-- No sidecar = book never actually read in KOReader
-- ReadHistory might be from just opening/previewing
logger.dbg("No sidecar file found - ignoring ReadHistory timestamp")
return 0
end
-- Sidecar exists, return the ReadHistory timestamp
return kr_timestamp
end
This ensures that if KOReader has no actual reading progress data, Kobo's timestamp will be considered newer (or equal), triggering a PULL from Kobo.
User Approval Dialogs
Depending on settings, the plugin may prompt the user before syncing:
function syncIfApproved(from_kobo, to_kobo, sync_callback, sync_details)
-- Check if user wants a prompt for this direction
local needs_approval = false
if from_kobo and self.settings.from_kobo_prompt then
needs_approval = true
end
if to_kobo and self.settings.to_kobo_prompt then
needs_approval = true
end
if needs_approval then
-- Show dialog with details
showSyncDialog(sync_details, function(approved)
if approved then
sync_callback()
end
end)
else
-- Silent sync
sync_callback()
end
end
Conflict Resolution Flow
graph TD
A[Sync Triggered] --> B[Read Both States]
B --> C{Both complete?}
C -->|Yes| D[Skip sync]
C -->|No| E{Compare timestamps}
E --> F{Which newer?}
F -->|Kobo newer| G{Kobo unopened?}
F -->|KOReader newer| K[PUSH to Kobo]
F -->|Equal| L[No sync needed]
G -->|Yes| H[Skip sync<br/>Keep KOReader state]
G -->|No| I{User approval needed?}
I -->|Yes| J[Show dialog]
I -->|No| M[PULL from Kobo]
J --> N{User approves?}
N -->|Yes| M
N -->|No| O[Cancel sync]
K --> P{User approval needed?}
P -->|Yes| Q[Show dialog]
P -->|No| R[Write to Kobo]
Q --> S{User approves?}
S -->|Yes| R
S -->|No| T[Cancel sync]
Why Chapter Boundaries?
When syncing TO Kobo, the position is rounded to chapter boundaries. This is a technical limitation, not a design choice.
The Problem
KOReader tracks position as:
percent_finished = 0.673 -- 67.3% through the book
Kobo tracks position as:
ChapterIDBookmarked = "chapter2.html#kobo.3.5"
-- Where "3.5" are coordinates (format unknown, possibly HTML-based)
The coordinate format is unknown. While KOReader tracks its own position data:
- Overall percentage (0.0 to 1.0)
- Current page in the document
- Internal positioning information
There is no known way to convert this into the x.y coordinate format that Kobo uses.
The Solution
The plugin:
- Calculates which chapter contains the 67.3% position
- Finds that chapter in Kobo's database using SQL
- Sets the bookmark to the start of that chapter
function findChapterForPercentage(conn, book_id, percent_read)
-- Use SQL to find the chapter that starts at or before the target position
-- ___FileOffset is already the cumulative percentage (calculated by Kobo)
local chapters_res = conn:exec(
"SELECT ContentID, ___FileOffset, ___FileSize FROM content " ..
"WHERE ContentID LIKE '" .. book_id .. "%' " ..
"AND ContentType = 9 " ..
"AND ___FileOffset <= " .. percent_read .. " " ..
"ORDER BY ___FileOffset DESC LIMIT 1"
)
if chapters_res and chapters_res[1] and #chapters_res[1] > 0 then
local chapter_id = chapters_res[1][1]
local chapter_offset = chapters_res[2][1]
local chapter_size = chapters_res[3][1]
-- Calculate progress within this chapter
local within_chapter = percent_read - chapter_offset
local chapter_percent = (within_chapter / chapter_size) * 100
-- Return the chapter bookmark
return chapter_id .. "#kobo.1.1" -- Start of chapter (default coordinate)
end
end
Why Not Calculate the Coordinates?
- Format unknown: The exact meaning and calculation of the
x.ycoordinate format used by Kobo is not documented or known - No conversion method: There is no known way to map KOReader's position data to Kobo's coordinate system
- Chapter-level fallback: Using
#kobo.1.1(start of chapter) is the safe default - Progress still tracked: The chapter's
___PercentReadfield stores within-chapter progress percentage
The Result
graph LR
A[KOReader<br/>67.3%<br/>Mid-chapter] -->|Sync to Kobo| B[Kobo<br/>67%<br/>Chapter start]
B -->|Sync back to KOReader| C[KOReader<br/>67.0%<br/>Chapter start]
style A fill:#e1f5ff
style B fill:#fff3e0
style C fill:#e1f5ff
Full Sync Decision Pseudocode
function syncBidirectional(book_id, doc_settings)
-- 1. Read states
local kobo_state = readKoboState(book_id)
local kr_percent = doc_settings:readSetting("percent_finished") or 0
local kr_timestamp = getValidatedKOReaderTimestamp(doc_path)
local kr_status = doc_settings:readSetting("summary").status
-- 2. Check if both complete
if areBothSidesComplete(kobo_state, kr_percent, kr_status) then
return false -- Skip sync
end
-- 3. Compare timestamps
if kobo_state.timestamp > kr_timestamp then
-- Kobo is newer -> PULL
return executePullFromKobo(book_id, doc_settings, kobo_state, kr_percent, kr_timestamp)
end
-- KOReader is newer or equal -> PUSH
return executePushToKobo(book_id, doc_settings, kobo_state, kr_percent, kr_timestamp)
end
Configuration Options
Users can control sync behavior through settings:
| Setting | Purpose | Default |
|---|---|---|
sync_enabled | Enable/disable all sync | ON |
auto_sync_enabled | Auto-sync on library access | ON |
from_kobo_enabled | Allow PULL from Kobo | ON |
to_kobo_enabled | Allow PUSH to Kobo | ON |
from_kobo_prompt | Prompt before PULL | SILENT |
to_kobo_prompt | Prompt before PUSH | SILENT |
These settings create different sync strategies:
- Kobo → KOReader only: Disable
to_kobo_enabled - KOReader → Kobo only: Disable
from_kobo_enabled - Manual control: Enable prompts for both directions
- Fully automatic: Disable all prompts (recommended)
See Settings Documentation for details.