verso/store/
library_view.rs1use 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}