#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::time::Duration;
use crate::InlyneEvent;
use notify::event::{EventKind, ModifyKind};
use notify::{RecommendedWatcher, RecursiveMode, Watcher as _};
use notify_debouncer_full::{
new_debouncer, DebounceEventHandler, DebounceEventResult, Debouncer, FileIdMap,
};
use winit::event_loop::EventLoopProxy;
trait Callback: Send + 'static {
fn file_reload(&self);
fn file_change(&self, contents: String);
}
impl Callback for EventLoopProxy<InlyneEvent> {
fn file_reload(&self) {
let _ = self.send_event(InlyneEvent::FileReload);
}
fn file_change(&self, contents: String) {
let _ = self.send_event(InlyneEvent::FileChange { contents });
}
}
struct FileChange {
new_path: PathBuf,
contents: String,
}
enum DebouncerAction {
ReregisterWatcher,
FileReload,
}
enum WatcherMsg {
Action(DebouncerAction),
FileChange(FileChange),
}
impl WatcherMsg {
fn file_change(new_path: PathBuf, contents: String) -> Self {
Self::FileChange(FileChange { new_path, contents })
}
}
struct MsgHandler(mpsc::Sender<WatcherMsg>);
impl DebounceEventHandler for MsgHandler {
fn handle_event(&mut self, debounced_event: DebounceEventResult) {
tracing::debug!("Received debounced file events: {:#?}", debounced_event);
match debounced_event {
Ok(events) => {
let mut maybe_action = None;
for ev in events {
match ev.event.kind {
EventKind::Modify(ModifyKind::Name(_)) | EventKind::Remove(_) => {
let _ = maybe_action.insert(DebouncerAction::ReregisterWatcher);
}
EventKind::Create(_) | EventKind::Modify(_) => {
let _ = maybe_action.get_or_insert(DebouncerAction::FileReload);
}
_ => {}
}
}
if let Some(action) = maybe_action {
let msg = WatcherMsg::Action(action);
let _ = self.0.send(msg);
} else {
tracing::trace!("Ignoring events")
}
}
Err(errs) => {
for err in errs {
tracing::warn!("File watcher error: {err}");
}
}
}
}
}
pub struct Watcher(mpsc::Sender<WatcherMsg>);
impl Watcher {
pub fn spawn(event_proxy: EventLoopProxy<InlyneEvent>, file_path: PathBuf) -> Self {
Self::spawn_inner(event_proxy, file_path)
}
fn spawn_inner<C: Callback>(reload_callback: C, file_path: PathBuf) -> Self {
let (msg_tx, msg_rx) = mpsc::channel();
let watcher = Self(msg_tx.clone());
let notify_watcher =
new_debouncer(Duration::from_millis(10), None, MsgHandler(msg_tx)).unwrap();
std::thread::spawn(move || {
endlessly_handle_messages(notify_watcher, msg_rx, reload_callback, file_path);
});
watcher
}
pub fn update_file(&self, new_path: &Path, contents: String) {
let msg = WatcherMsg::file_change(new_path.to_owned(), contents);
let _ = self.0.send(msg);
}
}
fn endlessly_handle_messages<C: Callback>(
mut watcher: Debouncer<RecommendedWatcher, FileIdMap>,
msg_rx: mpsc::Receiver<WatcherMsg>,
reload_callback: C,
mut file_path: PathBuf,
) {
let watcher = watcher.watcher();
watcher
.watch(&file_path, RecursiveMode::NonRecursive)
.unwrap();
let poll_registering_watcher = |watcher: &mut RecommendedWatcher, file_path: &Path| loop {
std::thread::sleep(Duration::from_millis(15));
let _ = watcher.unwatch(file_path);
if watcher
.watch(file_path, RecursiveMode::NonRecursive)
.is_ok()
{
break;
}
};
while let Ok(msg) = msg_rx.recv() {
match msg {
WatcherMsg::Action(DebouncerAction::ReregisterWatcher) => {
tracing::debug!("File may have been renamed/removed. Falling back to polling");
poll_registering_watcher(watcher, &file_path);
tracing::debug!("Successfully re-registered file watcher");
reload_callback.file_reload();
}
WatcherMsg::Action(DebouncerAction::FileReload) => {
tracing::debug!("Reloading file");
reload_callback.file_reload();
}
WatcherMsg::FileChange(FileChange { new_path, contents }) => {
tracing::info!("Updating file watcher path: {}", new_path.display());
let _ = watcher.unwatch(&file_path);
poll_registering_watcher(watcher, &new_path);
file_path = new_path;
reload_callback.file_change(contents);
}
}
}
tracing::warn!("File watcher channel dropped unexpectedly");
}