Skip to main content

verso/store/
progress.rs

1use rusqlite::{params, Connection, OptionalExtension};
2
3#[derive(Debug, Clone)]
4pub struct ProgressRow {
5    pub book_id: i64,
6    pub spine_idx: u32,
7    pub char_offset: u64,
8    pub anchor_hash: String,
9    pub percent: f32,
10    pub time_read_s: u64,
11    pub words_read: u64,
12}
13
14/// Insert or update the progress row for a book. `last_read_at` is always
15/// refreshed to `CURRENT_TIMESTAMP`.
16pub fn upsert(c: &mut Connection, row: &ProgressRow) -> anyhow::Result<()> {
17    c.execute(
18        "INSERT INTO progress(book_id, spine_idx, char_offset, anchor_hash,
19                              percent, time_read_s, words_read, last_read_at)
20         VALUES (?,?,?,?,?,?,?, CURRENT_TIMESTAMP)
21         ON CONFLICT(book_id) DO UPDATE SET
22           spine_idx    = excluded.spine_idx,
23           char_offset  = excluded.char_offset,
24           anchor_hash  = excluded.anchor_hash,
25           percent      = excluded.percent,
26           time_read_s  = excluded.time_read_s,
27           words_read   = excluded.words_read,
28           last_read_at = CURRENT_TIMESTAMP",
29        params![
30            row.book_id,
31            row.spine_idx,
32            row.char_offset,
33            row.anchor_hash,
34            row.percent,
35            row.time_read_s,
36            row.words_read,
37        ],
38    )?;
39    Ok(())
40}
41
42/// Load the progress row for a book. Returns `Ok(None)` when no row exists.
43pub fn load(c: &Connection, book_id: i64) -> anyhow::Result<Option<ProgressRow>> {
44    Ok(c.query_row(
45        "SELECT book_id, spine_idx, char_offset, anchor_hash,
46                percent, time_read_s, words_read
47         FROM progress WHERE book_id = ?",
48        params![book_id],
49        |r| {
50            Ok(ProgressRow {
51                book_id: r.get(0)?,
52                spine_idx: r.get(1)?,
53                char_offset: r.get(2)?,
54                anchor_hash: r.get(3)?,
55                percent: r.get(4)?,
56                time_read_s: r.get(5)?,
57                words_read: r.get(6)?,
58            })
59        },
60    )
61    .optional()?)
62}