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}