mirror of
https://git.alemi.dev/memo-cli.git
synced 2024-11-22 09:24:48 +01:00
chore: small fixes, refactored a little
This commit is contained in:
parent
03a958a23d
commit
408cb9fda1
10 changed files with 308 additions and 192 deletions
29
src/main.rs
29
src/main.rs
|
@ -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
154
src/model/memo.rs
Normal 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
2
src/model/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod memo;
|
||||||
|
pub mod state;
|
19
src/model/state.rs
Normal file
19
src/model/state.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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())?;
|
||||||
|
|
160
src/storage.rs
160
src/storage.rs
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
55
src/storage/mod.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue