diff --git a/visions/server/Cargo.toml b/visions/server/Cargo.toml index 7eede30..5bc25d1 100644 --- a/visions/server/Cargo.toml +++ b/visions/server/Cargo.toml @@ -15,7 +15,6 @@ warp = { version = "0.3" } mime_guess = "2.0.5" mime = "0.3.17" uuid = { version = "1.11.0", features = ["v4"] } -futures = "0.3.31" tokio-stream = "0.1.16" typeshare = "1.0.4" urlencoding = "2.1.3" @@ -24,6 +23,8 @@ rusqlite = "0.32.1" rusqlite_migration = { version = "1.3.1", features = ["from-directory"] } lazy_static = "1.5.0" include_dir = "0.7.4" +async-trait = "0.1.83" +futures = "0.3.31" [dev-dependencies] cool_asserts = "2.0.3" diff --git a/visions/server/Taskfile.yml b/visions/server/Taskfile.yml index 8785126..82a446d 100644 --- a/visions/server/Taskfile.yml +++ b/visions/server/Taskfile.yml @@ -7,7 +7,7 @@ tasks: test: cmds: - - cargo watch -x test + - cargo watch -x 'test -- --nocapture' dev: cmds: diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index 1f7005a..0ecf2c1 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -1,13 +1,10 @@ use std::{ collections::HashMap, - io::Read, - path::PathBuf, sync::{Arc, RwLock}, }; use mime::Mime; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use urlencoding::decode; use uuid::Uuid; use crate::{ @@ -27,6 +24,7 @@ struct WebsocketClient { pub struct AppState { pub asset_store: Box, + pub db: Box, pub clients: HashMap, pub tabletop: Tabletop, @@ -36,12 +34,14 @@ pub struct AppState { pub struct Core(Arc>); impl Core { - pub fn new(assetdb: A) -> Self + pub fn new(assetdb: A, db: DB) -> Self where A: Assets + Sync + Send + 'static, + DB: Database + Sync + Send + 'static, { Self(Arc::new(RwLock::new(AppState { asset_store: Box::new(assetdb), + db: Box::new(db), clients: HashMap::new(), tabletop: Tabletop { background_color: DEFAULT_BACKGROUND_COLOR, @@ -135,11 +135,13 @@ impl Core { #[cfg(test)] mod test { + use std::path::PathBuf; + use super::*; use cool_asserts::assert_matches; - use crate::asset_db::mocks::MemoryAssets; + use crate::{asset_db::mocks::MemoryAssets, database::{DbConn, DiskDb}}; fn test_core() -> Core { let assets = MemoryAssets::new(vec![ @@ -169,7 +171,9 @@ mod test { String::from("abcdefg").into_bytes(), ), ]); - Core::new(assets) + let memory_db: Option = None; + let conn = DbConn::new(memory_db); + Core::new(assets, conn) } #[tokio::test] diff --git a/visions/server/src/database.rs b/visions/server/src/database.rs index 7de5572..a960ae6 100644 --- a/visions/server/src/database.rs +++ b/visions/server/src/database.rs @@ -1,15 +1,16 @@ use std::{ - path::PathBuf, - sync::mpsc::{channel, Receiver, Sender}, + path::{Path, PathBuf}, thread::JoinHandle, }; +use async_trait::async_trait; use include_dir::{include_dir, Dir}; use lazy_static::lazy_static; use rusqlite::Connection; use rusqlite_migration::Migrations; use serde::{Deserialize, Serialize}; use thiserror::Error; +use tokio::sync::{mpsc, oneshot}; use uuid::Uuid; static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations"); @@ -26,6 +27,25 @@ pub enum Error { #[error("Unexpected response for message")] MessageMismatch, + + #[error("No response to request")] + NoResponse +} + +#[derive(Debug)] +enum Request { + Charsheet(CharacterId), +} + +#[derive(Debug)] +struct DatabaseRequest { + tx: oneshot::Sender, + req: Request, +} + +#[derive(Debug)] +enum DatabaseResponse { + Charsheet(Option), } #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] @@ -60,8 +80,12 @@ pub struct CharsheetRow { data: serde_json::Value, } -pub trait Database { - async fn charsheet(&self, id: CharacterId) -> Result, Error>; +#[async_trait] +pub trait Database: Send + Sync { + async fn charsheet( + &mut self, + id: CharacterId, + ) -> Result, Error>; } pub struct DiskDb { @@ -69,7 +93,10 @@ pub struct DiskDb { } impl DiskDb { - pub fn new(path: Option) -> Result { + pub fn new

(path: Option

) -> Result + where + P: AsRef, + { let mut conn = match path { None => Connection::open(":memory:").expect("to create a memory connection"), Some(path) => Connection::open(path).expect("to create connection"), @@ -134,42 +161,66 @@ impl DiskDb { } } -/* -enum DatabaseRequest { - Charsheet(CharacterId), -} - -enum DatabaseResponse { - Charsheet(Option), -} - -pub struct DiskDb { - db_handle: JoinHandle<()>, - to_db: Sender, - from_db: Receiver, -} - -impl DiskDb { - pub fn new(path: PathBuf) -> DiskDb { - DiskDb { - db_handle, - to_db: interface_to_db_tx, - from_db: db_to_interface_rx, +async fn db_handler(db: DiskDb, mut requestor: mpsc::Receiver) { + println!("Starting db_handler"); + while let Some(DatabaseRequest{ tx, req }) = requestor.recv().await { + println!("Request received: {:?}", req); + match req { + Request::Charsheet(id) => { + let sheet = db.charsheet(id); + println!("sheet retrieved: {:?}", sheet); + match sheet { + Ok(sheet) => tx.send(DatabaseResponse::Charsheet(sheet)).unwrap(), + _ => unimplemented!(), + } + } } } + println!("ending db_handler"); +} + +pub struct DbConn { + conn: mpsc::Sender, + handle: tokio::task::JoinHandle<()>, +} + +impl DbConn { + pub fn new

(path: Option

) -> Self + where + P: AsRef, + { + let (tx, rx) = mpsc::channel(5); + let db = DiskDb::new(path).unwrap(); + + let handle = tokio::spawn(async move { db_handler(db, rx).await; }); + + Self { conn: tx, handle } + } } -impl Database for DiskDb { - async fn charsheet(&self, id: CharacterId) -> Result, Error> { - self.to_db(DatabaseRequest::Charsheet(id)).unwrap(); - match self.from_db.recv() { - Ok(DatabaseResponse::Charsheet(sheet)) => Ok(sheet), +#[async_trait] +impl Database for DbConn { + async fn charsheet( + &mut self, + id: CharacterId, + ) -> Result, Error> { + let (tx, rx) = oneshot::channel::(); + + let request = DatabaseRequest{ + tx, + req: Request::Charsheet(id), + }; + self.conn.send(request).await.unwrap(); + match rx.await { + Ok(DatabaseResponse::Charsheet(row)) => Ok(row), Ok(_) => Err(Error::MessageMismatch), - Err(_) => unimplemented!(), + Err(err) => { + println!("error: {:?}", err); + Err(Error::NoResponse) + } } } } -*/ #[cfg(test)] mod test { @@ -181,12 +232,23 @@ mod test { #[test] fn it_can_retrieve_a_charsheet() { - let db = DiskDb::new(None).unwrap(); + let no_path: Option = None; + let db = DiskDb::new(no_path).unwrap(); assert_matches!(db.charsheet(CharacterId::from("1")), Ok(None)); let js: serde_json::Value = serde_json::from_str(soren).unwrap(); - let soren_id = db.save_charsheet(None, "candela".to_owned(), js.clone()).unwrap(); + let soren_id = db + .save_charsheet(None, "candela".to_owned(), js.clone()) + .unwrap(); assert_matches!(db.charsheet(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data)); } + + #[tokio::test] + async fn it_can_retrieve_a_charsheet_through_conn() { + let memory_db: Option = None; + let mut conn = DbConn::new(memory_db); + + assert_matches!(conn.charsheet(CharacterId::from("1")).await, Ok(None)); + } } diff --git a/visions/server/src/main.rs b/visions/server/src/main.rs index 80c8ae1..115fe8e 100644 --- a/visions/server/src/main.rs +++ b/visions/server/src/main.rs @@ -6,7 +6,7 @@ use std::{ use asset_db::{AssetId, FsAssets}; use authdb::AuthError; -use database::{CharacterId, Database}; +use database::DbConn; use handlers::{ handle_available_images, handle_connect_websocket, handle_file, handle_register_client, handle_set_background_image, handle_unregister_client, RegisterRequest, @@ -100,9 +100,9 @@ async fn handle_rejection(err: warp::Rejection) -> Result