Skip to content

Latest commit

 

History

History
401 lines (279 loc) · 23.5 KB

File metadata and controls

401 lines (279 loc) · 23.5 KB

TomeSync KOReader Plugin

Syncs reading progress and sessions between KOReader and your Tome library.


Setup

  1. Open Tome in your browser at http://<your-server>:<port>/settings
  2. Scroll to the KOReader section
  3. Click Download Plugin
  4. Extract the ZIP to koreader/plugins/ on your device so you have koreader/plugins/tomesync.koplugin/main.lua
  5. Restart KOReader

The plugin is pre-configured with your server URL and API key. No manual configuration needed.

Important: Download the plugin from the same address your e-reader can reach. If your server is at <your-server-ip>:8080, open the settings page at that address before downloading.


Features

  • Reading sync -- position, progress, and reading sessions sync between KOReader and Tome's web reader
  • Rating sync -- KOReader's native star rating and review sync both ways with Tome
  • Reading-history import -- backfill Tome's Stats with KOReader's own per-page reading history from before TomeSync (reading time and pages only -- never your read/unread status)
  • Series browser -- browse your library's series and download entire series to your device in one tap
  • Offline-safe -- everything works seamlessly when your server is unreachable; sessions queue and flush later
  • Context-aware menu -- different options when a book is open vs. from the home screen

How It Works

Book Matching

When you open a book, the plugin tries to match the file to a book in your Tome library:

  1. Checks its local cache for a known mapping
  2. If not cached, sends the filename to GET /api/tome-sync/resolve?filename=<name>
  3. The server matches by file path, title, and volume number (e.g. Vol. 1 in the filename matches series_index = 1)
  4. Once resolved, the mapping is cached locally so future opens are instant

Books downloaded via OPDS are matched automatically. Books sideloaded from other sources will also match as long as the filename contains the book title.

If no match is found, the plugin silently skips sync for that book.

When It Syncs

Event What happens
Open a book Pulls latest position from server. If the server is ahead, jumps to that position. Also reconciles the book's rating with Tome.
Every 50 page turns Pushes current position to server (heartbeat).
Close the lid (suspend) Pushes position, records a reading session, and pushes a rating you set this session.
Open the lid (resume) Starts a new session, pushes position, and flushes any pending offline sessions.
Close a book Pushes final position, records a reading session, and pushes a rating you set this session.
"Sync now" in menu Manual push of current position and flushes pending offline sessions.

Web Reader → KOReader Sync

Progress saved in Tome's built-in web reader is written to TomeSyncPosition on the server. When you next open the same book in KOReader, the plugin detects the server is ahead and jumps to the position you left off at in the browser. This makes the web reader and KOReader fully bidirectional — you can switch between them without losing your place.

Reading Sessions

Every time you close the lid or close a book, the plugin records a session with:

  • Start and end timestamps
  • Duration in seconds
  • Progress at start and end (percentage)
  • Number of page turns
  • Device name

Sessions shorter than 10 seconds are filtered out. Each session has a unique ID to prevent duplicates.

These sessions power the Stats page in Tome, showing reading time, streaks, and charts.

Reading-History Import

KOReader keeps its own per-page reading log in statistics.sqlite3 — often going back years, long before you installed TomeSync. The plugin can import that log into Tome so your Stats page reflects reading from before TomeSync existed.

  • Enable TomeSync > Auto-sync reading history on launch, or run it once from TomeSync > Sync reading history (also assignable to a gesture).
  • The first sync pushes your entire history; it is chunked and resumable — the server tracks a per-device watermark and ignores duplicates, so it survives the device sleeping or Wi-Fi dropping mid-sync. Later syncs send only new reading.
  • It imports reading time and pages only — it never changes your read/unread status. That stays yours to set (only the live progress sync above moves status).
  • Books are matched to your library by title/series/volume; anything the matcher can't place confidently is left out rather than guessed at, so nothing wrong appears in your stats. Multiple devices each sync their own history without double-counting the same reading.

