From 408cb9fda118d8ea56d77f0ae9c648c424d7bd4d Mon Sep 17 00:00:00 2001 From: alemidev Date: Tue, 13 Dec 2022 02:42:33 +0100 Subject: [PATCH] chore: small fixes, refactored a little --- src/main.rs | 29 +++++--- src/model/memo.rs | 154 ++++++++++++++++++++++++++++++++++++++++ src/model/mod.rs | 2 + src/model/state.rs | 19 +++++ src/remote.rs | 15 +++- src/storage.rs | 160 ------------------------------------------ src/storage/json.rs | 33 +++++---- src/storage/mod.rs | 55 +++++++++++++++ src/storage/sqlite.rs | 31 ++++++-- src/utils.rs | 2 +- 10 files changed, 308 insertions(+), 192 deletions(-) create mode 100644 src/model/memo.rs create mode 100644 src/model/mod.rs create mode 100644 src/model/state.rs delete mode 100644 src/storage.rs create mode 100644 src/storage/mod.rs diff --git a/src/main.rs b/src/main.rs index cacfba7..6c908a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod remote; mod storage; mod utils; +mod model; use chrono::{DateTime, Local, Utc}; use clap::{Parser, Subcommand}; @@ -10,9 +11,13 @@ use git_version::git_version; use notify_rust::Notification; use regex::Regex; use remote::RemoteSync; -use storage::{AuthStorage, Memo, MemoError, MemoStorage, SQLiteStorage, StateStorage, JsonStorage, SUPPORTED_FORMATS}; -use utils::{find_by_regex, find_db_file, parse_human_duration, HumanDisplay}; use std::path::PathBuf; +use storage::{ + AuthStorage, JsonStorage, MemoStorage, SQLiteStorage, StateStorage, + SUPPORTED_FORMATS, +}; +use model::memo::{Memo, MemoError}; +use utils::{find_by_regex, find_db_file, parse_human_duration, HumanDisplay}; const GIT_VERSION: &str = git_version!(); const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -75,11 +80,14 @@ fn main() { let mut db_path: PathBuf; let filename = args.name.unwrap_or("memostorage".to_string()); - if let Some(db) = args.path { // if we are given a specific path, just use that + if let Some(db) = args.path { + // if we are given a specific path, just use that db_path = PathBuf::from(db); - } else if let Some(path) = find_db_file(filename.as_str(), SUPPORTED_FORMATS) { // search up from cwd + } else if let Some(path) = find_db_file(filename.as_str(), SUPPORTED_FORMATS) { + // search up from cwd db_path = path; - } else { // default path + } else { + // default path //TODO: a less "nix-centered" default fallback, possibly configurable //protip: cfg!(windows)/cfg!(unix)/cfg!(target_os = "macos") will give us the info we need db_path = dirs::home_dir().unwrap(); @@ -89,7 +97,6 @@ fn main() { //TODO: permissions check, this will panic if it finds an existing db it can't write to - if let Some(ext) = db_path.extension() { match ext.to_str().unwrap() { "json" => run_commands(JsonStorage::new(db_path).unwrap(), args2).unwrap(), @@ -98,11 +105,13 @@ fn main() { } } else { println!("[!] no extension on db file"); + println!("something"); } } fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> - where T : MemoStorage + AuthStorage + StateStorage + RemoteSync +where + T: MemoStorage + AuthStorage + StateStorage + RemoteSync, { if args.sync { if storage.get_key().is_err() { @@ -148,6 +157,10 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> println!("[-] done task : {}", memo.body); } else if found { println!("[!] would remove multiple tasks"); + if let Some(other) = to_remove { + println!(" ]> {}", other.body); + } + println!(" ]> {}", memo.body); to_remove = None; break; } else { @@ -182,7 +195,7 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> m.due = Some(Utc::now() + parse_human_duration(d.as_str()).unwrap()); } } - storage.set(&m).unwrap(); + // storage.set(&m).unwrap(); // TODO fix editing memos storage.set_edit_time(Utc::now()).unwrap(); println!( "{} updated memo\n{}", diff --git a/src/model/memo.rs b/src/model/memo.rs new file mode 100644 index 0000000..4a22d76 --- /dev/null +++ b/src/model/memo.rs @@ -0,0 +1,154 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use uuid::Uuid; + +#[derive(Clone, Eq, Serialize, Deserialize)] +pub struct Memo { + pub id: Uuid, + pub body: String, + pub due: Option>, + pub done: Option>, + pub last_edit: DateTime, +} + +impl Memo { + pub fn new(body: String, due: Option>) -> Self { + Memo { body, due, ..Default::default() } + } +} + +impl Default for Memo { + fn default() -> Self { + Memo { + id: Uuid::new_v4(), + body: "".to_string(), + due: None, + done: None, + last_edit: Utc::now(), + } + } +} + +impl PartialEq for Memo { + fn eq(&self, other: &Memo) -> bool { + self.id == other.id + } +} + +impl Ord for Memo { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if self.due.is_some() { + if other.due.is_some() { + return self.due.unwrap().cmp(&other.due.unwrap()); + } else { + return std::cmp::Ordering::Less; + } + } else { + if other.due.is_some() { + return std::cmp::Ordering::Greater; + } else { + return self.last_edit.cmp(&other.last_edit); + } + } + } +} + +impl PartialOrd for Memo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Display for Memo { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut due_str = "null".to_string(); + if self.due.is_some() { + due_str = self.due.unwrap().to_string(); + } + let mut done_str = "null".to_string(); + if self.done.is_some() { + done_str = self.done.unwrap().to_string(); + } + return write!( + f, + "Memo(id={id}, body={body}, due={due}, done={done}, last_edit={last_edit})", + id = self.id, + body = self.body, + due = due_str, + done = done_str, + last_edit = self.last_edit.to_string(), + ); + } +} + + + +// ===|| Memo Error + + + +#[derive(Debug)] +pub struct MemoError { + pub cause: &'static str, + pub sqlite: Option, + pub io: Option, + pub ureq: Option, +} + +impl ToString for MemoError { + fn to_string(&self) -> String { + format!( + "MemoError(cause={cause}, sqlite={sqlite}, io={io}, ureq={ureq})", + cause = self.cause, + sqlite = if self.sqlite.is_some() { + self.sqlite.as_ref().unwrap().to_string() + } else { + "null".to_string() + }, + io = if self.io.is_some() { + self.io.as_ref().unwrap().to_string() + } else { + "null".to_string() + }, + ureq = if self.ureq.is_some() { + self.ureq.as_ref().unwrap().to_string() + } else { + "null".to_string() + }, + ) + } +} + +impl From for MemoError { + fn from(e: rusqlite::Error) -> Self { + Self { + cause: "sqlite", + sqlite: Some(e), + io: None, + ureq: None, + } + } +} + +impl From for MemoError { + fn from(e: std::io::Error) -> Self { + Self { + cause: "io", + sqlite: None, + io: Some(e), + ureq: None, + } + } +} + +impl From for MemoError { + fn from(e: ureq::Error) -> Self { + Self { + cause: "ureq", + sqlite: None, + io: None, + ureq: Some(e), + } + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..56733d7 --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,2 @@ +pub mod memo; +pub mod state; diff --git a/src/model/state.rs b/src/model/state.rs new file mode 100644 index 0000000..916c3d6 --- /dev/null +++ b/src/model/state.rs @@ -0,0 +1,19 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +pub struct State { + pub last_edit: DateTime, + pub last_sync: Option>, + pub last_memo: Option, +} + +impl Default for State { + fn default() -> Self { + State { + last_edit: Utc::now(), + last_sync: None, + last_memo: None, + } + } +} + diff --git a/src/remote.rs b/src/remote.rs index f097660..db85982 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -1,7 +1,13 @@ -use crate::storage::{MemoError, MemoStorage, AuthStorage}; +use crate::storage::{MemoStorage, AuthStorage}; +use crate::model::memo::MemoError; use std::collections::HashSet; use uuid::Uuid; use std::io::Read; +// use openssl::aes::{AesKey, aes_ige}; +// use openssl::symm::Mode; + +#[test] +fn always_succeeds() { } pub trait RemoteSync : AuthStorage+MemoStorage+Sized { fn serialize(&self) -> Result, MemoError>; @@ -27,7 +33,11 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized { .into_reader(); let mut data: Vec = Vec::new(); + // let mut encrypted : Vec = Vec::new(); resp.read_to_end(&mut data)?; + + // let key = AesKey::new_encrypt(self.get_key()?.as_bytes()).unwrap(); // TODO include into MemoError? + // aes_ige(data.as_slice(), encrypted.as_slice(), // TODO decrypt let other = Self::deserialize(data).unwrap(); @@ -49,7 +59,8 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized { if memo_ids.contains(&memo.id) { let old_memo = self.get(&memo.id)?; if memo.last_edit > old_memo.last_edit { - self.set(memo)?; + // self.set(memo)?; // TODO fix merging memos! + todo!() } } else { self.insert(memo.clone())?; diff --git a/src/storage.rs b/src/storage.rs deleted file mode 100644 index d22b473..0000000 --- a/src/storage.rs +++ /dev/null @@ -1,160 +0,0 @@ -pub mod sqlite; -pub mod json; - -pub use sqlite::SQLiteStorage; -pub use json::JsonStorage; - -use chrono::{DateTime, Utc}; -use std::fmt; -use uuid::Uuid; -use serde::{Serialize, Deserialize}; - -pub const SUPPORTED_FORMATS : &'static [&'static str] = &["db", "json"]; - -#[derive(Clone, Eq, Serialize, Deserialize)] -pub struct Memo { - pub id: Uuid, - pub body: String, - pub due: Option>, - pub done: Option>, - pub last_edit: DateTime, -} - -impl Memo { - pub fn new(body: String, due: Option>) -> Self { - return Memo{ - id: Uuid::new_v4(), - body, due, - done: None, - last_edit: Utc::now(), - }; - } -} - -impl PartialEq for Memo { - fn eq(&self, other: &Memo) -> bool { - self.id == other.id - } -} - -impl Ord for Memo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - if self.due.is_some() { - if other.due.is_some() { - return self.due.unwrap().cmp(&other.due.unwrap()); - } else { return std::cmp::Ordering::Less; } - } else { - if other.due.is_some() { - return std::cmp::Ordering::Greater; - } - else { - return self.last_edit.cmp(&other.last_edit); - } - } - } -} - -impl PartialOrd for Memo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl fmt::Display for Memo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut due_str = "null".to_string(); - if self.due.is_some() { - due_str = self.due.unwrap().to_string(); - } - let mut done_str = "null".to_string(); - if self.done.is_some() { - done_str = self.done.unwrap().to_string(); - } - return write!( - f, - "Memo(id={id}, body={body}, due={due}, done={done}, last_edit={last_edit})", - id = self.id, - body = self.body, - due = due_str, - done = done_str, - last_edit = self.last_edit.to_string(), - ); - } -} - -#[derive(Debug)] -pub struct MemoError { - pub cause: &'static str, - pub sqlite: Option, - pub io: Option, - pub ureq: Option, -} - -impl ToString for MemoError { - fn to_string(&self) -> String { - format!( - "MemoError(cause={cause}, sqlite={sqlite}, io={io}, ureq={ureq})", - cause=self.cause, - sqlite= if self.sqlite.is_some() { self.sqlite.as_ref().unwrap().to_string() } else { "null".to_string() }, - io= if self.io.is_some() { self.io.as_ref().unwrap().to_string() } else { "null".to_string() }, - ureq= if self.ureq.is_some() { self.ureq.as_ref().unwrap().to_string() } else { "null".to_string() }, - ) - } -} - -impl From for MemoError { - fn from(e: rusqlite::Error) -> Self { Self{cause: "sqlite", sqlite:Some(e), io:None, ureq:None} } -} - -impl From for MemoError { - fn from(e: std::io::Error) -> Self { Self{cause: "io", sqlite:None, io:Some(e), ureq:None} } -} - -impl From for MemoError { - fn from(e: ureq::Error) -> Self { Self{cause: "ureq", sqlite:None, io:None, ureq:Some(e)} } -} - -pub struct State { - pub last_edit: DateTime, - pub last_sync: Option>, -} - -pub trait MemoStorage { - fn all(&self, done: bool) -> Result, MemoError>; - fn get(&self, id: &Uuid) -> Result; - fn set(&mut self, memo: &Memo) -> Result; - fn del(&mut self, id: &Uuid) -> 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)) - } -} - -pub trait AuthStorage { - fn get_hash(&self) -> Result; - fn get_key(&self) -> Result; - fn set_key(&mut self, key: &str) -> Result, MemoError>; -} - -pub trait StateStorage { - fn set_state(&mut self, state: State) -> Result, MemoError>; - fn get_state(&self) -> Result; - - fn set_edit_time(&mut self, time: DateTime) -> Result<(), MemoError> { - let mut state = self.get_state().unwrap_or(State { - last_edit: time, - last_sync: None, - }); // TODO jank way to not fail on 1st use - state.last_edit = time; - self.set_state(state)?; - Ok(()) - } - - 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(()) - } -} diff --git a/src/storage/json.rs b/src/storage/json.rs index 153a583..e878b1e 100644 --- a/src/storage/json.rs +++ b/src/storage/json.rs @@ -3,11 +3,12 @@ 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 std::collections::LinkedList; -use crate::storage::{Memo, State, MemoError, AuthStorage, MemoStorage, StateStorage}; +use crate::storage::{Memo, State, MemoError, AuthStorage, MemoStorage, StateStorage, MemoSetField}; use crate::remote::RemoteSync; pub struct JsonStorage { @@ -85,12 +86,13 @@ impl StateStorage for JsonStorage { State { last_edit: self.data.last_edit, last_sync: self.data.last_sync, + last_memo: None, // TODO } ); } 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}); + 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()?; @@ -128,19 +130,20 @@ impl MemoStorage for JsonStorage { Ok(()) } - fn set(&mut self, m: &Memo) -> Result { - 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 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 { diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000..bdabd00 --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,55 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; +use crate::model::{memo::{Memo, MemoError}, state::State}; + +pub mod json; +pub mod sqlite; + +pub use json::JsonStorage; +pub use sqlite::SQLiteStorage; + +pub const SUPPORTED_FORMATS: &'static [&'static str] = &["db", "json"]; + +pub enum MemoSetField { + Body(String), + Due(Option>), + Done(Option>), +} + +pub trait MemoStorage { + fn all(&self, done: bool) -> Result, MemoError>; + 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)) + } +} + +pub trait AuthStorage { + fn get_hash(&self) -> Result; + fn get_key(&self) -> Result; + fn set_key(&mut self, key: &str) -> Result, MemoError>; +} + +pub trait StateStorage { + fn set_state(&mut self, state: State) -> Result, MemoError>; + fn get_state(&self) -> Result; + + 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(()) + } + + 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(()) + } +} diff --git a/src/storage/sqlite.rs b/src/storage/sqlite.rs index 0fc8b06..cc59f4e 100644 --- a/src/storage/sqlite.rs +++ b/src/storage/sqlite.rs @@ -11,6 +11,8 @@ use std::io::Write; use uuid::Uuid; use tempfile::NamedTempFile; +use super::MemoSetField; + pub struct SQLiteStorage { conn: Connection, path: PathBuf, @@ -68,7 +70,7 @@ impl AuthStorage for SQLiteStorage { })?); } - fn set_key(&mut self, key: &str) -> Result, MemoError> { + 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()); @@ -88,6 +90,7 @@ impl StateStorage for SQLiteStorage { return Ok(State { last_edit: row.get(0)?, last_sync: row.get(1).ok(), + ..Default::default() }); })?); } @@ -143,11 +146,27 @@ impl MemoStorage for SQLiteStorage { Ok(()) } - fn set(&mut self, m: &Memo) -> Result { - return Ok(self.conn.execute( - "UPDATE memo SET body = ?, due = ?, done = ?, last_edit = ? WHERE id = ?", - params![m.body, m.due, m.done, m.last_edit, m.id.to_string()], - )?); + 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 { diff --git a/src/utils.rs b/src/utils.rs index f0fdab2..9f57d1d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use crate::storage::Memo; +use crate::model::memo::Memo; use chrono::{Duration, Utc}; use regex::{Error, Regex}; use colored::Colorize;