feat: refactor storage, add tag, add tree view

This commit is contained in:
əlemi 2022-12-15 02:23:43 +01:00
parent 408cb9fda1
commit cfe29f0c12
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
8 changed files with 399 additions and 326 deletions

112
src/categorize.rs Normal file
View file

@ -0,0 +1,112 @@
use colored::Colorize;
use crate::{model::memo::Memo, utils::HumanDisplay};
pub struct MemoNode {
tag: String,
memo: Vec<Memo>,
sub: Vec<MemoNode>,
}
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<String>) {
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<Memo>) -> Tree {
let mut tree = Tree::new();
for memo in memos {
let fragments : Vec<String> = 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<String>) -> &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;
}
}
}

View file

@ -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<Commands>,
#[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<String>,
#[clap(short, long, help = "due time relative to now")]
due: Option<String>, // TODO allow to pass date
#[clap(short, long, help = "memo tag, used for categorizing")]
tag: Option<String>,
},
/// list existing memos
List {
#[clap(short, long, help = "list with given tag as root")]
tag: Option<String>,
#[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<String>,
#[clap(short, long, help = "set memo message")]
body: Option<String>,
#[clap(short, long, help = "set due time relative to now")]
@ -110,30 +121,31 @@ fn main() {
}
fn run_commands<T>(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()
// );
// }
// }
match &args.command {
Some(Commands::New { body, due }) => {
let ctx = storage.ctx().unwrap();
match args.command {
Some(Commands::New { body, due, tag }) => {
let mut due_date: Option<DateTime<Utc>> = 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,41 +208,64 @@ 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");
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()
// );
// }
// }
Ok(())
}
fn display_memos(all: Vec<Memo>, query: Option<String>, 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");
}
for m in all {
let tmp = if args.notify { m.human() } else { m.colored() };
builder.push_str(tmp.as_str());
builder.push('\n');
let tree = make_tree(all);
let mut tree_ref = &tree;
if let Some(q) = query {
tree_ref = traverse(tree_ref, q);
}
if args.notify {
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())
@ -240,24 +276,5 @@ where
println!("{} | {}", "memo-cli".bold(), timing);
print!("{}", builder);
}
}
}
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(())
}

View file

@ -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<DateTime<Utc>>,
pub done: Option<DateTime<Utc>>,
@ -13,6 +14,7 @@ pub struct Memo {
}
impl Memo {
#[allow(dead_code)] // TODO temporary
pub fn new(body: String, due: Option<DateTime<Utc>>) -> 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,

View file

@ -1,15 +1,15 @@
use chrono::{DateTime, Utc};
use uuid::Uuid;
pub struct State {
pub struct Context {
pub last_edit: DateTime<Utc>,
pub last_sync: Option<DateTime<Utc>>,
pub last_memo: Option<Uuid>,
}
impl Default for State {
impl Default for Context {
fn default() -> Self {
State {
Context {
last_edit: Utc::now(),
last_sync: None,
last_memo: None,

View file

@ -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<Vec<u8>, MemoError>;
fn deserialize(data: Vec<u8>) -> Result<Self, MemoError>;
@ -63,7 +62,7 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized {
todo!()
}
} else {
self.insert(memo.clone())?;
// self.insert(memo.clone())?;
count += 1;
}
}

View file

@ -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<String, MemoError> {
return Ok(self.data.hash.to_string());
}
// impl AuthStorage for JsonStorage {
// fn get_hash(&self) -> Result<String, MemoError> {
// return Ok(self.data.hash.to_string());
// }
//
// fn get_key(&self) -> Result<String, MemoError> {
// return Ok(self.data.key.to_string());
// }
//
// fn set_key(&mut self, key: &str) -> Result<Option<String>, 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<String, MemoError> {
return Ok(self.data.key.to_string());
}
fn set_key(&mut self, key: &str) -> Result<Option<String>, 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<State, MemoError> {
impl StorageDriver for JsonStorage {
fn ctx(&self) -> Result<Context, MemoError> {
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<Option<State>, 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<Vec<Memo>, MemoError> {
let mut results_due : LinkedList<Memo> = LinkedList::new();
let mut results_not_due : LinkedList<Memo> = 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<usize, MemoError> {
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<usize, MemoError> {
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<Memo, MemoError> {
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<Vec<u8>, MemoError> {
self.save()?;
return Ok(std::fs::read(&self.path).unwrap());
}
fn deserialize(data: Vec<u8>) -> Result<Self, MemoError> {
return Ok(JsonStorage{
path: PathBuf::new(),
data: serde_json::from_slice(data.as_slice()).unwrap(),
});
}
}
// impl RemoteSync for JsonStorage {
// fn serialize(&self) -> Result<Vec<u8>, MemoError> {
// self.save()?;
// return Ok(std::fs::read(&self.path).unwrap());
// }
//
// fn deserialize(data: Vec<u8>) -> Result<Self, MemoError> {
// return Ok(JsonStorage{
// path: PathBuf::new(),
// data: serde_json::from_slice(data.as_slice()).unwrap(),
// });
// }
//
// }

View file

@ -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<DateTime<Utc>>),
Done(Option<DateTime<Utc>>),
}
pub trait MemoStorage {
fn all(&self, done: bool) -> Result<Vec<Memo>, 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<Memo, MemoError>;
fn del(&mut self, id: &Uuid) -> Result<usize, MemoError>;
fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result<usize, MemoError>;
fn insert(&mut self, m: Memo) -> Result<(), MemoError>;
fn add(&mut self, body: String, due: Option<DateTime<Utc>>) -> 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<Vec<Memo>, MemoError>;
/// Return current storage context
fn ctx(&self) -> Result<Context, MemoError>;
fn update(&mut self, id: &Uuid, field: MemoSetField) -> Result<Memo, MemoError> {
let mut memo = self.get(id)?;
match field {
MemoSetField::Body(body) => memo.body = body,
MemoSetField::Due(due) => memo.due = due,
MemoSetField::Done(done) => memo.done = done,
}
pub trait AuthStorage {
fn get_hash(&self) -> Result<String, MemoError>;
fn get_key(&self) -> Result<String, MemoError>;
fn set_key(&mut self, key: &str) -> Result<Option<String>, MemoError>;
self.put(memo.clone())?;
Ok(memo)
}
pub trait StateStorage {
fn set_state(&mut self, state: State) -> Result<Option<State>, MemoError>;
fn get_state(&self) -> Result<State, MemoError>;
fn set_edit_time(&mut self, time: DateTime<Utc>) -> 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(())
fn add(&mut self, tag: String, body: String, due: Option<DateTime<Utc>>) -> Result<Memo, MemoError> {
let m = Memo {
id: Uuid::new_v4(),
tag, body, due,
last_edit: Utc::now(),
done: None,
};
self.put(m.clone())?;
Ok(m)
}
fn set_sync_time(&mut self, time: DateTime<Utc>) -> Result<(), MemoError> {
let mut state = self.get_state()?;
state.last_sync = Some(time);
self.set_state(state)?;
Ok(())
fn del(&mut self, id: &Uuid) -> Result<Memo, MemoError> {
let mut m = self.get(id)?;
m.done = Some(Utc::now());
self.put(m.clone())?;
return Ok(m);
}
}

View file

@ -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<NamedTempFile>,
@ -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<String, MemoError> {
return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
return Ok(row.get(1)?);
})?);
}
// impl AuthStorage for SQLiteStorage {
// fn get_hash(&self) -> Result<String, MemoError> {
// return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
// return Ok(row.get(1)?);
// })?);
// }
//
// fn get_key(&self) -> Result<String, MemoError> {
// return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
// return Ok(row.get(0)?);
// })?);
// }
//
// fn set_key(&mut self, key: &str) -> Result<Option<String>, 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<String, MemoError> {
return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
return Ok(row.get(0)?);
})?);
}
fn set_key(&mut self, key: &str) -> Result<Option<String>, 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<State, MemoError> {
impl StorageDriver for SQLiteStorage {
fn ctx(&self) -> Result<Context, MemoError> {
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<Option<State>, 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<Vec<Memo>, MemoError> {
let mut results_due : LinkedList<Memo> = LinkedList::new();
let mut results_not_due : LinkedList<Memo> = LinkedList::new();
@ -126,10 +114,11 @@ impl MemoStorage for SQLiteStorage {
let tgt = if row.get::<usize, DateTime<Utc>>(2).is_ok() { &mut results_due } else { &mut results_not_due };
tgt.push_back(Memo {
id: Uuid::parse_str(row.get::<usize, String>(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<usize, MemoError> {
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<usize, MemoError> {
return Ok(self.conn.execute(
"UPDATE memo SET done = ? WHERE id = ?",
params![Utc::now(), id.to_string()],
)?);
}
// fn del(&mut self, id: &Uuid) -> Result<usize, MemoError> {
// return Ok(self.conn.execute(
// "UPDATE memo SET done = ? WHERE id = ?",
// params![Utc::now(), id.to_string()],
// )?);
// }
fn get(&self, id: &Uuid) -> Result<Memo, MemoError> {
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::<usize, String>(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<Vec<u8>, 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<u8>) -> Result<Self, MemoError> {
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<Vec<u8>, 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<u8>) -> Result<Self, MemoError> {
// 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),
// });
// }
//
// }