From 5ab9f2c5af7b7be3f8584804a59235f123750373 Mon Sep 17 00:00:00 2001 From: alemidev Date: Tue, 29 Mar 2022 00:40:32 +0200 Subject: [PATCH] Track last_edit, make Memo sortable, fixes storages --- memostorage.json | 63 ++++++++++++++++++++----------------- src/main.rs | 16 +++++----- src/storage.rs | 72 ++++++++++++++++++++++++++++++++----------- src/storage/json.rs | 24 ++++++++------- src/storage/sqlite.rs | 25 ++++++++------- src/utils.rs | 3 +- 6 files changed, 123 insertions(+), 80 deletions(-) diff --git a/memostorage.json b/memostorage.json index d72c298..79451ea 100644 --- a/memostorage.json +++ b/memostorage.json @@ -2,86 +2,91 @@ "key": "", "hash": "", "last_sync": null, - "last_run": "2022-03-28T00:13:34.265758517Z", + "last_edit": "2022-03-28T22:38:22.622270502Z", "memo": [ - { - "id": "dba39f62-919e-4dcf-8c2b-65db2e11e6d5", - "body": "store last_edit and not last_run", - "due": "2022-04-04T00:03:02.096181142Z", - "done": null - }, - { - "id": "41a975f7-3f2d-4f72-bf0e-21de91631fbc", - "body": "store last_edit in memos", - "due": "2022-04-04T00:03:02.096181142Z", - "done": null - }, { "id": "eaac08b0-c367-4901-a980-0cda66efbb54", "body": "fix merge policy (always uses remote data)", "due": "2022-04-04T00:03:02.096181142Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "171b69de-677c-4a82-a57b-7f00af4ae7e3", "body": "data encryption", "due": "2022-04-04T00:03:02.096181142Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "d3cdd44f-96c1-4063-855a-8d55283be768", "body": "recurring memos", "due": "2022-04-27T00:03:41.531165213Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "78622159-8bdc-4c1f-bf04-147e165e0f6e", "body": "yml and toml formats?", "due": "2022-04-27T00:03:41.531165213Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "0f0df75d-6a0c-4e4e-9a3c-2075a045963c", "body": "improve server...", "due": "2022-07-06T00:04:05.889521359Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "69b7d74c-3bcb-4cf6-bfbe-4e703c9957a2", "body": "add title to each storage", "due": "2022-04-04T00:03:02.096181142Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "f81ffe23-ac41-4939-82e1-3a73066d1680", "body": "maybe implement memo dependancies?", "due": "2022-04-27T00:03:41.531165213Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "f665d76b-79ed-45de-9f1e-73863b02f08a", "body": "make android app synched with server", "due": "2022-07-06T00:04:05.889521359Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "a5a44ada-54fe-49e6-aebd-5353dc30ced4", "body": "add description to each storage", "due": "2022-04-04T00:06:05.528741299Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { "id": "bf6e3b8e-e941-43f0-a2a4-51f8cf08a65c", "body": "impl eq and ord for memo", "due": "2022-04-04T00:13:10.508332057Z", - "done": null + "done": null, + "last_edit": "2022-03-28T23:03:02.096181142Z" }, { - "id": "c03f6c49-ed72-4f52-8076-639278783598", - "body": "fix: json storage all() is not sorted", - "due": "2022-04-04T00:13:31.734454630Z", - "done": null + "id": "dba39f62-919e-4dcf-8c2b-65db2e11e6d5", + "body": "store last_edit and not last_run", + "due": "2022-04-04T00:03:02.096181142Z", + "done": "2022-03-28T22:38:20.101121011Z", + "last_edit": "2022-03-28T23:03:02.096181142Z" + }, + { + "id": "41a975f7-3f2d-4f72-bf0e-21de91631fbc", + "body": "store last_edit in memos", + "due": "2022-04-04T00:03:02.096181142Z", + "done": "2022-03-28T22:38:22.621730633Z", + "last_edit": "2022-03-28T23:03:02.096181142Z" } - ], - "archive": [] + ] } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 51b7619..cacfba7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -132,8 +132,9 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> } } let txt = body.join(" "); - storage.add(txt.as_str(), due_date).unwrap(); - println!("{} new memo: {}", "[+]".bold(), txt); + println!("{} new memo: {}", "[+]".bold(), &txt); + storage.add(txt, due_date).unwrap(); + storage.set_edit_time(Utc::now()).unwrap(); } Some(Commands::Done { search, many }) => { let rex = Regex::new(search.as_str()); @@ -157,6 +158,7 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> } 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 { @@ -181,6 +183,7 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> } } storage.set(&m).unwrap(); + storage.set_edit_time(Utc::now()).unwrap(); println!( "{} updated memo\n{}", format!("[{}]", ">".green()).bold().to_string(), @@ -193,9 +196,9 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> let timing = if let Some(state) = storage.get_state().ok() { let now = Local::now(); format!( - "last run: {}", + "last edit: {}", state - .last_run + .last_edit .with_timezone(&now.timezone()) .format("%a %d/%m %H:%M") ) @@ -224,11 +227,6 @@ fn run_commands(mut storage: T, args: Cli) -> Result<(), MemoError> println!("{} | {}", "memo-cli".bold(), timing); print!("{}", builder); } - - if !args.notify { - // shitty assumption: notification is run by a daemon TODO - storage.set_run_time(Utc::now()).unwrap(); - } } } diff --git a/src/storage.rs b/src/storage.rs index 774c36c..d22b473 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -5,24 +5,59 @@ pub use sqlite::SQLiteStorage; pub use json::JsonStorage; use chrono::{DateTime, Utc}; -use std::collections::LinkedList; use std::fmt; use uuid::Uuid; use serde::{Serialize, Deserialize}; pub const SUPPORTED_FORMATS : &'static [&'static str] = &["db", "json"]; -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Eq, Serialize, Deserialize)] pub struct Memo { pub id: Uuid, pub body: String, pub due: Option>, pub done: Option>, + pub last_edit: DateTime, } -pub struct State { - pub last_run: DateTime, - pub last_sync: Option>, +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 { @@ -37,11 +72,12 @@ impl fmt::Display for Memo { } return write!( f, - "Memo(id={id}, body={body}, due={due}, done={done})", + "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 + done = done_str, + last_edit = self.last_edit.to_string(), ); } } @@ -78,20 +114,20 @@ 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 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: &str, due: Option>) -> Result<(), MemoError> { - self.insert(Memo { - id: Uuid::new_v4(), - body: body.to_string(), - due: due, - done: None, - }) + fn add(&mut self, body: String, due: Option>) -> Result<(), MemoError> { + self.insert(Memo::new(body, due)) } } @@ -105,12 +141,12 @@ pub trait StateStorage { fn set_state(&mut self, state: State) -> Result, MemoError>; fn get_state(&self) -> Result; - fn set_run_time(&mut self, time: DateTime) -> Result<(), MemoError> { + fn set_edit_time(&mut self, time: DateTime) -> Result<(), MemoError> { let mut state = self.get_state().unwrap_or(State { - last_run: time, + last_edit: time, last_sync: None, }); // TODO jank way to not fail on 1st use - state.last_run = time; + state.last_edit = time; self.set_state(state)?; Ok(()) } diff --git a/src/storage/json.rs b/src/storage/json.rs index 1aefedc..153a583 100644 --- a/src/storage/json.rs +++ b/src/storage/json.rs @@ -20,9 +20,8 @@ struct Store { key: String, hash: String, last_sync: Option>, - last_run: DateTime, + last_edit: DateTime, memo: Vec, - archive: Vec, } impl Default for Store { @@ -31,9 +30,8 @@ impl Default for Store { key: "".to_string(), hash: "".to_string(), last_sync: None, - last_run: Utc::now(), + last_edit: Utc::now(), memo: Vec::new(), - archive: Vec::new(), }; } } @@ -85,29 +83,29 @@ impl StateStorage for JsonStorage { fn get_state(&self) -> Result { return Ok( State { - last_run: self.data.last_run, + last_edit: self.data.last_edit, last_sync: self.data.last_sync, } ); } fn set_state(&mut self, state: State) -> Result, MemoError> { - let old_state = Some(State{last_run: self.data.last_run, last_sync:self.data.last_sync}); + let old_state = Some(State{last_edit: self.data.last_edit, last_sync:self.data.last_sync}); self.data.last_sync = state.last_sync; - self.data.last_run = state.last_run; + self.data.last_edit = state.last_edit; self.save()?; return Ok(old_state); } } impl MemoStorage for JsonStorage { - fn all(&self, done: bool) -> Result, MemoError> { + fn all(&self, done: bool) -> Result, MemoError> { let mut results_due : LinkedList = LinkedList::new(); let mut results_not_due : LinkedList = LinkedList::new(); for memo in &self.data.memo { if done ^ memo.done.is_none() { // if done is true, has to not be none, if done is false, it has to be none - if memo.due.is_some() { + if memo.due.is_some() { // TODO I sort it afterwards this part is now useless results_due.push_back((*memo).clone()); } else { results_not_due.push_back((*memo).clone()); @@ -115,9 +113,13 @@ impl MemoStorage for JsonStorage { } } + results_due.append(&mut results_not_due); - return Ok(results_due); + let mut out : Vec = results_due.into_iter().collect(); + out.sort(); + + return Ok(out); } fn insert(&mut self, m: Memo) -> Result<(), MemoError> { @@ -151,7 +153,7 @@ impl MemoStorage for JsonStorage { } } if index >= 0 { - self.data.memo.remove(index as usize); + self.data.memo[index as usize].done = Some(Utc::now()); self.save()?; } return Ok(count); diff --git a/src/storage/sqlite.rs b/src/storage/sqlite.rs index 6d0365f..0fc8b06 100644 --- a/src/storage/sqlite.rs +++ b/src/storage/sqlite.rs @@ -30,13 +30,14 @@ impl SQLiteStorage { id TEXT PRIMARY KEY, body TEXT NOT NULL, due DATETIME, - done DATETIME DEFAULT NULL + done DATETIME DEFAULT NULL, + last_edit DATETIME NOT NULL );", [], )?; connection.execute( "CREATE TABLE IF NOT EXISTS state ( - last_run DATETIME DEFAULT NULL, + last_edit DATETIME DEFAULT NULL, last_sync DATETIME DEFAULT NULL );", [], @@ -85,7 +86,7 @@ impl StateStorage for SQLiteStorage { fn get_state(&self) -> Result { return Ok(self.conn.query_row("SELECT * FROM state", [], |row| { return Ok(State { - last_run: row.get(0)?, + last_edit: row.get(0)?, last_sync: row.get(1).ok(), }); })?); @@ -95,15 +96,15 @@ impl StateStorage for SQLiteStorage { let old_state = self.get_state().ok(); self.conn.execute("DELETE FROM state;", [])?; self.conn.execute( - "INSERT INTO state (last_run, last_sync) VALUES (?, ?);", - params![state.last_run, state.last_sync], + "INSERT INTO state (last_edit, last_sync) VALUES (?, ?);", + params![state.last_edit, state.last_sync], )?; return Ok(old_state); } } impl MemoStorage for SQLiteStorage { - fn all(&self, done: bool) -> Result, MemoError> { + fn all(&self, done: bool) -> Result, MemoError> { let mut results_due : LinkedList = LinkedList::new(); let mut results_not_due : LinkedList = LinkedList::new(); let not_null = if done { "NOT" } else { "" }; @@ -125,26 +126,27 @@ impl MemoStorage for SQLiteStorage { body: row.get(1)?, due: row.get(2).ok(), done: row.get(3).ok(), + last_edit: row.get(4)?, }); } results_due.append(&mut results_not_due); - return Ok(results_due); + return Ok(results_due.into_iter().collect()); } fn insert(&mut self, m: Memo) -> Result<(), MemoError> { self.conn.execute( - "INSERT INTO memo (id, body, due, done) VALUES (?, ?, ?, ?)", - params![m.id.to_string(), m.body, m.due, m.done], + "INSERT INTO memo (id, body, due, done, last_edit) VALUES (?, ?, ?, ?, ?)", + params![m.id.to_string(), m.body, m.due, m.done, m.last_edit], )?; Ok(()) } fn set(&mut self, m: &Memo) -> Result { return Ok(self.conn.execute( - "UPDATE memo SET body = ?, due = ?, done = ? WHERE id = ?", - params![m.body, m.due, m.done, m.id.to_string()], + "UPDATE memo SET body = ?, due = ?, done = ?, last_edit = ? WHERE id = ?", + params![m.body, m.due, m.done, m.last_edit, m.id.to_string()], )?); } @@ -165,6 +167,7 @@ impl MemoStorage for SQLiteStorage { body: row.get(1)?, due: row.get(2).ok(), done: row.get(3).ok(), + last_edit: row.get(4)?, }); }, )?); diff --git a/src/utils.rs b/src/utils.rs index fcbb3a1..f0fdab2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,6 @@ use crate::storage::Memo; use chrono::{Duration, Utc}; use regex::{Error, Regex}; use colored::Colorize; -use std::collections::LinkedList; use std::path::PathBuf; pub trait HumanDisplay { @@ -93,7 +92,7 @@ pub fn parse_human_duration(input: &str) -> Result { return Ok(Duration::seconds(secs)); } -pub fn find_by_regex(re: Regex, memos: LinkedList) -> Option { +pub fn find_by_regex(re: Regex, memos: Vec) -> Option { let mut found = false; let mut out: Option = None; for memo in memos {