use crate::DEFAULT_PORT; use log::*; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fs; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::{Path, PathBuf}; use toml::value::Array; use toml::Value; #[derive(Debug, Deserialize, Serialize)] struct TOMLConfig { audio: Option, servers: Option, } #[derive(Clone, Debug, Default)] pub struct Config { pub audio: AudioConfig, pub servers: Vec, } impl Config { pub fn write_default_cfg(&self, create: bool) -> Result<(), std::io::Error> { let path = default_cfg_path(); // Possible race here. It's fine since it shows when: // 1) the file doesn't exist when checked and is then created // 2) the file exists when checked but is then removed // If 1) we don't do anything anyway so it's fine, and if 2) we // immediately re-create the file which, while not perfect, at least // should work. Unless the file is removed AND the permissions // change, but then we don't have permissions so we can't // do anything anyways. if !create && !path.exists() { return Ok(()); } fs::write( &path, toml::to_string(&TOMLConfig::from(self.clone())).unwrap(), //TODO handle panic ) } } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct SoundEffect { pub event: String, pub file: String, } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct AudioConfig { pub input_volume: Option, pub output_volume: Option, pub sound_effects: Option>, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ServerConfig { pub name: String, pub host: String, pub port: Option, pub username: Option, pub password: Option, } impl ServerConfig { pub fn to_socket_addr(&self) -> Option { match (self.host.as_str(), self.port.unwrap_or(DEFAULT_PORT)) .to_socket_addrs() .map(|mut e| e.next()) { Ok(Some(addr)) => Some(addr), _ => None, } } } pub fn default_cfg_path() -> PathBuf { match dirs::config_dir() { Some(mut p) => { p.push("mumdrc"); p } //TODO This isn't cross platform. None => PathBuf::from("/etc/mumdrc") } } pub fn cfg_exists() -> bool { if let Ok(var) = std::env::var("XDG_CONFIG_HOME") { let path = format!("{}/mumdrc", var); if Path::new(&path).exists() { return true; } } else if let Ok(var) = std::env::var("HOME") { let path = format!("{}/.config/mumdrc", var); if Path::new(&path).exists() { return true; } } else if Path::new("/etc/mumdrc").exists() { return true; } false } impl TryFrom for Config { type Error = toml::de::Error; fn try_from(config: TOMLConfig) -> Result { Ok(Config { audio: config.audio.unwrap_or_default(), servers: config .servers .map(|servers| { servers .into_iter() .map(|s| s.try_into::()) .collect() }) .transpose()? .unwrap_or_default(), }) } } impl From for TOMLConfig { fn from(config: Config) -> Self { TOMLConfig { audio: if config.audio.output_volume.is_some() || config.audio.input_volume.is_some() { Some(config.audio) } else { None }, servers: Some( config .servers .into_iter() .map(|s| Value::try_from::(s).unwrap()) //TODO handle panic .collect(), ), } } } pub fn read_default_cfg() -> Config { let path = default_cfg_path(); match fs::read_to_string(&path) { Ok(s) => { let toml_config: TOMLConfig = toml::from_str(&s).expect("Invalid TOML in config file"); //TODO handle panic return Config::try_from(toml_config).expect("Invalid config in TOML"); //TODO handle panic }, Err(e) => { if matches!(e.kind(), std::io::ErrorKind::NotFound) && !path.exists() { warn!("Config file not found"); } else { error!("Error reading config file: {}", e); } return Config::default(); } } }