mirror of
https://git.alemi.dev/memo-cli.git
synced 2024-11-14 05:39:19 +01:00
feat: refactor storage, add tag, add tree view
This commit is contained in:
parent
408cb9fda1
commit
cfe29f0c12
8 changed files with 399 additions and 326 deletions
112
src/categorize.rs
Normal file
112
src/categorize.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
195
src/main.rs
195
src/main.rs
|
@ -2,7 +2,9 @@ mod remote;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod categorize;
|
||||||
|
|
||||||
|
use categorize::{make_tree, format_tree, traverse};
|
||||||
use chrono::{DateTime, Local, Utc};
|
use chrono::{DateTime, Local, Utc};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
@ -10,13 +12,13 @@ use const_format::concatcp;
|
||||||
use git_version::git_version;
|
use git_version::git_version;
|
||||||
use notify_rust::Notification;
|
use notify_rust::Notification;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use remote::RemoteSync;
|
// use remote::RemoteSync;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use storage::{
|
use storage::{
|
||||||
AuthStorage, JsonStorage, MemoStorage, SQLiteStorage, StateStorage,
|
JsonStorage, SQLiteStorage,
|
||||||
SUPPORTED_FORMATS,
|
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};
|
use utils::{find_by_regex, find_db_file, parse_human_duration, HumanDisplay};
|
||||||
|
|
||||||
const GIT_VERSION: &str = git_version!();
|
const GIT_VERSION: &str = git_version!();
|
||||||
|
@ -32,10 +34,6 @@ const VERSION: &str = concatcp!(PKG_VERSION, "-", GIT_VERSION);
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
command: Option<Commands>,
|
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")]
|
#[clap(short, long, help = "synchronize memo db")]
|
||||||
sync: bool,
|
sync: bool,
|
||||||
#[clap(long, help = "name of db file, without extension")]
|
#[clap(long, help = "name of db file, without extension")]
|
||||||
|
@ -55,6 +53,17 @@ enum Commands {
|
||||||
body: Vec<String>,
|
body: Vec<String>,
|
||||||
#[clap(short, long, help = "due time relative to now")]
|
#[clap(short, long, help = "due time relative to now")]
|
||||||
due: Option<String>, // TODO allow to pass date
|
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
|
/// mark existing memo as done
|
||||||
Done {
|
Done {
|
||||||
|
@ -65,6 +74,8 @@ enum Commands {
|
||||||
/// change existing memo
|
/// change existing memo
|
||||||
Edit {
|
Edit {
|
||||||
search: String,
|
search: String,
|
||||||
|
#[clap(short, long, help = "set memo tag")]
|
||||||
|
tag: Option<String>,
|
||||||
#[clap(short, long, help = "set memo message")]
|
#[clap(short, long, help = "set memo message")]
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
#[clap(short, long, help = "set due time relative to now")]
|
#[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>
|
fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
|
||||||
where
|
where T: StorageDriver,
|
||||||
T: MemoStorage + AuthStorage + StateStorage + RemoteSync,
|
|
||||||
{
|
{
|
||||||
if args.sync {
|
// if args.sync {
|
||||||
if storage.get_key().is_err() {
|
// if storage.get_key().is_err() {
|
||||||
let key = rpassword::prompt_password("[$] new passphrase: ").unwrap();
|
// let key = rpassword::prompt_password("[$] new passphrase: ").unwrap();
|
||||||
storage.set_key(key.as_str()).unwrap();
|
// storage.set_key(key.as_str()).unwrap();
|
||||||
}
|
// }
|
||||||
let res = storage.download(
|
// let res = storage.download(
|
||||||
storage.get_hash().unwrap().as_str(),
|
// storage.get_hash().unwrap().as_str(),
|
||||||
"http://127.0.0.1:8443",
|
// "http://127.0.0.1:8443",
|
||||||
);
|
// );
|
||||||
if res.is_ok() {
|
// if res.is_ok() {
|
||||||
println!("[v] downloaded remote db");
|
// println!("[v] downloaded remote db");
|
||||||
} else {
|
// } else {
|
||||||
println!(
|
// println!(
|
||||||
"[!] could not fetch db : {}",
|
// "[!] could not fetch db : {}",
|
||||||
res.err().unwrap().to_string()
|
// res.err().unwrap().to_string()
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
let ctx = storage.ctx().unwrap();
|
||||||
|
|
||||||
match &args.command {
|
match args.command {
|
||||||
Some(Commands::New { body, due }) => {
|
Some(Commands::New { body, due, tag }) => {
|
||||||
let mut due_date: Option<DateTime<Utc>> = None;
|
let mut due_date: Option<DateTime<Utc>> = None;
|
||||||
if let Some(d) = due {
|
if let Some(d) = due {
|
||||||
if d.len() > 0 {
|
if d.len() > 0 {
|
||||||
|
@ -142,8 +154,7 @@ where
|
||||||
}
|
}
|
||||||
let txt = body.join(" ");
|
let txt = body.join(" ");
|
||||||
println!("{} new memo: {}", "[+]".bold(), &txt);
|
println!("{} new memo: {}", "[+]".bold(), &txt);
|
||||||
storage.add(txt, due_date).unwrap();
|
storage.add(tag.unwrap_or("".to_string()), txt, due_date).unwrap();
|
||||||
storage.set_edit_time(Utc::now()).unwrap();
|
|
||||||
}
|
}
|
||||||
Some(Commands::Done { search, many }) => {
|
Some(Commands::Done { search, many }) => {
|
||||||
let rex = Regex::new(search.as_str());
|
let rex = Regex::new(search.as_str());
|
||||||
|
@ -152,7 +163,7 @@ where
|
||||||
if let Some(re) = rex.ok() {
|
if let Some(re) = rex.ok() {
|
||||||
for memo in storage.all(false).unwrap() {
|
for memo in storage.all(false).unwrap() {
|
||||||
if re.is_match(memo.body.as_str()) {
|
if re.is_match(memo.body.as_str()) {
|
||||||
if *many {
|
if many {
|
||||||
storage.del(&memo.id).unwrap();
|
storage.del(&memo.id).unwrap();
|
||||||
println!("[-] done task : {}", memo.body);
|
println!("[-] done task : {}", memo.body);
|
||||||
} else if found {
|
} else if found {
|
||||||
|
@ -171,20 +182,22 @@ where
|
||||||
}
|
}
|
||||||
if let Some(rm) = to_remove {
|
if let Some(rm) = to_remove {
|
||||||
storage.del(&rm.id).unwrap();
|
storage.del(&rm.id).unwrap();
|
||||||
storage.set_edit_time(Utc::now()).unwrap();
|
|
||||||
println!("{} done memo: {}", "[-]".bold(), rm.body);
|
println!("{} done memo: {}", "[-]".bold(), rm.body);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("{} invalid regex", format!("[{}]", "!".red()).bold());
|
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(
|
let mut m = find_by_regex(
|
||||||
regex::Regex::new(search.as_str()).unwrap(),
|
regex::Regex::new(search.as_str()).unwrap(),
|
||||||
storage.all(false).unwrap(),
|
storage.all(false).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(t) = tag {
|
||||||
|
m.tag = t;
|
||||||
|
}
|
||||||
if let Some(b) = body {
|
if let Some(b) = body {
|
||||||
m.body = b.to_owned();
|
m.body = b.to_owned();
|
||||||
}
|
}
|
||||||
|
@ -195,69 +208,73 @@ where
|
||||||
m.due = Some(Utc::now() + parse_human_duration(d.as_str()).unwrap());
|
m.due = Some(Utc::now() + parse_human_duration(d.as_str()).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// storage.set(&m).unwrap(); // TODO fix editing memos
|
let m_str = m.colored();
|
||||||
storage.set_edit_time(Utc::now()).unwrap();
|
storage.put(m).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"{} updated memo\n{}",
|
"{} updated memo\n{}",
|
||||||
format!("[{}]", ">".green()).bold().to_string(),
|
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 => {
|
None => {
|
||||||
let all = storage.all(args.old).unwrap();
|
let all = storage.all(false).unwrap();
|
||||||
let mut builder = String::new();
|
display_memos(all, None, &ctx, false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.sync {
|
// if args.sync {
|
||||||
let res = storage.upload(
|
// let res = storage.upload(
|
||||||
storage.get_hash().unwrap().as_str(),
|
// storage.get_hash().unwrap().as_str(),
|
||||||
"http://127.0.0.1:8443",
|
// "http://127.0.0.1:8443",
|
||||||
);
|
// );
|
||||||
if res.is_ok() {
|
// if res.is_ok() {
|
||||||
println!("[^] uploaded local db");
|
// println!("[^] uploaded local db");
|
||||||
storage.set_sync_time(Utc::now()).unwrap();
|
// storage.set_sync_time(Utc::now()).unwrap();
|
||||||
} else {
|
// } else {
|
||||||
println!(
|
// println!(
|
||||||
"[!] could not upload db : {}",
|
// "[!] could not upload db : {}",
|
||||||
res.err().unwrap().to_string()
|
// res.err().unwrap().to_string()
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Ok(())
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use uuid::Uuid;
|
||||||
#[derive(Clone, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Eq, Serialize, Deserialize)]
|
||||||
pub struct Memo {
|
pub struct Memo {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
|
pub tag: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub due: Option<DateTime<Utc>>,
|
pub due: Option<DateTime<Utc>>,
|
||||||
pub done: Option<DateTime<Utc>>,
|
pub done: Option<DateTime<Utc>>,
|
||||||
|
@ -13,6 +14,7 @@ pub struct Memo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memo {
|
impl Memo {
|
||||||
|
#[allow(dead_code)] // TODO temporary
|
||||||
pub fn new(body: String, due: Option<DateTime<Utc>>) -> Self {
|
pub fn new(body: String, due: Option<DateTime<Utc>>) -> Self {
|
||||||
Memo { body, due, ..Default::default() }
|
Memo { body, due, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
@ -22,6 +24,7 @@ impl Default for Memo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Memo {
|
Memo {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
|
tag: "".to_string(),
|
||||||
body: "".to_string(),
|
body: "".to_string(),
|
||||||
due: None,
|
due: None,
|
||||||
done: None,
|
done: None,
|
||||||
|
@ -72,8 +75,9 @@ impl fmt::Display for Memo {
|
||||||
}
|
}
|
||||||
return write!(
|
return write!(
|
||||||
f,
|
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,
|
id = self.id,
|
||||||
|
tag = self.tag,
|
||||||
body = self.body,
|
body = self.body,
|
||||||
due = due_str,
|
due = due_str,
|
||||||
done = done_str,
|
done = done_str,
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub struct State {
|
pub struct Context {
|
||||||
pub last_edit: DateTime<Utc>,
|
pub last_edit: DateTime<Utc>,
|
||||||
pub last_sync: Option<DateTime<Utc>>,
|
pub last_sync: Option<DateTime<Utc>>,
|
||||||
pub last_memo: Option<Uuid>,
|
pub last_memo: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for Context {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
State {
|
Context {
|
||||||
last_edit: Utc::now(),
|
last_edit: Utc::now(),
|
||||||
last_sync: None,
|
last_sync: None,
|
||||||
last_memo: None,
|
last_memo: None,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::storage::{MemoStorage, AuthStorage};
|
use crate::{model::memo::MemoError, storage::StorageDriver};
|
||||||
use crate::model::memo::MemoError;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
@ -9,7 +8,7 @@ use std::io::Read;
|
||||||
#[test]
|
#[test]
|
||||||
fn always_succeeds() { }
|
fn always_succeeds() { }
|
||||||
|
|
||||||
pub trait RemoteSync : AuthStorage+MemoStorage+Sized {
|
pub trait RemoteSync : StorageDriver+Sized {
|
||||||
fn serialize(&self) -> Result<Vec<u8>, MemoError>;
|
fn serialize(&self) -> Result<Vec<u8>, MemoError>;
|
||||||
fn deserialize(data: Vec<u8>) -> Result<Self, MemoError>;
|
fn deserialize(data: Vec<u8>) -> Result<Self, MemoError>;
|
||||||
|
|
||||||
|
@ -63,7 +62,7 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.insert(memo.clone())?;
|
// self.insert(memo.clone())?;
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@ use std::io::BufReader;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use uuid::Uuid;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use sha2::{Digest, Sha512};
|
// use sha2::{Digest, Sha512};
|
||||||
use std::collections::LinkedList;
|
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 {
|
pub struct JsonStorage {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -60,30 +60,30 @@ impl JsonStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthStorage for JsonStorage {
|
// impl AuthStorage for JsonStorage {
|
||||||
fn get_hash(&self) -> Result<String, MemoError> {
|
// fn get_hash(&self) -> Result<String, MemoError> {
|
||||||
return Ok(self.data.hash.to_string());
|
// 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> {
|
impl StorageDriver for JsonStorage {
|
||||||
return Ok(self.data.key.to_string());
|
fn ctx(&self) -> Result<Context, MemoError> {
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
|
||||||
return Ok(
|
return Ok(
|
||||||
State {
|
Context {
|
||||||
last_edit: self.data.last_edit,
|
last_edit: self.data.last_edit,
|
||||||
last_sync: self.data.last_sync,
|
last_sync: self.data.last_sync,
|
||||||
last_memo: None, // TODO
|
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> {
|
fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError> {
|
||||||
let mut results_due : LinkedList<Memo> = LinkedList::new();
|
let mut results_due : LinkedList<Memo> = LinkedList::new();
|
||||||
let mut results_not_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);
|
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.memo.push(m);
|
||||||
|
self.data.last_edit = Utc::now();
|
||||||
self.save()?;
|
self.save()?;
|
||||||
Ok(())
|
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> {
|
fn get(&self, id: &uuid::Uuid) -> Result<Memo, MemoError> {
|
||||||
for memo in &self.data.memo {
|
for memo in &self.data.memo {
|
||||||
if memo.id == *id {
|
if memo.id == *id {
|
||||||
|
@ -173,17 +133,17 @@ impl MemoStorage for JsonStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl RemoteSync for JsonStorage {
|
// impl RemoteSync for JsonStorage {
|
||||||
fn serialize(&self) -> Result<Vec<u8>, MemoError> {
|
// fn serialize(&self) -> Result<Vec<u8>, MemoError> {
|
||||||
self.save()?;
|
// self.save()?;
|
||||||
return Ok(std::fs::read(&self.path).unwrap());
|
// return Ok(std::fs::read(&self.path).unwrap());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fn deserialize(data: Vec<u8>) -> Result<Self, MemoError> {
|
// fn deserialize(data: Vec<u8>) -> Result<Self, MemoError> {
|
||||||
return Ok(JsonStorage{
|
// return Ok(JsonStorage{
|
||||||
path: PathBuf::new(),
|
// path: PathBuf::new(),
|
||||||
data: serde_json::from_slice(data.as_slice()).unwrap(),
|
// data: serde_json::from_slice(data.as_slice()).unwrap(),
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use crate::model::{memo::{Memo, MemoError}, state::State};
|
use crate::model::{memo::{Memo, MemoError}, state::Context};
|
||||||
|
|
||||||
pub mod json;
|
pub mod json;
|
||||||
pub mod sqlite;
|
pub mod sqlite;
|
||||||
|
@ -10,46 +10,56 @@ pub use sqlite::SQLiteStorage;
|
||||||
|
|
||||||
pub const SUPPORTED_FORMATS: &'static [&'static str] = &["db", "json"];
|
pub const SUPPORTED_FORMATS: &'static [&'static str] = &["db", "json"];
|
||||||
|
|
||||||
|
#[allow(dead_code)] // TODO temp
|
||||||
pub enum MemoSetField {
|
pub enum MemoSetField {
|
||||||
Body(String),
|
Body(String),
|
||||||
Due(Option<DateTime<Utc>>),
|
Due(Option<DateTime<Utc>>),
|
||||||
Done(Option<DateTime<Utc>>),
|
Done(Option<DateTime<Utc>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MemoStorage {
|
pub trait StorageDriver {
|
||||||
fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError>;
|
|
||||||
|
/// 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 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> {
|
/// Return a vector with clones of all elements in storage (optionally include concluded ones)
|
||||||
self.insert(Memo::new(body, due))
|
fn all(&self, done:bool) -> Result<Vec<Memo>, MemoError>;
|
||||||
}
|
|
||||||
}
|
/// Return current storage context
|
||||||
|
fn ctx(&self) -> Result<Context, MemoError>;
|
||||||
|
|
||||||
pub trait AuthStorage {
|
fn update(&mut self, id: &Uuid, field: MemoSetField) -> Result<Memo, MemoError> {
|
||||||
fn get_hash(&self) -> Result<String, MemoError>;
|
let mut memo = self.get(id)?;
|
||||||
fn get_key(&self) -> Result<String, MemoError>;
|
|
||||||
fn set_key(&mut self, key: &str) -> Result<Option<String>, MemoError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait StateStorage {
|
match field {
|
||||||
fn set_state(&mut self, state: State) -> Result<Option<State>, MemoError>;
|
MemoSetField::Body(body) => memo.body = body,
|
||||||
fn get_state(&self) -> Result<State, MemoError>;
|
MemoSetField::Due(due) => memo.due = due,
|
||||||
|
MemoSetField::Done(done) => memo.done = done,
|
||||||
|
}
|
||||||
|
|
||||||
fn set_edit_time(&mut self, time: DateTime<Utc>) -> Result<(), MemoError> {
|
self.put(memo.clone())?;
|
||||||
let mut state = self.get_state()
|
|
||||||
.unwrap_or(State { last_edit: time, ..Default::default() });
|
Ok(memo)
|
||||||
state.last_edit = time;
|
|
||||||
self.set_state(state)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_sync_time(&mut self, time: DateTime<Utc>) -> Result<(), MemoError> {
|
fn add(&mut self, tag: String, body: String, due: Option<DateTime<Utc>>) -> Result<Memo, MemoError> {
|
||||||
let mut state = self.get_state()?;
|
let m = Memo {
|
||||||
state.last_sync = Some(time);
|
id: Uuid::new_v4(),
|
||||||
self.set_state(state)?;
|
tag, body, due,
|
||||||
Ok(())
|
last_edit: Utc::now(),
|
||||||
|
done: None,
|
||||||
|
};
|
||||||
|
self.put(m.clone())?;
|
||||||
|
Ok(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
use crate::remote::RemoteSync;
|
// use crate::remote::RemoteSync;
|
||||||
use crate::storage::{AuthStorage, Memo, MemoError, MemoStorage, State, StateStorage};
|
use crate::model::{memo::{Memo, MemoError}, state::Context};
|
||||||
|
use crate::storage::StorageDriver;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
// use rusqlite::DatabaseName::Main;
|
// use rusqlite::DatabaseName::Main;
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use sha2::{Digest, Sha512};
|
// use sha2::{Digest, Sha512};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
use std::fs;
|
|
||||||
use std::io::Write;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use super::MemoSetField;
|
|
||||||
|
|
||||||
pub struct SQLiteStorage {
|
pub struct SQLiteStorage {
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
path: PathBuf,
|
// path: PathBuf,
|
||||||
|
|
||||||
#[allow(dead_code)] // I just use this to bind the tempfile life with the SQLiteStorage obj
|
#[allow(dead_code)] // I just use this to bind the tempfile life with the SQLiteStorage obj
|
||||||
tmpfile: Option<NamedTempFile>,
|
tmpfile: Option<NamedTempFile>,
|
||||||
|
@ -30,6 +27,7 @@ impl SQLiteStorage {
|
||||||
connection.execute(
|
connection.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS memo (
|
"CREATE TABLE IF NOT EXISTS memo (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
body TEXT NOT NULL,
|
body TEXT NOT NULL,
|
||||||
due DATETIME,
|
due DATETIME,
|
||||||
done DATETIME DEFAULT NULL,
|
done DATETIME DEFAULT NULL,
|
||||||
|
@ -41,53 +39,55 @@ impl SQLiteStorage {
|
||||||
"CREATE TABLE IF NOT EXISTS state (
|
"CREATE TABLE IF NOT EXISTS state (
|
||||||
last_edit DATETIME DEFAULT NULL,
|
last_edit DATETIME DEFAULT NULL,
|
||||||
last_sync DATETIME DEFAULT NULL
|
last_sync DATETIME DEFAULT NULL
|
||||||
|
last_memo TEXT DEFAULT NULL
|
||||||
);",
|
);",
|
||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
connection.execute(
|
|
||||||
"CREATE TABLE IF NOT EXISTS auth (
|
// connection.execute(
|
||||||
key TEXT PRIMARY KEY,
|
// "CREATE TABLE IF NOT EXISTS auth (
|
||||||
hash TEXT NOT NULL
|
// 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 {
|
// impl AuthStorage for SQLiteStorage {
|
||||||
fn get_hash(&self) -> Result<String, MemoError> {
|
// fn get_hash(&self) -> Result<String, MemoError> {
|
||||||
return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
|
// return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
|
||||||
return Ok(row.get(1)?);
|
// 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> {
|
impl StorageDriver for SQLiteStorage {
|
||||||
return Ok(self.conn.query_row("SELECT * FROM auth", [], |row| {
|
fn ctx(&self) -> Result<Context, MemoError> {
|
||||||
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> {
|
|
||||||
return Ok(self.conn.query_row("SELECT * FROM state", [], |row| {
|
return Ok(self.conn.query_row("SELECT * FROM state", [], |row| {
|
||||||
return Ok(State {
|
return Ok(Context {
|
||||||
last_edit: row.get(0)?,
|
last_edit: row.get(0)?,
|
||||||
last_sync: row.get(1).ok(),
|
last_sync: row.get(1).ok(),
|
||||||
..Default::default()
|
..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> {
|
fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError> {
|
||||||
let mut results_due : LinkedList<Memo> = LinkedList::new();
|
let mut results_due : LinkedList<Memo> = LinkedList::new();
|
||||||
let mut results_not_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 };
|
let tgt = if row.get::<usize, DateTime<Utc>>(2).is_ok() { &mut results_due } else { &mut results_not_due };
|
||||||
tgt.push_back(Memo {
|
tgt.push_back(Memo {
|
||||||
id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
|
id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
|
||||||
body: row.get(1)?,
|
tag: row.get(1)?,
|
||||||
due: row.get(2).ok(),
|
body: row.get(2)?,
|
||||||
done: row.get(3).ok(),
|
due: row.get(3).ok(),
|
||||||
last_edit: row.get(4)?,
|
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());
|
return Ok(results_due.into_iter().collect());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, m: Memo) -> Result<(), MemoError> {
|
fn put(&mut self, m: Memo) -> Result<(), MemoError> {
|
||||||
self.conn.execute(
|
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 (?, ?, ?, ?, ?)",
|
"INSERT INTO memo (id, body, due, done, last_edit) VALUES (?, ?, ?, ?, ?)",
|
||||||
params![m.id.to_string(), m.body, m.due, m.done, m.last_edit],
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result<usize, MemoError> {
|
// fn del(&mut self, id: &Uuid) -> Result<usize, MemoError> {
|
||||||
match field {
|
// return Ok(self.conn.execute(
|
||||||
MemoSetField::Body(body) => {
|
// "UPDATE memo SET done = ? WHERE id = ?",
|
||||||
Ok(self.conn.execute(
|
// params![Utc::now(), id.to_string()],
|
||||||
"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 get(&self, id: &Uuid) -> Result<Memo, MemoError> {
|
fn get(&self, id: &Uuid) -> Result<Memo, MemoError> {
|
||||||
return Ok(self.conn.query_row(
|
return Ok(self.conn.query_row(
|
||||||
"SELECT * FROM memo WHERE id = ? AND done = 0",
|
"SELECT * FROM memo WHERE id = ?",
|
||||||
params![id.to_string()],
|
params![id.to_string()],
|
||||||
|row| {
|
|row| {
|
||||||
return Ok(Memo {
|
return Ok(Memo {
|
||||||
id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
|
id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
|
||||||
body: row.get(1)?,
|
tag: row.get(1)?,
|
||||||
due: row.get(2).ok(),
|
body: row.get(2)?,
|
||||||
done: row.get(3).ok(),
|
due: row.get(3).ok(),
|
||||||
last_edit: row.get(4)?,
|
done: row.get(4).ok(),
|
||||||
|
last_edit: row.get(5)?,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)?);
|
)?);
|
||||||
|
@ -194,23 +165,23 @@ impl MemoStorage for SQLiteStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl RemoteSync for SQLiteStorage {
|
// impl RemoteSync for SQLiteStorage {
|
||||||
fn serialize(&self) -> Result<Vec<u8>, MemoError> {
|
// 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
|
// return Ok(fs::read(&self.path).unwrap()); // TODO can I do this? just read the db while it's open
|
||||||
// let tmpfile = NamedTempFile::new()?;
|
// // let tmpfile = NamedTempFile::new()?;
|
||||||
// self.conn.backup(Main, tmpfile.path(), None).unwrap();
|
// // self.conn.backup(Main, tmpfile.path(), None).unwrap();
|
||||||
// return Ok(fs::read(tmpfile).unwrap());
|
// // return Ok(fs::read(tmpfile).unwrap());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fn deserialize(data: Vec<u8>) -> Result<Self, MemoError> {
|
// fn deserialize(data: Vec<u8>) -> Result<Self, MemoError> {
|
||||||
let mut tmpfile = NamedTempFile::new()?;
|
// let mut tmpfile = NamedTempFile::new()?;
|
||||||
tmpfile.write(data.as_slice())?;
|
// tmpfile.write(data.as_slice())?;
|
||||||
let path = tmpfile.path();
|
// let path = tmpfile.path();
|
||||||
return Ok(SQLiteStorage{
|
// return Ok(SQLiteStorage{
|
||||||
conn: Connection::open(path).unwrap(),
|
// conn: Connection::open(path).unwrap(),
|
||||||
path: path.to_path_buf(),
|
// path: path.to_path_buf(),
|
||||||
tmpfile: Some(tmpfile),
|
// tmpfile: Some(tmpfile),
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
|
Loading…
Reference in a new issue