Skip to main content

verso/store/
library_view.rs

1use rusqlite::Connection;
2
3#[derive(Debug, Clone, Copy)]
4pub enum Sort {
5    LastRead,
6    Title,
7    Author,
8    Progress,
9    Added,
10}
11
12#[derive(Debug, Clone, Copy)]
13pub enum Filter {
14    All,
15    Reading,
16    Unread,
17    Finished,
18    Broken,
19}
20
21#[derive(Debug, Clone)]
22pub struct Row {
23    pub book_id: i64,
24    pub title: String,
25    pub author: Option<String>,
26    pub pages: Option<u64>,
27    pub progress_pct: Option<f32>,
28    pub time_left_s: Option<u64>,
29    pub last_read_at: Option<String>,
30    pub finished_at: Option<String>,
31    pub parse_error: Option<String>,
32}
33
34pub fn list_rows(c: &Connection, sort: Sort, filter: Filter) -> anyhow::Result<Vec<Row>> {
35    let mut where_sql = "WHERE b.deleted_at IS NULL".to_string();
36    match filter {
37        Filter::Reading => where_sql
38            .push_str(" AND p.percent IS NOT NULL AND (b.finished_at IS NULL) AND p.percent > 0"),
39        Filter::Unread => where_sql.push_str(" AND (p.percent IS NULL OR p.percent = 0)"),
40        Filter::Finished => where_sql.push_str(" AND b.finished_at IS NOT NULL"),
41        Filter::Broken => where_sql.push_str(" AND b.parse_error IS NOT NULL"),
42        Filter::All => {}
43    }
44    let order_sql = match sort {
45        Sort::LastRead => "ORDER BY p.last_read_at DESC NULLS LAST",
46        Sort::Title => "ORDER BY b.title_norm ASC",
47        Sort::Author => "ORDER BY b.author_norm ASC",
48        Sort::Progress => "ORDER BY p.percent DESC NULLS LAST",
49        Sort::Added => "ORDER BY b.added_at DESC",
50    };
51    let sql = format!(
52        "SELECT b.id, b.title, b.author, b.page_count,
53                              p.percent, p.last_read_at, b.finished_at, b.parse_error,
54                              b.word_count, p.words_read
55                       FROM books b LEFT JOIN progress p ON p.book_id = b.id
56                       {where_sql} {order_sql}"
57    );
58    let mut stmt = c.prepare(&sql)?;
59    let mut out = Vec::new();
60    let rows = stmt.query_map([], |r| {
61        Ok({
62            let pages: Option<u64> = r.get(3)?;
63            let percent: Option<f32> = r.get(4)?;
64            let word_count: Option<u64> = r.get(8)?;
65            let words_read: Option<u64> = r.get(9)?;
66            let time_left_s = match (word_count, percent) {
67                (Some(w), Some(p)) => {
68                    let remaining_words = (w as f32 * (1.0 - p / 100.0)).max(0.0) as u64;
69                    Some((remaining_words as f64 / 250.0 * 60.0) as u64)
70                }
71                _ => None,
72            };
73            let _ = words_read;
74            Row {
75                book_id: r.get(0)?,
76                title: r.get(1)?,
77                author: r.get(2)?,
78                pages,
79                progress_pct: percent,
80                time_left_s,
81                last_read_at: r.get(5)?,
82                finished_at: r.get(6)?,
83                parse_error: r.get(7)?,
84            }
85        })
86    })?;
87    for row in rows {
88        out.push(row?);
89    }
90    Ok(out)
91}