diff --git a/back_end/Cargo.lock b/back_end/Cargo.lock index 0ab459c..200c91f 100644 --- a/back_end/Cargo.lock +++ b/back_end/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -104,6 +115,18 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.7.0" @@ -134,6 +157,8 @@ name = "four_line_dropper_backend" version = "0.1.0" dependencies = [ "bytes", + "rand", + "rusqlite", "serde_json", "tokio", "warp", @@ -226,6 +251,18 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] [[package]] name = "headers" @@ -367,6 +404,16 @@ version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +[[package]] +name = "libsqlite3-sys" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb644c388dfaefa18035c12614156d285364769e818893da0dda9030c80ad2ba" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "lock_api" version = "0.4.6" @@ -546,6 +593,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -624,6 +677,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "rusqlite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + [[package]] name = "ryu" version = "1.0.9" @@ -976,6 +1044,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/back_end/Cargo.toml b/back_end/Cargo.toml index 6f88a14..2149c40 100644 --- a/back_end/Cargo.toml +++ b/back_end/Cargo.toml @@ -10,3 +10,5 @@ tokio = { version = "1", features = ["full"] } warp = "0.3" serde_json = "1.0" bytes = "1.1" +rusqlite = "0.27.0" +rand = "0.8.4" diff --git a/back_end/src/db_handler.rs b/back_end/src/db_handler.rs new file mode 100644 index 0000000..de42c2b --- /dev/null +++ b/back_end/src/db_handler.rs @@ -0,0 +1,107 @@ +use std::sync::mpsc::{Receiver, SyncSender}; +use std::thread; + +use rand::{thread_rng, Rng}; +use rusqlite::Connection; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum DBFirstRun { + FirstRun, + NotFirstRun, +} + +fn init_conn(sqlite_path: &str, first_run: DBFirstRun) -> Result { + if let Ok(conn) = Connection::open(sqlite_path) { + conn.execute("PRAGMA foreign_keys = ON;", []) + .expect("Should be able to enable \"foreign_keys\""); + let result = conn.execute( + " + CREATE TABLE players (id INTEGER PRIMARY KEY NOT NULL, + date_added TEXT NOT NULL, + game_id INTEGER, + FOREIGN KEY(game_id) REFERENCES games(id) ON DELETE CASCADE); + ", + [], + ); + if result.is_ok() { + if first_run == DBFirstRun::FirstRun { + println!("Created \"players\" table"); + } + } else if first_run == DBFirstRun::FirstRun { + println!("\"players\" table exists"); + } + + let result = conn.execute( + " + CREATE TABLE games (id INTEGER PRIMARY KEY NOT NULL, + cyan_player INTEGER UNIQUE NOT NULL, + magenta_player INTEGER UNIQUE NOT NULL, + date_changed TEXT NOT NULL, + board TEXT NOT NULL, + status INTEGER NOT NULL, + FOREIGN KEY(cyan_player) REFERENCES players (id), + FOREIGN KEY(magenta_player) REFERENCES players (id)); + ", + [], + ); + if result.is_ok() { + if first_run == DBFirstRun::FirstRun { + println!("Created \"games\" table"); + } + } else if first_run == DBFirstRun::FirstRun { + println!("\"games\" table exists"); + } + Ok(conn) + } else { + Err(String::from("Failed to get connection")) + } +} + +pub fn start_db_handler_thread(rx: Receiver>, sqlite_path: String) { + thread::spawn(move || { + // temporarily get conn which should initialize on first setup of db + if let Ok(_conn) = init_conn(&sqlite_path, DBFirstRun::FirstRun) { + } else { + println!("ERROR: Failed init sqlite db connection"); + return; + } + + loop { + let result = rx.recv(); + //println!("db_handler: Got result from rx"); + + if let Ok(player_tx) = result { + //println!("db_handler: Got player_tx from rx"); + // got request to create new player, create new player + let mut player_id: u32 = thread_rng().gen(); + let conn = init_conn(&sqlite_path, DBFirstRun::NotFirstRun) + .expect("DB connection should be available"); + loop { + let mut stmt = conn + .prepare("SELECT id FROM players WHERE id = ?;") + .expect("Should be able to prepare DB statement"); + match stmt.query_row([player_id], |_row| Ok(())) { + Ok(_) => { + player_id = thread_rng().gen(); + } + Err(_) => break, + } + } + conn.execute( + "INSERT INTO players (id, date_added) VALUES (?, datetime());", + [player_id], + ) + .unwrap_or_else(|_| { + panic!("Should be able to insert new player with id {}", player_id) + }); + player_tx + .send(player_id) + .expect("Should be able to send back valid player id"); + } else { + println!("db_handler: Failed to get player_tx"); + } + // Pair up players + // TODO + } // loop end + }); +} diff --git a/back_end/src/json_handlers.rs b/back_end/src/json_handlers.rs index cbccb1b..3d9b6d9 100644 --- a/back_end/src/json_handlers.rs +++ b/back_end/src/json_handlers.rs @@ -1,14 +1,18 @@ +use std::{ + sync::mpsc::{sync_channel, Receiver, SyncSender}, + time::Duration, +}; + use serde_json::Value; -pub fn handle_json(root: Value) -> Result { +pub fn handle_json(root: Value, tx: SyncSender>) -> Result { if let Some(Value::String(type_str)) = root.get("type") { + let (player_tx, player_rx) = sync_channel::(8); match type_str.as_str() { - "pairing_request" => handle_pairing_request(root), + "pairing_request" => handle_pairing_request(tx, player_tx, player_rx), "check_pairing" => handle_check_pairing(root), "place_token" => handle_place_token(root), - "whose_turn" => handle_whose_turn(root), "disconnect" => handle_disconnect(root), - "request_board_state" => handle_request_board_state(root), "game_state" => handle_game_state(root), _ => Err("{\"type\":\"invalid_type\"}".into()), } @@ -17,8 +21,22 @@ pub fn handle_json(root: Value) -> Result { } } -fn handle_pairing_request(root: Value) -> Result { - Err("{\"type\":\"unimplemented\"}".into()) +fn handle_pairing_request( + tx: SyncSender>, + player_tx: SyncSender, + player_rx: Receiver, +) -> Result { + if tx.send(player_tx).is_err() { + return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into()); + } + if let Ok(pid) = player_rx.recv_timeout(Duration::from_secs(5)) { + Ok(format!( + "{{\"type\":\"pairing_response\", \"id\": \"{}\", \"status\": \"waiting\"}}", + pid + )) + } else { + Err("{\"type\":\"pairing_response\", \"status\":\"internal_error_timeout\"}".into()) + } } fn handle_check_pairing(root: Value) -> Result { @@ -29,18 +47,10 @@ fn handle_place_token(root: Value) -> Result { Err("{\"type\":\"unimplemented\"}".into()) } -fn handle_whose_turn(root: Value) -> Result { - Err("{\"type\":\"unimplemented\"}".into()) -} - fn handle_disconnect(root: Value) -> Result { Err("{\"type\":\"unimplemented\"}".into()) } -fn handle_request_board_state(root: Value) -> Result { - Err("{\"type\":\"unimplemented\"}".into()) -} - fn handle_game_state(root: Value) -> Result { Err("{\"type\":\"unimplemented\"}".into()) } diff --git a/back_end/src/main.rs b/back_end/src/main.rs index c4b39da..8b82821 100644 --- a/back_end/src/main.rs +++ b/back_end/src/main.rs @@ -1,22 +1,37 @@ +mod db_handler; mod json_handlers; +const SQLITE_DB_PATH: &str = "./fourLineDropper.db"; + +use std::sync::mpsc::{sync_channel, SyncSender}; + +use db_handler::start_db_handler_thread; use warp::{Filter, Rejection}; #[tokio::main] async fn main() { + let (db_tx, db_rx) = sync_channel::>(32); + let db_tx_clone = db_tx.clone(); + + start_db_handler_thread(db_rx, SQLITE_DB_PATH.into()); + let route = warp::body::content_length_limit(1024 * 32) .and(warp::body::bytes()) - .and_then(|bytes: bytes::Bytes| async move { - let body_str_result = std::str::from_utf8(bytes.as_ref()); - if let Ok(body_str) = body_str_result { - let json_result = serde_json::from_str(body_str); - if let Ok(json_value) = json_result { - Ok(json_handlers::handle_json(json_value).unwrap_or_else(|e| e)) + .and_then(move |bytes: bytes::Bytes| { + let db_tx_clone = db_tx_clone.clone(); + async move { + let body_str_result = std::str::from_utf8(bytes.as_ref()); + if let Ok(body_str) = body_str_result { + let json_result = serde_json::from_str(body_str); + if let Ok(json_value) = json_result { + Ok(json_handlers::handle_json(json_value, db_tx_clone) + .unwrap_or_else(|e| e)) + } else { + Ok(String::from("{\"type\": \"invalid_syntax\"}")) + } } else { - Ok(String::from("{\"type\": \"invalid_syntax\"}")) + Ok::(String::from("{\"type\": \"invalid_syntax\"}")) } - } else { - Ok::(String::from("{\"type\": \"invalid_syntax\"}")) } });