fj_host/watcher.rs
1use std::{collections::HashSet, ffi::OsStr, path::Path};
2
3use crossbeam_channel::Sender;
4use notify::Watcher as _;
5
6use crate::{Error, HostCommand};
7
8/// Watches a model for changes, reloading it continually
9pub struct Watcher {
10 _watcher: Box<dyn notify::Watcher>,
11}
12
13impl Watcher {
14 /// Watch the provided model for changes
15 pub fn watch_model(
16 watch_path: impl AsRef<Path>,
17 host_tx: Sender<HostCommand>,
18 ) -> Result<Self, Error> {
19 let watch_path = watch_path.as_ref();
20
21 let mut watcher = notify::recommended_watcher(
22 move |event: notify::Result<notify::Event>| {
23 // Unfortunately the `notify` documentation doesn't say when
24 // this might happen, so no idea if it needs to be handled.
25 let event = event.expect("Error handling watch event");
26
27 // Various acceptable ModifyKind kinds. Varies across platforms
28 // (e.g. MacOs vs. Windows10)
29 if let notify::EventKind::Modify(
30 notify::event::ModifyKind::Any
31 | notify::event::ModifyKind::Data(
32 notify::event::DataChange::Any
33 | notify::event::DataChange::Content,
34 ),
35 ) = event.kind
36 {
37 let file_ext = event
38 .paths
39 .get(0)
40 .expect("File path missing in watch event")
41 .extension();
42
43 let black_list = HashSet::from([
44 OsStr::new("swp"),
45 OsStr::new("tmp"),
46 OsStr::new("swx"),
47 ]);
48
49 if let Some(ext) = file_ext {
50 if black_list.contains(ext) {
51 return;
52 }
53 }
54
55 // This will panic, if the other end is disconnected, which
56 // is probably the result of a panic on that thread, or the
57 // application is being shut down.
58 //
59 // Either way, not much we can do about it here.
60 host_tx
61 .send(HostCommand::TriggerEvaluation)
62 .expect("Channel is disconnected");
63 }
64 },
65 )?;
66
67 watcher.watch(watch_path, notify::RecursiveMode::Recursive)?;
68
69 Ok(Self {
70 _watcher: Box::new(watcher),
71 })
72 }
73}