use failure::Error; use inotify::{Inotify, WatchMask, EventMask}; use serde::de::DeserializeOwned; use std::fs::File; use std::io::BufReader; use std::path::Path; use std::path::PathBuf; /// Reloadable config file pub struct ConfigFile { is_dummy : bool, config: T, path: PathBuf, inotify: Option, inotify_buffer: [u8; 1024], } impl ConfigFile where T: Default + std::fmt::Debug + DeserializeOwned, { pub fn borrow(&self) -> &T { &self.config } /// Create a new reloadable config file /// If loading fails, an error is printed and default values are be used pub fn new>(path: P) -> Result { let loaded = Self::load(&path); if let Err(e) = &loaded { eprintln!("Config file failed to load/parse: {}", e); } Ok(Self { is_dummy: false, config: loaded.unwrap_or(T::default()), path: path.as_ref().to_owned(), inotify: Self::setup_inotify(&path), inotify_buffer: [0; 1024], }) } /// New without a backing file #[allow(dead_code)] pub fn new_from_defaults() -> Self { Self { is_dummy: true, config: T::default(), path: "".into(), inotify: None, inotify_buffer: [0; 1024] } } /// Try to install inotify fn setup_inotify>(path: P) -> Option { let inotify = Inotify::init(); if let Err(e) = inotify { eprintln!("Failed to set up inotify for config file reload!\n{}", e); return None; } let mut inotify = inotify.unwrap(); let rv = inotify.add_watch(&path, WatchMask::MODIFY | WatchMask::CLOSE); match rv { Err(e) => { eprintln!("Failed to add inotify watch for config!\n{}", e); None } Ok(_) => Some(inotify), } } /// Update the config file if needed (there are inotify events) /// - if inotify failed to init, or an error occurs trying to get events from it, /// the 'def' value is used (true = reload if unsure) pub fn update_if_needed(&mut self, def: bool) -> Result { if self.is_dummy { debug!("Config is dummy, no path to reload."); return Ok(false); } let needed = match self.inotify.as_mut() { Some(ino) => { let rv = ino.read_events(&mut self.inotify_buffer); match rv { Ok(evs) => { let mut changed = false; let mut need_reinstall = false; for ev in evs { if ev.mask.contains(EventMask::IGNORED) { debug!("Inotify watcher got removed by OS??"); changed = true; need_reinstall = true; } if ev.mask.contains(EventMask::MODIFY) { changed = true; } } if changed { info!("Config file changes detected, reloading"); } if need_reinstall { debug!("Reinstalling inotify"); self.inotify = Self::setup_inotify(&self.path); } changed } _ => { error!("Failed to get inotify events."); def } } } None => def, }; if needed { self.update()?; } else { debug!("Config update not needed."); } Ok(needed) } /// load and parse the config file fn load>(path: P) -> Result { info!("Loading config file @ {}", path.as_ref().to_str().unwrap_or("???")); let file = File::open(&path)?; let reader = BufReader::new(file); let val : T = serde_json::from_reader(reader)?; debug!("Loaded config:\n{:#?}", val); Ok(val) } /// Reload unconditionally pub fn update(&mut self) -> Result<(), Error> { if self.is_dummy { debug!("Config is dummy, no path to reload."); return Ok(()); } self.config = Self::load(&self.path)?; Ok(()) } }