2022-03-18 10:29:38 +00:00
|
|
|
use std::sync::mpsc::{Receiver, SyncSender};
|
|
|
|
use std::thread;
|
|
|
|
|
|
|
|
use rand::{thread_rng, Rng};
|
|
|
|
use rusqlite::Connection;
|
|
|
|
|
2022-03-29 06:02:20 +00:00
|
|
|
pub type GetIDSenderType = (u32, Option<bool>);
|
|
|
|
|
2022-03-28 07:31:53 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum DBHandlerRequest {
|
2022-03-29 06:02:20 +00:00
|
|
|
GetID(SyncSender<GetIDSenderType>),
|
2022-03-28 07:31:53 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 10:29:38 +00:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
|
|
enum DBFirstRun {
|
|
|
|
FirstRun,
|
|
|
|
NotFirstRun,
|
|
|
|
}
|
|
|
|
|
2022-03-29 06:02:20 +00:00
|
|
|
struct DBHandler {
|
|
|
|
rx: Receiver<DBHandlerRequest>,
|
|
|
|
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 = init_conn(&self.sqlite_path, 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 send_result = player_tx.send((player_id, None));
|
|
|
|
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::GetID(player_tx)
|
|
|
|
} // match db_request
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 10:29:38 +00:00
|
|
|
fn init_conn(sqlite_path: &str, first_run: DBFirstRun) -> Result<Connection, String> {
|
|
|
|
if let Ok(conn) = Connection::open(sqlite_path) {
|
|
|
|
conn.execute("PRAGMA foreign_keys = ON;", [])
|
2022-03-18 14:43:15 +00:00
|
|
|
.map_err(|e| format!("Should be able to handle \"foreign_keys\": {:?}", e))?;
|
2022-03-18 10:29:38 +00:00
|
|
|
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,
|
2022-03-28 08:13:32 +00:00
|
|
|
cyan_player INTEGER UNIQUE,
|
|
|
|
magenta_player INTEGER UNIQUE,
|
2022-03-28 08:10:49 +00:00
|
|
|
date_added TEXT NOT NULL,
|
2022-03-18 10:29:38 +00:00
|
|
|
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 {
|
2022-03-18 14:43:15 +00:00
|
|
|
Err(String::from("Failed to open connection"))
|
2022-03-18 10:29:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 14:43:15 +00:00
|
|
|
pub fn start_db_handler_thread(
|
2022-03-28 07:31:53 +00:00
|
|
|
rx: Receiver<DBHandlerRequest>,
|
2022-03-18 14:43:15 +00:00
|
|
|
sqlite_path: String,
|
|
|
|
shutdown_tx: SyncSender<()>,
|
|
|
|
) {
|
2022-03-29 06:02:20 +00:00
|
|
|
let mut handler = DBHandler {
|
|
|
|
rx,
|
|
|
|
sqlite_path,
|
|
|
|
shutdown_tx,
|
|
|
|
};
|
2022-03-18 10:29:38 +00:00
|
|
|
thread::spawn(move || {
|
|
|
|
// temporarily get conn which should initialize on first setup of db
|
2022-03-29 06:02:20 +00:00
|
|
|
if let Ok(_conn) = init_conn(&handler.sqlite_path, DBFirstRun::FirstRun) {
|
2022-03-18 10:29:38 +00:00
|
|
|
} else {
|
|
|
|
println!("ERROR: Failed init sqlite db connection");
|
2022-03-29 06:02:20 +00:00
|
|
|
handler.shutdown_tx.send(()).ok();
|
2022-03-18 10:29:38 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-18 14:43:15 +00:00
|
|
|
'outer: loop {
|
2022-03-29 06:02:20 +00:00
|
|
|
if handler.handle_request() {
|
|
|
|
handler.shutdown_tx.send(()).ok();
|
|
|
|
break 'outer;
|
2022-03-18 10:29:38 +00:00
|
|
|
}
|
2022-03-29 06:02:20 +00:00
|
|
|
}
|
2022-03-18 10:29:38 +00:00
|
|
|
});
|
|
|
|
}
|