diff options
author | stale <redkugelblitzin@gmail.com> | 2022-07-01 20:53:11 -0300 |
---|---|---|
committer | stale <redkugelblitzin@gmail.com> | 2022-07-01 20:53:11 -0300 |
commit | fe851ee7ea31caa48a6d9a8ce1ddbb30854f2321 (patch) | |
tree | 27a5f19799d7c3ba654ea09ac3eb41ef7d9dfde0 /src | |
parent | c68dc16bef476203a4424c4b857f16eeb8f0e119 (diff) |
more config options and a good refactoring
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 205 | ||||
-rw-r--r-- | src/types.rs | 20 |
2 files changed, 117 insertions, 108 deletions
diff --git a/src/main.rs b/src/main.rs index 07ba00e..abb2e48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,11 @@ use std::{ sync::Arc, collections::HashMap, num::NonZeroUsize, + path::PathBuf, }; use futures::stream::StreamExt; +use tokio::sync::RwLock; +use serde::Deserialize; mod types; mod livepos; @@ -13,42 +16,47 @@ mod conn; mod minesweeper; use types::*; -use tokio::sync::RwLock; - -const FONT_FILE: &[u8] = include_bytes!("../assets/VT323-Regular.ttf"); const CONF_FILE: &str = "./conf.json"; -fn main() -> Result<(), Box<dyn Error>> { - let conf = Config { - cert: "./cert.pem".to_owned(), - pkey: "./cert.rsa".to_owned(), - index_pg: "./assets/index.html".to_owned(), - room_pg: "./assets/room.html".to_owned(), - client_code: "./assets/client.js".to_owned(), - stylesheet: "./assets/style.css".to_owned(), - socket_addr: ([0,0,0,0],31235).into(), - }; +#[derive(Deserialize)] +struct ConfPaths { + pub cert: PathBuf, + pub pkey: PathBuf, + pub assets: PathBuf, + pub index_page: PathBuf, + pub room_page: PathBuf, +} +#[derive(Deserialize)] +struct ConfServer { + pub listen_on: SocketAddr, +} +#[derive(Deserialize)] +struct ConfLimits { + pub board_area: usize, + pub room_slots: usize, + pub form_size: u64, +} +#[derive(Deserialize)] +struct Conf { + pub paths: ConfPaths, + pub server: ConfServer, + pub limits: ConfLimits, +} +fn main() -> Result<(), Box<dyn Error>> { + let conf: Conf = serde_json::from_str(&std::fs::read_to_string(CONF_FILE)?)?; tokio_main(conf) } #[tokio::main] -async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> { - let conf_file: serde_json::Value = serde_json::from_str(&tokio::fs::read_to_string(CONF_FILE).await?)?; - let area_limit: usize = conf_file.get("area_limit") - .expect("no area_limit field in the conf.json file") - .as_u64().expect("area_limit not a number") as usize; - let room_limit: usize = conf_file.get("room_limit") - .expect("no room_limit field in the conf.json file") - .as_u64().expect("room_limit not a number") as usize; +async fn tokio_main(conf: Conf) -> Result<(), Box<dyn Error>> { + let conf = Arc::new(conf); let rooms: RoomMap = Arc::new(RwLock::new(HashMap::new())); let public_rooms = Arc::new(RwLock::new(HashMap::new())); use warp::*; - let index = path::end().and(fs::file(conf.index_pg.clone())); - let style = path!("s.css").and(fs::file(conf.stylesheet.clone())); - let code = path!("c.js").and(fs::file(conf.client_code.clone())); - let font = path!("f.ttf").map(|| FONT_FILE); + let index = path::end().and(fs::file(conf.paths.index_page.clone())); + let assets = any().and(fs::dir(conf.paths.assets.clone())); let listing = { let rooms = rooms.clone(); let pubs = public_rooms.clone(); @@ -77,31 +85,30 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> { }; let roomspace = { let rooms = rooms.clone(); + let conf = conf.clone(); path!("rspace").and_then(move || { let r = rooms.clone(); + let conf = conf.clone(); async move { let empty_len = empty_rooms(r.clone()).await.len(); - let space = room_limit - r.read().await.len() + empty_len; - Ok::<_,std::convert::Infallible>( - hyper::Response::builder() - .status(hyper::StatusCode::OK) - .body(hyper::Body::from(space.to_string())) - .unwrap() - ) + let space = conf.limits.room_slots - r.read().await.len() + empty_len; + Ok::<String, std::convert::Infallible>(space.to_string()) } }) }; let rform_recv = { let rooms = rooms.clone(); let pubs = public_rooms.clone(); - post().and(path("r")).and(body::content_length_limit(4096)).and(body::form()) + let conf = conf.clone(); + post().and(path("r")).and(body::content_length_limit(conf.limits.form_size)).and(body::form()) .and_then(move |rinfo: HashMap<String, String>| { println!("{:?}", rinfo); let rooms = rooms.clone(); let pubs = pubs.clone(); + let conf = conf.clone(); async move { - let slots_available = room_limit - rooms.read().await.len(); + let slots_available = conf.limits.room_slots - rooms.read().await.len(); let empty = empty_rooms(rooms.clone()).await; if slots_available < 1 { if slots_available + empty.len() > 0 { @@ -111,61 +118,26 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> { } } - if let (Some(w),Some(h),Some(num),Some(denom),access,asfm,limit) = ( - rinfo.get("rwidth").and_then(|wt| wt.parse::<NonZeroUsize>().ok()), - rinfo.get("rheight").and_then(|ht| ht.parse::<NonZeroUsize>().ok()), - rinfo.get("rration").and_then(|nt| nt.parse::<usize>().ok()), - rinfo.get("rratiod").and_then(|dt| dt.parse::<NonZeroUsize>().ok()), - rinfo.get("raccess"), - rinfo.get("ralwayssafe1move"), - rinfo.get("rlimit").and_then(|l| l.parse::<usize>().ok()), - ) { - if w.get()*h.get() > area_limit { - return Err(reject::custom(BoardTooBig)) - } - let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm.is_some() }; - let mut rooms = rooms.write().await; - let uid = types::RoomId::new_in(&rooms); - let name = { - let n = rinfo.get("rname").unwrap().to_owned(); - if n.is_empty() { uid.to_string() } else { n } - }; - - let players = PlayerMap::default(); - - let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); - let game_handle = tokio::spawn(gameloop(cmd_rx, players.clone(), board_conf)); + let mut rooms = rooms.write().await; + let uid = RoomId::new_among(rooms.keys()); - let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel(); - let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx)); - - let room_conf = RoomConf { - name, - player_cap: match limit { Some(i) => i, None => usize::MAX }, - public: access.is_some(), - board_conf, - }; - let new_room = Room { - conf: room_conf, - players, - game_driver: game_handle, - cmd_stream: cmd_tx, - livepos_driver: livepos_handle, - pos_stream: pos_tx, - }; - if access.is_some() { - pubs.write().await.insert(uid.clone(), serde_json::to_string(&new_room.conf).unwrap()); - } - rooms.insert(uid.clone(), Arc::new(RwLock::new(new_room))); + match room_from_form(uid.clone(), &rinfo, &conf) { + Ok((room, access)) => { + if access { + pubs.write().await.insert(uid.clone(), serde_json::to_string(&room.conf).unwrap()); + } + rooms.insert(uid.clone(), Arc::new(RwLock::new(room))); - Ok( - hyper::Response::builder() - .status(hyper::StatusCode::SEE_OTHER) - .header(hyper::header::LOCATION, format!("./room/{uid}")) - .body(hyper::Body::empty()) - .unwrap() - ) - } else { Err(reject::custom(BadFormData)) } + Ok( + hyper::Response::builder() + .status(hyper::StatusCode::SEE_OTHER) + .header(hyper::header::LOCATION, format!("./room/{uid}")) + .body(hyper::Body::empty()) + .unwrap() + ) + }, + Err(e) => Err(e), + } } }) }; @@ -197,7 +169,7 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> { } }) .or(prefix.and(path::end()) - .and(fs::file(conf.room_pg.clone())) + .and(fs::file(conf.paths.room_page.clone())) .then(move |id: String, f: fs::File| { let rooms = rooms_lobby.clone(); async move { @@ -214,27 +186,26 @@ async fn tokio_main(conf: Config) -> Result<(), Box<dyn Error>> { let route = get() .and(index) - .or(style) - .or(code) - .or(font) .or(listing) .or(roomspace) .or(rform_recv) .or(room) + .or(assets) .recover(error_handler); let server = warp::serve(route) .tls() - .cert_path(conf.cert) - .key_path(conf.pkey) - .run(conf.socket_addr); - println!("Serving on {}", conf.socket_addr); + .cert_path(conf.paths.cert.clone()) + .key_path(conf.paths.pkey.clone()) + .run(conf.server.listen_on); + println!("Serving on {}", conf.server.listen_on); server.await; Ok(()) } // If a move is made, broadcast new board, else just send current board async fn gameloop(mut move_rx: tokio::sync::mpsc::UnboundedReceiver<MetaMove>, players: PlayerMapData, bconf: minesweeper::BoardConf) { + // FIXME: push new board if and only if there aren't any remaining commands in the queue use minesweeper::*; use flate2::{ Compression, write::DeflateEncoder }; use std::io::Write; @@ -315,6 +286,50 @@ async fn empty_rooms(rooms: RoomMap) -> Vec<RoomId> { .collect::<Vec<RoomId>>().await } +fn room_from_form(uid: RoomId, rinfo: &HashMap<String,String>, conf: &Conf) -> Result<(types::Room, bool), Rejection> { + if let (Some(w),Some(h),Some(num),Some(denom),access,asfm,limit) = ( + rinfo.get("rwidth").and_then(|wt| wt.parse::<NonZeroUsize>().ok()), + rinfo.get("rheight").and_then(|ht| ht.parse::<NonZeroUsize>().ok()), + rinfo.get("rration").and_then(|nt| nt.parse::<usize>().ok()), + rinfo.get("rratiod").and_then(|dt| dt.parse::<NonZeroUsize>().ok()), + rinfo.get("raccess"), + rinfo.get("ralwayssafe1move"), + rinfo.get("rlimit").and_then(|l| l.parse::<usize>().ok()), + ) { + if w.get()*h.get() > conf.limits.board_area { + return Err(warp::reject::custom(BoardTooBig)) + } + let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm.is_some() }; + let name = { + let n = rinfo.get("rname").unwrap().to_owned(); + if n.is_empty() { uid.to_string() } else { n } + }; + + let players = PlayerMap::default(); + + let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); + let game_handle = tokio::spawn(gameloop(cmd_rx, players.clone(), board_conf)); + + let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel(); + let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx)); + + let room_conf = RoomConf { + name, + player_cap: match limit { Some(i) => i, None => usize::MAX }, + public: access.is_some(), + board_conf, + }; + Ok((Room { + conf: room_conf, + players, + game_driver: game_handle, + cmd_stream: cmd_tx, + livepos_driver: livepos_handle, + pos_stream: pos_tx, + }, access.is_some())) + } else { Err(warp::reject::custom(BadFormData)) } +} + async fn remove_room<T>(rooms: RoomMap, pubs: Arc<RwLock<HashMap<RoomId,T>>>, id: RoomId) { { let mut rwl = rooms.write().await; diff --git a/src/types.rs b/src/types.rs index f9f166a..7cdd44c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -14,17 +14,6 @@ use serde::Serialize; use crate::minesweeper; use crate::livepos; -#[derive(Debug, Clone)] -pub struct Config { - pub cert: String, - pub pkey: String, - pub index_pg: String, - pub room_pg: String, - pub client_code: String, - pub stylesheet: String, - pub socket_addr: SocketAddr, -} - #[derive(Debug, Serialize, Clone)] pub struct RoomConf { pub name: String, @@ -83,14 +72,19 @@ impl std::borrow::Borrow<str> for RoomId { } impl RoomId { - pub fn new_in<T>(map: &HashMap<RoomId, T>) -> Self { + pub fn new_among<'a, I>(existing: I) -> Self + where + I: IntoIterator<Item = &'a RoomId>, + <I as IntoIterator>::IntoIter: Clone, + { use rand::{ thread_rng, Rng, distributions::Alphanumeric }; let id = RoomId(thread_rng() .sample_iter(&Alphanumeric) .take(16) .map(char::from) .collect::<String>()); - if map.contains_key(&id) { RoomId::new_in(map) } + let existing = existing.into_iter(); + if existing.clone().any(|x| *x == id) { Self::new_among(existing) } else { id } } } |