use serde::Serialize; use validator::Validate; use super::definitions::config::Config; use std::fs; use std::sync::{Arc, Mutex, MutexGuard}; use pwhash::sha512_crypt; use thiserror::Error; #[derive(Error, Debug)] pub enum ConfigError { #[error("Json Error")] SerdeError(#[from] serde_json::Error), #[error("Validation Error")] ValidatonError(#[from] validator::ValidationErrors), #[error("Hash Error")] HashError(#[from] pwhash::error::Error), #[error("Unsupported config version")] UnsupportedVersionError, /// Represents all other cases of `std::io::Error`. #[error(transparent)] IOError(#[from] std::io::Error), } pub const CURRENT_CONFIG_PATH: &str = "config.json"; pub const PENDING_CONFIG_PATH: &str = "pending.json"; #[derive(Clone)] pub struct ConfigManager { shared_data: Arc>, } pub struct ConfigTransaction<'a> { shared_data: MutexGuard<'a, SharedData>, pub config: Config, } struct SharedData { current_config: Config, pending_config: Config, changelog: Vec, } #[derive(Clone, Serialize)] pub struct Change { pub action: ChangeAction, pub path: &'static str, pub id: String, } #[derive(Clone, Serialize)] pub enum ChangeAction { Create, Update, Delete, } // Note, using unwarp on a mutex lock is ok since that only errors with mutex poisoning impl ConfigManager { pub fn new() -> Result { Ok(Self { shared_data: Arc::new(Mutex::new(SharedData { current_config: read_file_to_config(CURRENT_CONFIG_PATH)?, // TODO Dont Fail if pending config is missing, use current instead pending_config: read_file_to_config(PENDING_CONFIG_PATH)?, // TODO Figure out how to restore changes changelog: Vec::new(), })), }) } pub fn get_current_config(&self) -> Config { self.shared_data.lock().unwrap().current_config.clone() } pub fn get_pending_config(&self) -> Config { self.shared_data.lock().unwrap().pending_config.clone() } pub fn get_pending_changelog(&self) -> Vec { self.shared_data.lock().unwrap().changelog.clone() } pub fn apply_pending_changes(&mut self) -> Result<(), ConfigError> { let mut data = self.shared_data.lock().unwrap(); // TODO run Apply functions // TODO Revert on Apply Failure and Return write_config_to_file(CURRENT_CONFIG_PATH, data.pending_config.clone())?; // TODO revert if config save fails // TODO Remove Pending Config File data.current_config = data.pending_config.clone(); data.changelog = Vec::new(); Ok(()) } pub fn discard_pending_changes(&mut self) -> Result<(), ConfigError> { let mut data = self.shared_data.lock().unwrap(); // TODO Remove Pending Config File data.pending_config = data.current_config.clone(); data.changelog = Vec::new(); Ok(()) } pub fn start_transaction(&mut self) -> ConfigTransaction { let data = self.shared_data.lock().unwrap(); ConfigTransaction { config: data.pending_config.clone(), shared_data: data, } } } impl<'a> ConfigTransaction<'a> { pub fn commit(mut self, change: Change) -> Result<(), ConfigError> { let ch = self.config.clone(); ch.validate()?; self.shared_data.pending_config = ch.clone(); self.shared_data.changelog.push(change); write_config_to_file(PENDING_CONFIG_PATH, ch.clone())?; Ok(()) } pub fn revert(self) {} } fn read_file_to_config(path: &str) -> Result { let data = fs::read_to_string(path)?; let conf: Config = serde_json::from_str(&data)?; if conf.config_version != 1 { return Err(ConfigError::UnsupportedVersionError); } Ok(conf) } fn write_config_to_file(path: &str, conf: Config) -> Result<(), ConfigError> { let data: String = serde_json::to_string_pretty(&conf)?; fs::write(path, data)?; Ok(()) } pub fn generate_default_config(path: &str) -> Result<(), ConfigError> { let mut conf = Config::default(); let hash = sha512_crypt::hash("nfsense")?; conf.config_version = 1; conf.system.users.insert( "admin".to_string(), crate::definitions::system::User { comment: "Default Admin".to_string(), hash: hash, }, ); write_config_to_file(path, conf) }