mcserver 0.1.10

A command line interface which simplifies minecraft server management with zellij and mcrcon
use crate::{
    config_defs::{DynamicConfig, StaticConfig},
    error::{Error, Result},
};
use std::{
    env, fs,
    ops::Deref,
    path::{Path, PathBuf},
    sync::{Mutex, MutexGuard, OnceLock},
};

include!(concat!(env!("OUT_DIR"), "/generated_cfg.rs"));
pub use generated_cfg::*;

pub struct AutoConfig {
    value: OnceLock<Mutex<DynamicConfig>>,
    initial_value: OnceLock<DynamicConfig>,
}

impl AutoConfig {
    const fn new() -> Self {
        Self {
            value: OnceLock::new(),
            initial_value: OnceLock::new(),
        }
    }

    pub fn write(&self) -> Result<()> {
        let Some(mutex) = self.get() else {
            return Ok(());
        };

        let guard = mutex.lock().map_err(|_| Error::ConfigMutexPoisoned)?;

        if let Some(initial_value) = self.initial_value.get() {
            if *initial_value == *guard {
                return Ok(());
            }
        } else {
            eprintln!("Initial configuration value not set");
        }

        fs::create_dir_all(get_config_directory()?)?;
        fs::write(get_config_file()?, toml::to_string(&*guard)?)?;
        Ok(())
    }
}

impl Deref for AutoConfig {
    type Target = OnceLock<Mutex<DynamicConfig>>;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

pub static CONFIG: AutoConfig = AutoConfig::new();

static CONFIG_DIRECTORY: OnceLock<PathBuf> = OnceLock::new();
static CONFIG_FILE: OnceLock<PathBuf> = OnceLock::new();

static EXPANDED_SERVERS_DIR: OnceLock<PathBuf> = OnceLock::new();

fn get_config_directory() -> Result<&'static Path> {
    if let Some(path) = CONFIG_DIRECTORY.get() {
        return Ok(path.as_path());
    }

    let path = shellexpand::full(STATIC_CONFIG.dynamic_config_path)?;
    Ok(CONFIG_DIRECTORY
        .get_or_init(|| PathBuf::from(&*path))
        .as_path())
}

fn get_config_file() -> Result<&'static Path> {
    if let Some(path) = CONFIG_FILE.get() {
        return Ok(path.as_path());
    }

    let path = get_config_directory()?.join("config.toml");
    Ok(CONFIG_FILE.get_or_init(|| path).as_path())
}

pub fn get_static() -> &'static StaticConfig {
    &STATIC_CONFIG
}

pub fn get() -> Result<MutexGuard<'static, DynamicConfig>> {
    if let Some(mutex) = CONFIG.get() {
        return mutex.lock().map_err(|_| Error::ConfigMutexPoisoned);
    }

    let config_dir = get_config_directory()?;
    let config_file = get_config_file()?;

    let config: DynamicConfig = if config_file.exists() {
        let toml_string = fs::read_to_string(config_file)?;
        toml::from_str(&toml_string)?
    } else {
        fs::create_dir_all(config_dir)?;
        let config = get_default_dynamic_config();
        fs::write(config_file, toml::to_string(config)?)?;
        config.clone()
    };

    CONFIG.initial_value.get_or_init(|| config.clone());

    CONFIG
        .get_or_init(|| Mutex::new(config))
        .lock()
        .map_err(|_| Error::ConfigMutexPoisoned)
}

pub fn get_expanded_servers_dir() -> Result<&'static Path> {
    if let Some(dir) = EXPANDED_SERVERS_DIR.get() {
        return Ok(dir.as_path());
    }

    let config = get()?;
    let dir = shellexpand::full(&config.servers_directory)?;
    Ok(EXPANDED_SERVERS_DIR
        .get_or_init(|| PathBuf::from(&*dir))
        .as_path())
}

pub fn get_current_server_directory() -> Result<String> {
    let servers_dir = get_expanded_servers_dir()?;
    let current_dir = env::current_dir()?;

    if !current_dir.starts_with(servers_dir) {
        return Err(Error::InvalidServersDirectory);
    }

    let server = current_dir
        .strip_prefix(servers_dir)?
        .components()
        .next()
        .ok_or(Error::NoServerChild)?
        .as_os_str()
        .to_string_lossy()
        .to_string();

    Ok(server)
}

pub fn get_default_server_owned() -> Result<Option<String>> {
    Ok(get()?.default_server.clone())
}

pub fn server_or_current<S>(server: S) -> Result<String>
where
    S: Into<String> + for<'a> PartialEq<&'a str>,
{
    if server == "." {
        get_current_server_directory()
    } else {
        Ok(server.into())
    }
}

#[macro_export]
macro_rules! unwrap_server_or_default {
    ($server:expr) => {
        (|| -> Result<String> {
            use $crate::{
                config::{get_default_server_owned, server_or_current},
                error::Error,
            };

            let server = match $server {
                Some(server) => server,
                None => get_default_server_owned()
                    .wrap_err("Failed to get configuration")?
                    .ok_or(Error::NoDefaultServer)?,
            };

            Ok(server_or_current(server)?)
        })()
    };
}