note-taking/src/notes/mod.rs

129 lines
4 KiB
Rust
Raw Normal View History

use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use std::time::SystemTime;
use chrono::{DateTime, Utc};
use egui::{RichText, Ui};
use pwbox::sodium::Sodium;
use pwbox::{ErasedPwBox, Eraser, Suite};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoteMetadata {
pub date: SystemTime,
pub arbitrary: Option<HashMap<String, String>>,
}
#[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,
}
}
impl Note {
pub fn encrypt(&self, password: &str) -> anyhow::Result<Self> {
let mut rng = rand::thread_rng();
match self {
Note::Decrypted { id, text, metadata, title } => {
let pwbox = Sodium::build_box(&mut rng)
.seal(password.to_string().into_bytes(), text.clone().into_bytes())?;
let mut eraser = Eraser::new();
eraser.add_suite::<Sodium>();
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)?,
})
}
_ => Ok(self.clone()),
}
}
pub fn decrypt(&self, password: &str) -> anyhow::Result<Self> {
match self {
Note::Encrypted { id, title, metadata, encrypted_text } => {
let mut eraser = Eraser::new();
eraser.add_suite::<Sodium>();
let erased: ErasedPwBox = serde_json::from_str(encrypted_text)?;
let decrypted_bytes = eraser.restore(&erased)?.open(password)?.to_vec();
let decrypted_text = String::from_utf8(decrypted_bytes)?;
Ok(Self::Decrypted {
id: id.clone(),
title: title.clone(),
metadata: metadata.clone(),
text: decrypted_text,
})
}
_ => Ok(self.clone()),
}
}
pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option<Self> {
let mut value = None;
ui.group(|ui| {
match self {
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 { id, title, metadata, text, .. } => {
render_title_and_metadata(ui, title, metadata, true, (id.clone(), Box::new(cb)));
ui.label(text);
}
};
});
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<String>, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box<impl FnOnce(String)>)) {
ui.horizontal(|ui| {
ui.label(RichText::new(title).size(14.0));
let date: DateTime<Utc> = metadata.date.into();
ui.separator();
ui.label(date.format("%d-%m-%y %H:%M").to_string());
if let Some(arbitrary) = &metadata.arbitrary {
for (k, v) in arbitrary.iter() {
ui.separator();
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();
}