use chrono::{DateTime, Utc}; use rusqlite::{params, Connection, Error}; use std::fmt; pub struct Memo { pub id: u32, pub body: String, pub due: Option>, } impl fmt::Display for Memo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut due_str = "null".to_string(); if self.due.is_some() { due_str = self.due.unwrap().to_string(); } return write!( f, "Memo(id={id}, body={body}, due={due})", id = self.id, body = self.body, due = due_str, ); } } pub trait MemoStorage { fn all(&self, done: bool) -> Result, Error>; fn add(&self, body: &str, due: Option>) -> Result<(), Error>; fn set(&self, memo: &Memo) -> Result; fn del(&self, id: u32) -> Result; fn get(&self, id: u32) -> Result; } // SQLiteStorage pub struct SQLiteStorage { conn: Connection, } pub fn open_sqlite_storage(path: &str) -> Result { let connection = Connection::open(path)?; // TODO check if table exist and is valid connection.execute( "CREATE TABLE IF NOT EXISTS memo ( id INTEGER PRIMARY KEY, body TEXT NOT NULL, due DATETIME, done BOOL DEFAULT FALSE );", [], )?; return Ok(SQLiteStorage { conn: connection }); } impl MemoStorage for SQLiteStorage { fn all(&self, done: bool) -> Result, Error> { let mut results = Vec::new(); /* * SQLite considers NULL as smallest value, so we will always get events with no due date * first. To circumvent this, we first query all memos with a due date, and then all * others. This is kinda jank but will do for now. */ { let mut statement = self.conn.prepare( "SELECT * FROM memo WHERE due IS NOT NULL AND done = ? ORDER BY due, id", )?; let mut rows = statement.query(params![done as u8])?; while let Some(row) = rows.next()? { results.push(Memo { id: row.get(0)?, body: row.get(1)?, due: row.get(2)?, }); } } { let mut statement = self .conn .prepare("SELECT * FROM memo WHERE due IS NULL AND done = ? ORDER BY due, id")?; let mut rows = statement.query(params![done as u8])?; while let Some(row) = rows.next()? { results.push(Memo { id: row.get(0)?, body: row.get(1)?, due: row.get(2)?, }); } } return Ok(results); } fn add(&self, body: &str, due: Option>) -> Result<(), Error> { // TODO join these 2 ifs? if due.is_some() { self.conn.execute( "INSERT INTO memo (body, due) VALUES (?, ?)", params![body, due], )?; } else { self.conn .execute("INSERT INTO memo (body) VALUES (?)", params![body])?; } return Ok(()); } fn set(&self, memo: &Memo) -> Result { let count = self.conn.execute( "UPDATE memo SET body = ?, due = ? WHERE id = ?", params![memo.body, memo.due, memo.id], )?; if count > 0 { return Ok(true); } else { return Ok(false); } } fn del(&self, id: u32) -> Result { let count = self .conn .execute("UPDATE memo SET done = 1 WHERE id = ?", params![id])?; if count > 0 { return Ok(true); } else { return Ok(false); } } fn get(&self, id: u32) -> Result { return Ok(self.conn.query_row( "SELECT * FROM memo WHERE id = ? AND done = 0", params![id], |row| { return Ok(Memo { id: row.get(0)?, body: row.get(1)?, due: row.get(2).unwrap_or(None), }); }, )?); } }