verso-reader 0.1.0

A terminal EPUB reader with vim navigation, a Kindle-style library, and Markdown highlight export
Documentation
use rusqlite::Connection;

#[derive(Debug, Clone, Copy)]
pub enum Sort {
    LastRead,
    Title,
    Author,
    Progress,
    Added,
}

#[derive(Debug, Clone, Copy)]
pub enum Filter {
    All,
    Reading,
    Unread,
    Finished,
    Broken,
}

#[derive(Debug, Clone)]
pub struct Row {
    pub book_id: i64,
    pub title: String,
    pub author: Option<String>,
    pub pages: Option<u64>,
    pub progress_pct: Option<f32>,
    pub time_left_s: Option<u64>,
    pub last_read_at: Option<String>,
    pub finished_at: Option<String>,
    pub parse_error: Option<String>,
}

pub fn list_rows(c: &Connection, sort: Sort, filter: Filter) -> anyhow::Result<Vec<Row>> {
    let mut where_sql = "WHERE b.deleted_at IS NULL".to_string();
    match filter {
        Filter::Reading => where_sql
            .push_str(" AND p.percent IS NOT NULL AND (b.finished_at IS NULL) AND p.percent > 0"),
        Filter::Unread => where_sql.push_str(" AND (p.percent IS NULL OR p.percent = 0)"),
        Filter::Finished => where_sql.push_str(" AND b.finished_at IS NOT NULL"),
        Filter::Broken => where_sql.push_str(" AND b.parse_error IS NOT NULL"),
        Filter::All => {}
    }
    let order_sql = match sort {
        Sort::LastRead => "ORDER BY p.last_read_at DESC NULLS LAST",
        Sort::Title => "ORDER BY b.title_norm ASC",
        Sort::Author => "ORDER BY b.author_norm ASC",
        Sort::Progress => "ORDER BY p.percent DESC NULLS LAST",
        Sort::Added => "ORDER BY b.added_at DESC",
    };
    let sql = format!(
        "SELECT b.id, b.title, b.author, b.page_count,
                              p.percent, p.last_read_at, b.finished_at, b.parse_error,
                              b.word_count, p.words_read
                       FROM books b LEFT JOIN progress p ON p.book_id = b.id
                       {where_sql} {order_sql}"
    );
    let mut stmt = c.prepare(&sql)?;
    let mut out = Vec::new();
    let rows = stmt.query_map([], |r| {
        Ok({
            let pages: Option<u64> = r.get(3)?;
            let percent: Option<f32> = r.get(4)?;
            let word_count: Option<u64> = r.get(8)?;
            let words_read: Option<u64> = r.get(9)?;
            let time_left_s = match (word_count, percent) {
                (Some(w), Some(p)) => {
                    let remaining_words = (w as f32 * (1.0 - p / 100.0)).max(0.0) as u64;
                    Some((remaining_words as f64 / 250.0 * 60.0) as u64)
                }
                _ => None,
            };
            let _ = words_read;
            Row {
                book_id: r.get(0)?,
                title: r.get(1)?,
                author: r.get(2)?,
                pages,
                progress_pct: percent,
                time_left_s,
                last_read_at: r.get(5)?,
                finished_at: r.get(6)?,
                parse_error: r.get(7)?,
            }
        })
    })?;
    for row in rows {
        out.push(row?);
    }
    Ok(out)
}