parent
fa1979c2bb
commit
46c01c6c38
@ -0,0 +1,150 @@ |
||||
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<T> { |
||||
is_dummy : bool, |
||||
config: T, |
||||
path: PathBuf, |
||||
inotify: Option<Inotify>, |
||||
inotify_buffer: [u8; 1024], |
||||
} |
||||
|
||||
impl<T> ConfigFile<T> |
||||
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<P: AsRef<Path>>(path: P) -> Result<Self, Error> { |
||||
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<P: AsRef<Path>>(path: P) -> Option<Inotify> { |
||||
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<bool, Error> { |
||||
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<P: AsRef<Path>>(path: P) -> Result<T, Error> { |
||||
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(()) |
||||
} |
||||
} |
Loading…
Reference in new issue