Reading Status

The server automatically updates the book's reading status based on progress:

  • unread to reading when progress > 0%
  • reading to read when progress >= 99%

Series Download

The plugin can browse and download entire series directly to your device.

From the home screen (no book open):

  1. Open the wrench menu, find TomeSync > Browse series
  2. A list shows all series in your Tome library with book counts and authors
  3. Tap a series to download all books in it

From inside a book:

  • Download full series -- downloads every book in the current book's series
  • Download rest of series -- downloads only books after your current volume (based on series_index)

How downloads work:

  • Books are organized by book type and series: <home_dir>/<book_type>/<Series Name>/ (e.g. books/manga/One Piece/, books/light_novel/Black Summoner/). The base directory is KOReader's home folder if set, falling back to download_dir then lastdir.
  • The book type (manga, light_novel, book, comic) comes from Tome's metadata
  • Filenames mirror Tome's OPDS naming: Vol. N — Title.ext for volumes in a series, plain Title.ext otherwise. This keeps light-novel volumes that share a title from clobbering each other.
  • Format preference: epub > kepub.epub > cbz > pdf > mobi > azw3
  • Books already on the device are skipped (matched by book ID, not filename)
  • A live progress popup shows "Downloading N of M — Title" as each book is fetched
  • Downloaded books are automatically registered in the book map, so sync works immediately when you open them
  • A summary shows downloaded/skipped/failed counts when finished

Download location & naming

By default downloads are filed as described above (<book_type>/<Series Name>/Vol. N — Title.ext, standalones under their author). Settings → Download location & naming changes this. The setting applies to every way books reach the device — the series browser, the in-book series downloads, and the Send-to-KOReader inbox all share one downloader. It is per-device and stored in KOReader's own settings, so your phone can use a different layout than your e-reader, and it survives plugin updates.

Three options:

  • Default (type and series folders) — the layout above. This is also what runs when no template is set, on exactly the same code path as before the setting existed.
  • Flat in home folder — every book lands directly in the home folder, named Series - NN - Title.ext. Series and volume are dropped for books that have none, so a standalone is just Title.ext. For people who keep everything unsorted in one place.
  • Custom template — build the path yourself, Sonarr/Radarr-style.

Custom templates

A template is a free-form string: tokens, literal text, and / folder separators in any combination. The dialog pre-fills a suggestion ({book_type}/{series}/{volume:00} - {title}) purely as a starting point — delete it and write anything. Tokens can repeat, literals can appear anywhere, folders can nest as deep as you like.

Token reference (the complete set — anything else is rejected):

Token Renders as Empty when
{book_type} Tome book-type slug (manga, light_novel, novel, ...) never (falls back to book)
{series} series name book has no series
{volume} series index as-is (3, 12, 1.5) book has no series index
{volume:00} zero-padded series index (03); pad width = number of zeros, so {volume:000} gives 003. Fractional volumes (1.5) are never padded. book has no series index
{title} book title never
{author} author name author unknown

Case modifiers: wrap a token name in Lower(...) or Upper(...){Lower(series)}, {Upper(book_type)}. No inner braces: {Lower({series})} is not valid syntax.

Rules:

  • / starts a new folder. Everything between slashes becomes one folder (or, for the last part, the filename).
  • The file extension (.epub, .cbz, ...) is appended automatically — never write it in the template.
  • Anything that isn't a token is literal text and is kept verbatim.

Examples (Berserk vol. 3, "The Egg of the King", Kentaro Miura, type manga):

Template Result
{title} The Egg of the King.epub
{series} - {volume:00} - {title} Berserk - 03 - The Egg of the King.epub (this is the flat preset)
Tome/{series}/{title} Tome/Berserk/The Egg of the King.epub — literal folder name
{author}/{series} {volume:000} {title} Kentaro Miura/Berserk 003 The Egg of the King.epub
{Upper(series)}/vol{volume} - {Lower(title)} BERSERK/vol3 - the egg of the king.epub
{book_type}/{series}/{series} v{volume} manga/Berserk/Berserk v3.epub — tokens may repeat

