Track last_edit, make Memo sortable, fixes storages

This commit is contained in:
əlemi 2022-03-29 00:40:32 +02:00
parent dd503d5702
commit 5ab9f2c5af
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
6 changed files with 123 additions and 80 deletions

View file

@ -2,86 +2,91 @@
"key": "", "key": "",
"hash": "", "hash": "",
"last_sync": null, "last_sync": null,
"last_run": "2022-03-28T00:13:34.265758517Z", "last_edit": "2022-03-28T22:38:22.622270502Z",
"memo": [ "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", "id": "eaac08b0-c367-4901-a980-0cda66efbb54",
"body": "fix merge policy (always uses remote data)", "body": "fix merge policy (always uses remote data)",
"due": "2022-04-04T00:03:02.096181142Z", "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", "id": "171b69de-677c-4a82-a57b-7f00af4ae7e3",
"body": "data encryption", "body": "data encryption",
"due": "2022-04-04T00:03:02.096181142Z", "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", "id": "d3cdd44f-96c1-4063-855a-8d55283be768",
"body": "recurring memos", "body": "recurring memos",
"due": "2022-04-27T00:03:41.531165213Z", "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", "id": "78622159-8bdc-4c1f-bf04-147e165e0f6e",
"body": "yml and toml formats?", "body": "yml and toml formats?",
"due": "2022-04-27T00:03:41.531165213Z", "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", "id": "0f0df75d-6a0c-4e4e-9a3c-2075a045963c",
"body": "improve server...", "body": "improve server...",
"due": "2022-07-06T00:04:05.889521359Z", "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", "id": "69b7d74c-3bcb-4cf6-bfbe-4e703c9957a2",
"body": "add title to each storage", "body": "add title to each storage",
"due": "2022-04-04T00:03:02.096181142Z", "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", "id": "f81ffe23-ac41-4939-82e1-3a73066d1680",
"body": "maybe implement memo dependancies?", "body": "maybe implement memo dependancies?",
"due": "2022-04-27T00:03:41.531165213Z", "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", "id": "f665d76b-79ed-45de-9f1e-73863b02f08a",
"body": "make android app synched with server", "body": "make android app synched with server",
"due": "2022-07-06T00:04:05.889521359Z", "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", "id": "a5a44ada-54fe-49e6-aebd-5353dc30ced4",
"body": "add description to each storage", "body": "add description to each storage",
"due": "2022-04-04T00:06:05.528741299Z", "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", "id": "bf6e3b8e-e941-43f0-a2a4-51f8cf08a65c",
"body": "impl eq and ord for memo", "body": "impl eq and ord for memo",
"due": "2022-04-04T00:13:10.508332057Z", "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", "id": "dba39f62-919e-4dcf-8c2b-65db2e11e6d5",
"body": "fix: json storage all() is not sorted", "body": "store last_edit and not last_run",
"due": "2022-04-04T00:13:31.734454630Z", "due": "2022-04-04T00:03:02.096181142Z",
"done": null "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": []
} }

View file

@ -132,8 +132,9 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
} }
} }
let txt = body.join(" "); 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 }) => { Some(Commands::Done { search, many }) => {
let rex = Regex::new(search.as_str()); let rex = Regex::new(search.as_str());
@ -157,6 +158,7 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
} }
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 {
@ -181,6 +183,7 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
} }
} }
storage.set(&m).unwrap(); storage.set(&m).unwrap();
storage.set_edit_time(Utc::now()).unwrap();
println!( println!(
"{} updated memo\n{}", "{} updated memo\n{}",
format!("[{}]", ">".green()).bold().to_string(), format!("[{}]", ">".green()).bold().to_string(),
@ -193,9 +196,9 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
let timing = if let Some(state) = storage.get_state().ok() { let timing = if let Some(state) = storage.get_state().ok() {
let now = Local::now(); let now = Local::now();
format!( format!(
"last run: {}", "last edit: {}",
state state
.last_run .last_edit
.with_timezone(&now.timezone()) .with_timezone(&now.timezone())
.format("%a %d/%m %H:%M") .format("%a %d/%m %H:%M")
) )
@ -224,11 +227,6 @@ fn run_commands<T>(mut storage: T, args: Cli) -> Result<(), MemoError>
println!("{} | {}", "memo-cli".bold(), timing); println!("{} | {}", "memo-cli".bold(), timing);
print!("{}", builder); print!("{}", builder);
} }
if !args.notify {
// shitty assumption: notification is run by a daemon TODO
storage.set_run_time(Utc::now()).unwrap();
}
} }
} }

View file

