use crate::{ model::{card::Card, deck::Entry}, space_repetition, util::serialization, }; use anyhow::Result; use rand::{rngs::ThreadRng, Rng}; use rusqlite::{params, Connection}; use rusqlite_migration::{Migrations, M}; use std::time::SystemTime; pub fn init() -> Result { let mut conn = Connection::open("database.db")?; let migrations = Migrations::new(vec![M::up(include_str!("sql/1-init.sql"))]); migrations.to_latest(&mut conn)?; Ok(conn) } pub fn add_missing_deck_entries(conn: &Connection, entries: Vec) -> Result<()> { let now = get_current_time()?; let ready = now; let state = serde_json::to_string(&space_repetition::init())?; let mut rng = rand::thread_rng(); for entry in entries { let concat_1 = serialization::words_to_line(&entry.part_1); let concat_2 = serialization::words_to_line(&entry.part_2); for w in entry.part_1.iter() { insert(&conn, &mut rng, now, ready, &w, &concat_2, &state)?; } for w in entry.part_2.iter() { insert(&conn, &mut rng, now, ready, &w, &concat_1, &state)?; } } delete_read_before(&conn, now)?; Ok(()) } fn insert( conn: &Connection, rng: &mut ThreadRng, now: u64, ready: u64, question: &String, responses: &String, state: &String, ) -> Result<()> { // Subtract a random amount of time so that cards are not initially given in the same order as // in the deck let ready_sub: u64 = rng.gen_range(0..100); conn.execute( " INSERT INTO cards (question, responses, state, created, deck_read, ready) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (question) DO UPDATE SET deck_read = ?, deleted = null ", params![question, responses, state, now, now, ready - ready_sub, now], )?; Ok(()) } fn delete_read_before(conn: &Connection, t: u64) -> Result<()> { conn.execute( "UPDATE cards SET deleted = ? WHERE deck_read < ?", params![t, t], )?; Ok(()) } pub fn pick_ready(conn: &Connection) -> Option { let now = get_current_time().ok()?; let mut stmt = conn .prepare( " SELECT question, responses, state FROM cards WHERE ready <= ? AND deleted IS NULL ORDER BY ready LIMIT 1 ", ) .ok()?; let mut rows = stmt.query([now]).ok()?; let row = rows.next().ok()??; let state_str: String = row.get(2).ok()?; let responses_str: String = row.get(1).ok()?; Some(Card { question: row.get(0).ok()?, responses: serialization::line_to_words(&responses_str), state: serde_json::from_str(&state_str).ok()?, }) } pub fn update(conn: &Connection, question: &String, state: &space_repetition::State) -> Result<()> { let now = get_current_time()?; let ready = now + state.get_interval_seconds(); let state_str = serde_json::to_string(state)?; conn.execute( " UPDATE cards SET state = ?, updated = ?, ready = ? WHERE question = ? ", params![state_str, now, ready, question], )?; Ok(()) } fn get_current_time() -> Result { Ok(SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH)? .as_secs()) }