Books without a series (or volume, or author): empty tokens disappear, together with orphaned separators around them — spaces, -, _ and , left dangling by a vanished token are tidied up, and a folder segment that renders entirely empty is dropped. So one template serves series books and standalones:

Template Series book Standalone
{series} - {volume:00} - {title} Berserk - 03 - The Egg of the King.epub Frankenstein.epub
{book_type}/{series}/{volume:00} - {title} manga/Berserk/03 - The Egg of the King.epub novel/Frankenstein.epub (series folder dropped)

One caveat: the cleanup removes orphaned separators, not orphaned words. A literal glued to a token — Vol. {volume} — leaves Vol behind when the book has no volume (Vol - Title.epub). If your library mixes series books and standalones, keep bare separators around tokens that can be empty ({volume:00} - {title}), the way both presets do. There is no conditional syntax ("include this text only when the token exists") — if you need that, open an issue.

Validation and safety:

  • Templates are validated when you tap Save: an unknown token (or a template that renders to nothing) is rejected on the spot with an error, and a valid template shows a preview of the resulting filename. A typo can't silently misfile your downloads.
  • Every path segment is sanitized for the filesystem, and .. segments are discarded — a template can never place files outside the download base folder.
  • A / inside a metadata value (a title like Fate/Zero) becomes -; only slashes you write in the template create folders.
  • If a saved template somehow renders empty for a particular book (e.g. it only uses {series} and the book has none), that book falls back to the default layout rather than failing.
  • Leave the dialog's input empty and save to clear the template and restore the default layout.

Changing the setting later only affects future downloads: books already on the device are remembered by book ID and stay skipped regardless of where they live, and previously downloaded files are not moved.


Offline and Unreachable Server

The plugin is designed to work seamlessly when your server is not reachable -- whether you are on a plane, commuting without VPN access, or your server is simply down.

What happens when you are offline

  • No WiFi at all: Every sync request is skipped instantly. There is zero delay or freezing. You will not notice anything different while reading.
  • WiFi connected but server unreachable (e.g. public WiFi without VPN): Requests time out after 5 seconds. After 3 consecutive failures, the plugin stops trying for the rest of the session. No further delays.
  • Reading sessions are saved locally: If a session cannot be sent to the server (because you are offline when you close the lid), it is saved to disk and retried automatically the next time you open the lid with a working connection.

Typical commute flow

  1. You start reading on the train (no server access)
  2. All sync silently skips -- reading is completely uninterrupted
  3. You close the lid -- the session is saved locally on your device
  4. You get home, open a book -- within the first 50 page turns, the plugin flushes all saved sessions to the server
  5. Your Stats page and reading progress are up to date

Sessions also flush when you tap "Sync now" in the menu, or when WiFi reconnects and you resume reading.

Limits

  • Up to 50 sessions can be saved locally. If you go offline for an extremely long time, the oldest sessions are dropped to prevent unbounded storage use.
  • Sessions saved locally survive KOReader restarts -- they are stored in KOReader's settings file.
  • The backoff counter (3 failures before giving up) resets automatically on the next successful request. You can also reset it manually via Settings → Test connection in the plugin menu.

Menu

The plugin menu is context-aware. It self-registers in the wrench menu (after Calibre) under TomeSync and shows different options depending on whether a book is open.

Always visible

Option Description
Browse series Opens the series browser. Lists all series with book count and author. Tap to download.
Sync reading history Imports KOReader's per-page reading log into Tome's Stats (time and pages only). First run backfills everything; chunked and resumable. See Reading-History Import.
Settings Submenu with persistent options and diagnostics (see below).
About Version info (semver + build).

Only when a book is open

