From cfe29f0c127e5593cb8e26568cdaf095cfe5a378 Mon Sep 17 00:00:00 2001 From: alemidev Date: Thu, 15 Dec 2022 02:23:43 +0100 Subject: [PATCH] feat: refactor storage, add tag, add tree view --- src/categorize.rs | 112 +++++++++++++++++++++++ src/main.rs | 195 ++++++++++++++++++++++------------------ src/model/memo.rs | 6 +- src/model/state.rs | 6 +- src/remote.rs | 7 +- src/storage/json.rs | 126 +++++++++----------------- src/storage/mod.rs | 68 ++++++++------ src/storage/sqlite.rs | 205 ++++++++++++++++++------------------------ 8 files changed, 399 insertions(+), 326 deletions(-) create mode 100644 src/categorize.rs diff --git a/src/categorize.rs b/src/categorize.rs new file mode 100644 index 0000000..ca6d89c --- /dev/null +++ b/src/categorize.rs @@ -0,0 +1,112 @@ +use colored::Colorize; + +use crate::{model::memo::Memo, utils::HumanDisplay}; + +pub struct MemoNode { + tag: String, + memo: Vec, + sub: Vec, +} + +pub type Tree = MemoNode; + +impl Tree { + pub fn new() -> Tree { + Tree { + tag: "".into(), + memo: vec![], + sub: vec![], + } + } +} + +pub fn format_tree(node: &MemoNode, depth: i32, colored: bool) -> String { + let mut out = "".to_string(); + let mut prefix = "".to_string(); // TODO string builder? + for i in 0..depth { + if i == depth - 1 { + prefix.push_str("├"); + } else { + prefix.push_str("│"); + } + } + + if colored { + prefix = prefix.bright_black().to_string(); + } + + + for memo in &node.memo { + let memo_str = if colored { memo.colored() } else { memo.human() }; + let sep = if colored { "╴".bright_black().to_string() } else { "╴".to_string() }; + out.push_str(format!("{}{}{}\n", prefix, sep, memo_str).as_str()); + } + + for sub in &node.sub { + let sep = if colored { "┐".bright_black().to_string() } else { "┐".to_string() }; + out.push_str(format!("{}{} #{}\n", prefix, sep, sub.tag).as_str()); + out.push_str(format_tree(sub, depth + 1, colored).as_str()); + } + + // if prefix.len() > 0 { + // out.push_str(&prefix.replace("├", "╵\n")); + // } + + out +} + +fn insert(memo: Memo, node:&mut MemoNode, frag: Vec) { + match frag.first() { + None => node.memo.push(memo), + Some(x) => { + if !node.sub.iter().any(|sub| &sub.tag == x) { // TODO have to iter twice coz borrows + node.sub.push(MemoNode { + tag: x.clone(), + memo: vec![], + sub: vec![], + }); + } + let sub = node.sub.iter_mut().find(|sub| &sub.tag == x).expect("Could not find matching node"); + let sub_frag = frag.iter().enumerate().filter(|(i,_x)| i > &0).map(|(_i,x)| x.clone()).collect(); + insert(memo, sub, sub_frag); + } + } +} + + +pub fn make_tree(memos: Vec) -> Tree { + let mut tree = Tree::new(); + + for memo in memos { + let fragments : Vec = memo.tag.split(".") + .map(|x| x.to_string()) + .filter(|x| x.len() > 0) + .collect(); + + insert(memo, &mut tree, fragments); + } + + tree +} + +pub fn traverse(tree: &Tree, query: String) -> &Tree { + return recursive_traverse( + tree, + query.split(".").filter(|x| x.len() > 0).map(|x| x.to_string()).collect() + ); +} + +fn recursive_traverse(tree: &Tree, query: Vec) -> &Tree { + match query.first() { + None => { return tree; }, + Some(frag) => { + for sub in &tree.sub { + if &sub.tag == frag { + let sub_frag = query.iter().enumerate().filter(|(i, _x)| i > &0).map(|(_i,x)| x.clone()).collect(); + return recursive_traverse(sub, sub_frag); + } + } + return tree; + } + } +} diff --git a/src/main.rs b/src/main.rs index 6c908a2..a61c55d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,9 @@ mod remote; mod storage; mod utils; mod model; +mod categorize; +use categorize::{make_tree, format_tree, traverse}; use chrono::{DateTime, Local, Utc}; use clap::{Parser, Subcommand}; use colored::Colorize; @@ -10,13 +12,13 @@ use const_format::concatcp; use git_version::git_version; use notify_rust::Notification; use regex::Regex; -use remote::RemoteSync; +// use remote::RemoteSync; use std::path::PathBuf; use storage::{ - AuthStorage, JsonStorage, MemoStorage, SQLiteStorage, StateStorage, - SUPPORTED_FORMATS, + JsonStorage, SQLiteStorage, + SUPPORTED_FORMATS, StorageDriver, }; -use model::memo::{Memo, MemoError}; +use model::{memo::{Memo, MemoError}, state::Context}; use utils::{find_by_regex, find_db_file, parse_human_duration, HumanDisplay}; const GIT_VERSION: &str = git_version!(); @@ -32,10 +34,6 @@ const VERSION: &str = concatcp!(PKG_VERSION, "-", GIT_VERSION); struct Cli { #[clap(subcommand)] command: Option, - #[clap(short, long, help = "show memos in a notification")] - notify: bool, - #[clap(long, help = "show completed tasks")] - old: bool, #[clap(short, long, help = "synchronize memo db")] sync: bool, #[clap(long, help = "name of db file, without extension")] @@ -55,6 +53,17 @@ enum Commands { body: Vec, #[clap(short, long, help = "due time relative to now")] due: Option, // TODO allow to pass date + #[clap(short, long, help = "memo tag, used for categorizing")] + tag: Option, + }, + /// list existing memos + List { + #[clap(short, long, help = "list with given tag as root")] + tag: Option, + #[clap(short, long, help = "show memos in a notification")] + notify: bool, + #[clap(long, help = "show completed tasks")] + old: bool, }, /// mark existing memo as done Done { @@ -65,6 +74,8 @@ enum Commands { /// change existing memo Edit { search: String, + #[clap(short, long, help = "set memo tag")] + tag: Option, #[clap(short, long, help = "set memo message")] body: Option, #[clap(short, long, help = "set due time relative to now")] @@ -110,30 +121,31 @@ fn main() { } fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> -where - T: MemoStorage + AuthStorage + StateStorage + RemoteSync, +where T: StorageDriver, { - if args.sync { - if storage.get_key().is_err() { - let key = rpassword::prompt_password("[$] new passphrase: ").unwrap(); - storage.set_key(key.as_str()).unwrap(); - } - let res = storage.download( - storage.get_hash().unwrap().as_str(), - "http://127.0.0.1:8443", - ); - if res.is_ok() { - println!("[v] downloaded remote db"); - } else { - println!( - "[!] could not fetch db : {}", - res.err().unwrap().to_string() - ); - } - } + // if args.sync { + // if storage.get_key().is_err() { + // let key = rpassword::prompt_password("[$] new passphrase: ").unwrap(); + // storage.set_key(key.as_str()).unwrap(); + // } + // let res = storage.download( + // storage.get_hash().unwrap().as_str(), + // "http://127.0.0.1:8443", + // ); + // if res.is_ok() { + // println!("[v] downloaded remote db"); + // } else { + // println!( + // "[!] could not fetch db : {}", + // res.err().unwrap().to_string() + // ); + // } + // } + + let ctx = storage.ctx().unwrap(); - match &args.command { - Some(Commands::New { body, due }) => { + match args.command { + Some(Commands::New { body, due, tag }) => { let mut due_date: Option> = None; if let Some(d) = due { if d.len() > 0 { @@ -142,8 +154,7 @@ where } let txt = body.join(" "); println!("{} new memo: {}", "[+]".bold(), &txt); - storage.add(txt, due_date).unwrap(); - storage.set_edit_time(Utc::now()).unwrap(); + storage.add(tag.unwrap_or("".to_string()), txt, due_date).unwrap(); } Some(Commands::Done { search, many }) => { let rex = Regex::new(search.as_str()); @@ -152,7 +163,7 @@ where if let Some(re) = rex.ok() { for memo in storage.all(false).unwrap() { if re.is_match(memo.body.as_str()) { - if *many { + if many { storage.del(&memo.id).unwrap(); println!("[-] done task : {}", memo.body); } else if found { @@ -171,20 +182,22 @@ where } if let Some(rm) = to_remove { storage.del(&rm.id).unwrap(); - storage.set_edit_time(Utc::now()).unwrap(); println!("{} done memo: {}", "[-]".bold(), rm.body); } } else { println!("{} invalid regex", format!("[{}]", "!".red()).bold()); } } - Some(Commands::Edit { search, body, due }) => { + Some(Commands::Edit { search, tag, body, due }) => { let mut m = find_by_regex( regex::Regex::new(search.as_str()).unwrap(), storage.all(false).unwrap(), ) .unwrap(); + if let Some(t) = tag { + m.tag = t; + } if let Some(b) = body { m.body = b.to_owned(); } @@ -195,69 +208,73 @@ where m.due = Some(Utc::now() + parse_human_duration(d.as_str()).unwrap()); } } - // storage.set(&m).unwrap(); // TODO fix editing memos - storage.set_edit_time(Utc::now()).unwrap(); + let m_str = m.colored(); + storage.put(m).unwrap(); println!( "{} updated memo\n{}", format!("[{}]", ">".green()).bold().to_string(), - m.colored(), + m_str, ); } + Some(Commands::List { tag, notify, old }) => { + let all = storage.all(old).unwrap(); + display_memos(all, tag, &ctx, notify); + }, None => { - let all = storage.all(args.old).unwrap(); - let mut builder = String::new(); - let timing = if let Some(state) = storage.get_state().ok() { - let now = Local::now(); - format!( - "last edit: {}", - state - .last_edit - .with_timezone(&now.timezone()) - .format("%a %d/%m %H:%M") - ) - } else { - Local::now().format("%a %d/%m, %H:%M").to_string() - }; - if args.old { - builder.push_str("Archived memos:\n"); - } - if all.len() < 1 { - builder.push_str("[ ] nothing to remember\n"); - } - for m in all { - let tmp = if args.notify { m.human() } else { m.colored() }; - builder.push_str(tmp.as_str()); - builder.push('\n'); - } - if args.notify { - Notification::new() - .summary(format!("memo-cli | {}", timing).as_str()) - .body(builder.as_str()) - //.icon("") //soon... - .show() - .unwrap(); - } else { - println!("{} | {}", "memo-cli".bold(), timing); - print!("{}", builder); - } + let all = storage.all(false).unwrap(); + display_memos(all, None, &ctx, false); } } - if args.sync { - let res = storage.upload( - storage.get_hash().unwrap().as_str(), - "http://127.0.0.1:8443", - ); - if res.is_ok() { - println!("[^] uploaded local db"); - storage.set_sync_time(Utc::now()).unwrap(); - } else { - println!( - "[!] could not upload db : {}", - res.err().unwrap().to_string() - ); - } - } + // if args.sync { + // let res = storage.upload( + // storage.get_hash().unwrap().as_str(), + // "http://127.0.0.1:8443", + // ); + // if res.is_ok() { + // println!("[^] uploaded local db"); + // storage.set_sync_time(Utc::now()).unwrap(); + // } else { + // println!( + // "[!] could not upload db : {}", + // res.err().unwrap().to_string() + // ); + // } + // } Ok(()) } + +fn display_memos(all: Vec, query: Option, ctx: &Context, notify: bool) { + // let all = storage.all(args.old).unwrap(); + let mut builder = String::new(); + let timing = format!( + "last edit: {}", + ctx + .last_edit + .with_timezone(&Local::now().timezone()) + .format("%a %d/%m %H:%M") + ); + if all.len() < 1 { + builder.push_str("[ ] nothing to remember\n"); + } + + let tree = make_tree(all); + let mut tree_ref = &tree; + if let Some(q) = query { + tree_ref = traverse(tree_ref, q); + } + builder.push_str(format_tree(tree_ref, 0, !notify).as_str()); + if notify { + Notification::new() + .summary(format!("memo-cli | {}", timing).as_str()) + .body(builder.as_str()) + //.icon("") //soon... + .show() + .unwrap(); + } else { + println!("{} | {}", "memo-cli".bold(), timing); + print!("{}", builder); + } + +} diff --git a/src/model/memo.rs b/src/model/memo.rs index 4a22d76..d591902 100644 --- a/src/model/memo.rs +++ b/src/model/memo.rs @@ -6,6 +6,7 @@ use uuid::Uuid; #[derive(Clone, Eq, Serialize, Deserialize)] pub struct Memo { pub id: Uuid, + pub tag: String, pub body: String, pub due: Option>, pub done: Option>, @@ -13,6 +14,7 @@ pub struct Memo { } impl Memo { + #[allow(dead_code)] // TODO temporary pub fn new(body: String, due: Option>) -> Self { Memo { body, due, ..Default::default() } } @@ -22,6 +24,7 @@ impl Default for Memo { fn default() -> Self { Memo { id: Uuid::new_v4(), + tag: "".to_string(), body: "".to_string(), due: None, done: None, @@ -72,8 +75,9 @@ impl fmt::Display for Memo { } return write!( f, - "Memo(id={id}, body={body}, due={due}, done={done}, last_edit={last_edit})", + "Memo(id={id}, tag={tag}, body={body}, due={due}, done={done}, last_edit={last_edit})", id = self.id, + tag = self.tag, body = self.body, due = due_str, done = done_str, diff --git a/src/model/state.rs b/src/model/state.rs index 916c3d6..15d25fc 100644 --- a/src/model/state.rs +++ b/src/model/state.rs @@ -1,15 +1,15 @@ use chrono::{DateTime, Utc}; use uuid::Uuid; -pub struct State { +pub struct Context { pub last_edit: DateTime, pub last_sync: Option>, pub last_memo: Option, } -impl Default for State { +impl Default for Context { fn default() -> Self { - State { + Context { last_edit: Utc::now(), last_sync: None, last_memo: None, diff --git a/src/remote.rs b/src/remote.rs index db85982..176e770 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -1,5 +1,4 @@ -use crate::storage::{MemoStorage, AuthStorage}; -use crate::model::memo::MemoError; +use crate::{model::memo::MemoError, storage::StorageDriver}; use std::collections::HashSet; use uuid::Uuid; use std::io::Read; @@ -9,7 +8,7 @@ use std::io::Read; #[test] fn always_succeeds() { } -pub trait RemoteSync : AuthStorage+MemoStorage+Sized { +pub trait RemoteSync : StorageDriver+Sized { fn serialize(&self) -> Result, MemoError>; fn deserialize(data: Vec) -> Result; @@ -63,7 +62,7 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized { todo!() } } else { - self.insert(memo.clone())?; + // self.insert(memo.clone())?; count += 1; } } diff --git a/src/storage/json.rs b/src/storage/json.rs index e878b1e..bd4fbc3 100644 --- a/src/storage/json.rs +++ b/src/storage/json.rs @@ -3,13 +3,13 @@ use std::io::BufReader; use std::path::PathBuf; use chrono::{DateTime, Utc}; use serde::{Serialize, Deserialize}; -use uuid::Uuid; use std::io::Write; -use sha2::{Digest, Sha512}; +// use sha2::{Digest, Sha512}; use std::collections::LinkedList; -use crate::storage::{Memo, State, MemoError, AuthStorage, MemoStorage, StateStorage, MemoSetField}; -use crate::remote::RemoteSync; +// use crate::remote::RemoteSync; +use crate::storage::StorageDriver; +use crate::model::{memo::{Memo, MemoError}, state::Context}; pub struct JsonStorage { path: PathBuf, @@ -60,30 +60,30 @@ impl JsonStorage { } } -impl AuthStorage for JsonStorage { - fn get_hash(&self) -> Result { - return Ok(self.data.hash.to_string()); - } +// impl AuthStorage for JsonStorage { +// fn get_hash(&self) -> Result { +// return Ok(self.data.hash.to_string()); +// } +// +// fn get_key(&self) -> Result { +// return Ok(self.data.key.to_string()); +// } +// +// fn set_key(&mut self, key: &str) -> Result, MemoError> { +// let old_key = Some(self.data.key.to_string()); +// let mut hasher = Sha512::new(); +// hasher.update(key.as_bytes()); +// self.data.hash = base64::encode(hasher.finalize()); +// self.data.key = key.to_string(); +// self.save()?; +// return Ok(old_key); +// } +// } - fn get_key(&self) -> Result { - return Ok(self.data.key.to_string()); - } - - fn set_key(&mut self, key: &str) -> Result, MemoError> { - let old_key = Some(self.data.key.to_string()); - let mut hasher = Sha512::new(); - hasher.update(key.as_bytes()); - self.data.hash = base64::encode(hasher.finalize()); - self.data.key = key.to_string(); - self.save()?; - return Ok(old_key); - } -} - -impl StateStorage for JsonStorage { - fn get_state(&self) -> Result { +impl StorageDriver for JsonStorage { + fn ctx(&self) -> Result { return Ok( - State { + Context { last_edit: self.data.last_edit, last_sync: self.data.last_sync, last_memo: None, // TODO @@ -91,16 +91,6 @@ impl StateStorage for JsonStorage { ); } - fn set_state(&mut self, state: State) -> Result, MemoError> { - let old_state = Some(State{last_edit: self.data.last_edit, last_sync:self.data.last_sync, last_memo:None}); // TODO - self.data.last_sync = state.last_sync; - self.data.last_edit = state.last_edit; - self.save()?; - return Ok(old_state); - } -} - -impl MemoStorage for JsonStorage { fn all(&self, done: bool) -> Result, MemoError> { let mut results_due : LinkedList = LinkedList::new(); let mut results_not_due : LinkedList = LinkedList::new(); @@ -124,44 +114,14 @@ impl MemoStorage for JsonStorage { return Ok(out); } - fn insert(&mut self, m: Memo) -> Result<(), MemoError> { + fn put(&mut self, m: Memo) -> Result<(), MemoError> { + self.data.memo.retain(|x| x.id != m.id); self.data.memo.push(m); + self.data.last_edit = Utc::now(); self.save()?; Ok(()) } - fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result { - todo!() - // let mut count = 0; - // for (_i, memo) in self.data.memo.iter().enumerate() { - // if memo.id == m.id { - // // TODO improve - // // self.data.memo[i].body = m.body; - // // self.data.memo[i].due = m.due; - // // self.data.memo[i].done = m.done; - // count += 1; - // } - // } - // self.save()?; - // Ok(count) - } - - fn del(&mut self, id: &uuid::Uuid) -> Result { - let mut count = 0; - let mut index : i32 = -1; - for (i, memo) in self.data.memo.iter().enumerate() { - if memo.id == *id { - index = i as i32; - count += 1; - } - } - if index >= 0 { - self.data.memo[index as usize].done = Some(Utc::now()); - self.save()?; - } - return Ok(count); - } - fn get(&self, id: &uuid::Uuid) -> Result { for memo in &self.data.memo { if memo.id == *id { @@ -173,17 +133,17 @@ impl MemoStorage for JsonStorage { } -impl RemoteSync for JsonStorage { - fn serialize(&self) -> Result, MemoError> { - self.save()?; - return Ok(std::fs::read(&self.path).unwrap()); - } - - fn deserialize(data: Vec) -> Result { - return Ok(JsonStorage{ - path: PathBuf::new(), - data: serde_json::from_slice(data.as_slice()).unwrap(), - }); - } - -} +// impl RemoteSync for JsonStorage { +// fn serialize(&self) -> Result, MemoError> { +// self.save()?; +// return Ok(std::fs::read(&self.path).unwrap()); +// } +// +// fn deserialize(data: Vec) -> Result { +// return Ok(JsonStorage{ +// path: PathBuf::new(), +// data: serde_json::from_slice(data.as_slice()).unwrap(), +// }); +// } +// +// } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index bdabd00..4e0d037 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use uuid::Uuid; -use crate::model::{memo::{Memo, MemoError}, state::State}; +use crate::model::{memo::{Memo, MemoError}, state::Context}; pub mod json; pub mod sqlite; @@ -10,46 +10,56 @@ pub use sqlite::SQLiteStorage; pub const SUPPORTED_FORMATS: &'static [&'static str] = &["db", "json"]; +#[allow(dead_code)] // TODO temp pub enum MemoSetField { Body(String), Due(Option>), Done(Option>), } -pub trait MemoStorage { - fn all(&self, done: bool) -> Result, MemoError>; +pub trait StorageDriver { + + /// Insert a new element into storage, replacing any previous one. updates last_edit in ctx + fn put(&mut self, memo: Memo) -> Result<(), MemoError>; + + /// Get memo from storage with matching id fn get(&self, id: &Uuid) -> Result; - fn del(&mut self, id: &Uuid) -> Result; - fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result; - fn insert(&mut self, m: Memo) -> Result<(), MemoError>; - fn add(&mut self, body: String, due: Option>) -> Result<(), MemoError> { - self.insert(Memo::new(body, due)) - } -} + /// Return a vector with clones of all elements in storage (optionally include concluded ones) + fn all(&self, done:bool) -> Result, MemoError>; + + /// Return current storage context + fn ctx(&self) -> Result; -pub trait AuthStorage { - fn get_hash(&self) -> Result; - fn get_key(&self) -> Result; - fn set_key(&mut self, key: &str) -> Result, MemoError>; -} + fn update(&mut self, id: &Uuid, field: MemoSetField) -> Result { + let mut memo = self.get(id)?; -pub trait StateStorage { - fn set_state(&mut self, state: State) -> Result, MemoError>; - fn get_state(&self) -> Result; + match field { + MemoSetField::Body(body) => memo.body = body, + MemoSetField::Due(due) => memo.due = due, + MemoSetField::Done(done) => memo.done = done, + } - fn set_edit_time(&mut self, time: DateTime) -> Result<(), MemoError> { - let mut state = self.get_state() - .unwrap_or(State { last_edit: time, ..Default::default() }); - state.last_edit = time; - self.set_state(state)?; - Ok(()) + self.put(memo.clone())?; + + Ok(memo) } - fn set_sync_time(&mut self, time: DateTime) -> Result<(), MemoError> { - let mut state = self.get_state()?; - state.last_sync = Some(time); - self.set_state(state)?; - Ok(()) + fn add(&mut self, tag: String, body: String, due: Option>) -> Result { + let m = Memo { + id: Uuid::new_v4(), + tag, body, due, + last_edit: Utc::now(), + done: None, + }; + self.put(m.clone())?; + Ok(m) + } + + fn del(&mut self, id: &Uuid) -> Result { + let mut m = self.get(id)?; + m.done = Some(Utc::now()); + self.put(m.clone())?; + return Ok(m); } } diff --git a/src/storage/sqlite.rs b/src/storage/sqlite.rs index cc59f4e..80331fe 100644 --- a/src/storage/sqlite.rs +++ b/src/storage/sqlite.rs @@ -1,21 +1,18 @@ -use crate::remote::RemoteSync; -use crate::storage::{AuthStorage, Memo, MemoError, MemoStorage, State, StateStorage}; +// use crate::remote::RemoteSync; +use crate::model::{memo::{Memo, MemoError}, state::Context}; +use crate::storage::StorageDriver; use chrono::{DateTime, Utc}; // use rusqlite::DatabaseName::Main; use rusqlite::{params, Connection}; -use sha2::{Digest, Sha512}; +// use sha2::{Digest, Sha512}; use std::path::PathBuf; use std::collections::LinkedList; -use std::fs; -use std::io::Write; use uuid::Uuid; use tempfile::NamedTempFile; -use super::MemoSetField; - pub struct SQLiteStorage { conn: Connection, - path: PathBuf, + // path: PathBuf, #[allow(dead_code)] // I just use this to bind the tempfile life with the SQLiteStorage obj tmpfile: Option, @@ -30,6 +27,7 @@ impl SQLiteStorage { connection.execute( "CREATE TABLE IF NOT EXISTS memo ( id TEXT PRIMARY KEY, + tag TEXT NOT NULL, body TEXT NOT NULL, due DATETIME, done DATETIME DEFAULT NULL, @@ -41,53 +39,55 @@ impl SQLiteStorage { "CREATE TABLE IF NOT EXISTS state ( last_edit DATETIME DEFAULT NULL, last_sync DATETIME DEFAULT NULL + last_memo TEXT DEFAULT NULL );", [], )?; - connection.execute( - "CREATE TABLE IF NOT EXISTS auth ( - key TEXT PRIMARY KEY, - hash TEXT NOT NULL - );", - [], - )?; + + // connection.execute( + // "CREATE TABLE IF NOT EXISTS auth ( + // key TEXT PRIMARY KEY, + // hash TEXT NOT NULL + // );", + // [], + // )?; } - return Ok(SQLiteStorage { conn: connection , path, tmpfile: None }); + return Ok(SQLiteStorage { conn: connection , tmpfile: None }); } } -impl AuthStorage for SQLiteStorage { - fn get_hash(&self) -> Result { - return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| { - return Ok(row.get(1)?); - })?); - } +// impl AuthStorage for SQLiteStorage { +// fn get_hash(&self) -> Result { +// return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| { +// return Ok(row.get(1)?); +// })?); +// } +// +// fn get_key(&self) -> Result { +// return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| { +// return Ok(row.get(0)?); +// })?); +// } +// +// fn set_key(&mut self, key: &str) -> Result, MemoError> { // TODO move the hashing itself to a default method +// let old_key = self.get_key().ok(); +// let mut hasher = Sha512::new(); +// hasher.update(key.as_bytes()); +// let hash = base64::encode(hasher.finalize()); +// self.conn.execute("DELETE FROM auth;", [])?; +// self.conn.execute( +// "INSERT INTO auth (key, hash) VALUES (?, ?);", +// params![key, hash], +// )?; +// return Ok(old_key); +// } +// } - fn get_key(&self) -> Result { - return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| { - return Ok(row.get(0)?); - })?); - } - - fn set_key(&mut self, key: &str) -> Result, MemoError> { // TODO move the hashing itself to a default method - let old_key = self.get_key().ok(); - let mut hasher = Sha512::new(); - hasher.update(key.as_bytes()); - let hash = base64::encode(hasher.finalize()); - self.conn.execute("DELETE FROM auth;", [])?; - self.conn.execute( - "INSERT INTO auth (key, hash) VALUES (?, ?);", - params![key, hash], - )?; - return Ok(old_key); - } -} - -impl StateStorage for SQLiteStorage { - fn get_state(&self) -> Result { +impl StorageDriver for SQLiteStorage { + fn ctx(&self) -> Result { return Ok(self.conn.query_row("SELECT * FROM state", [], |row| { - return Ok(State { + return Ok(Context { last_edit: row.get(0)?, last_sync: row.get(1).ok(), ..Default::default() @@ -95,18 +95,6 @@ impl StateStorage for SQLiteStorage { })?); } - fn set_state(&mut self, state: State) -> Result, MemoError> { - let old_state = self.get_state().ok(); - self.conn.execute("DELETE FROM state;", [])?; - self.conn.execute( - "INSERT INTO state (last_edit, last_sync) VALUES (?, ?);", - params![state.last_edit, state.last_sync], - )?; - return Ok(old_state); - } -} - -impl MemoStorage for SQLiteStorage { fn all(&self, done: bool) -> Result, MemoError> { let mut results_due : LinkedList = LinkedList::new(); let mut results_not_due : LinkedList = LinkedList::new(); @@ -126,10 +114,11 @@ impl MemoStorage for SQLiteStorage { let tgt = if row.get::>(2).is_ok() { &mut results_due } else { &mut results_not_due }; tgt.push_back(Memo { id: Uuid::parse_str(row.get::(0)?.as_str()).unwrap(), - body: row.get(1)?, - due: row.get(2).ok(), - done: row.get(3).ok(), - last_edit: row.get(4)?, + tag: row.get(1)?, + body: row.get(2)?, + due: row.get(3).ok(), + done: row.get(4).ok(), + last_edit: row.get(5)?, }); } @@ -138,55 +127,37 @@ impl MemoStorage for SQLiteStorage { return Ok(results_due.into_iter().collect()); } - fn insert(&mut self, m: Memo) -> Result<(), MemoError> { - self.conn.execute( + fn put(&mut self, m: Memo) -> Result<(), MemoError> { + let tx = self.conn.transaction()?; + tx.execute("DELETE FROM memo WHERE id = ?", params![m.id.to_string()])?; + tx.execute( "INSERT INTO memo (id, body, due, done, last_edit) VALUES (?, ?, ?, ?, ?)", params![m.id.to_string(), m.body, m.due, m.done, m.last_edit], )?; + tx.execute("UPDATE state SET last_edit = ?", params![Utc::now()])?; + tx.commit()?; Ok(()) } - fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result { - match field { - MemoSetField::Body(body) => { - Ok(self.conn.execute( - "UPDATE memo SET body = ?, last_edit = ? WHERE id = ?", - params![body, Utc::now(), id.to_string()], - )?) - }, - MemoSetField::Due(due) => { - Ok(self.conn.execute( - "UPDATE memo SET due = ?, last_edit = ? WHERE id = ?", - params![due, Utc::now(), id.to_string()] - )?) - }, - MemoSetField::Done(done) => { - Ok(self.conn.execute( - "UPDATE memo SET done = ?, last_edit = ? WHERE id = ?", - params![done, Utc::now(), id.to_string()] - )?) - }, - } - } - - fn del(&mut self, id: &Uuid) -> Result { - return Ok(self.conn.execute( - "UPDATE memo SET done = ? WHERE id = ?", - params![Utc::now(), id.to_string()], - )?); - } + // fn del(&mut self, id: &Uuid) -> Result { + // return Ok(self.conn.execute( + // "UPDATE memo SET done = ? WHERE id = ?", + // params![Utc::now(), id.to_string()], + // )?); + // } fn get(&self, id: &Uuid) -> Result { return Ok(self.conn.query_row( - "SELECT * FROM memo WHERE id = ? AND done = 0", + "SELECT * FROM memo WHERE id = ?", params![id.to_string()], |row| { return Ok(Memo { id: Uuid::parse_str(row.get::(0)?.as_str()).unwrap(), - body: row.get(1)?, - due: row.get(2).ok(), - done: row.get(3).ok(), - last_edit: row.get(4)?, + tag: row.get(1)?, + body: row.get(2)?, + due: row.get(3).ok(), + done: row.get(4).ok(), + last_edit: row.get(5)?, }); }, )?); @@ -194,23 +165,23 @@ impl MemoStorage for SQLiteStorage { } -impl RemoteSync for SQLiteStorage { - fn serialize(&self) -> Result, MemoError> { - return Ok(fs::read(&self.path).unwrap()); // TODO can I do this? just read the db while it's open - // let tmpfile = NamedTempFile::new()?; - // self.conn.backup(Main, tmpfile.path(), None).unwrap(); - // return Ok(fs::read(tmpfile).unwrap()); - } - - fn deserialize(data: Vec) -> Result { - let mut tmpfile = NamedTempFile::new()?; - tmpfile.write(data.as_slice())?; - let path = tmpfile.path(); - return Ok(SQLiteStorage{ - conn: Connection::open(path).unwrap(), - path: path.to_path_buf(), - tmpfile: Some(tmpfile), - }); - } - -} +// impl RemoteSync for SQLiteStorage { +// fn serialize(&self) -> Result, MemoError> { +// return Ok(fs::read(&self.path).unwrap()); // TODO can I do this? just read the db while it's open +// // let tmpfile = NamedTempFile::new()?; +// // self.conn.backup(Main, tmpfile.path(), None).unwrap(); +// // return Ok(fs::read(tmpfile).unwrap()); +// } +// +// fn deserialize(data: Vec) -> Result { +// let mut tmpfile = NamedTempFile::new()?; +// tmpfile.write(data.as_slice())?; +// let path = tmpfile.path(); +// return Ok(SQLiteStorage{ +// conn: Connection::open(path).unwrap(), +// path: path.to_path_buf(), +// tmpfile: Some(tmpfile), +// }); +// } +// +// }