Improve configuration file parsing, add log_level to config, log server version, and move mctypes into crate::net

This commit is contained in:
Garen Tyler 2022-04-19 13:05:17 -06:00
parent 4d928973a6
commit 4849a7903d
No known key found for this signature in database
GPG Key ID: E3BF83D66394FD92
12 changed files with 170 additions and 44 deletions

10
Cargo.lock generated
View File

@ -117,6 +117,7 @@ dependencies = [
"radix64",
"serde",
"serde_json",
"substring",
"tokio",
"toml",
"uuid",
@ -539,6 +540,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "substring"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86"
dependencies = [
"autocfg",
]
[[package]]
name = "syn"
version = "1.0.91"

View File

@ -5,6 +5,7 @@ edition = "2021"
license = "MIT"
name = "composition"
version = "0.1.0"
build = "build.rs"
[dependencies]
async-trait = "0.1.48"
@ -21,6 +22,7 @@ serde_json = "1.0.59"
tokio = {version = "1", features = ["full"]}
toml = "0.5"
uuid = "0.8.2"
substring = "1.4.5"
# colorful = "0.2.1"
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)

9
build.rs Normal file
View File

@ -0,0 +1,9 @@
use std::process::Command;
fn main() {
if let Ok(output) = Command::new("git").args(&["rev-parse", "HEAD"]).output() {
let git_hash = String::from_utf8_lossy(&output.stdout).to_string();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
} else {
println!("cargo:rustc-env=GIT_HASH=00000000");
}
}

View File

@ -2,3 +2,4 @@ favicon = "server-icon.png"
max_players = 20
motd = "Hello world!"
port = 25565
log_level = "debug"

138
src/config.rs Normal file
View File

@ -0,0 +1,138 @@
use crate::prelude::*;
pub struct Config {
pub port: u16,
pub max_players: usize,
pub motd: String,
pub favicon: String,
pub server_string: String,
pub log_level: log::LevelFilter,
pub server_version: String,
}
impl Default for Config {
fn default() -> Self {
let server_version = format!(
"composition/{} ({})",
env!("CARGO_PKG_VERSION"),
env!("GIT_HASH").substring(0, 8)
);
Config {
port: 25565,
max_players: 20,
motd: "Hello world!".to_owned(),
favicon: "server-icon.png".to_owned(),
server_string: server_version.clone(),
log_level: if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
},
server_version,
}
}
}
impl Config {
pub fn from_file(filename: &str) -> Config {
let read_file = |filename: &str| -> std::io::Result<String> {
use std::{fs::File, io::prelude::*};
let mut data = String::new();
let mut file = File::open(filename)?;
file.read_to_string(&mut data)?;
Ok(data)
};
if let Ok(config) = read_file(filename) {
Config::parse(&config)
} else {
Config::default()
}
}
pub fn parse(data: &str) -> Config {
let mut config = Config::default();
let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String {
if let Some(s) = cfg.get(field) {
if let Some(s) = s.as_str() {
return s.to_owned();
} else {
warn!("{}", error);
}
} else {
warn!("{}", error);
}
default.to_owned()
};
if let Ok(cfg) = data.parse::<toml::Value>() {
if let Some(&toml::Value::Integer(port)) = cfg.get("port") {
if port < u16::MIN as i64 || port > u16::MAX as i64 {
warn!("Config port must be an integer in the range of {}-{}, using default port: {}", u16::MIN, u16::MAX, config.port);
} else {
config.port = port as u16;
}
} else {
warn!(
"Config port must be an integer in the range of {}-{}, using default port: {}",
u16::MIN,
u16::MAX,
config.port
);
}
if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") {
if max_players < 0 {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
} else {
config.max_players = max_players as usize;
}
} else {
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
}
config.motd = get_string(
&cfg,
"motd",
&config.motd,
&format!(
"Config motd must be a string, using default motd: \"{}\"",
config.motd
),
);
config.favicon = get_string(
&cfg,
"favicon",
&config.favicon,
&format!(
"Config favicon must be a string, using default favicon: \"{}\"",
config.favicon
),
);
let default_log_level = format!("{}", config.log_level).to_ascii_lowercase();
config.log_level = match &get_string(
&cfg,
"log_level",
&default_log_level,
&format!(
"Config log_level must be a string, using default log_level: {}",
default_log_level
),
)[..]
{
"off" => log::LevelFilter::Off,
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => {
warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace");
config.log_level
}
};
config
} else {
warn!("Could not parse configuration file, using default");
config
}
}
}