Option Description
Download full series Downloads all books in the current book's series.
Download rest of series Downloads books after the current volume only.
Sync now Pushes current position, highlights/notes, and flushes any pending offline sessions.
Tracking: on / paused Pauses automatic session tracking and syncing for the current KOReader run. Resets to on at the next start — it is not a permanent setting. Manual actions (Sync now, downloads) still work while paused.
Pending sessions (N) Shows how many sessions are queued for sync. Tap for details.

Settings submenu

Option Description
Auto-connect WiFi when needed Opt-in toggle, off by default. When a TomeSync action needs the server and WiFi is down, KOReader re-establishes the connection first (honouring your KOReader WiFi prompt/auto-enable setting), then runs the action. Helps devices that aggressively sleep WiFi, e.g. PocketBook. Only user-initiated actions reconnect — background tracking never turns the radio on.
Download location & naming Where downloads (series, inbox) are filed: the default type/series layout, flat in the home folder, or a custom token template. Per-device. See Download location & naming.
Test connection Verifies the server is reachable. Also resets the backoff counter if the server was previously unreachable.
Re-resolve all books Wipes the local filename-to-book-ID cache. Use if a book matched incorrectly.
Check for updates Fetches the latest plugin build from your server and installs it if newer (then prompts to restart).
Auto-check for updates on launch Opt-in toggle. When on, TomeSync checks for updates shortly after startup and prompts only when one is available.
Auto-sync reading history on launch Opt-in toggle, off by default. When on, TomeSync pushes new KOReader reading history to Tome shortly after startup (the first run backfills your whole history; chunked and resumable). Reading time and pages only — never your read/unread status. See Reading-History Import.

Gestures

TomeSync registers bindable gesture actions (KOReader Settings → Taps and gestures → Gesture manager, listed under General — available in both the reader and the file manager):

Action Does
TomeSync: Open menu Pops the full context-aware TomeSync menu as a standalone popup.
TomeSync: Browse series Jumps straight to the series browser/downloader.
TomeSync: Sync highlights Pushes the current book's highlights and notes to Tome immediately.
TomeSync: Sync reading history Imports KOReader's per-page reading history into Tome's Stats (time and pages only).

Highlights & notes sync

Highlights and notes you make in KOReader sync to Tome — and to your other KOReader devices. They also appear in a Highlights & Notes section on the book's detail page (the highlighted text, your note, and the chapter). Sync happens automatically alongside position sync — when you open a book (pulls others' highlights), on suspend, on closing the book, and via Sync now — and can be triggered manually with the TomeSync: Sync highlights gesture.

How conflicts resolve. Identity is the highlight's position, so the same passage highlighted on two devices is one highlight. Edits to the same highlight's note resolve last-write-wins (newest edit). Deletes propagate via tombstones — a highlight you remove on one device is removed on the others and won't reappear. Device→device sync needs both devices to have the Tome-served copy of the book (identical file → matching positions); a sideloaded different copy won't line up.

Rendering highlights inside the web reader is a separate, later step — for now the web side shows them as a list on the detail page.


Ratings & reviews sync

KOReader has its own Book status screen with a 1–5 star rating and a review (long-press a book → Book status, or the end-of-book screen). The plugin keeps that in step with the rating and review on Tome's book detail page, both ways:

  • Rate a book on the web and the next time you open it in KOReader (for a book that came from Tome), the stars and review are written into the book.
  • Rate a book in KOReader and it flows up to Tome when you close or suspend.

Ratings are per user and private to you. Reading status (reading / finished) is left untouched — that already syncs separately via reading progress.

How conflicts resolve. The plugin remembers the last value it synced for each book, so normally only the side that actually changed is sent. If a book's rating changed on both the web and the device since the last sync, Tome wins — it is the single source of truth. As with highlights, this needs the Tome-served copy of the book so the plugin can match it to the right library entry; a sideloaded different copy won't sync its rating.


API Keys

