mirror of
https://git.alemi.dev/memo-cli.git
synced 2024-11-25 00:54:49 +01:00
Track last_edit, make Memo sortable, fixes storages
This commit is contained in:
parent
dd503d5702
commit
5ab9f2c5af
6 changed files with 123 additions and 80 deletions
|
@ -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": []
|
||||
]
|
||||
}
|
16
src/main.rs
16
src/main.rs
|
@ -132,8 +132,9 @@ fn run_commands<T>(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<T>(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<T>(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<T>(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<T>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<DateTime<Utc>>,
|
||||
pub done: Option<DateTime<Utc>>,
|
||||
pub last_edit: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub last_run: DateTime<Utc>,
|
||||
pub last_sync: Option<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 {
|
||||
|
@ -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<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<LinkedList<Memo>, MemoError>;
|
||||
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: &str, due: Option<DateTime<Utc>>) -> 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<DateTime<Utc>>) -> Result<(), MemoError> {
|
||||
self.insert(Memo::new(body, due))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,12 +141,12 @@ pub trait StateStorage {
|
|||
fn set_state(&mut self, state: State) -> Result<Option<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 {
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -20,9 +20,8 @@ struct Store {
|
|||
key: String,
|
||||
hash: String,
|
||||
last_sync: Option<DateTime<Utc>>,
|
||||
last_run: DateTime<Utc>,
|
||||
last_edit: DateTime<Utc>,
|
||||
memo: Vec<Memo>,
|
||||
archive: Vec<Memo>,
|
||||
}
|
||||
|
||||
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<State, MemoError> {
|
||||
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<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_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<LinkedList<Memo>, MemoError> {
|
||||
fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError> {
|
||||
let mut results_due : LinkedList<Memo> = LinkedList::new();
|
||||
let mut results_not_due : LinkedList<Memo> = 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<Memo> = 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);
|
||||
|
|
|
@ -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<State, MemoError> {
|
||||
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<LinkedList<Memo>, MemoError> {
|
||||
fn all(&self, done: bool) -> Result<Vec<Memo>, MemoError> {
|
||||
let mut results_due : LinkedList<Memo> = LinkedList::new();
|
||||
let mut results_not_due : LinkedList<Memo> = 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<usize, MemoError> {
|
||||
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)?,
|
||||
});
|
||||
},
|
||||
)?);
|
||||
|
|
|
@ -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<Duration, Error> {
|
|||
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 out: Option<Memo> = None;
|
||||
for memo in memos {
|
||||
|
|
Loading…
Reference in a new issue