use crate::config::{read_file, Args, Config}; use clap::Arg; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use std::ffi::OsStr; use std::io::Write; use std::{fs::File, path::Path, path::PathBuf}; use tracing::{error, trace, warn}; pub static DEFAULT_SERVER_ARGS: Lazy = Lazy::new(ServerArgs::default); /// The main server configuration struct. #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] #[serde(default)] pub struct ServerConfig { #[serde(rename = "version-string")] pub version: String, pub port: u16, pub max_players: usize, pub motd: String, pub server_icon: PathBuf, #[serde(skip)] pub server_icon_bytes: Vec, } impl Default for ServerConfig { fn default() -> Self { ServerConfig { version: Config::get_formatted_version(crate::config::Subcommand::Server), port: 25565, max_players: 20, motd: "Hello world!".to_owned(), server_icon: PathBuf::from("server-icon.png"), server_icon_bytes: include_bytes!("../server-icon.png").to_vec(), } } } impl ServerConfig { pub fn instance() -> &'static Self { &Config::instance().server } /// Load the server icon. pub fn load_icon(&mut self) { self.server_icon = ServerArgs::instance() .as_ref() .map(|s| s.server_icon.clone()) .unwrap_or(DEFAULT_SERVER_ARGS.server_icon.clone()); let server_icon_path = Path::new(&self.server_icon); if server_icon_path.exists() { if let Ok(server_icon_bytes) = read_file(server_icon_path) { self.server_icon_bytes = server_icon_bytes; } else { warn!("Could not read server icon file, using default"); } } else { warn!( "Server icon file does not exist, creating {}", server_icon_path.to_str().unwrap_or("") ); self.write_server_icon(server_icon_path); } } pub fn write_server_icon(&self, path: &Path) { trace!("ServerConfig.write_server_icon()"); if let Ok(mut file) = File::options() .write(true) .create(true) .truncate(true) .open(path) { if file.write_all(&self.server_icon_bytes).is_ok() { return; } } error!("Could not write server icon file"); std::process::exit(1); } } #[derive(Debug)] pub struct ServerArgs { pub server_icon: PathBuf, } impl Default for ServerArgs { fn default() -> Self { let config = Config::default(); ServerArgs { server_icon: config.server.server_icon, } } } impl ServerArgs { pub fn instance() -> Option<&'static Self> { Args::instance().server.as_ref() } pub fn command() -> clap::Command { clap::Command::new("server") .about("Run composition in server mode") .arg( Arg::new("server-icon") .long("server-icon") .help("Server icon file path") .value_hint(clap::ValueHint::FilePath) .default_value(OsStr::new(&DEFAULT_SERVER_ARGS.server_icon)), ) } pub fn parse(m: clap::ArgMatches) -> Self { let mut server_args = ServerArgs::default(); server_args.server_icon = m .get_one::("server-icon") .map_or(server_args.server_icon, PathBuf::from); server_args } }