Skip to content

Commit 9fa79e6

Browse files
bndct-devopsclaude
andcommitted
prep: clean up repo for public release
Remove personal IPs, NAS paths, and internal plan docs. Rewrite README for public audience. Update all docs with generic paths. Add TomeSync plugin v2 with offline session flush fix and pending sessions menu. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 21ffb8c commit 9fa79e6

9 files changed

Lines changed: 217 additions & 409 deletions

File tree

README.md

Lines changed: 87 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,93 @@ Built with FastAPI, React, and SQLite. Ships as a single Docker image.
99
- **Library management** -- scan books from folders, upload files (including bulk), organize into libraries with icons and categories (novels, manga, comics, etc.)
1010
- **Metadata extraction** -- automatically pulls metadata and covers from EPUB, PDF, CBZ, and CBR files, including ComicInfo.xml parsing
1111
- **Metadata fetching** -- search Google Books, OpenLibrary, and Hardcover for metadata with a side-by-side diff UI
12-
- **Built-in reader** -- read EPUBs, manga, comics, and webtoons directly in the browser (see [Reader](#reader) below)
12+
- **Built-in reader** -- read EPUBs, manga, comics, and webtoons directly in the browser
1313
- **Reading status** -- per-user tracking (unread/reading/read) with progress percentage
14+
- **Reading stats** -- session tracking, reading streaks, time-of-day patterns, and charts
15+
- **KOReader sync** -- sync reading positions and sessions via TomeSync plugin with offline support
16+
- **OPDS feed** -- browse and download from KOReader, Panels, Chunky, or any OPDS client
17+
- **Bindery** -- inbox for incoming books with metadata preview and batch accept/reject
1418
- **Cover picker** -- search Google Books and OpenLibrary for covers, or upload your own
15-
- **Authentication** -- JWT-based auth with first-run setup wizard, admin user management, granular permissions, and force password change on first login
16-
- **Libraries and filters** -- create public or private libraries, assign books via many-to-many relationships, save URL-based filter presets
17-
- **Series browsing** -- inline series detail panel with volume grid, progress bars, continue reading, and mark-all-read
19+
- **Authentication** -- JWT auth with setup wizard, admin management, granular permissions, and force password change
20+
- **Libraries and filters** -- public or private libraries, many-to-many book assignment, saved filter presets
21+
- **Series browsing** -- inline series detail panel with volume grid, progress bars, and continue reading
1822
- **Bulk operations** -- multi-select for bulk library assignment, metadata editing, metadata fetching, and ZIP download
23+
- **Quick Connect** -- sign in on new devices with a 6-character code instead of typing credentials
24+
- **OPDS PINs** -- short app-specific passwords for OPDS clients, easy to type on e-ink keyboards
1925
- **Themes** -- 9 themes including light, dark, Catppuccin (Latte, Frappe, Macchiato, Mocha), Nord, Neon, and 8-bit
20-
- **OPDS feed** -- browse and download your library from KOReader, Panels, Chunky, or any OPDS client
21-
- **KOSync** -- sync reading positions between KOReader devices and Tome via the built-in KOSync-compatible API
22-
- **Quick Connect** -- sign in on new devices without typing credentials: get a 6-character code on the login page, approve it in Settings > Security on a device where you're already logged in
23-
- **OPDS PINs** -- generate short app-specific passwords for OPDS clients; 6 lowercase characters, easy to type on e-ink keyboards, revocable per device
2426
- **Audit logging** -- tracks administrative actions
25-
- **Import script** -- `scripts/import_library.py` for bulk ingestion of existing collections
27+
- **Import script** -- bulk ingestion of existing collections from the command line
28+
29+
## Screenshots
30+
31+
*Coming soon.*
32+
33+
## Quick Start
34+
35+
### Docker (recommended)
36+
37+
```bash
38+
docker run -d \
39+
-p 8080:8080 \
40+
-v /path/to/data:/data \
41+
-v /path/to/ebooks:/books:ro \
42+
-v /path/to/bindery:/bindery \
43+
-e TOME_SECRET_KEY=changeme \
44+
ghcr.io/benedictpetutschnig/tome
45+
```
46+
47+
Or use Docker Compose -- copy `docker-compose.example.yml` to `docker-compose.yml`, edit the values, and run:
48+
49+
```bash
50+
docker compose up -d
51+
```
52+
53+
Open `http://localhost:8080` and follow the first-run setup to create your admin account.
54+
55+
### Volumes
56+
57+
| Mount point | Purpose |
58+
|--------------|--------------------------------------|
59+
| `/data` | SQLite database and cover cache |
60+
| `/books` | Your ebook library (read-only is fine) |
61+
| `/bindery` | Incoming folder for new books |
62+
63+
### Environment Variables
64+
65+
| Variable | Required | Default | Description |
66+
|-----------------------|----------|------------|------------------------------------------|
67+
| `TOME_SECRET_KEY` | Yes | -- | JWT signing secret |
68+
| `TOME_DATA_DIR` | No | `/data` | SQLite DB and cover cache directory |
69+
| `TOME_LIBRARY_DIR` | No | `/books` | Ebook library root |
70+
| `TOME_INCOMING_DIR` | No | `/bindery` | Incoming/Bindery folder |
71+
| `TOME_PORT` | No | `8080` | HTTP port |
72+
| `TOME_HARDCOVER_TOKEN`| No | -- | Hardcover API token for metadata lookups |
2673

2774
## Reader
2875

29-
Tome has a built-in reader that handles EPUBs, manga/comics (CBZ/CBR), and PDFs directly in the browser. Click the "Read" button on any book detail page or the play icon on series volume covers to open it.
76+
Tome has a built-in reader that handles EPUBs, manga/comics (CBZ/CBR), and PDFs directly in the browser. Click the "Read" button on any book detail page to open it.
3077

3178
### EPUB Reader
3279

3380
- Table of contents sidebar
3481
- Three themes: light, sepia, dark
3582
- Adjustable font size and font family (serif, sans-serif, monospace)
36-
- Reading position saved automatically via EPUB CFI -- reopen a book and you're right where you left off
83+
- Reading position saved automatically via EPUB CFI
3784
- Progress percentage tracked and visible on the dashboard
3885

3986
### Comic/Manga Reader (CBZ/CBR)
4087

4188
Pages are streamed individually from the server -- no need to download the entire archive before reading.
4289

4390
- **Page navigation** -- click/tap left or right half of the screen, use arrow keys, or swipe on mobile
44-
- **Two-page spread** -- auto-enabled on wide screens, toggle with `S` key. Pages display side-by-side like an open book
45-
- **RTL (right-to-left)** -- auto-enabled for manga book types. Page order and navigation direction flip so manga reads correctly. Toggle with `R` key
91+
- **Two-page spread** -- auto-enabled on wide screens, toggle with `S` key
92+
- **RTL (right-to-left)** -- auto-enabled for manga book types, toggle with `R` key
4693
- **Fit modes** -- fit-to-width or fit-to-height, toggle with `W` key
47-
- **Pinch-to-zoom** -- on mobile, pinch to zoom into panels. Double-tap to reset. Pan while zoomed
48-
- **Webtoon/scroll mode** -- for manhwa and vertical-scroll comics. Toggle via the toolbar button (stacked rows icon). Renders all pages in a continuous vertical scroll instead of page-by-page
49-
- **Page thumbnails** -- toggle a thumbnail strip at the bottom to jump to any page at a glance
94+
- **Pinch-to-zoom** -- on mobile, pinch to zoom into panels, double-tap to reset
95+
- **Webtoon/scroll mode** -- continuous vertical scroll for manhwa and webtoons
96+
- **Page thumbnails** -- thumbnail strip to jump to any page
5097
- **Fullscreen** -- press `F` to toggle
51-
- **Theme support** -- reader background respects your chosen theme (no white flash between pages in dark mode)
52-
- **Progress tracking** -- current page saved automatically as `comic:{page}` in the reading position field. Reopen and you're on the same page
53-
- **Preloading** -- adjacent pages are preloaded in the background for instant page turns
98+
- **Preloading** -- adjacent pages preloaded for instant page turns
5499

55100
### Keyboard Shortcuts (Comic Reader)
56101

@@ -75,86 +120,48 @@ Pages are streamed individually from the server -- no need to download the entir
75120

76121
### ComicInfo.xml Support
77122

78-
CBZ and CBR files containing a `ComicInfo.xml` (the ComicRack/Kavita/Komga standard) get automatic metadata extraction:
123+
CBZ and CBR files containing a `ComicInfo.xml` get automatic metadata extraction:
79124

80125
- Title, series, volume/issue number, author, publisher, year, language, description
81126
- Genre tags imported automatically
82127
- Manga flag detected -- books with `<Manga>Yes</Manga>` are auto-assigned the Manga book type and default to RTL reading
83128

84-
### Organizing Manga
129+
## KOReader Integration
85130

86-
Manga in Tome is organized by **volumes**, not chapters. A volume is a single CBZ/CBR file containing all pages for that volume.
131+
### TomeSync Plugin
87132

88-
- If a volume exists, that's your book. Chapters are pages within it.
89-
- If no volume exists yet (ongoing series), individual chapter CBZs work as standalone books with their own `series_index`.
90-
- When a new volume is released, delete the chapter entries and import the volume.
133+
Sync reading progress and sessions between KOReader and Tome. The plugin is generated from the Settings page with your server URL and API key baked in.
91134

92-
## Quick Connect
135+
- Syncs on book open, every 50 page turns, lid close/open, and book close
136+
- Records reading sessions (duration, progress, page turns) for the Stats page
137+
- Offline support: sessions are saved locally and flushed automatically when connectivity returns
138+
- See [docs/koreader-plugin.md](docs/koreader-plugin.md) for full documentation
93139

94-
Quick Connect lets you sign in on a new device without typing your password -- useful on mobile, shared computers, or anywhere with an awkward keyboard.
140+
### OPDS
95141

96-
1. On the login page, tap **Quick Connect** to get a 6-character code.
97-
2. On a device where you're already logged in, go to **Settings > Security** and enter the code.
98-
3. The new device is signed in immediately.
142+
Browse and download your library from any OPDS-compatible reader. The feed is at `/opds`.
99143

100-
Codes expire after a few minutes and can only be used once.
101-
102-
## OPDS PINs
144+
### OPDS PINs
103145

104146
OPDS PINs are short app-specific passwords for authenticating OPDS clients (KOReader, Panels, Chunky, etc.). Typing a full password on an e-ink keyboard is painful -- a 6-character PIN is much easier.
105147

106148
To set one up:
107149

108150
1. Go to **Settings > KOReader > OPDS PINs** and generate a new PIN.
109151
2. In your OPDS client, enter your Tome username and the PIN as the password.
110-
3. The OPDS feed URL is `http://your-tome-host/opds`.
152+
3. The OPDS feed URL is `http://<your-server>:8080/opds`.
111153

112154
Each PIN is independent -- you can have one per device and revoke any of them without affecting your main password or other devices. Your regular password continues to work alongside any PINs you've created.
113155

114-
## Screenshots
115-
116-
*Coming soon.*
117-
118-
## Quick Start
119-
120-
### Docker (recommended)
121-
122-
```bash
123-
docker run -d \
124-
-p 8080:8080 \
125-
-v /path/to/data:/data \
126-
-v /path/to/ebooks:/books:ro \
127-
-v /path/to/bindery:/bindery \
128-
-e TOME_SECRET_KEY=changeme \
129-
ghcr.io/you/tome
130-
```
131-
132-
Or use Docker Compose -- copy `docker-compose.example.yml` to `docker-compose.yml`, edit the values, and run:
133-
134-
```bash
135-
docker compose up -d
136-
```
137-
138-
Open `http://localhost:8080` and follow the first-run setup to create your admin account.
139-
140-
### Volumes
156+
## Quick Connect
141157

142-
| Mount point | Purpose |
143-
|--------------|--------------------------------------|
144-
| `/data` | SQLite database and cover cache |
145-
| `/books` | Your ebook library (read-only is fine) |
146-
| `/bindery` | Incoming folder for new books |
158+
Quick Connect lets you sign in on a new device without typing your password -- useful on mobile, shared computers, or anywhere with an awkward keyboard.
147159

148-
## Environment Variables
160+
1. On the login page, tap **Quick Connect** to get a 6-character code.
161+
2. On a device where you're already logged in, go to **Settings > Security** and enter the code.
162+
3. The new device is signed in immediately.
149163

150-
| Variable | Required | Default | Description |
151-
|-----------------------|----------|------------|------------------------------------------|
152-
| `TOME_SECRET_KEY` | Yes | -- | JWT signing secret |
153-
| `TOME_DATA_DIR` | No | `/data` | SQLite DB and cover cache directory |
154-
| `TOME_LIBRARY_DIR` | No | `/books` | Ebook library root |
155-
| `TOME_INCOMING_DIR` | No | `/bindery` | Incoming/Bindery folder |
156-
| `TOME_PORT` | No | `8080` | HTTP port |
157-
| `TOME_HARDCOVER_TOKEN`| No | -- | Hardcover API token for metadata lookups |
164+
Codes expire after a few minutes and can only be used once.
158165

159166
## Development
160167

@@ -194,6 +201,7 @@ tome/
194201
contexts/ # AuthContext
195202
lib/ # API client, utilities
196203
scripts/ # import and maintenance scripts
204+
docs/ # feature documentation
197205
```
198206

199207
### Tech Stack
@@ -207,11 +215,11 @@ tome/
207215
| Icons | Lucide React |
208216
| Auth | JWT via python-jose |
209217

210-
## Roadmap
218+
## Documentation
211219

212-
- Bindery UI -- review and approve incoming uploads
213-
- Chapter TOC in comic reader -- navigate by chapter within a volume
214-
- Stats dashboard improvements
220+
- [KOReader Plugin](docs/koreader-plugin.md) -- TomeSync setup, sync behavior, offline support, troubleshooting
221+
- [Import Script](docs/import.md) -- bulk importing an existing ebook collection
222+
- [Bindery Deployment](docs/bindery-deployment.md) -- setting up the Bindery inbox
215223

216224
## License
217225

backend/api/tome_sync.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
router = APIRouter(tags=["tome-sync"])
2525
logger = logging.getLogger(__name__)
2626

27+
TOMESYNC_PLUGIN_VERSION = "2" # bump when plugin code changes
28+
2729

2830
# ── API key auth ──────────────────────────────────────────────────────────────
2931

@@ -334,6 +336,13 @@ def revoke_api_key(
334336
db.commit()
335337

336338

339+
# ── Plugin version ────────────────────────────────────────────────────────────
340+
341+
@router.get("/plugin/version")
342+
def plugin_version() -> dict:
343+
return {"version": TOMESYNC_PLUGIN_VERSION}
344+
345+
337346
# ── Plugin download ───────────────────────────────────────────────────────────
338347

339348
@router.get("/plugin/koreader")
@@ -422,7 +431,7 @@ def _main_lua(server_url: str, api_key: str, username: str) -> str:
422431
-- ── HTTP client ──────────────────────────────────────────────────────────────
423432
424433
local HEARTBEAT_PAGES = 50
425-
local PLUGIN_VERSION = "1"
434+
local PLUGIN_VERSION = "{TOMESYNC_PLUGIN_VERSION}"
426435
427436
local function urlEncode(s)
428437
return s:gsub("([^%w%-%.%_%~])", function(c)
@@ -578,6 +587,8 @@ def _main_lua(server_url: str, api_key: str, username: str) -> str:
578587
percentage = pct,
579588
device = deviceName(),
580589
}})
590+
-- Flush any offline sessions while we know WiFi is up
591+
self:_flushPendingSessions()
581592
end
582593
end
583594
@@ -756,10 +767,15 @@ def _main_lua(server_url: str, api_key: str, username: str) -> str:
756767
local pct = self:_getCurrentPercentage()
757768
local prog = self:_getCurrentProgress()
758769
self:_pushPosition()
770+
self:_flushPendingSessions()
771+
local pending = #self.pending_sessions
772+
local msg = string.format("Synced: %.1f%%", pct * 100)
773+
if pending > 0 then
774+
msg = msg .. string.format("\\n%d session(s) still pending", pending)
775+
end
759776
UIManager:show(InfoMessage:new{{
760-
text = string.format("Synced: %.4f (%.1f%%)\\nbook_id: %s\\nprogress: %s",
761-
pct, pct * 100, tostring(self.book_id), tostring(prog)),
762-
timeout = 5,
777+
text = msg,
778+
timeout = 4,
763779
}})
764780
end,
765781
}},
@@ -795,6 +811,35 @@ def _main_lua(server_url: str, api_key: str, username: str) -> str:
795811
}})
796812
end,
797813
}},
814+
{{
815+
text_func = function()
816+
local n = #self.pending_sessions
817+
if n > 0 then
818+
return string.format("Pending sessions (%d)", n)
819+
end
820+
return "Pending sessions (0)"
821+
end,
822+
callback = function()
823+
local n = #self.pending_sessions
824+
if n == 0 then
825+
UIManager:show(InfoMessage:new{{
826+
text = "No pending sessions.",
827+
timeout = 3,
828+
}})
829+
else
830+
local lines = string.format("%d session(s) waiting to sync.\\n", n)
831+
for i, s in ipairs(self.pending_sessions) do
832+
if i > 5 then lines = lines .. "\\n..."; break end
833+
lines = lines .. string.format("\\n%s (%s)",
834+
s.started_at or "?", s.device or "?")
835+
end
836+
UIManager:show(InfoMessage:new{{
837+
text = lines,
838+
timeout = 8,
839+
}})
840+
end
841+
end,
842+
}},
798843
{{
799844
text = "About",
800845
callback = function()

0 commit comments

Comments
 (0)