You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
4.5 KiB
151 lines
4.5 KiB
6 years ago
|
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(())
|
||
|
}
|
||
|
}
|