work (new network + db initial working state)
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -2274,6 +2274,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
@@ -2599,6 +2608,7 @@ dependencies = [
|
||||
"rcgen",
|
||||
"ron",
|
||||
"scrypt",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wgpu 27.0.1",
|
||||
@@ -3878,13 +3888,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.46"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
@@ -3899,9 +3911,9 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.26"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
||||
@@ -27,6 +27,7 @@ keyring = { version = "3.6.3", features = ["apple-native", "sync-secret-service"
|
||||
bitcode = "0.6.9"
|
||||
dashmap = "6.1.0"
|
||||
fjall = "3.0.1"
|
||||
time = { version = "0.3.47", features = ["local-offset"] }
|
||||
|
||||
[[bin]]
|
||||
name = "openworm-client"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::ClientEvent;
|
||||
use dashmap::DashMap;
|
||||
use openworm::net::{
|
||||
AccountCreated, ClientMsg, ClientMsgInst, CreateAccount, RecvHandler, RequestId, SERVER_NAME,
|
||||
ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
|
||||
AccountCreated, ClientMsg, ClientMsgInst, ClientRequestMsg, CreateAccount, RecvHandler,
|
||||
RequestId, SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
|
||||
};
|
||||
use quinn::{
|
||||
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
|
||||
@@ -60,11 +60,11 @@ impl NetHandle {
|
||||
pub async fn request<R: RequestMsg>(&self, msg: R) -> Result<R::Result, ()> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
self.send_(NetCtrlMsg::Request(msg.into(), send));
|
||||
let Ok(recv) = recv.await else { todo!() };
|
||||
let Ok(recv) = recv.await else { return Err(()) };
|
||||
if let Some(res) = R::result(recv) {
|
||||
Ok(res)
|
||||
} else {
|
||||
todo!()
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,17 +167,24 @@ pub async fn connect(msg: impl MsgHandler, info: ConnectInfo) -> Result<NetHandl
|
||||
tokio::spawn(recv_uni(conn_, recv.clone()));
|
||||
tokio::spawn(async move {
|
||||
while let Some(msg) = ui_recv.recv().await {
|
||||
let request_id = req_id.next();
|
||||
match msg {
|
||||
NetCtrlMsg::Send(msg) => {
|
||||
let msg = ClientMsgInst::from(msg);
|
||||
let msg = ClientRequestMsg {
|
||||
id: request_id,
|
||||
msg: msg.into(),
|
||||
};
|
||||
if send_uni(&conn, msg).await.is_err() {
|
||||
println!("disconnected from server");
|
||||
break;
|
||||
}
|
||||
}
|
||||
NetCtrlMsg::Request(msg, send) => {
|
||||
let msg = ClientMsgInst::from(msg);
|
||||
recv.requests.insert(req_id.next(), send);
|
||||
let msg = ClientRequestMsg {
|
||||
id: request_id,
|
||||
msg: msg.into(),
|
||||
};
|
||||
recv.requests.insert(request_id, send);
|
||||
if send_uni(&conn, msg).await.is_err() {
|
||||
println!("disconnected from server");
|
||||
break;
|
||||
@@ -215,7 +222,7 @@ struct ServerRecv<F: MsgHandler> {
|
||||
impl<F: MsgHandler> RecvHandler<ServerRespMsg> for ServerRecv<F> {
|
||||
async fn msg(&self, resp: ServerRespMsg) {
|
||||
let msg = resp.msg.into();
|
||||
if let Some(id) = resp.request_id
|
||||
if let Some(id) = resp.id
|
||||
&& let Some((_, send)) = self.requests.remove(&id)
|
||||
{
|
||||
send.send(msg);
|
||||
|
||||
@@ -40,7 +40,7 @@ pub fn start(rsc: &mut Rsc) -> WeakWidget {
|
||||
|
||||
pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
|
||||
let url = field("", "server", rsc);
|
||||
let token = field("", "account token", rsc);
|
||||
let token = field("", "account creation token", rsc);
|
||||
let username = field("", "username", rsc);
|
||||
let password = field("", "password", rsc);
|
||||
|
||||
@@ -81,6 +81,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
|
||||
else {
|
||||
return fail("failed to create account");
|
||||
};
|
||||
println!("account created!!!!");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
pub struct DBMsg {
|
||||
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
use std::{marker::PhantomData, path::Path};
|
||||
|
||||
use bitcode::{Decode, DecodeOwned, Encode};
|
||||
use fjall::{Database, Keyspace, KeyspaceCreateOptions, UserValue};
|
||||
|
||||
pub struct DbU64(u64);
|
||||
pub const DB_VERSION: DbU64 = DbU64(0);
|
||||
|
||||
impl Into<UserValue> for DbU64 {
|
||||
fn into(self) -> UserValue {
|
||||
UserValue::new(&self.0.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct User {
|
||||
pub username: String,
|
||||
pub password_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
pub struct Msg {
|
||||
pub user: u64,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Db {
|
||||
pub db: fjall::Database,
|
||||
pub msgs: DbMap<u64, Msg>,
|
||||
pub users: DbMap<u64, User>,
|
||||
pub usernames: DbMap<String, u64>,
|
||||
}
|
||||
|
||||
pub struct DbMap<K, V> {
|
||||
keyspace: Keyspace,
|
||||
_pd: PhantomData<(K, V)>,
|
||||
}
|
||||
|
||||
pub trait Key {
|
||||
type Output<'a>: AsRef<[u8]>
|
||||
where
|
||||
Self: 'a;
|
||||
fn bytes(&self) -> Self::Output<'_>;
|
||||
}
|
||||
|
||||
impl Key for String {
|
||||
type Output<'a> = &'a Self;
|
||||
fn bytes(&self) -> Self::Output<'_> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for str {
|
||||
type Output<'a> = &'a Self;
|
||||
fn bytes(&self) -> Self::Output<'_> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for u64 {
|
||||
type Output<'a> = [u8; 8];
|
||||
|
||||
fn bytes(&self) -> Self::Output<'_> {
|
||||
self.to_be_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Key, V: Encode + DecodeOwned> DbMap<K, V> {
|
||||
pub fn insert(&self, k: &K, v: &V) {
|
||||
self.keyspace.insert_(k, v);
|
||||
}
|
||||
|
||||
pub fn get(&self, k: &K) -> Option<V> {
|
||||
self.keyspace.get_(k)
|
||||
}
|
||||
|
||||
pub fn init_unique(&self, k: &K) -> bool {
|
||||
self.keyspace
|
||||
.compare_and_swap(k.bytes(), None as Option<&[u8]>, Some(&[0]))
|
||||
.unwrap()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn iter_all(&self) -> impl Iterator<Item = V> {
|
||||
self.keyspace.iter_all()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_db(path: impl AsRef<Path>) -> Db {
|
||||
let config = fjall::Config::new(path.as_ref());
|
||||
let db = Database::open(config).expect("failed to open database");
|
||||
let info = open_ks("info", &db);
|
||||
if !info.contains_key("version").unwrap() {
|
||||
println!("no previous db found, creating new");
|
||||
info.insert("version", DB_VERSION);
|
||||
} else {
|
||||
let version: u64 = info.get("version").expect("failed to read db version");
|
||||
println!("found existing db version {version}");
|
||||
if version != DB_VERSION {
|
||||
panic!("non matching db version! (auto update in the future)");
|
||||
}
|
||||
}
|
||||
Db {
|
||||
msgs: open_ks("msg", &db),
|
||||
users: open_ks("user", &db),
|
||||
usernames: open_ks("username", &db),
|
||||
db,
|
||||
}
|
||||
}
|
||||
|
||||
trait DbUtil {
|
||||
fn insert_<V: Encode>(&self, k: &(impl Key + ?Sized), v: V);
|
||||
fn get_<V: DecodeOwned>(&self, k: &(impl Key + ?Sized)) -> Option<V>;
|
||||
fn iter_all<V: DecodeOwned>(&self) -> impl Iterator<Item = V>;
|
||||
}
|
||||
|
||||
impl DbUtil for Tree {
|
||||
fn insert_<V: Encode>(&self, k: &(impl Key + ?Sized), v: &V) {
|
||||
let bytes = bitcode::encode(v);
|
||||
self.insert(k.bytes(), bytes).unwrap();
|
||||
}
|
||||
|
||||
fn get_<V: Decode<()>>(&self, k: &(impl Key + ?Sized)) -> Option<V> {
|
||||
let bytes = self.get(k.bytes()).unwrap()?;
|
||||
Some(
|
||||
bincode::decode_from_slice(&bytes, BINCODE_CONFIG)
|
||||
.unwrap()
|
||||
.0,
|
||||
)
|
||||
}
|
||||
|
||||
fn iter_all<V: Decode<()>>(&self) -> impl Iterator<Item = V> {
|
||||
self.iter().map(|r| {
|
||||
bincode::decode_from_slice(&r.unwrap().1, BINCODE_CONFIG)
|
||||
.unwrap()
|
||||
.0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_ks<K, V>(name: &str, db: &Database) -> DbMap<K, V> {
|
||||
DbMap {
|
||||
keyspace: db.keyspace(name, KeyspaceCreateOptions::default).unwrap(),
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Clone for DbMap<K, V> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
keyspace: self.keyspace.clone(),
|
||||
_pd: self._pd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub fn flush(&self) {
|
||||
// test to see if it gets dropped and just works
|
||||
// self.db.persist(fjall::PersistMode::SyncAll).unwrap();
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
// mod data;
|
||||
mod db;
|
||||
mod net;
|
||||
|
||||
use crate::db::{Db, Msg, User, open_db};
|
||||
use crate::db::{Db, Msg, User};
|
||||
use clap::Parser;
|
||||
use net::{ClientSender, ConAccepter, listen};
|
||||
use openworm::{
|
||||
net::{
|
||||
ClientMsg, ClientMsgInst, CreateAccount, DisconnectReason, LoadMsg, RecvHandler,
|
||||
ServerError, ServerMsg, install_crypto_provider,
|
||||
AccountCreated, ClientMsg, ClientRequestMsg, CreateAccount, DisconnectReason, LoadMsg,
|
||||
RecvHandler, ServerError, ServerMsg, install_crypto_provider,
|
||||
},
|
||||
rsc::DataDir,
|
||||
};
|
||||
@@ -43,7 +42,7 @@ fn main() {
|
||||
pub async fn run_server(port: u16) {
|
||||
let dir = DataDir::default();
|
||||
let path = dir.get();
|
||||
let db: Db = open_db(path.join("server.db"));
|
||||
let db = Db::open(path.join("server_db"));
|
||||
let handler = ServerListener {
|
||||
senders: Default::default(),
|
||||
count: 0.into(),
|
||||
@@ -56,9 +55,6 @@ pub async fn run_server(port: u16) {
|
||||
endpoint.close(0u32.into(), &[]);
|
||||
let _ = handle.await;
|
||||
endpoint.wait_idle().await;
|
||||
println!("saving...");
|
||||
db.flush();
|
||||
println!("saved");
|
||||
}
|
||||
|
||||
type ClientId = u64;
|
||||
@@ -76,7 +72,7 @@ pub enum ClientState {
|
||||
}
|
||||
|
||||
impl ConAccepter for ServerListener {
|
||||
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientMsgInst> {
|
||||
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientRequestMsg> {
|
||||
let id = self.count.fetch_add(1, Ordering::Release);
|
||||
self.senders.write().await.insert(id, send.clone());
|
||||
ClientHandler {
|
||||
@@ -97,29 +93,33 @@ struct ClientHandler {
|
||||
state: Arc<RwLock<ClientState>>,
|
||||
}
|
||||
|
||||
impl RecvHandler<ClientMsgInst> for ClientHandler {
|
||||
impl RecvHandler<ClientRequestMsg> for ClientHandler {
|
||||
async fn connect(&self) -> () {
|
||||
println!("connected: {:?}", self.send.remote().ip());
|
||||
}
|
||||
async fn msg(&self, msg: ClientMsgInst) {
|
||||
let msg = ClientMsg::from(msg);
|
||||
async fn msg(&self, req: ClientRequestMsg) {
|
||||
let msg = ClientMsg::from(req.msg);
|
||||
let replier = self.send.replier(req.id);
|
||||
match msg {
|
||||
ClientMsg::SendMsg(msg) => {
|
||||
let ClientState::Authed(uid) = &*self.state.read().await else {
|
||||
let _ = self.send.send(ServerError::NotLoggedIn).await;
|
||||
let _ = replier.send(ServerError::NotLoggedIn).await;
|
||||
return;
|
||||
};
|
||||
let msg = Msg {
|
||||
user: *uid,
|
||||
author: *uid,
|
||||
content: msg.content,
|
||||
};
|
||||
let id = self.db.generate_id().unwrap();
|
||||
self.db.msgs.insert(&id, &msg);
|
||||
// TODO: it is technically possible to send 2 messages at the exact same time...
|
||||
// should probably append a number if one already exists at that time,
|
||||
// but also I can't see this ever happening...?
|
||||
// should be an easy fix later (write tx)
|
||||
let timestamp = time::OffsetDateTime::now_utc().unix_timestamp_nanos();
|
||||
self.db.msgs.insert(×tamp, &msg);
|
||||
let mut handles = Vec::new();
|
||||
let user: User = self.db.users.get(uid).unwrap();
|
||||
let msg = LoadMsg {
|
||||
content: msg.content,
|
||||
user: user.username,
|
||||
author: *uid,
|
||||
};
|
||||
for (&id, send) in self.senders.read().await.iter() {
|
||||
if id == self.id {
|
||||
@@ -128,7 +128,12 @@ impl RecvHandler<ClientMsgInst> for ClientHandler {
|
||||
let send = send.clone();
|
||||
let msg = msg.clone();
|
||||
let fut = async move {
|
||||
let _ = send.send(msg).await;
|
||||
let _ = send
|
||||
.send(LoadMsg {
|
||||
content: msg.content,
|
||||
author: msg.author,
|
||||
})
|
||||
.await;
|
||||
};
|
||||
handles.push(tokio::spawn(fut));
|
||||
}
|
||||
@@ -138,27 +143,19 @@ impl RecvHandler<ClientMsgInst> for ClientHandler {
|
||||
}
|
||||
ClientMsg::RequestMsgs => {
|
||||
let ClientState::Authed(_uid) = &*self.state.read().await else {
|
||||
let _ = self.send.send(ServerError::NotLoggedIn).await;
|
||||
let _ = replier.send(ServerError::NotLoggedIn).await;
|
||||
return;
|
||||
};
|
||||
let msgs = self
|
||||
.db
|
||||
.msgs
|
||||
.iter_all()
|
||||
.map(|msg| {
|
||||
let user = self
|
||||
.db
|
||||
.users
|
||||
.get(&msg.user)
|
||||
.map(|user| user.username.to_string())
|
||||
.unwrap_or("deleted user".to_string());
|
||||
LoadMsg {
|
||||
content: msg.content,
|
||||
user,
|
||||
}
|
||||
.values()
|
||||
.map(|msg| LoadMsg {
|
||||
content: msg.content,
|
||||
author: msg.author,
|
||||
})
|
||||
.collect();
|
||||
let _ = self.send.send(ServerMsg::LoadMsgs(msgs)).await;
|
||||
let _ = replier.send(ServerMsg::LoadMsgs(msgs)).await;
|
||||
}
|
||||
ClientMsg::CreateAccount(info) => {
|
||||
let CreateAccount {
|
||||
@@ -167,28 +164,41 @@ impl RecvHandler<ClientMsgInst> for ClientHandler {
|
||||
password,
|
||||
login_key,
|
||||
} = &info;
|
||||
if !self.db.usernames.init_unique(username) {
|
||||
let _ = self.send.send(ServerError::UsernameTaken).await;
|
||||
return;
|
||||
}
|
||||
let id = self.db.generate_id().unwrap();
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
|
||||
let hash = Scrypt
|
||||
.hash_password_customized(password.as_bytes(), None, None, params, &salt)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
self.db.users.insert(
|
||||
&id,
|
||||
&User {
|
||||
username: username.clone(),
|
||||
password_hash: hash,
|
||||
},
|
||||
);
|
||||
let mut id;
|
||||
loop {
|
||||
let mut tx = self.db.write_tx();
|
||||
if tx.has_key(&self.db.usernames, username.clone()) {
|
||||
let _ = replier.send(ServerError::UsernameTaken).await;
|
||||
return;
|
||||
}
|
||||
id = rand::random();
|
||||
while tx.has_key(&self.db.users, id) {
|
||||
id = rand::random();
|
||||
}
|
||||
tx.insert(
|
||||
&self.db.users,
|
||||
&id,
|
||||
&User {
|
||||
username: username.clone(),
|
||||
password_hash: hash.clone(),
|
||||
bio: String::new(),
|
||||
pfp: None,
|
||||
},
|
||||
);
|
||||
tx.insert(&self.db.usernames, username, &id);
|
||||
if tx.commit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("account created: \"{username}\"");
|
||||
self.db.usernames.insert(&username, &id);
|
||||
*self.state.write().await = ClientState::Authed(id);
|
||||
// let _ = self.send.send(ServerMsg::Login()).await;
|
||||
let _ = replier.send(AccountCreated {}).await;
|
||||
} // ClientMsgType::Login { username, password } => {
|
||||
// let Some(id) = self.db.usernames.get(&username) else {
|
||||
// let _ = self.send.send(ServerError::UnknownUsername).await;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use openworm::net::{
|
||||
ClientMsg, ClientMsgInst, RecvHandler, SERVER_NAME, SendResult, ServerMsg, ServerMsgInst,
|
||||
ClientRequestMsg, RecvHandler, RequestId, SERVER_NAME, SendResult, ServerMsg, ServerRespMsg,
|
||||
recv_uni, send_uni,
|
||||
};
|
||||
use quinn::{
|
||||
@@ -63,8 +63,34 @@ impl ClientSender {
|
||||
pub fn remote(&self) -> SocketAddr {
|
||||
self.conn.remote_address()
|
||||
}
|
||||
|
||||
pub fn replier(&self, id: RequestId) -> ClientReplier {
|
||||
ClientReplier {
|
||||
conn: self.conn.clone(),
|
||||
req_id: id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult {
|
||||
let msg = ServerMsgInst::from(msg.into());
|
||||
let msg = ServerRespMsg {
|
||||
id: None,
|
||||
msg: msg.into().into(),
|
||||
};
|
||||
send_uni(&self.conn, msg).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientReplier {
|
||||
conn: Connection,
|
||||
req_id: RequestId,
|
||||
}
|
||||
|
||||
impl ClientReplier {
|
||||
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult {
|
||||
let msg = ServerRespMsg {
|
||||
id: Some(self.req_id),
|
||||
msg: msg.into().into(),
|
||||
};
|
||||
send_uni(&self.conn, msg).await
|
||||
}
|
||||
}
|
||||
@@ -73,7 +99,7 @@ pub trait ConAccepter: Send + Sync + 'static {
|
||||
fn accept(
|
||||
&self,
|
||||
send: ClientSender,
|
||||
) -> impl Future<Output = impl RecvHandler<ClientMsgInst>> + Send;
|
||||
) -> impl Future<Output = impl RecvHandler<ClientRequestMsg>> + Send;
|
||||
}
|
||||
|
||||
pub fn listen(
|
||||
|
||||
@@ -17,6 +17,8 @@ pub enum ServerMsgInst {
|
||||
ServerErrorV0(ServerErrorV0) = 3,
|
||||
}
|
||||
|
||||
pub type UserIdV0 = u64;
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct CreateAccountV0 {
|
||||
pub username: String,
|
||||
@@ -56,7 +58,7 @@ pub struct SendMsgV0 {
|
||||
#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct LoadMsgV0 {
|
||||
pub content: String,
|
||||
pub user: String,
|
||||
pub author: UserIdV0,
|
||||
}
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
|
||||
@@ -21,6 +21,7 @@ pub type LoadMsg = LoadMsgV0;
|
||||
pub type ServerError = ServerErrorV0;
|
||||
pub type CreateAccount = CreateAccountV0;
|
||||
pub type AccountCreated = AccountCreatedV0;
|
||||
pub type UserId = UserIdV0;
|
||||
|
||||
impl From<CreateAccount> for ClientMsg {
|
||||
fn from(value: CreateAccount) -> Self {
|
||||
@@ -33,3 +34,15 @@ impl From<ServerError> for ServerMsg {
|
||||
Self::ServerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadMsg> for ServerMsg {
|
||||
fn from(value: LoadMsg) -> Self {
|
||||
Self::LoadMsg(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountCreated> for ServerMsg {
|
||||
fn from(value: AccountCreated) -> Self {
|
||||
Self::AccountCreated(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ impl RequestId {
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct ClientRequestMsg {
|
||||
pub request_id: Option<RequestId>,
|
||||
pub id: RequestId,
|
||||
pub msg: ClientMsgInst,
|
||||
}
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct ServerRespMsg {
|
||||
pub request_id: Option<RequestId>,
|
||||
pub id: Option<RequestId>,
|
||||
pub msg: ServerMsgInst,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user