#![feature(exit_status_error)]
mod config;
use debounce::MixedEventDebouncer;
use inotify::{Event, EventMask, Inotify, WatchDescriptor, WatchMask};
use std::collections::HashMap;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::{exit, Command};
use std::sync::Arc;
use std::time::Duration;
#[derive(PartialEq, Eq)]
struct File {
wd: WatchDescriptor,
name: OsString,
}
impl From<Event<&OsStr>> for File {
fn from(event: Event<&OsStr>) -> Self {
File {
wd: event.wd,
name: event.name.unwrap().to_owned(),
}
}
}
struct Processor {
path: PathBuf,
command: String,
args: Vec<String>,
debounce: u64,
keep_file: bool,
}
fn register_processor(inotify: &mut Inotify, processor: &Processor) -> Option<WatchDescriptor> {
println!(
"Registering {:?} for \"{} {}\"",
processor.path,
processor.command,
processor.args.join(" ")
);
inotify
.add_watch(
&processor.path,
WatchMask::CLOSE_WRITE | WatchMask::MOVED_TO | WatchMask::MOVED_FROM,
)
.map_err(|e| eprintln!("Error: {}", e))
.ok()
}
fn process(
filename: &Path,
command: &str,
args: &[String],
keep_file: bool,
) -> Result<(), Box<dyn Error>> {
Command::new(command)
.args(args)
.arg(filename)
.status()?
.exit_ok()?;
if !keep_file {
println!("Removing {:?}", filename);
fs::remove_file(filename)?;
}
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
let mut inotify = Inotify::init()?;
let processors: Arc<HashMap<_, _>> = Arc::new(
confy::load::<config::Config>("nfp")?
.processors
.iter()
.flat_map(|p| {
p.paths().map(move |path| Processor {
path,
command: p.command.clone(),
args: p.args.clone(),
debounce: p.debounce,
keep_file: p.keep_file,
})
})
.filter_map(|p| register_processor(&mut inotify, &p).map(|d| (d, p)))
.collect(),
);
if processors.len() == 0 {
eprintln!("No processors registered, exiting.");
exit(1);
}
let processors_clone = Arc::clone(&processors);
let debouncer = MixedEventDebouncer::new(move |file: File| {
let processor = &processors_clone[&file.wd];
let filename = processor.path.join(&file.name);
println!("Processing {:?}", filename);
process(
&filename,
&processor.command,
&processor.args[..],
processor.keep_file,
)
.unwrap_or_else(|e| eprintln!("Error: {}", e));
});
let mut buffer = [0; 1024];
let mut cookie = u32::MAX;
loop {
for event in inotify.read_events_blocking(&mut buffer)? {
match event.mask {
EventMask::MOVED_FROM => cookie = event.cookie,
_ if cookie != event.cookie => {
let delay = Duration::from_millis(processors[&event.wd].debounce);
debouncer.put(event.into(), delay)
}
_ => (),
}
}
}
}