use std::sync::mpsc::{Receiver, SyncSender}; use std::thread; use rand::{thread_rng, Rng}; use rusqlite::{params, Connection}; pub type GetIDSenderType = (u32, Option); /// first bool is player exists, /// second bool is if paired, /// third bool is if cyan player pub type CheckPairingType = (bool, bool, bool); #[derive(Clone, Debug)] pub enum DBHandlerRequest { GetID(SyncSender), CheckPairing { id: u32, response_sender: SyncSender, }, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum DBFirstRun { FirstRun, NotFirstRun, } struct DBHandler { rx: Receiver, sqlite_path: String, shutdown_tx: SyncSender<()>, } impl DBHandler { /// Returns true if should break out of outer loop fn handle_request(&mut self) -> bool { let rx_recv_result = self.rx.recv(); if let Err(e) = rx_recv_result { println!("Failed to get DBHandlerRequest: {:?}", e); self.shutdown_tx.send(()).ok(); return false; } let db_request = rx_recv_result.unwrap(); match db_request { DBHandlerRequest::GetID(player_tx) => { // got request to create new player, create new player let mut player_id: u32 = thread_rng().gen(); let conn_result = self.get_conn(DBFirstRun::NotFirstRun); if let Err(e) = conn_result { println!("Failed to get sqlite db connection: {:?}", e); self.shutdown_tx.send(()).ok(); return false; } let conn = conn_result.unwrap(); loop { let stmt_result = conn.prepare("SELECT id FROM players WHERE id = ?;"); if let Err(e) = stmt_result { println!("Failed to create sqlite statement: {:?}", e); self.shutdown_tx.send(()).ok(); return true; } let mut stmt = stmt_result.unwrap(); match stmt.query_row([player_id], |_row| Ok(())) { Ok(_) => { player_id = thread_rng().gen(); } Err(_) => break, } } let insert_result = conn.execute( "INSERT INTO players (id, date_added) VALUES (?, datetime());", [player_id], ); if let Err(e) = insert_result { println!("Failed to insert into sqlite db: {:?}", e); self.shutdown_tx.send(()).ok(); return true; } let pair_up_result = self.pair_up_players(Some(&conn)); if let Err(e) = pair_up_result { println!("Failed to pair up players: {}", e); return true; } // Check if current player has been paired let mut is_cyan_player_opt: Option = None; let check_player_row = conn.query_row("SELECT games.cyan_player FROM players JOIN games WHERE games.id = players.game_id AND players.id = ?;", [player_id], |row| row.get::(0)); if let Ok(cyan_player) = check_player_row { if cyan_player == player_id { // is paired, is cyan_player is_cyan_player_opt = Some(true); } else { // is paired, not cyan_player is_cyan_player_opt = Some(false); } } else if check_player_row.is_err() { // not paired, can do nothing here } let send_result = player_tx.send((player_id, is_cyan_player_opt)); if let Err(e) = send_result { println!("Failed to send back player id: {:?}", e); self.shutdown_tx.send(()).ok(); return true; } send_result.unwrap(); } DBHandlerRequest::CheckPairing { id, response_sender, } => { let check_result = self.check_if_player_is_paired(id); if let Ok((exists, is_paired, is_cyan)) = check_result { let send_result = response_sender.send((exists, is_paired, is_cyan)); if let Err(e) = send_result { println!("Failed to send back check pairing status: {:?}", e); self.shutdown_tx.send(()).ok(); return true; } send_result.unwrap(); } else { } } // DBHandlerRequest::GetID(player_tx) } // match db_request false } fn get_conn(&self, first_run: DBFirstRun) -> Result { if let Ok(conn) = Connection::open(&self.sqlite_path) { conn.execute("PRAGMA foreign_keys = ON;", []) .map_err(|e| format!("Should be able to handle \"foreign_keys\": {:?}", e))?; 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, magenta_player INTEGER UNIQUE, date_added 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 open connection")) } } fn pair_up_players(&self, conn: Option<&Connection>) -> Result<(), String> { if let Some(conn) = conn { let mut to_pair: Option = None; let mut unpaired_players_stmt = conn .prepare("SELECT id FROM players WHERE game_id ISNULL ORDER BY date_added;") .map_err(|e| format!("{:?}", e))?; let mut unpaired_players_rows = unpaired_players_stmt .query([]) .map_err(|e| format!("{:?}", e))?; while let Some(row) = unpaired_players_rows .next() .map_err(|e| format!("{:?}", e))? { if to_pair.is_none() { to_pair = Some(row.get(0).map_err(|e| format!("{:?}", e))?); } else { let players: [u32; 2] = [ to_pair.take().unwrap(), row.get(0).map_err(|e| format!("{:?}", e))?, ]; self.create_game(Some(conn), &players)?; } } Ok(()) } else { let conn = self.get_conn(DBFirstRun::NotFirstRun)?; self.pair_up_players(Some(&conn)) } } fn create_game(&self, conn: Option<&Connection>, players: &[u32; 2]) -> Result { if let Some(conn) = conn { let mut game_id: u32 = thread_rng().gen(); { let mut get_game_stmt = conn .prepare("SELECT id FROM games WHERE id = ?;") .map_err(|e| format!("{:?}", e))?; while get_game_stmt.query_row([game_id], |_row| Ok(())).is_ok() { game_id = thread_rng().gen(); } } // TODO randomize players (or first-come-first-serve ok to do?) conn.execute( "INSERT INTO games (id, cyan_player, magenta_player, date_added, board, status) VALUES (?, ?, ?, datetime(), ?, 0);", params![game_id, players[0], players[1], new_board()] ) .map_err(|e| format!("{:?}", e))?; conn.execute( "UPDATE players SET game_id = ? WHERE id = ?", [game_id, players[0]], ) .map_err(|e| format!("{:?}", e))?; conn.execute( "UPDATE players SET game_id = ? WHERE id = ?", [game_id, players[1]], ) .map_err(|e| format!("{:?}", e))?; Ok(game_id) } else { let conn = self.get_conn(DBFirstRun::NotFirstRun)?; self.create_game(Some(&conn), players) } } fn check_if_player_is_paired(&self, player_id: u32) -> Result { { let player_exists_result = self.check_if_player_exists(player_id); if player_exists_result.is_err() || !player_exists_result.unwrap() { // player doesn't exist return Ok((false, false, true)); } } let conn = self.get_conn(DBFirstRun::NotFirstRun)?; let check_player_row = conn.query_row("SELECT games.cyan_player FROM players JOIN games where games.id = players.game_id AND players.id = ?;", [player_id], |row| row.get::(0)); if let Ok(cyan_player) = check_player_row { if cyan_player == player_id { // is cyan player Ok((true, true, true)) } else { // is magenta player Ok((true, true, false)) } } else if let Err(rusqlite::Error::QueryReturnedNoRows) = check_player_row { // is not paired Ok((true, false, true)) } else if let Err(e) = check_player_row { Err(format!("check_if_player_is_paired: {:?}", e)) } else { unreachable!(); } } fn check_if_player_exists(&self, player_id: u32) -> Result { let conn = self.get_conn(DBFirstRun::NotFirstRun)?; let check_player_row = conn.query_row("SELECT id FROM players WHERE id = ?;", [player_id], |row| { row.get::(0) }); if let Ok(_id) = check_player_row { Ok(true) } else { Ok(false) } } } pub fn start_db_handler_thread( rx: Receiver, sqlite_path: String, shutdown_tx: SyncSender<()>, ) { let mut handler = DBHandler { rx, sqlite_path, shutdown_tx, }; thread::spawn(move || { // temporarily get conn which should initialize on first setup of db if let Ok(_conn) = handler.get_conn(DBFirstRun::FirstRun) { } else { println!("ERROR: Failed init sqlite db connection"); handler.shutdown_tx.send(()).ok(); return; } 'outer: loop { if handler.handle_request() { handler.shutdown_tx.send(()).ok(); break 'outer; } } }); } fn new_board() -> String { let mut board = String::with_capacity(56); for _i in 0..56 { board.push('a'); } board }