Rust daemon that skips rap songs in (not only) Spotify radios via MPRIS and MusicBrainz
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.
rapblock/src/config_file.rs

150 lines
4.5 KiB

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(())
}
}