chore: small fixes, refactored a little

This commit is contained in:
əlemi 2022-12-13 02:42:33 +01:00
parent 03a958a23d
commit 408cb9fda1
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
10 changed files with 308 additions and 192 deletions

View file

@ -1,6 +1,7 @@
mod remote; mod remote;
mod storage; mod storage;
mod utils; mod utils;
mod model;
use chrono::{DateTime, Local, Utc}; use chrono::{DateTime, Local, Utc};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@ -10,9 +11,13 @@ 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 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 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 GIT_VERSION: &str = git_version!();
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@ -75,11 +80,14 @@ fn main() {
let mut db_path: PathBuf; let mut db_path: PathBuf;
let filename = args.name.unwrap_or("memostorage".to_string()); 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); 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; db_path = path;
} else { // default path } else {
// default path
//TODO: a less "nix-centered" default fallback, possibly configurable //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 //protip: cfg!(windows)/cfg!(unix)/cfg!(target_os = "macos") will give us the info we need
db_path = dirs::home_dir().unwrap(); 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 //TODO: permissions check, this will panic if it finds an existing db it can't write to
if let Some(ext) = db_path.extension() { if let Some(ext) = db_path.extension() {
match ext.to_str().unwrap() { match ext.to_str().unwrap() {
"json" => run_commands(JsonStorage::new(db_path).unwrap(), args2).unwrap(), "json" => run_commands(JsonStorage::new(db_path).unwrap(), args2).unwrap(),
@ -98,11 +105,13 @@ fn main() {
} }
} else { } else {
println!("[!] no extension on db file"); println!("[!] no extension on db file");
println!("something");
} }
} }
fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError> fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
where T : MemoStorage + AuthStorage + StateStorage + RemoteSync where
T: MemoStorage + AuthStorage + StateStorage + RemoteSync,
{ {
if args.sync { if args.sync {
if storage.get_key().is_err() { if storage.get_key().is_err() {
@ -148,6 +157,10 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
println!("[-] done task : {}", memo.body); println!("[-] done task : {}", memo.body);
} else if found { } else if found {
println!("[!] would remove multiple tasks"); println!("[!] would remove multiple tasks");
if let Some(other) = to_remove {
println!(" ]> {}", other.body);
}
println!(" ]> {}", memo.body);
to_remove = None; to_remove = None;
break; break;
} else { } else {
@ -182,7 +195,7 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
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(); // storage.set(&m).unwrap(); // TODO fix editing memos
storage.set_edit_time(Utc::now()).unwrap(); storage.set_edit_time(Utc::now()).unwrap();
println!( println!(
"{} updated memo\n{}", "{} updated memo\n{}",

154
src/model/memo.rs Normal file
View file

@ -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<DateTime<Utc>>,
pub done: Option<DateTime<Utc>>,
pub last_edit: DateTime<Utc>,
}
impl Memo {
pub fn new(body: String, due: Option<DateTime<Utc>>) -> 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<std::cmp::Ordering> {
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<rusqlite::Error>,
pub io: Option<std::io::Error>,
pub ureq: Option<ureq::Error>,
}
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<rusqlite::Error> for MemoError {
fn from(e: rusqlite::Error) -> Self {
Self {
cause: "sqlite",
sqlite: Some(e),
io: None,
ureq: None,
}
}
}
impl From<std::io::Error> for MemoError {
fn from(e: std::io::Error) -> Self {
Self {
cause: "io",
sqlite: None,
io: Some(e),
ureq: None,
}
}
}
impl From<ureq::Error> for MemoError {
fn from(e: ureq::Error) -> Self {
Self {
cause: "ureq",
sqlite: None,
io: None,
ureq: Some(e),
}
}
}

2
src/model/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod memo;
pub mod state;

19
src/model/state.rs Normal file
View file

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

View file

@ -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 std::collections::HashSet;
use uuid::Uuid; use uuid::Uuid;
use std::io::Read; use std::io::Read;
// use openssl::aes::{AesKey, aes_ige};
// use openssl::symm::Mode;
#[test]
fn always_succeeds() { }
pub trait RemoteSync : AuthStorage+MemoStorage+Sized { pub trait RemoteSync : AuthStorage+MemoStorage+Sized {
fn serialize(&self) -> Result<Vec<u8>, MemoError>; fn serialize(&self) -> Result<Vec<u8>, MemoError>;
@ -27,7 +33,11 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized {
.into_reader(); .into_reader();
let mut data: Vec<u8> = Vec::new(); let mut data: Vec<u8> = Vec::new();
// let mut encrypted : Vec<u8> = Vec::new();
resp.read_to_end(&mut data)?; 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 // TODO decrypt
let other = Self::deserialize(data).unwrap(); let other = Self::deserialize(data).unwrap();
@ -49,7 +59,8 @@ pub trait RemoteSync : AuthStorage+MemoStorage+Sized {
if memo_ids.contains(&memo.id) { if memo_ids.contains(&memo.id) {
let old_memo = self.get(&memo.id)?; let old_memo = self.get(&memo.id)?;
if memo.last_edit > old_memo.last_edit { if memo.last_edit > old_memo.last_edit {
self.set(memo)?; // self.set(memo)?; // TODO fix merging memos!
todo!()
} }
} else { } else {
self.insert(memo.clone())?; self.insert(memo.clone())?;

View file

@ -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<DateTime<Utc>>,
pub done: Option<DateTime<Utc>>,
pub last_edit: DateTime<Utc>,
}
impl Memo {
pub fn new(body: String, due: Option<DateTime<Utc>>) -> 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<std::cmp::Ordering> {
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<rusqlite::Error>,
pub io: Option<std::io::Error>,
pub ureq: Option<ureq::Error>,
}
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<rusqlite::Error> for MemoError {
fn from(e: rusqlite::Error) -> Self { Self{cause: "sqlite", sqlite:Some(e), io:None, ureq:None} }
}
impl From<std::io::Error> for MemoError {
fn from(e: std::io::Error) -> Self { Self{cause: "io", sqlite:None, io:Some(e), ureq:None} }
}
impl From<ureq::Error> 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<Utc>,
pub last_sync: Option<DateTime<Utc>>,
}
pub trait MemoStorage {
fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError>;
fn get(&self, id: &Uuid) -> Result<Memo, MemoError>;
fn set(&mut self, memo: &Memo) -> Result<usize, MemoError>;
fn del(&mut self, id: &Uuid) -> 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))
}
}
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>;
}
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,
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<Utc>) -> Result<(), MemoError> {
let mut state = self.get_state()?;
state.last_sync = Some(time);
self.set_state(state)?;
Ok(())
}
}

View file

@ -3,11 +3,12 @@ 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}; use crate::storage::{Memo, State, MemoError, AuthStorage, MemoStorage, StateStorage, MemoSetField};
use crate::remote::RemoteSync; use crate::remote::RemoteSync;
pub struct JsonStorage { pub struct JsonStorage {
@ -85,12 +86,13 @@ impl StateStorage for JsonStorage {
State { State {
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
} }
); );
} }
fn set_state(&mut self, state: State) -> Result<Option<State>, MemoError> { 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}); 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_sync = state.last_sync;
self.data.last_edit = state.last_edit; self.data.last_edit = state.last_edit;
self.save()?; self.save()?;
@ -128,19 +130,20 @@ impl MemoStorage for JsonStorage {
Ok(()) Ok(())
} }
fn set(&mut self, m: &Memo) -> Result<usize, MemoError> { fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result<usize, MemoError> {
let mut count = 0; todo!()
for (_i, memo) in self.data.memo.iter().enumerate() { // let mut count = 0;
if memo.id == m.id { // for (_i, memo) in self.data.memo.iter().enumerate() {
// TODO improve // if memo.id == m.id {
// self.data.memo[i].body = m.body; // // TODO improve
// self.data.memo[i].due = m.due; // // self.data.memo[i].body = m.body;
// self.data.memo[i].done = m.done; // // self.data.memo[i].due = m.due;
count += 1; // // self.data.memo[i].done = m.done;
} // count += 1;
} // }
self.save()?; // }
Ok(count) // self.save()?;
// Ok(count)
} }
fn del(&mut self, id: &uuid::Uuid) -> Result<usize, MemoError> { fn del(&mut self, id: &uuid::Uuid) -> Result<usize, MemoError> {

55
src/storage/mod.rs Normal file
View file

@ -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<DateTime<Utc>>),
Done(Option<DateTime<Utc>>),
}
pub trait MemoStorage {
fn all(&self, done: bool) -> Result<Vec<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> {
self.insert(Memo::new(body, due))
}
}
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>;
}
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 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(())
}
}

