diff --git a/Cargo.toml b/Cargo.toml index 578ba68..2f645e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" edition = "2021" [dependencies] +serde = { version = "1.0", features = ["derive"] } egui = "0.21" eframe = "0.21" anyhow = "1" log = "0.4" simple_logger = "4.0" chrono = "0.4" -serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rand = "0.8" pwbox = "0.5" diff --git a/src/main.rs b/src/main.rs index 30f641b..bdb0443 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,10 @@ -#![windows_subsystem = "windows"] +#![cfg_attr( + all( + target_os = "windows", + not(debug_assertions), + ), + windows_subsystem = "windows" +)] use std::collections::{VecDeque}; use std::fs; @@ -23,6 +29,7 @@ enum CurrentMode { struct App { notes: Vec, + update_notes_next: bool, password: Option, tested_password: bool, mode: CurrentMode, @@ -60,6 +67,7 @@ fn main() { let app = App { password: None, + update_notes_next: false, tested_password: false, notes, mode: CurrentMode::PasswordInput, @@ -93,6 +101,12 @@ impl eframe::App for App { } } + if self.update_notes_next { + self.notes = self.saving.read_notes().unwrap(); + self.update_notes_next = false; + info!("reloaded notes"); + } + match self.mode { CurrentMode::View => { let password = self.password.as_ref().unwrap(); @@ -109,7 +123,14 @@ impl eframe::App for App { ui.separator(); for note in &mut self.notes { - if let Some(new_note) = note.render(ui, password) { + let render_result = note.render(ui, password, |id| { + let _ = &self.saving.delete_note(&id).unwrap(); + info!("note with id {} was successfully deleted", id); + + self.update_notes_next = true; + }); + + if let Some(new_note) = render_result { *note = new_note; } ui.add_space(10.0); @@ -141,6 +162,11 @@ impl eframe::App for App { ui.add_space(10.0); if ui.button("add note").clicked() { let note = Note::Decrypted { + id: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() + .to_string(), title: self.title_buffer.clone(), metadata: NoteMetadata { date: SystemTime::now(), diff --git a/src/notes/mod.rs b/src/notes/mod.rs index 071db55..83607c8 100644 --- a/src/notes/mod.rs +++ b/src/notes/mod.rs @@ -15,11 +15,13 @@ pub struct NoteMetadata { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Note { Encrypted { + id: String, title: String, metadata: NoteMetadata, encrypted_text: String, }, Decrypted { + id: String, title: String, metadata: NoteMetadata, text: String, @@ -31,7 +33,7 @@ impl Note { let mut rng = rand::thread_rng(); match self { - Note::Decrypted { text, metadata, title } => { + Note::Decrypted { id, text, metadata, title } => { let pwbox = Sodium::build_box(&mut rng) .seal(password.to_string().into_bytes(), text.clone().into_bytes())?; @@ -40,6 +42,7 @@ impl Note { let erased: ErasedPwBox = eraser.erase(&pwbox)?; Ok(Self::Encrypted { + id: id.clone(), title: title.clone(), metadata: metadata.clone(), encrypted_text: serde_json::to_string(&erased)?, @@ -51,7 +54,7 @@ impl Note { pub fn decrypt(&self, password: &str) -> anyhow::Result { match self { - Note::Encrypted { title, metadata, encrypted_text } => { + Note::Encrypted { id, title, metadata, encrypted_text } => { let mut eraser = Eraser::new(); eraser.add_suite::(); let erased: ErasedPwBox = serde_json::from_str(encrypted_text)?; @@ -60,6 +63,7 @@ impl Note { let decrypted_text = String::from_utf8(decrypted_bytes)?; Ok(Self::Decrypted { + id: id.clone(), title: title.clone(), metadata: metadata.clone(), text: decrypted_text, @@ -69,18 +73,18 @@ impl Note { } } - pub fn render(&self, ui: &mut Ui, password: &str) -> Option { + pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option { let mut value = None; ui.group(|ui| { match self { - Note::Encrypted { title, metadata, .. } => { - render_title_and_metadata(ui, title, metadata); + Note::Encrypted { id, title, metadata, .. } => { + render_title_and_metadata(ui, title, metadata, false, (id.clone(), Box::new(cb))); if ui.button("decrypt note").clicked() { value = Some(self.decrypt(password).unwrap()); } } - Note::Decrypted { title, metadata, text } => { - render_title_and_metadata(ui, title, metadata); + Note::Decrypted { id, title, metadata, text, .. } => { + render_title_and_metadata(ui, title, metadata, true, (id.clone(), Box::new(cb))); ui.label(text); } }; @@ -88,15 +92,22 @@ impl Note { value } + + pub fn id(&self) -> String { + match self { + Note::Encrypted { id, .. } => id.clone(), + Note::Decrypted { id, .. } => id.clone(), + } + } } -fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata) { +fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box)) { ui.horizontal(|ui| { ui.label(RichText::new(title).size(14.0)); let date: DateTime = metadata.date.into(); ui.separator(); - ui.label(date.format("%d-%m-%y").to_string()); + ui.label(date.format("%d-%m-%y %H:%M").to_string()); if let Some(arbitrary) = &metadata.arbitrary { for (k, v) in arbitrary.iter() { @@ -104,6 +115,15 @@ fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &N ui.label(RichText::new(format!("{}: {}", k, v)).monospace()); } } + + ui.separator(); + if show_delete_button { + let (id, cb) = delete_info; + if ui.button("delete note").clicked() { + cb(id); + } + } }); + ui.separator(); } \ No newline at end of file diff --git a/src/saving/mod.rs b/src/saving/mod.rs index 7f5854f..7ed51db 100644 --- a/src/saving/mod.rs +++ b/src/saving/mod.rs @@ -1,6 +1,6 @@ +use std::collections::VecDeque; use std::fs; use std::path::{Path, PathBuf}; -use std::time::SystemTime; use crate::notes::Note; pub struct Saving { @@ -15,27 +15,36 @@ impl Saving { } pub fn read_notes(&self) -> anyhow::Result> { - let mut notes = vec![]; + let mut notes = VecDeque::new(); let paths = fs::read_dir(&self.path)?; for path in paths { let path = path?.path(); if path.is_file() { - notes.push(serde_json::from_str(&fs::read_to_string(path)?)?); + notes.push_front(serde_json::from_str(&fs::read_to_string(path)?)?); } } - Ok(notes) + Ok(notes.into()) } pub fn save_note(&self, note: Note, password: String) -> anyhow::Result<()> { let note = note.encrypt(&password)?; let mut path = self.path.clone().join("_"); - path.set_file_name(format!("{}.json", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis())); + path.set_file_name(format!("{}.json", note.id())); fs::write(path, serde_json::to_string(¬e)?)?; Ok(()) } + + pub fn delete_note(&self, id: &str) -> anyhow::Result<()> { + let mut path = self.path.clone().join("_"); + path.set_file_name(format!("{}.json", id)); + + fs::remove_file(path)?; + + Ok(()) + } } \ No newline at end of file