added merge, use keys to sync, encrypt sent db

there's still much to do: server discards symbols in filename but base64
might produce symbols. The edit sync is broken: new memos will be
synched but not edited memos. Also it's pretty janky but I have to start
somewhere.
This commit is contained in:
əlemi 2022-03-23 02:42:08 +01:00
parent 62ee3370be
commit 497879b839
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
4 changed files with 159 additions and 75 deletions

View file

@ -10,14 +10,17 @@ edition = "2021"
[dependencies] [dependencies]
regex = "1.5.5" regex = "1.5.5"
sha2 = "0.10.2"
base64 = "0.13.0" base64 = "0.13.0"
chrono = "0.4.19" chrono = "0.4.19"
colored = "2.0.0" colored = "2.0.0"
rpassword = "6.0.1"
git-version = "0.3.5" # ughh just for git hash git-version = "0.3.5" # ughh just for git hash
const_format = "0.2.22" # ughh just for git hash const_format = "0.2.22" # ughh just for git hash
libnotify = "1.0.3" libnotify = "1.0.3"
uuid = { version = "0.8.2", features = ["v4"] }
clap = { version = "3.1.6", features = ["derive"] } clap = { version = "3.1.6", features = ["derive"] }
rusqlite = { version="0.27.0", features=["chrono"] } rusqlite = { version="0.27.0", features=["chrono", "backup"] }
ureq = { version="2.4.0", features=["json"] } ureq = { version="2.4.0", features=["json"] }
[profile.release] [profile.release]

View file

