work (new network + db initial working state)
This commit is contained in:
55
src/bin/server/db/mod.rs
Normal file
55
src/bin/server/db/mod.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
mod util;
|
||||
mod ver;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use util::*;
|
||||
use ver::*;
|
||||
|
||||
pub const DB_VERSION: u64 = 0;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Db {
|
||||
db: Database,
|
||||
pub msgs: DbMap<MsgId, Msg>,
|
||||
pub users: DbMap<UserId, User>,
|
||||
pub usernames: DbMap<String, UserId>,
|
||||
}
|
||||
|
||||
pub type UserId = UserIdV0;
|
||||
pub type MsgId = MsgIdV0;
|
||||
pub type ChannelId = ChannelIdV0;
|
||||
pub type ImageId = ImageIdV0;
|
||||
pub type User = UserV0;
|
||||
pub type Msg = MsgV0;
|
||||
pub type ChannelInfo = ChannelV0;
|
||||
|
||||
impl Db {
|
||||
pub fn open(path: impl AsRef<Path>) -> Db {
|
||||
let db = Database::open(path);
|
||||
let info: DbMap<String, u64> = DbMap::open("info", &db);
|
||||
if let Some(version) = info.get("version") {
|
||||
println!("found existing db version {version}");
|
||||
if version != DB_VERSION {
|
||||
panic!("non matching db version! (auto update in the future)");
|
||||
}
|
||||
} else {
|
||||
println!("no previous db found, creating new");
|
||||
info.insert("version", &DB_VERSION);
|
||||
}
|
||||
Db {
|
||||
msgs: DbMap::open("msg", &db),
|
||||
users: DbMap::open("user", &db),
|
||||
usernames: DbMap::open("username", &db),
|
||||
db,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Db {
|
||||
type Target = Database;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
202
src/bin/server/db/util.rs
Normal file
202
src/bin/server/db/util.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use std::{marker::PhantomData, path::Path};
|
||||
|
||||
use bitcode::{DecodeOwned, Encode};
|
||||
use fjall::{KeyspaceCreateOptions, OptimisticTxDatabase, OptimisticTxKeyspace, Readable, Slice};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Database(fjall::OptimisticTxDatabase);
|
||||
|
||||
pub struct DbMap<K, V> {
|
||||
db: Database,
|
||||
keyspace: OptimisticTxKeyspace,
|
||||
_pd: PhantomData<(K, V)>,
|
||||
}
|
||||
|
||||
impl<K, V> Clone for DbMap<K, V> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
db: self.db.clone(),
|
||||
keyspace: self.keyspace.clone(),
|
||||
_pd: self._pd.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> DbMap<K, V> {
|
||||
pub fn open(name: &str, db: &Database) -> DbMap<K, V> {
|
||||
DbMap {
|
||||
db: db.clone(),
|
||||
keyspace: db.0.keyspace(name, KeyspaceCreateOptions::default).unwrap(),
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Key<Output = K>, V: Encode + DecodeOwned> DbMap<K, V> {
|
||||
// TODO: K2 IS NOT A SAFE ABSTRACTION!! need to have KeyLike which has key assoc type
|
||||
pub fn insert<K2: Key<Output = K> + ?Sized>(&self, k: &K2, v: &V) {
|
||||
let k = Slice::new(k.to_bytes().as_ref());
|
||||
let v = Slice::new(&bitcode::encode(v));
|
||||
self.keyspace.insert(k, v).unwrap();
|
||||
}
|
||||
|
||||
pub fn get<K2: Key<Output = K> + ?Sized>(&self, k: &K2) -> Option<V> {
|
||||
let k = Slice::new(k.to_bytes().as_ref());
|
||||
let v = self.keyspace.get(k).unwrap()?;
|
||||
Some(bitcode::decode(&v).unwrap())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (K, V)> {
|
||||
self.db.read_tx().iter(self)
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = V> {
|
||||
self.db.read_tx().values(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn open(path: impl AsRef<Path>) -> Self {
|
||||
let inner = OptimisticTxDatabase::builder(path.as_ref())
|
||||
.open()
|
||||
.expect("failed to open database");
|
||||
Self(inner)
|
||||
}
|
||||
pub fn read_tx(&self) -> ReadTx {
|
||||
ReadTx(self.0.read_tx())
|
||||
}
|
||||
|
||||
pub fn write_tx(&self) -> WriteTx {
|
||||
WriteTx(self.0.write_tx().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadTx(fjall::Snapshot);
|
||||
|
||||
impl ReadTx {
|
||||
pub fn values<K, V: DecodeOwned>(
|
||||
&self,
|
||||
map: &DbMap<K, V>,
|
||||
) -> impl Iterator<Item = V> + use<K, V> {
|
||||
self.0.iter(&map.keyspace).map(|g| {
|
||||
let v = g.value().unwrap();
|
||||
bitcode::decode(&v).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter<K: Key<Output = K>, V: DecodeOwned>(
|
||||
&self,
|
||||
map: &DbMap<K, V>,
|
||||
) -> impl Iterator<Item = (K, V)> + use<K, V> {
|
||||
self.0.iter(&map.keyspace).map(|g| {
|
||||
let (k, v) = g.into_inner().unwrap();
|
||||
(K::from_bytes(&k), bitcode::decode(&v).unwrap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteTx(fjall::OptimisticWriteTx);
|
||||
|
||||
impl WriteTx {
|
||||
pub fn values<K, V: DecodeOwned>(
|
||||
&self,
|
||||
map: &DbMap<K, V>,
|
||||
) -> impl Iterator<Item = V> + use<K, V> {
|
||||
self.0.iter(&map.keyspace).map(|g| {
|
||||
let v = g.value().unwrap();
|
||||
bitcode::decode(&v).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter<K: Key<Output = K>, V: DecodeOwned>(
|
||||
&self,
|
||||
map: &DbMap<K, V>,
|
||||
) -> impl Iterator<Item = (K, V)> + use<K, V> {
|
||||
self.0.iter(&map.keyspace).map(|g| {
|
||||
let (k, v) = g.into_inner().unwrap();
|
||||
(K::from_bytes(&k), bitcode::decode(&v).unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get<K: Key<Output = K>, V: DecodeOwned>(&self, map: &DbMap<K, V>, k: K) -> Option<V> {
|
||||
let k = Slice::new(k.to_bytes().as_ref());
|
||||
let v = self.0.get(&map.keyspace, k).unwrap()?;
|
||||
Some(bitcode::decode(&v).unwrap())
|
||||
}
|
||||
|
||||
pub fn has_key<K: Key<Output = K>, V>(&self, map: &DbMap<K, V>, k: K) -> bool {
|
||||
let k = Slice::new(k.to_bytes().as_ref());
|
||||
self.0.get(&map.keyspace, k).unwrap().is_some()
|
||||
}
|
||||
|
||||
// TODO: K2 IS NOT A SAFE ABSTRACTION!! need to have KeyLike which has key assoc type
|
||||
pub fn insert<K: Key<Output = K>, K2: Key<Output = K> + ?Sized, V: Encode>(
|
||||
&mut self,
|
||||
map: &DbMap<K, V>,
|
||||
k: &K2,
|
||||
v: &V,
|
||||
) {
|
||||
let k = Slice::new(k.to_bytes().as_ref());
|
||||
let v = Slice::new(&bitcode::encode(v));
|
||||
self.0.insert(&map.keyspace, k, v);
|
||||
}
|
||||
|
||||
pub fn commit(self) -> bool {
|
||||
self.0.commit().unwrap().is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Key {
|
||||
type Input<'a>: AsRef<[u8]>
|
||||
where
|
||||
Self: 'a;
|
||||
type Output;
|
||||
fn to_bytes(&self) -> Self::Input<'_>;
|
||||
fn from_bytes(bytes: &[u8]) -> Self::Output;
|
||||
}
|
||||
|
||||
impl Key for String {
|
||||
type Input<'a> = &'a Self;
|
||||
type Output = String;
|
||||
fn to_bytes(&self) -> Self::Input<'_> {
|
||||
self
|
||||
}
|
||||
fn from_bytes(bytes: &[u8]) -> Self {
|
||||
Self::from_utf8(bytes.to_vec()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for str {
|
||||
type Input<'a> = &'a Self;
|
||||
type Output = String;
|
||||
fn to_bytes(&self) -> Self::Input<'_> {
|
||||
self
|
||||
}
|
||||
fn from_bytes(bytes: &[u8]) -> String {
|
||||
String::from_utf8(bytes.to_vec()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for u64 {
|
||||
type Input<'a> = [u8; 8];
|
||||
type Output = u64;
|
||||
|
||||
fn to_bytes(&self) -> Self::Input<'_> {
|
||||
self.to_be_bytes()
|
||||
}
|
||||
fn from_bytes(bytes: &[u8]) -> Self {
|
||||
Self::from_be_bytes(bytes.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for i128 {
|
||||
type Input<'a> = [u8; 16];
|
||||
type Output = i128;
|
||||
|
||||
fn to_bytes(&self) -> Self::Input<'_> {
|
||||
self.to_be_bytes()
|
||||
}
|
||||
fn from_bytes(bytes: &[u8]) -> Self {
|
||||
Self::from_be_bytes(bytes.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
23
src/bin/server/db/ver.rs
Normal file
23
src/bin/server/db/ver.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
pub type UserIdV0 = u64;
|
||||
pub type MsgIdV0 = i128;
|
||||
pub type ChannelIdV0 = u64;
|
||||
pub type ImageIdV0 = u64;
|
||||
|
||||
#[derive(bitcode::Encode, bitcode::Decode)]
|
||||
pub struct UserV0 {
|
||||
pub username: String,
|
||||
pub password_hash: String,
|
||||
pub pfp: Option<ImageIdV0>,
|
||||
pub bio: String,
|
||||
}
|
||||
|
||||
#[derive(bitcode::Encode, bitcode::Decode)]
|
||||
pub struct MsgV0 {
|
||||
pub content: String,
|
||||
pub author: UserIdV0,
|
||||
}
|
||||
|
||||
pub struct ChannelV0 {
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
}
|
||||
Reference in New Issue
Block a user