use std::{ fmt::Debug, marker::PhantomData, sync::atomic::{AtomicU64, Ordering}, }; use eyre::{eyre, WrapErr}; use redb::ReadableTable; use serde::{de::DeserializeOwned, Serialize}; use snitch_lib::LogMsg; use crate::Opt; pub struct Database { inner: redb::Database, next_log_id: AtomicU64, } #[derive(Debug)] struct Serialized { packed: Vec, _type: PhantomData, } pub(crate) fn open(opt: &Opt) -> eyre::Result { let inner = redb::Database::create(&opt.db) .context(eyre!("Failed to open database at {}", opt.db.display()))?; let txn = inner.begin_write()?; let next_log_id = txn .open_table(table::LOG_MESSAGE)? .range::(..)? .next_back() .transpose() .ok() .flatten() .map(|(id, _)| id.value() + 1) .unwrap_or_default(); txn.commit()?; Ok(Database { inner, next_log_id: next_log_id.into(), }) } mod table { use super::Serialized; use redb::TableDefinition; use snitch_lib::LogMsg; pub const LOG_MESSAGE: TableDefinition> = TableDefinition::new("log_message"); } impl Database { pub fn write_log(&self, log: &LogMsg) -> eyre::Result { let txn = self.inner.begin_write()?; let id = self.next_log_id.fetch_add(1, Ordering::SeqCst); txn.open_table(table::LOG_MESSAGE)? .insert(id, Serialized::serialize(log))?; txn.commit()?; Ok(id) } pub fn get_all_log_messages(&self) -> eyre::Result> { let txn = self.inner.begin_read()?; txn.open_table(table::LOG_MESSAGE)? .range::(..)? .map(|result| { let (id, msg) = result?; let id = id.value(); let msg = msg.value(); msg.deserialize().map(|msg| (id, msg)) }) .collect() } } impl Serialized where T: Debug + Serialize + DeserializeOwned, { pub fn serialize(value: &T) -> Self { let packed = rmp_serde::to_vec(&value).unwrap(); Self { packed, _type: PhantomData, } } pub fn deserialize(&self) -> eyre::Result { rmp_serde::from_slice(&self.packed).wrap_err("Failed to deserialize") } } impl redb::Value for Serialized where T: Debug + Serialize + DeserializeOwned, { type SelfType<'a> = Self where Self: 'a; type AsBytes<'a> = &'a [u8] where Self: 'a; fn fixed_width() -> Option { None } fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> where Self: 'a, { Serialized { packed: data.to_vec(), _type: PhantomData, } } fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> where Self: 'a, Self: 'b, { &value.packed } fn type_name() -> redb::TypeName { redb::TypeName::new(std::any::type_name::>()) } }