use crate::{
error::{Error, Result},
server,
};
use serde::Deserialize;
use std::{
cell::UnsafeCell,
collections::HashMap,
env,
ffi::OsStr,
fmt::{self, Debug, Formatter},
fs, io,
path::{Path, PathBuf},
};
const DEFAULT_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/generated_config.toml"));
const CONFIG_DIRECTORY_NAME: &str = "mcserver";
const CONFIG_FILE_NAME: &str = "config.toml";
#[derive(Clone, Deserialize, PartialEq)]
pub struct Password(pub String);
impl AsRef<OsStr> for Password {
fn as_ref(&self) -> &OsStr {
OsStr::new(&self.0)
}
}
impl Debug for Password {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "(hidden)")
}
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct RconConfig {
pub server_address: Option<String>,
pub port: Option<u16>,
pub password: Option<Password>,
}
#[derive(Debug, Deserialize)]
#[serde(from = "String")]
pub struct ServersDirectory {
raw_string: String,
expanded: UnsafeCell<Option<PathBuf>>,
}
impl From<String> for ServersDirectory {
fn from(value: String) -> Self {
Self {
raw_string: value,
expanded: UnsafeCell::new(None),
}
}
}
impl ServersDirectory {
pub fn expand(&self) -> Result<&Path> {
unsafe {
if let Some(expanded) = &*(self.expanded.get()) {
return Ok(expanded);
}
let expanded_path = PathBuf::from(shellexpand::full(&self.raw_string)?.into_owned());
Ok((*self.expanded.get()).insert(expanded_path))
}
}
}
#[derive(Debug, Deserialize)]
pub struct Config {
pub default_java_args: String,
pub contact: String,
pub nogui: bool,
pub servers_directory: ServersDirectory,
pub default_server: Option<String>,
pub rcon: Option<HashMap<String, RconConfig>>,
}
pub fn get_directory() -> Result<PathBuf> {
let config_dir = dirs::config_dir()
.ok_or(Error::UnresolvedConfigDirectory)?
.join(CONFIG_DIRECTORY_NAME);
Ok(config_dir)
}
pub fn load_or_create(config_directory: impl AsRef<Path>) -> Result<Config> {
let config_file_path = config_directory.as_ref().join(CONFIG_FILE_NAME);
match fs::read_to_string(&config_file_path) {
Ok(config_str) => Ok(toml::from_str(&config_str)?),
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
fs::create_dir_all(&config_directory)?;
fs::write(&config_file_path, DEFAULT_CONFIG)?;
Ok(toml::from_str(DEFAULT_CONFIG)?)
} else {
Err(Error::Io(err))
}
}
}
}
pub fn get_current_server_directory(servers_dir: impl AsRef<Path>) -> Result<String> {
let mut server_path = env::current_dir()?;
loop {
if fs::exists(server_path.join(server::METADATA_DIRECTORY_NAME))? {
break;
}
server_path = server_path
.parent()
.ok_or(Error::InvalidServersDirectory)?
.to_path_buf();
}
let server = server_path
.strip_prefix(servers_dir)
.map_err(|_| Error::InvalidServersDirectory)?
.to_string_lossy()
.into_owned();
Ok(server)
}
pub fn server_or_current<S>(server: S, config: &Config) -> Result<String>
where
S: Into<String> + for<'a> PartialEq<&'a str>,
{
if server == "." {
get_current_server_directory(config.servers_directory.expand()?)
} else {
Ok(server.into())
}
}
pub fn handle_server_arg(server: Option<String>, config: &Config) -> Result<String> {
match server {
Some(server) => server_or_current(server, config),
None => match &config.default_server {
Some(server) => Ok(server.clone()),
None => Err(Error::NoDefaultServer),
},
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn paths() {
let expected_config_dir =
PathBuf::from(shellexpand::tilde("~/.config/mcserver/").into_owned());
assert_eq!(
&get_directory().expect("Failed to get config directory"),
&expected_config_dir
);
}
#[test]
fn something() {}
}