The plugin authenticates with an API key (Authorization: Bearer tk_...), not your login password. Keys are managed in Tome's settings page:

  • A key is auto-created when you first download the plugin
  • You can create additional keys or revoke existing ones
  • Revoking a key immediately disables any plugin using it

Updating the Plugin

Once the plugin is installed, it updates itself — open the TomeSync menu and tap Settings → Check for updates (or enable Auto-check for updates on launch). It downloads the latest build from your server, swaps it in atomically, and asks you to restart. Your server URL, API key, and reading history carry over automatically.

The plugin version is shown on the Settings page next to "TomeSync Plugin" (v<semver> (build N)); the build integer is what drives update comparisons.

How self-update can't brick the plugin

The plugin is split into a frozen shim (main.lua, never replaced) and a replaceable implementation (main_impl.lua). On every load the shim runs a rollback state machine backed by main_impl.lua.bak:

  • A download that fails validation (too small, doesn't compile, missing sentinels) is rejected before anything is swapped — files untouched.
  • A syntax-broken update fails to load and is rolled back on the same launch.
  • An init-crashing update doesn't confirm itself, so the shim rolls it back on the next launch.
  • If even the backup is unusable, the shim loads a valid inert stub ("TomeSync failed to load — reinstall"); KOReader itself is never affected.

Manual install (first deploy / recovery)

The first build carrying the shim+impl must be installed by hand once (every update after that is in-app). This is also the fallback if you ever need to force a clean reinstall.

Via SSH (recommended for Kindle)

# 1. Download plugin ZIP from Tome Settings page, unzip it
# 2. Deploy in one command:
ssh root@<kindle-ip> "rm -rf /mnt/us/koreader/plugins/tomesync.koplugin" && \
scp -r tomesync.koplugin root@<kindle-ip>:/mnt/us/koreader/plugins/
# 3. Restart KOReader from its menu: Settings > Device > Restart KOReader

No cable or USB mode needed. Requires SSH enabled on KOReader (Settings > Network > SSH server).

Via USB

  1. Exit KOReader
  2. Connect via USB cable
  3. Replace koreader/plugins/tomesync.koplugin/ with the new version
  4. Eject and restart KOReader

The downloaded plugin has your server URL and API key baked in, so there is nothing to configure after extracting.


Troubleshooting

Plugin does not appear in the menu:

  • Make sure the folder structure is koreader/plugins/tomesync.koplugin/main.lua (not double-nested)
  • Restart KOReader after installing
  • Check koreader/crash.log for errors

"Connection failed" on test:

  • Verify your server is running and reachable from the e-reader's network
  • If you are on a VPN (e.g. Tailscale), make sure it is connected on the e-reader
  • Re-download the plugin if your server address changed

Wrong book matched:

  • Use "Re-resolve all books" in the plugin menu, then re-open the book
  • The resolve endpoint matches by title and volume number; ambiguous titles may match incorrectly

Progress shows 0% on the web after syncing:

  • The plugin only syncs when the book is matched to a Tome book ID
  • Check the plugin menu: if "Sync now" is greyed out, the book is not matched

Stats page is empty:

  • Sessions are recorded when you close the lid or close a book. If you just started using the plugin, read for a while and close the lid to generate your first session.
  • If you were previously using an older version of the plugin that only recorded sessions on book close, re-download the latest version. The new version records a session every time you close the lid.

KOReader froze or became unresponsive:

  • Older versions of the plugin could freeze KOReader for up to 60 seconds per request when the server was unreachable. Re-download the latest version, which has a 5-second timeout and automatic backoff.

Files

The plugin consists of three files (plus a backup created on first update):

File Purpose
_meta.lua Plugin metadata (name, description)
main.lua Frozen stable shim — loads the implementation and runs the anti-brick rollback state machine. No config; never replaced by self-update.
main_impl.lua All plugin logic, HTTP client, and config (server URL, API key baked in at download time). The only file self-update replaces.
main_impl.lua.bak Last confirmed-good implementation, written automatically before each update so the shim can roll back.