@ -72,8 +72,14 @@ fn main() {
db_path = db; db_path = db;
} }
let storage = open_sqlite_storage(&db_path, true).unwrap();
if args.sync { if args.sync {
let res = SQLiteStorage::fetch("asdasd", "http://127.0.0.1:8443"); if storage.get_key().is_err() {
let key = rpassword::prompt_password("[$] new passphrase: ").unwrap();
storage.set_key(key.as_str()).unwrap();
}
let res = storage.fetch(storage.get_hash().unwrap().as_str(), "http://127.0.0.1:8443");
if res.is_ok() { if res.is_ok() {
println!("[v] downloaded remote db"); println!("[v] downloaded remote db");
} else { } else {
@ -81,8 +87,6 @@ fn main() {
} }
} }
let storage = open_sqlite_storage(&db_path).unwrap();
match args.command { match args.command {
Some(Commands::New { body, due }) => { Some(Commands::New { body, due }) => {
let mut due_date: Option<DateTime<Utc>> = None; let mut due_date: Option<DateTime<Utc>> = None;
@ -103,7 +107,7 @@ fn main() {
for memo in storage.all(false).unwrap() { for memo in storage.all(false).unwrap() {
if re.is_match(memo.body.as_str()) { if re.is_match(memo.body.as_str()) {
if many { if many {
storage.del(memo.id).unwrap(); storage.del(&memo.id).unwrap();
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");
@ -116,7 +120,7 @@ fn main() {
} }
} }
if let Some(rm) = to_remove { if let Some(rm) = to_remove {
storage.del(rm.id).unwrap(); storage.del(&rm.id).unwrap();
println!("{} done memo: {}", "[-]".bold(), rm.body); println!("{} done memo: {}", "[-]".bold(), rm.body);
} }
} else { } else {
@ -178,11 +182,15 @@ fn main() {
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();
}
} }
} }
if args.sync { if args.sync {
let res = SQLiteStorage::store("asdasd", "http://127.0.0.1:8443"); let res = storage.store(storage.get_hash().unwrap().as_str(), "http://127.0.0.1:8443");
if res.is_ok() { if res.is_ok() {
println!("[^] uploaded local db"); println!("[^] uploaded local db");
storage.set_sync_time(Utc::now()).unwrap(); storage.set_sync_time(Utc::now()).unwrap();
@ -190,6 +198,4 @@ fn main() {
println!("[!] could not upload db : {}", res.err().unwrap().to_string()); println!("[!] could not upload db : {}", res.err().unwrap().to_string());
} }
} }
storage.set_run_time(Utc::now()).unwrap();
} }

View file

@ -1,18 +1,54 @@
use crate::storage::SQLiteStorage; use crate::storage::{Memo, SQLiteStorage, MemoStorage, AuthStorage, open_sqlite_storage};
use std::fs; use std::fs;
use std::io::{Write, Read}; use std::io::{Write, Read};
use rusqlite::params;
use std::collections::HashSet;
use uuid::Uuid;
pub trait RemoteSync { pub trait RemoteSync : AuthStorage+MemoStorage {
fn store(hash:&str, server:&str) -> Result<(), ureq::Error>; fn store(&self, hash:&str, server:&str) -> Result<(), ureq::Error>;
fn fetch(hash:&str, server:&str) -> Result<(), ureq::Error>; fn fetch(&self, hash:&str, server:&str) -> Result<(), ureq::Error>;
fn merge(&self, other:&Self) -> Result<(), rusqlite::Error>;
} }
impl RemoteSync for SQLiteStorage { impl RemoteSync for SQLiteStorage {
fn store(hash:&str, server:&str) -> Result<(), ureq::Error> { fn merge(&self, other: &SQLiteStorage) -> Result<(), rusqlite::Error> {
let home_dir = env!("HOME").to_string(); let mut memo_ids : HashSet<Uuid> = HashSet::new();
let contents = fs::read(home_dir + "/.local/share/memo-cli.db")?; for memo in self.all(false)?.iter() {
memo_ids.insert(memo.id);
}
for memo in self.all(true)?.iter() {
memo_ids.insert(memo.id);
}
let mut stmt = other.conn.prepare("SELECT * FROM memo")?;
let r = stmt.query_map([], |row| {
let id = Uuid::parse_str(row.get::<usize,String>(0)?.as_str()).unwrap();
let m = Memo{
id: id,
body: row.get(1)?,
due: row.get(2).ok(),
done: row.get(3).ok(),
};
if memo_ids.contains(&id) {
self.set(&m)?; // TODO only replace if more recently edited!!
} else {
self.insert(&m)?;
}
Ok(())
})?.count();
Ok(())
}
fn store(&self, hash:&str, server:&str) -> Result<(), ureq::Error> {
let tmpfile = "asd"; // TODO
self.conn.backup(rusqlite::DatabaseName::Main, tmpfile, None).unwrap();
let tmp_db = open_sqlite_storage(tmpfile, false).unwrap(); // TODO
tmp_db.conn.execute(format!("PRAGMA key = {} ;", self.get_key().unwrap()).as_str(), []).unwrap(); // For some reason it won't work with normal params! ???
tmp_db.conn.close().unwrap();
let contents = fs::read(tmpfile)?;
let dest = server.to_string() + "/put"; let dest = server.to_string() + "/put";
let _resp = ureq::post(dest.as_str()) let _resp = ureq::post(dest.as_str())
.send_json(ureq::json!({ .send_json(ureq::json!({
@ -22,7 +58,7 @@ impl RemoteSync for SQLiteStorage {
return Ok(()); return Ok(());
} }
fn fetch(hash:&str, server:&str) -> Result<(), ureq::Error> { fn fetch(&self, hash:&str, server:&str) -> Result<(), ureq::Error> {
let dest = server.to_string() + "/get"; let dest = server.to_string() + "/get";
let mut resp = ureq::post(dest.as_str()) let mut resp = ureq::post(dest.as_str())
.send_json(ureq::json!({ .send_json(ureq::json!({
@ -30,11 +66,22 @@ impl RemoteSync for SQLiteStorage {
"payload":"" "payload":""
}))?.into_reader(); }))?.into_reader();
let home_dir = env!("HOME").to_string(); let tmpfile = "asd"; // TODO
let mut f = fs::File::create(home_dir + "/.local/share/memo-cli.db")?; {
let mut data : Vec<u8> = vec![0;0]; let mut f = fs::File::create(tmpfile)?;
resp.read_to_end(&mut data)?; let mut data : Vec<u8> = vec![0;0];
f.write(data.as_slice())?; resp.read_to_end(&mut data)?;
f.write(data.as_slice())?;
}
let tmp_db = open_sqlite_storage(tmpfile, false).unwrap();
tmp_db.conn.execute(format!("PRAGMA key = {} ;", self.get_key().unwrap()).as_str(), []).unwrap(); // For some reason it won't work with normal params! ???
self.merge(&tmp_db).unwrap();
tmp_db.conn.close().unwrap();
// TODO delete tempfile
return Ok(()); return Ok(());
} }
} }

View file

@ -1,11 +1,14 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use rusqlite::{params, Connection, Error}; use rusqlite::{params, Connection, Error};
use std::fmt; use std::fmt;
use uuid::Uuid;
use sha2::{Sha512, Digest};
pub struct Memo { pub struct Memo {
pub id: u32, 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 struct State { pub struct State {
@ -31,13 +34,15 @@ impl fmt::Display for Memo {
pub trait MemoStorage { pub trait MemoStorage {
fn all(&self, done: bool) -> Result<Vec<Memo>, Error>; fn all(&self, done: bool) -> Result<Vec<Memo>, Error>;
fn add(&self, body: &str, due: Option<DateTime<Utc>>) -> Result<(), Error>;
fn set(&self, memo: &Memo) -> Result<bool, Error>; fn set(&self, memo: &Memo) -> Result<bool, Error>;
fn del(&self, id: u32) -> Result<bool, Error>; fn del(&self, id: &Uuid) -> Result<bool, Error>;
fn get(&self, id: u32) -> Result<Memo, Error>; fn get(&self, id: &Uuid) -> Result<Memo, Error>;
fn add(&self, body: &str, due: Option<DateTime<Utc>>) -> Result<(), Error>;
fn insert(&self, m: &Memo) -> Result<(), Error>;
} }
pub trait AuthStorage { pub trait AuthStorage {
fn get_hash(&self) -> Result<String, Error>;
fn get_key(&self) -> Result<String, Error>; fn get_key(&self) -> Result<String, Error>;
fn set_key(&self, key:&str) -> Result<Option<String>, Error>; fn set_key(&self, key:&str) -> Result<Option<String>, Error>;
} }
@ -64,38 +69,52 @@ pub trait StateStorage {
// SQLiteStorage // SQLiteStorage
pub struct SQLiteStorage { pub struct SQLiteStorage {
conn: Connection, pub conn: Connection, // TODO make back private
} }
pub fn open_sqlite_storage(path: &str) -> Result<SQLiteStorage, Error> { pub fn open_sqlite_storage(path: &str, init: bool) -> Result<SQLiteStorage, Error> {
let connection = Connection::open(path)?; let connection = Connection::open(path)?;
// TODO check if table exist and is valid // TODO check if table exist and is valid
connection.execute( if init {
"CREATE TABLE IF NOT EXISTS memo ( connection.execute(
id INTEGER PRIMARY KEY, "CREATE TABLE IF NOT EXISTS memo (
body TEXT NOT NULL, id TEXT PRIMARY KEY,
due DATETIME, body TEXT NOT NULL,
done DATETIME DEFAULT NULL due DATETIME,
);", done DATETIME DEFAULT NULL
[], );",
)?; [],
connection.execute( )?;
"CREATE TABLE IF NOT EXISTS state ( connection.execute(
last_run DATETIME DEFAULT NULL, "CREATE TABLE IF NOT EXISTS state (
last_sync DATETIME DEFAULT NULL last_run DATETIME DEFAULT NULL,
);", last_sync DATETIME DEFAULT NULL
[], );",
)?; [],
connection.execute( )?;
"CREATE TABLE IF NOT EXISTS auth ( connection.execute(
key TEXT PRIMARY KEY "CREATE TABLE IF NOT EXISTS auth (
);", key TEXT PRIMARY KEY,
[], hash TEXT NOT NULL
)?; );",
[],
)?;
}
return Ok(SQLiteStorage { conn: connection }); return Ok(SQLiteStorage { conn: connection });
} }
impl AuthStorage for SQLiteStorage { impl AuthStorage for SQLiteStorage {
fn get_hash(&self) -> Result<String, Error> {
return Ok(self.conn.query_row(
"SELECT * FROM auth", [],
|row| {
return Ok(row.get(1)?);
},
)?);
}
fn get_key(&self) -> Result<String, Error> { fn get_key(&self) -> Result<String, Error> {
return Ok(self.conn.query_row( return Ok(self.conn.query_row(
"SELECT * FROM auth", [], "SELECT * FROM auth", [],
@ -107,10 +126,13 @@ impl AuthStorage for SQLiteStorage {
fn set_key(&self, key: &str) -> Result<Option<String>, Error> { fn set_key(&self, key: &str) -> Result<Option<String>, Error> {
let old_key = self.get_key().ok(); let old_key = self.get_key().ok();
let mut hasher = Sha512::new();
hasher.update(key.as_bytes());
let hash = base64::encode(hasher.finalize());
self.conn.execute("DELETE FROM auth;", [])?; self.conn.execute("DELETE FROM auth;", [])?;
self.conn.execute( self.conn.execute(
"INSERT INTO auth VALUES (?);", "INSERT INTO auth (key, hash) VALUES (?, ?);",
params![key] params![key, hash]
)?; )?;
return Ok(old_key); return Ok(old_key);
} }
@ -163,9 +185,10 @@ impl MemoStorage for SQLiteStorage {
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
results.push(Memo { results.push(Memo {
id: row.get(0)?, id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
body: row.get(1)?, body: row.get(1)?,
due: row.get(2)?, due: row.get(2).ok(),
done: row.get(3).ok(),
}); });
} }
} }
@ -182,9 +205,10 @@ impl MemoStorage for SQLiteStorage {
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
results.push(Memo { results.push(Memo {
id: row.get(0)?, id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
body: row.get(1)?, body: row.get(1)?,
due: row.get(2)?, due: row.get(2).ok(),
done: row.get(3).ok(),
}); });
} }
} }
@ -193,23 +217,26 @@ impl MemoStorage for SQLiteStorage {
} }
fn add(&self, body: &str, due: Option<DateTime<Utc>>) -> Result<(), Error> { fn add(&self, body: &str, due: Option<DateTime<Utc>>) -> Result<(), Error> {
// TODO join these 2 ifs? self.insert(&Memo{
if due.is_some() { id: Uuid::new_v4(),
self.conn.execute( body: body.to_string(),
"INSERT INTO memo (body, due) VALUES (?, ?)", due: due,
params![body, due], done: None
)?; })
} else { }
self.conn
.execute("INSERT INTO memo (body) VALUES (?)", params![body])?; fn insert(&self, m: &Memo) -> Result<(), Error> {
} self.conn.execute(
return Ok(()); "INSERT INTO memo (id, body, due, done) VALUES (?, ?, ?, ?)",
params![m.id.to_string(), m.body, m.due, m.done],
)?;
Ok(())
} }
fn set(&self, memo: &Memo) -> Result<bool, Error> { fn set(&self, memo: &Memo) -> Result<bool, Error> {
let count = self.conn.execute( let count = self.conn.execute(
"UPDATE memo SET body = ?, due = ? WHERE id = ?", "UPDATE memo SET body = ?, due = ?, done = ? WHERE id = ?",
params![memo.body, memo.due, memo.id], params![memo.body, memo.due, memo.done, memo.id.to_string()],
)?; )?;
if count > 0 { if count > 0 {
return Ok(true); return Ok(true);
@ -218,10 +245,10 @@ impl MemoStorage for SQLiteStorage {
} }
} }
fn del(&self, id: u32) -> Result<bool, Error> { fn del(&self, id: &Uuid) -> Result<bool, Error> {
let count = self let count = self
.conn .conn
.execute("UPDATE memo SET done = ? WHERE id = ?", params![Utc::now(), id])?; .execute("UPDATE memo SET done = ? WHERE id = ?", params![Utc::now(), id.to_string()])?;
if count > 0 { if count > 0 {
return Ok(true); return Ok(true);
} else { } else {
@ -229,15 +256,16 @@ impl MemoStorage for SQLiteStorage {
} }
} }
fn get(&self, id: u32) -> Result<Memo, Error> { fn get(&self, id: &Uuid) -> Result<Memo, Error> {
return Ok(self.conn.query_row( return Ok(self.conn.query_row(
"SELECT * FROM memo WHERE id = ? AND done = 0", "SELECT * FROM memo WHERE id = ? AND done = 0",
params![id], params![id.to_string()],
|row| { |row| {
return Ok(Memo { return Ok(Memo {
id: row.get(0)?, id: Uuid::parse_str(row.get::<usize, String>(0)?.as_str()).unwrap(),
body: row.get(1)?, body: row.get(1)?,
due: row.get(2).unwrap_or(None), due: row.get(2).ok(),
done: row.get(3).ok(),
}); });
}, },
)?); )?);