2022-01-08 20:34:09 +00:00
|
|
|
use super::{config, config::PathConfig, copy, path, path::ResPathBuf};
|
2021-12-30 21:53:29 +00:00
|
|
|
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
2022-01-02 16:14:05 +00:00
|
|
|
use simplelog::{error, paris, trace, warn};
|
2022-01-01 17:10:57 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashSet,
|
|
|
|
error::Error,
|
|
|
|
path::PathBuf,
|
|
|
|
sync::mpsc::{channel, Receiver, Sender, TryRecvError},
|
|
|
|
thread,
|
|
|
|
time::Duration,
|
|
|
|
};
|
2021-12-30 13:37:53 +00:00
|
|
|
|
2021-12-30 18:18:52 +00:00
|
|
|
// TODO(jrpotter): Add pid file to only allow one daemon at a time.
|
2022-01-01 03:49:34 +00:00
|
|
|
// TODO(jrpotter): Sync files to local git repository.
|
2021-12-30 13:37:53 +00:00
|
|
|
|
2021-12-30 19:17:42 +00:00
|
|
|
// ========================================
|
2021-12-30 21:53:29 +00:00
|
|
|
// Polling
|
2021-12-30 19:17:42 +00:00
|
|
|
// ========================================
|
|
|
|
|
2021-12-30 21:53:29 +00:00
|
|
|
enum PollEvent {
|
|
|
|
Pending(PathBuf),
|
|
|
|
Clear,
|
2021-12-30 18:18:52 +00:00
|
|
|
}
|
2021-12-30 13:37:53 +00:00
|
|
|
|
2021-12-30 21:53:29 +00:00
|
|
|
fn resolve_pending(tx: &Sender<DebouncedEvent>, pending: &HashSet<PathBuf>) -> Vec<PathBuf> {
|
|
|
|
let mut to_remove = vec![];
|
|
|
|
for path in pending {
|
2022-01-01 01:12:21 +00:00
|
|
|
match path::soft_resolve(&path) {
|
2021-12-30 21:53:29 +00:00
|
|
|
Ok(Some(resolved)) => {
|
|
|
|
to_remove.push(path.clone());
|
|
|
|
tx.send(DebouncedEvent::Create(resolved.into()))
|
|
|
|
.expect("File watcher channel closed.");
|
|
|
|
}
|
|
|
|
Ok(None) => (),
|
|
|
|
Err(e) => {
|
|
|
|
to_remove.push(path.clone());
|
2022-01-02 16:14:05 +00:00
|
|
|
error!(
|
2021-12-30 21:53:29 +00:00
|
|
|
"Encountered unexpected error {} when processing path {}",
|
|
|
|
e,
|
|
|
|
path.display()
|
|
|
|
)
|
|
|
|
}
|
2021-12-30 18:18:52 +00:00
|
|
|
}
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
to_remove
|
|
|
|
}
|
|
|
|
|
2021-12-31 14:41:44 +00:00
|
|
|
fn poll_pending(tx: Sender<DebouncedEvent>, rx: Receiver<PollEvent>, freq_secs: u64) {
|
2021-12-30 21:53:29 +00:00
|
|
|
let mut pending = HashSet::new();
|
|
|
|
loop {
|
|
|
|
match rx.try_recv() {
|
|
|
|
Ok(PollEvent::Pending(path)) => {
|
|
|
|
pending.insert(path);
|
|
|
|
}
|
|
|
|
Ok(PollEvent::Clear) => pending.clear(),
|
|
|
|
Err(TryRecvError::Empty) => {
|
|
|
|
resolve_pending(&tx, &pending).iter().for_each(|r| {
|
|
|
|
pending.remove(r);
|
|
|
|
});
|
2021-12-31 14:41:44 +00:00
|
|
|
thread::sleep(Duration::from_secs(freq_secs));
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
Err(TryRecvError::Disconnected) => panic!("Polling channel closed."),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
// File Watcher
|
|
|
|
// ========================================
|
|
|
|
|
|
|
|
struct WatchState<'a> {
|
|
|
|
poll_tx: Sender<PollEvent>,
|
|
|
|
watcher: &'a mut RecommendedWatcher,
|
|
|
|
watching: HashSet<ResPathBuf>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> WatchState<'a> {
|
|
|
|
pub fn new(
|
|
|
|
poll_tx: Sender<PollEvent>,
|
|
|
|
watcher: &'a mut RecommendedWatcher,
|
|
|
|
) -> notify::Result<Self> {
|
2021-12-30 19:17:42 +00:00
|
|
|
Ok(WatchState {
|
2021-12-30 21:53:29 +00:00
|
|
|
poll_tx,
|
|
|
|
watcher,
|
|
|
|
watching: HashSet::new(),
|
2021-12-30 19:17:42 +00:00
|
|
|
})
|
2021-12-30 18:18:52 +00:00
|
|
|
}
|
2021-12-30 21:53:29 +00:00
|
|
|
|
|
|
|
fn send_poll(&self, event: PollEvent) {
|
|
|
|
self.poll_tx.send(event).expect("Polling channel closed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn watch(&mut self, path: ResPathBuf) {
|
|
|
|
match self.watcher.watch(&path, RecursiveMode::NonRecursive) {
|
|
|
|
Ok(()) => {
|
|
|
|
self.watching.insert(path);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
2022-01-02 16:14:05 +00:00
|
|
|
error!(
|
2021-12-30 21:53:29 +00:00
|
|
|
"Encountered unexpected error {} when watching path {}",
|
|
|
|
e,
|
|
|
|
path.unresolved().display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads in the new path config, updating all watched and pending files
|
|
|
|
/// according to the packages in the specified config.
|
2022-01-02 18:48:18 +00:00
|
|
|
pub fn update(&mut self, pc: &PathConfig) {
|
2021-12-30 21:53:29 +00:00
|
|
|
self.send_poll(PollEvent::Clear);
|
|
|
|
for path in &self.watching {
|
|
|
|
match self.watcher.unwatch(&path) {
|
|
|
|
Ok(()) => (),
|
|
|
|
Err(e) => {
|
2022-01-02 16:14:05 +00:00
|
|
|
error!(
|
2021-12-30 21:53:29 +00:00
|
|
|
"Encountered unexpected error {} when unwatching path {}",
|
|
|
|
e,
|
|
|
|
path.unresolved().display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.watching.clear();
|
2022-01-07 17:30:46 +00:00
|
|
|
for (_, packages) in &pc.config.packages {
|
|
|
|
for path in packages {
|
2022-01-01 01:12:21 +00:00
|
|
|
match path::soft_resolve(&path) {
|
2021-12-30 21:53:29 +00:00
|
|
|
Ok(None) => self.send_poll(PollEvent::Pending(path.clone())),
|
|
|
|
Ok(Some(n)) => self.watch(n),
|
|
|
|
Err(_) => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-30 18:18:52 +00:00
|
|
|
}
|
2021-12-30 13:37:53 +00:00
|
|
|
|
2021-12-30 19:17:42 +00:00
|
|
|
// ========================================
|
|
|
|
// Daemon
|
|
|
|
// ========================================
|
|
|
|
|
2022-01-08 20:34:09 +00:00
|
|
|
pub fn launch(mut pc: PathConfig, freq_secs: u64) -> Result<(), Box<dyn Error>> {
|
2021-12-30 21:53:29 +00:00
|
|
|
let (poll_tx, poll_rx) = channel();
|
|
|
|
let (watch_tx, watch_rx) = channel();
|
|
|
|
let watch_tx1 = watch_tx.clone();
|
|
|
|
// `notify-rs` internally uses `fs::canonicalize` on each path we try to
|
|
|
|
// watch, but this fails if no file exists at the given path. In these
|
|
|
|
// cases, we rely on a basic polling strategy to check if the files ever
|
|
|
|
// come into existence.
|
2021-12-31 14:41:44 +00:00
|
|
|
thread::spawn(move || poll_pending(watch_tx, poll_rx, freq_secs));
|
2021-12-30 21:53:29 +00:00
|
|
|
// Track our original config file separately from the other files that may
|
|
|
|
// be defined in the config. We want to make sure we're always alerted on
|
|
|
|
// changes to it for hot reloading purposes, and not worry that our wrapper
|
|
|
|
// will ever clear it from its watch state.
|
2021-12-31 14:41:44 +00:00
|
|
|
let mut watcher: RecommendedWatcher = Watcher::new(watch_tx1, Duration::from_secs(freq_secs))?;
|
2022-01-02 18:48:18 +00:00
|
|
|
watcher.watch(&pc.homesync_yml, RecursiveMode::NonRecursive)?;
|
2021-12-30 21:53:29 +00:00
|
|
|
let mut state = WatchState::new(poll_tx, &mut watcher)?;
|
2022-01-02 18:48:18 +00:00
|
|
|
state.update(&pc);
|
2021-12-30 13:37:53 +00:00
|
|
|
loop {
|
2022-01-08 20:34:09 +00:00
|
|
|
copy::stage(&pc)?;
|
2022-01-06 12:46:39 +00:00
|
|
|
// Received paths should always be fully resolved.
|
2021-12-30 21:53:29 +00:00
|
|
|
match watch_rx.recv() {
|
2022-01-02 16:14:05 +00:00
|
|
|
Ok(DebouncedEvent::NoticeWrite(p)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
trace!("NoticeWrite '{}'", p.display());
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
2022-01-02 16:14:05 +00:00
|
|
|
Ok(DebouncedEvent::NoticeRemove(p)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
trace!("NoticeRemove '{}'", p.display());
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
Ok(DebouncedEvent::Create(p)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
trace!("Create '{}'", p.display());
|
2022-01-02 18:48:18 +00:00
|
|
|
if pc.homesync_yml == p {
|
|
|
|
pc = config::reload(&pc)?;
|
|
|
|
state.update(&pc);
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(DebouncedEvent::Write(p)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
trace!("Write '{}'", p.display());
|
2022-01-02 18:48:18 +00:00
|
|
|
if pc.homesync_yml == p {
|
|
|
|
pc = config::reload(&pc)?;
|
|
|
|
state.update(&pc);
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Do not try reloading our primary config in any of the following
|
|
|
|
// cases since it may lead to undesired behavior. If our config has
|
|
|
|
// e.g. been removed, let's just keep using what we have in memory
|
|
|
|
// in the chance it may be added back.
|
|
|
|
Ok(DebouncedEvent::Chmod(p)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
trace!("Chmod '{}'", p.display());
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
Ok(DebouncedEvent::Remove(p)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
if pc.homesync_yml == p {
|
|
|
|
warn!(
|
|
|
|
"Removed primary config '{}'. Continuing to use last loaded state",
|
|
|
|
p.display()
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
trace!("Remove '{}'", p.display());
|
|
|
|
}
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
Ok(DebouncedEvent::Rename(src, dst)) => {
|
2022-01-06 12:46:39 +00:00
|
|
|
if pc.homesync_yml == src && pc.homesync_yml != dst {
|
|
|
|
warn!(
|
|
|
|
"Renamed primary config '{}'. Continuing to use last loaded state",
|
|
|
|
src.display()
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
trace!("Renamed '{}' to '{}'", src.display(), dst.display())
|
|
|
|
}
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
Ok(DebouncedEvent::Rescan) => {
|
2022-01-02 16:14:05 +00:00
|
|
|
trace!("Rescanning");
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
2022-01-02 16:14:05 +00:00
|
|
|
Ok(DebouncedEvent::Error(e, path)) => {
|
|
|
|
warn!(
|
2022-01-06 12:46:39 +00:00
|
|
|
"Error {} at '{}'",
|
2022-01-02 16:14:05 +00:00
|
|
|
e,
|
|
|
|
path.unwrap_or_else(|| PathBuf::from("N/A")).display()
|
|
|
|
);
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
|
|
|
Err(e) => {
|
2022-01-02 16:14:05 +00:00
|
|
|
error!("Watch error: {:?}", e);
|
2021-12-30 21:53:29 +00:00
|
|
|
}
|
2021-12-30 13:37:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|