/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin */ use bincode::{Decode, Encode}; use jellycommon::{ user::{NodeUserData, User}, ExtendedNode, Node, }; use log::info; use serde::{Deserialize, Serialize}; use std::{borrow::Borrow, ops::Deref, path::Path}; pub use redb::*; pub const T_USER: TableDefinition<&str, Ser> = TableDefinition::new("user"); pub const T_USER_NODE: TableDefinition<(&str, &str), Ser> = TableDefinition::new("user_node"); pub const T_INVITE: TableDefinition<&str, Ser<()>> = TableDefinition::new("invite"); pub const T_NODE: TableDefinition<&str, Ser> = TableDefinition::new("node"); pub const T_NODE_EXTENDED: TableDefinition<&str, Ser> = TableDefinition::new("node-ext"); pub const T_NODE_IMPORT: TableDefinition<&str, Ser, Node)>>> = TableDefinition::new("node-import"); pub struct DataAcid { pub inner: redb::Database, } impl DataAcid { pub fn open(path: &Path) -> Result { info!("database"); let db = redb::Database::create(path)?; let r = Self { inner: db }; { // this creates all tables such that read operations on them do not fail. let txn = r.begin_write()?; drop(txn.open_table(T_INVITE)?); drop(txn.open_table(T_USER)?); drop(txn.open_table(T_USER_NODE)?); drop(txn.open_table(T_NODE)?); drop(txn.open_table(T_NODE_IMPORT)?); drop(txn.open_table(T_NODE_EXTENDED)?); txn.commit()?; } info!("ready"); Ok(r) } } impl Deref for DataAcid { type Target = Database; fn deref(&self) -> &Self::Target { &self.inner } } pub trait TableExt { fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result>; fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()>; fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result>; } impl<'a, 'b, 'c, Key, Value, KeyRef> TableExt for TableDefinition<'a, Key, Ser> where Key: Borrow<::SelfType<'b>> + redb::RedbKey, Value: Encode + Decode + std::fmt::Debug + Serialize + for<'x> Deserialize<'x>, KeyRef: Borrow<::SelfType<'c>>, { fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result> { let txn = db.inner.begin_read()?; let table = txn.open_table(self)?; let user = table.get(key)?.map(|v| v.value().0); drop(table); Ok(user) } fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()> { let txn = db.inner.begin_write()?; let mut table = txn.open_table(self)?; table.insert(key, Ser(value))?; drop(table); txn.commit()?; Ok(()) } fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result> { let txn = db.inner.begin_write()?; let mut table = txn.open_table(self)?; let prev = table.remove(key)?.map(|v| v.value().0); drop(table); txn.commit()?; Ok(prev) } } // pub trait TableIterExt< // 'a, // Key: redb::RedbKey + 'static, // Value: redb::RedbValue + 'static, // F: FnOnce(&redb::Range<'a, Key, Value>) -> anyhow::Result, // T: 'static, // > // { // fn iter(self, db: &'a DataAcid, f: F) -> anyhow::Result; // } // impl<'a, Key, Value, F, T> TableIterExt<'a, Key, Value, F, T> // for TableDefinition<'static, Key, Value> // where // Key: redb::RedbKey, // Value: redb::RedbValue, // F: FnOnce(&redb::Range<'a, Key, Value>) -> anyhow::Result, // T: 'static, // { // fn iter(self, db: &DataAcid, f: F) -> anyhow::Result { // let txn = db.begin_read()?; // let table = txn.open_table(self)?; // let iter = table.iter()?; // let ret = f(&iter)?; // drop(iter); // drop(table); // drop(txn); // Ok(ret) // } // } #[derive(Debug)] #[cfg(not(feature = "db_json"))] pub struct Ser(pub T); #[cfg(not(feature = "db_json"))] impl RedbValue for Ser { type SelfType<'a> = Ser where Self: 'a; type AsBytes<'a> = Vec where Self: 'a; fn fixed_width() -> Option { None } fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> where Self: 'a, { Ser(bincode::decode_from_slice(data, bincode::config::legacy()) .unwrap() .0) } fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> where Self: 'a, Self: 'b, { bincode::encode_to_vec(&value.0, bincode::config::legacy()).unwrap() } fn type_name() -> redb::TypeName { TypeName::new("bincode") } } #[derive(Debug)] #[cfg(feature = "db_json")] pub struct Ser(pub T); #[cfg(feature = "db_json")] impl Deserialize<'a> + std::fmt::Debug> RedbValue for Ser { type SelfType<'a> = Ser where Self: 'a; type AsBytes<'a> = Vec where Self: 'a; fn fixed_width() -> Option { None } fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> where Self: 'a, { Ser(serde_json::from_slice(data).unwrap()) } fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> where Self: 'a, Self: 'b, { serde_json::to_vec(&value.0).unwrap() } fn type_name() -> redb::TypeName { TypeName::new("json") } }