@ -5,24 +5,59 @@ pub use sqlite::SQLiteStorage;
pub use json::JsonStorage; pub use json::JsonStorage;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::collections::LinkedList;
use std::fmt; use std::fmt;
use uuid::Uuid; use uuid::Uuid;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
pub const SUPPORTED_FORMATS : &'static [&'static str] = &["db", "json"]; pub const SUPPORTED_FORMATS : &'static [&'static str] = &["db", "json"];
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Eq, Serialize, Deserialize)]
pub struct Memo { pub struct Memo {
pub id: Uuid, pub id: Uuid,
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>>,
pub last_edit: DateTime<Utc>,
} }
pub struct State { impl Memo {
pub last_run: DateTime<Utc>, pub fn new(body: String, due: Option<DateTime<Utc>>) -> Self {
pub last_sync: Option<DateTime<Utc>>, 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 { impl fmt::Display for Memo {
@ -37,11 +72,12 @@ impl fmt::Display for Memo {
} }
return write!( return write!(
f, 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, id = self.id,
body = self.body, body = self.body,
due = due_str, due = due_str,
done = done_str done = done_str,
last_edit = self.last_edit.to_string(),
); );
} }
} }
@ -78,20 +114,20 @@ impl From<ureq::Error> for MemoError {
fn from(e: ureq::Error) -> Self { Self{cause: "ureq", sqlite:None, io:None, ureq:Some(e)} } 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 { pub trait MemoStorage {
fn all(&self, done: bool) -> Result<LinkedList<Memo>, MemoError>; fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError>;
fn get(&self, id: &Uuid) -> Result<Memo, MemoError>; fn get(&self, id: &Uuid) -> Result<Memo, MemoError>;
fn set(&mut self, memo: &Memo) -> Result<usize, MemoError>; fn set(&mut self, memo: &Memo) -> Result<usize, MemoError>;
fn del(&mut self, id: &Uuid) -> Result<usize, MemoError>; fn del(&mut self, id: &Uuid) -> Result<usize, MemoError>;
fn insert(&mut self, m: Memo) -> Result<(), MemoError>; fn insert(&mut self, m: Memo) -> Result<(), MemoError>;
fn add(&mut self, body: &str, due: Option<DateTime<Utc>>) -> Result<(), MemoError> { fn add(&mut self, body: String, due: Option<DateTime<Utc>>) -> Result<(), MemoError> {
self.insert(Memo { self.insert(Memo::new(body, due))
id: Uuid::new_v4(),
body: body.to_string(),
due: due,
done: None,
})
} }
} }
@ -105,12 +141,12 @@ pub trait StateStorage {
fn set_state(&mut self, state: State) -> Result<Option<State>, MemoError>; fn set_state(&mut self, state: State) -> Result<Option<State>, MemoError>;
fn get_state(&self) -> Result<State, MemoError>; fn get_state(&self) -> Result<State, MemoError>;
fn set_run_time(&mut self, time: DateTime<Utc>) -> Result<(), MemoError> { fn set_edit_time(&mut self, time: DateTime<Utc>) -> Result<(), MemoError> {
let mut state = self.get_state().unwrap_or(State { let mut state = self.get_state().unwrap_or(State {
last_run: time, last_edit: time,
last_sync: None, last_sync: None,
}); // TODO jank way to not fail on 1st use }); // TODO jank way to not fail on 1st use
state.last_run = time; state.last_edit = time;
self.set_state(state)?; self.set_state(state)?;
Ok(()) Ok(())
} }

View file

@ -20,9 +20,8 @@ struct Store {
key: String, key: String,
hash: String, hash: String,
last_sync: Option<DateTime<Utc>>, last_sync: Option<DateTime<Utc>>,
last_run: DateTime<Utc>, last_edit: DateTime<Utc>,
memo: Vec<Memo>, memo: Vec<Memo>,
archive: Vec<Memo>,
} }
impl Default for Store { impl Default for Store {
@ -31,9 +30,8 @@ impl Default for Store {
key: "".to_string(), key: "".to_string(),
hash: "".to_string(), hash: "".to_string(),
last_sync: None, last_sync: None,
last_run: Utc::now(), last_edit: Utc::now(),
memo: Vec::new(), memo: Vec::new(),
archive: Vec::new(),
}; };
} }
} }
@ -85,29 +83,29 @@ impl StateStorage for JsonStorage {
fn get_state(&self) -> Result<State, MemoError> { fn get_state(&self) -> Result<State, MemoError> {
return Ok( return Ok(
State { State {
last_run: self.data.last_run, last_edit: self.data.last_edit,
last_sync: self.data.last_sync, last_sync: self.data.last_sync,
} }
); );
} }
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_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_sync = state.last_sync;
self.data.last_run = state.last_run; self.data.last_edit = state.last_edit;
self.save()?; self.save()?;
return Ok(old_state); return Ok(old_state);
} }
} }
impl MemoStorage for JsonStorage { impl MemoStorage for JsonStorage {
fn all(&self, done: bool) -> Result<LinkedList<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();
for memo in &self.data.memo { 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 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()); results_due.push_back((*memo).clone());
} else { } else {
results_not_due.push_back((*memo).clone()); results_not_due.push_back((*memo).clone());
@ -115,9 +113,13 @@ impl MemoStorage for JsonStorage {
} }
} }
results_due.append(&mut results_not_due); results_due.append(&mut results_not_due);
return Ok(results_due); let mut out : Vec<Memo> = results_due.into_iter().collect();
out.sort();
return Ok(out);
} }
fn insert(&mut self, m: Memo) -> Result<(), MemoError> { fn insert(&mut self, m: Memo) -> Result<(), MemoError> {
@ -151,7 +153,7 @@ impl MemoStorage for JsonStorage {
} }
} }
if index >= 0 { if index >= 0 {
self.data.memo.remove(index as usize); self.data.memo[index as usize].done = Some(Utc::now());
self.save()?; self.save()?;
} }
return Ok(count); return Ok(count);

