use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::time::{SystemTime, UNIX_EPOCH}; use std::{env::home_dir, fs}; use crate::day_info::{DayInfo, Event}; use chrono::NaiveDate; use redb::{Database, Error, ReadableDatabase, ReadableTable, TableDefinition}; const MAIN_TABLE: TableDefinition> = TableDefinition::new("main"); // Vec const TAG_TABLE: TableDefinition = TableDefinition::new("tags"); pub struct DB { db: Database, } impl DB { pub fn new() -> Result { let mut path = home_dir().ok_or(Error::DatabaseAlreadyOpen)?; path.push(".local/share/cars"); fs::create_dir_all(&path)?; path.push("main.redb"); let db = Database::create(path)?; let write_txn = db.begin_write()?; { let _main_table = write_txn.open_table(MAIN_TABLE)?; let _tag_table = write_txn.open_table(TAG_TABLE)?; } write_txn.commit()?; Ok(Self { db }) } pub fn get_day(&self, day: NaiveDate) -> Result, Error> { match self.db.begin_read()?.open_table(MAIN_TABLE)?.get(day)? { None => Ok(None), Some(data) => { let events = data.value(); let mut output = DayInfo::default(); for (tag_id, event_description) in events { let tag_name = self.db.begin_read()?.open_table(TAG_TABLE)?.get(tag_id)?; output.events.push(match tag_name { Some(_name) => Event::new(Some(tag_id), event_description), None => Event::new(None, event_description), }); } Ok(Some(output)) } } } pub fn add_event(&mut self, day: NaiveDate, event: Event) -> Result<(), Error> { let mut new_vec = match self.db.begin_read()?.open_table(MAIN_TABLE)?.get(day)? { Some(d) => d.value(), None => vec![], }; new_vec.push(( event.tag.expect("You should pass a valid event"), event.description, )); let write_txn = self.db.begin_write()?; { let mut table = write_txn.open_table(MAIN_TABLE)?; table.insert(day, new_vec)?; } write_txn.commit()?; Ok(()) } pub fn remove_event(&mut self, day: NaiveDate, index: usize) -> Result<(), Error> { let mut old_vec = match self.db.begin_read()?.open_table(MAIN_TABLE)?.get(day)? { Some(d) => d.value(), None => vec![], }; if old_vec.len() <= index { return Ok(()); } old_vec.remove(index); let write_txn = self.db.begin_write()?; { let mut table = write_txn.open_table(MAIN_TABLE)?; table.insert(day, old_vec)?; } write_txn.commit()?; Ok(()) } pub fn get_tags(&self) -> Result, Error> { self.db .begin_read()? .open_table(TAG_TABLE)? .iter()? .map(|x| -> Result<(u64, String), Error> { let val = x?; Ok((val.0.value(), val.1.value())) }) .collect() } pub fn get_tag_name(&self, id: u64) -> Result, Error> { match self.db.begin_read()?.open_table(TAG_TABLE)?.get(id)? { Some(name) => Ok(Some(name.value())), None => Ok(None), } } pub fn create_tag(&mut self, tag_name: String) -> Result { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos() as u64; let mut hasher = DefaultHasher::new(); tag_name.hash(&mut hasher); let tag_hash = hasher.finish(); let tag_id = now.wrapping_add(tag_hash); let write_txn = self.db.begin_write()?; { let mut tag_table = write_txn.open_table(TAG_TABLE)?; tag_table.insert(tag_id, tag_name)?; } write_txn.commit()?; std::thread::sleep(std::time::Duration::from_micros(1)); Ok(tag_id) } pub fn delete_tag(&mut self, tag_id: u64) -> Result<(), Error> { let write_txn = self.db.begin_write()?; { let mut table = write_txn.open_table(TAG_TABLE)?; table.remove(tag_id)?; } write_txn.commit()?; Ok(()) } }