How to watch path to file that doesn't exist yet?
When I call .watch() with the path to a file that doesn't exist yet, I get
The system cannot find the file specified. (os error 2)
But I also want to get notified when the file is created (not just when it changes). My code cannot assume that the file that is watched already exists. Is there a way? :)
Btw, is there also a way to keep watching the path when the file is deleted, to get notified when it's recreated at the same path later?
Sorry for the delay!
So, this seems to be the upstream behavior from notify... I imagine it's not possible to allow this in a cross-platform fashion, but that's just my guess for why it works this way.
What I've done in the past when I needed to do something like this was watch the parent directory, since then you'll be notified of any file that's created, deleted, etc. within that directory. Would that be satisfactory for your use-case?
We could perhaps try to abstract that, though there are likely performance considerations when watching large directories.
The parent dir might also not exist yet. No component of the path can be assumed to exist. And the file might get deleted and recreated later (also its parent etc).
I wrapped it like this, which works for my use case:
use hotwatch::*;
use std::{
mem,
path::{Path, PathBuf},
time::{Duration, Instant},
};
pub struct ColdWatch {
hotwatch: Hotwatch,
pending_paths_to_watch_asap: Vec<(bool, PathBuf, Box<dyn FnMut(Event) + Send + 'static>)>,
last_retry_time: Instant,
}
impl ColdWatch {
pub fn new_with_custom_delay(delay: Duration) -> Result<Self, Error> {
Ok(Self {
hotwatch: Hotwatch::new_with_custom_delay(delay)?,
pending_paths_to_watch_asap: vec![],
last_retry_time: Instant::now(),
})
}
pub fn watch<P, F>(
&mut self,
also_run_before_watching: bool,
path: P,
mut handler: F,
) -> Result<(), Error>
where
P: AsRef<Path>,
F: FnMut(Event) + Send + 'static,
{
let path = path.as_ref();
if path.exists() {
if also_run_before_watching {
info!("run before watching: handler for {}", path.display());
handler(Event::Write(path.to_path_buf()));
}
info!("starting to watch {}", path.display());
self.hotwatch.watch(path, handler)
} else {
info!("watching {} as soon as it exists", path.display());
self.pending_paths_to_watch_asap.push((
also_run_before_watching,
path.to_owned(),
Box::new(handler),
));
Ok(())
}
}
pub fn unwatch<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
let path = path.as_ref();
self.pending_paths_to_watch_asap.retain(|(_, p, _)| p != path);
self.hotwatch.unwatch(path)
}
// call this function periodically to start watching pending paths
pub fn retry_watching_pending_paths(&mut self) -> Result<(), Error> {
let now = Instant::now();
if now >= self.last_retry_time + Duration::from_secs(5) {
self.last_retry_time = now;
for (also_run_before_watching, path, handler) in
mem::take(&mut self.pending_paths_to_watch_asap)
{
if path.exists() {
self.watch(also_run_before_watching, path, handler)?;
} else {
self.pending_paths_to_watch_asap.push((
also_run_before_watching,
path,
handler,
));
}
}
}
Ok(())
}
}
Maybe it could be integrated into this crate? :)
My code above is working well and I realized that I wanted to use it in multiple crates. @francesca64 It would be nice if this functionality can be added to this crate, so that others can also benefit from it :)
Btw, in my real-world use, I needed the type to impl Debug, so I did this:
#[derive(Derivative)]
#[derivative(Debug)]
pub struct ColdWatch {
hotwatch: Hotwatch,
#[derivative(Debug = "ignore")]
pending_paths_to_watch_asap: Vec<(bool, PathBuf, Box<dyn FnMut(Event) + Send + 'static>)>,
last_retry_time: Instant,
}
but the rest of the code is the same.
@Boscop sorry to leave you hanging! I'd gladly accept a PR for this, or I could add it myself, if you'd prefer.
@francesca64 I'd prefer if you do it :)
Because maybe you want to make some changes, e.g. maybe you can think of a better name than ColdWatch, something that makes more semantic sense.
(In the for-loop in retry_watching_pending_paths, it should probably just call self.watch(also_run_before_watching, path, handler)?; because watch already checks path.exists() and appends to pending_paths_to_watch_asap otherwise.)
I think it would even make sense to make it the default behavior that registering a watcher for a path that doesn't exist yet will take effect as soon as the path exists (maybe by having a background thread running, that repeatedly calls retry_watching_pending_paths every second).
Also I think it would make sense for HotWatch's constructors to have a boolean flag like also_run_before_watching, which runs the handler when the watcher is registered (before the first file change).
You may easily watch a path for a file that isn't created yet.
You watch for the path to change and for the Create event to happen. You always check the path in the Create event to be the one you want. Once this is done, right in the callback, you add this path using hotwatch::watch, so that it will be watched by the hotwatch.
@vityafx How do you mean? Do you mean if the path doesn't exist, I have to call hotwatch::watch twice with the same path? (Once before getting the Create event and once when I receive it.)
Why do I have to call hotwatch::watch a second time with the same path?