View File

@ -1,50 +1,16 @@
#[macro_use]
extern crate lazy_static;
pub mod mctypes;
pub mod config;
pub mod net;
pub mod server;
use crate::prelude::*;
use std::sync::mpsc::{self, Receiver};
#[derive(Serialize, Deserialize)]
pub struct Config {
pub port: u16,
pub max_players: usize,
pub motd: String,
pub favicon: String,
}
pub static PROTOCOL_VERSION: i32 = 757;
lazy_static! {
pub static ref CONFIG: Config = {
let config_from_file = || -> std::io::Result<Config> {
use std::{fs::File, io::prelude::*};
let mut data = String::new();
let mut file = File::open("composition.toml")?;
file.read_to_string(&mut data)?;
if let Ok(c) = toml::from_str::<Config>(&data) {
Ok(c)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Could not parse toml",
))
}
};
if let Ok(c) = config_from_file() {
c
} else {
warn!("Could not load config from file, using default");
Config {
port: 25565,
max_players: 20,
motd: "Hello world!".to_owned(),
favicon: "server-icon.png".to_owned(),
}
}
};
pub static ref CONFIG: Config = Config::from_file("composition.toml");
pub static ref FAVICON: std::io::Result<Vec<u8>> = {
use std::{fs::File, io::prelude::*};
let mut data = vec![];
@ -70,15 +36,12 @@ pub fn init() -> Receiver<()> {
message = message,
))
})
.level(if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
})
.level(log::LevelFilter::Trace)
.chain(std::io::stdout())
.chain(fern::log_file("output.log").unwrap())
.apply()
.unwrap();
log::set_max_level(CONFIG.log_level);
// Set up the ctrl-c handler.
let (ctrlc_tx, ctrlc_rx) = mpsc::channel();
ctrlc::set_handler(move || {
@ -94,7 +57,7 @@ pub async fn start_server() -> server::Server {
}
pub mod prelude {
pub use crate::{mctypes::*, CONFIG, FAVICON, PROTOCOL_VERSION, START_TIME};
pub use crate::{config::Config, CONFIG, FAVICON, PROTOCOL_VERSION, START_TIME};
pub use log::*;
pub use serde::{Deserialize, Serialize};
pub use serde_json::json;
@ -102,6 +65,7 @@ pub mod prelude {
pub type JSON = serde_json::Value;
pub type NBT = fastnbt::Value;
pub use std::collections::VecDeque;
pub use substring::Substring;
pub use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[derive(Clone, Debug, PartialEq)]
pub enum ParseError {

View File

@ -5,7 +5,7 @@ use std::time::Duration;
#[tokio::main]
pub async fn main() {
let ctrlc_rx = composition::init();
info!("Starting server...");
info!("Starting {}", composition::CONFIG.server_version);
let mut server = composition::start_server().await;
info!("Done! Start took {:?}", composition::START_TIME.elapsed());

View File

@ -1,6 +1,8 @@
pub mod mctypes;
pub mod packets;
use crate::prelude::*;
use mctypes::*;
pub use packets::Packet;
use std::time::Instant;
use tokio::net::TcpStream;

View File

@ -1,4 +1,4 @@
use super::NetworkClientState;
use super::{mctypes::*, NetworkClientState};
use crate::prelude::*;
#[derive(Clone, PartialEq, Debug)]