View file

@ -30,13 +30,14 @@ impl SQLiteStorage {
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
body TEXT NOT NULL, body TEXT NOT NULL,
due DATETIME, due DATETIME,
done DATETIME DEFAULT NULL done DATETIME DEFAULT NULL,
last_edit DATETIME NOT NULL
);", );",
[], [],
)?; )?;
connection.execute( connection.execute(
"CREATE TABLE IF NOT EXISTS state ( "CREATE TABLE IF NOT EXISTS state (
last_run DATETIME DEFAULT NULL, last_edit DATETIME DEFAULT NULL,
last_sync DATETIME DEFAULT NULL last_sync DATETIME DEFAULT NULL
);", );",
[], [],
@ -85,7 +86,7 @@ impl StateStorage for SQLiteStorage {
fn get_state(&self) -> Result<State, MemoError> { 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(State {
last_run: row.get(0)?, last_edit: row.get(0)?,
last_sync: row.get(1).ok(), last_sync: row.get(1).ok(),
}); });
})?); })?);
@ -95,15 +96,15 @@ impl StateStorage for SQLiteStorage {
let old_state = self.get_state().ok(); let old_state = self.get_state().ok();
self.conn.execute("DELETE FROM state;", [])?; self.conn.execute("DELETE FROM state;", [])?;
self.conn.execute( self.conn.execute(
"INSERT INTO state (last_run, last_sync) VALUES (?, ?);", "INSERT INTO state (last_edit, last_sync) VALUES (?, ?);",
params![state.last_run, state.last_sync], params![state.last_edit, state.last_sync],
)?; )?;
return Ok(old_state); return Ok(old_state);
} }
} }
impl MemoStorage for SQLiteStorage { impl MemoStorage for SQLiteStorage {
fn all(&self, done: bool) -> Result<LinkedList<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();
let not_null = if done { "NOT" } else { "" }; let not_null = if done { "NOT" } else { "" };
@ -125,26 +126,27 @@ impl MemoStorage for SQLiteStorage {
body: row.get(1)?, body: row.get(1)?,
due: row.get(2).ok(), due: row.get(2).ok(),
done: row.get(3).ok(), done: row.get(3).ok(),
last_edit: row.get(4)?,
}); });
} }
results_due.append(&mut results_not_due); 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> { fn insert(&mut self, m: Memo) -> Result<(), MemoError> {
self.conn.execute( self.conn.execute(
"INSERT INTO memo (id, body, due, done) VALUES (?, ?, ?, ?)", "INSERT INTO memo (id, body, due, done, last_edit) VALUES (?, ?, ?, ?, ?)",
params![m.id.to_string(), m.body, m.due, m.done], params![m.id.to_string(), m.body, m.due, m.done, m.last_edit],
)?; )?;
Ok(()) Ok(())
} }
fn set(&mut self, m: &Memo) -> Result<usize, MemoError> { fn set(&mut self, m: &Memo) -> Result<usize, MemoError> {
return Ok(self.conn.execute( return Ok(self.conn.execute(
"UPDATE memo SET body = ?, due = ?, done = ? WHERE id = ?", "UPDATE memo SET body = ?, due = ?, done = ?, last_edit = ? WHERE id = ?",
params![m.body, m.due, m.done, m.id.to_string()], 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)?, body: row.get(1)?,
due: row.get(2).ok(), due: row.get(2).ok(),
done: row.get(3).ok(), done: row.get(3).ok(),
last_edit: row.get(4)?,
}); });
}, },
)?); )?);

View file

@ -2,7 +2,6 @@ use crate::storage::Memo;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use regex::{Error, Regex}; use regex::{Error, Regex};
use colored::Colorize; use colored::Colorize;
use std::collections::LinkedList;
use std::path::PathBuf; use std::path::PathBuf;
pub trait HumanDisplay { pub trait HumanDisplay {
@ -93,7 +92,7 @@ pub fn parse_human_duration(input: &str) -> Result<Duration, Error> {
return Ok(Duration::seconds(secs)); return Ok(Duration::seconds(secs));
} }
pub fn find_by_regex(re: Regex, memos: LinkedList<Memo>) -> Option<Memo> { pub fn find_by_regex(re: Regex, memos: Vec<Memo>) -> Option<Memo> {
let mut found = false; let mut found = false;
let mut out: Option<Memo> = None; let mut out: Option<Memo> = None;
for memo in memos { for memo in memos {