#![feature(if_let_guard)] use anyhow::{Context, Result}; use axum::{routing::get, Router}; use lib::models::*; use std::{ collections::HashMap, fs::{read_to_string, File}, io::{BufRead, BufReader}, net::SocketAddr, sync::{Arc, RwLock}, }; use tokio::sync::broadcast; use tower_http::services::ServeDir; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; pub mod api; use crate::api::*; /// Parse json for card data fn load_cards_from_json(path: &str) -> Result<(CardPacks, CardPacksMeta)> { let data: String = read_to_string(path).with_context(|| format!("Invalid JSON path: \"{}\"", path))?; let jayson: Vec = serde_json::from_str(&data) .with_context(|| format!("The contents of \"{path}\" is not valid JSON."))?; let mut official: Vec = vec![]; let mut unofficial: Vec = vec![]; let mut official_meta: Vec = vec![]; let mut unofficial_meta: Vec = vec![]; for set in jayson { let mut num_white = 0; let mut num_black = 0; let mut pack: Option = Option::None; if let Some(white) = set.white { num_white = white.len(); if num_white > 0 { pack = Some(white[0].pack) } } if let Some(black) = set.black { num_black = black.len(); if num_black > 0 { pack = Some(black[0].pack) } } let meta = CardPackMeta { name: set.name, pack: pack.expect("No card pack number!"), num_white, num_black, }; if set.official { official_meta.push(meta); } else { unofficial_meta.push(meta); } } tracing::debug!("{} official", official.len()); tracing::debug!("{} official meta", official_meta.len()); tracing::debug!("{} unofficial", unofficial.len()); tracing::debug!("{} unofficial meta", unofficial_meta.len()); tracing::debug!("{:#?}", official_meta[0]); tracing::debug!("{:#?}", unofficial_meta[0]); official.shrink_to_fit(); unofficial.shrink_to_fit(); let packs = CardPacks { official, unofficial, }; official_meta.shrink_to_fit(); unofficial_meta.shrink_to_fit(); let packs_meta = CardPacksMeta { official_meta, unofficial_meta, }; Ok((packs, packs_meta)) } /// Parse name list fn load_names(path: &str) -> Result> { let f = File::open(path).with_context(|| format!("Invalid names path: \"{}\"", path))?; let f = BufReader::new(f); let mut buf = vec![]; for line in f.lines() { buf.push(line?) } Ok(buf) } // Our shared state pub struct AppState { // We require unique usernames. This tracks which usernames have been taken. online_users: RwLock>>>, offline_users: RwLock>>>, // Channel used to send messages to all connected clients. tx: broadcast::Sender, // Master card decks packs: CardPacks, packs_meta: CardPacksMeta, // Games list games: RwLock>, // chatrooms: Mutex>>, first_names: Vec, last_names: Vec, } #[tokio::main] async fn main() -> Result<()> { // stuff for logging tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "server=trace,tower_http=trace,lib=trace".into()), ) .with(tracing_subscriber::fmt::layer()) .init(); // Set up application state for use with with_state(). // Main Broadcast Channel let (tx, _rx) = broadcast::channel(100); let online_users = RwLock::new(HashMap::>>::new()); let offline_users = RwLock::new(HashMap::>>::new()); let (packs, packs_meta) = load_cards_from_json("data/cah-cards-full.json")?; let games = RwLock::new(vec![]); let first_names = load_names("data/first.txt")?; let last_names = load_names("data/last.txt")?; let app_state = Arc::new(AppState { online_users, offline_users, tx, packs, packs_meta, games, first_names, last_names, }); // set routes and apply state let app = Router::new() .route("/websocket", get(websocket_connection_handler)) .nest_service("/", ServeDir::new("dist")) .with_state(app_state); // send it let address = "0.0.0.0:3030"; let listener = tokio::net::TcpListener::bind(address) .await .with_context(|| format!("{} is not a valid bind address.", address))?; tracing::info!("listening on {}", listener.local_addr()?); axum::serve( listener, app.into_make_service_with_connect_info::(), ) .await?; Ok(()) }