View file

@ -11,6 +11,8 @@ 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,
@ -68,7 +70,7 @@ impl AuthStorage for SQLiteStorage {
})?); })?);
} }
fn set_key(&mut self, key: &str) -> Result<Option<String>, MemoError> { 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 old_key = self.get_key().ok();
let mut hasher = Sha512::new(); let mut hasher = Sha512::new();
hasher.update(key.as_bytes()); hasher.update(key.as_bytes());
@ -88,6 +90,7 @@ impl StateStorage for SQLiteStorage {
return Ok(State { return Ok(State {
last_edit: row.get(0)?, last_edit: row.get(0)?,
last_sync: row.get(1).ok(), last_sync: row.get(1).ok(),
..Default::default()
}); });
})?); })?);
} }
@ -143,11 +146,27 @@ impl MemoStorage for SQLiteStorage {
Ok(()) Ok(())
} }
fn set(&mut self, m: &Memo) -> Result<usize, MemoError> { fn set(&mut self, id: &Uuid, field: MemoSetField) -> Result<usize, MemoError> {
return Ok(self.conn.execute( match field {
"UPDATE memo SET body = ?, due = ?, done = ?, last_edit = ? WHERE id = ?", MemoSetField::Body(body) => {
params![m.body, m.due, m.done, m.last_edit, m.id.to_string()], 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> { fn del(&mut self, id: &Uuid) -> Result<usize, MemoError> {

View file

@ -1,4 +1,4 @@
use crate::storage::Memo; use crate::model::memo::Memo;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use regex::{Error, Regex}; use regex::{Error, Regex};
use colored::Colorize; use colored::Colorize;