1 //! An implementation of `loader::Handle`, based on `walkdir` and `notify`.
3 //! The file watching bits here are untested and quite probably buggy. For this
4 //! reason, by default we don't watch files and rely on editor's file watching
7 //! Hopefully, one day a reliable file watching/walking crate appears on
8 //! crates.io, and we can reduce this to trivial glue code.
10 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
14 use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
15 use notify::{RecommendedWatcher, RecursiveMode, Watcher};
16 use paths::{AbsPath, AbsPathBuf};
21 pub struct NotifyHandle {
22 // Relative order of fields below is significant.
23 sender: Sender<Message>,
24 _thread: jod_thread::JoinHandle,
29 Config(loader::Config),
30 Invalidate(AbsPathBuf),
33 impl loader::Handle for NotifyHandle {
34 fn spawn(sender: loader::Sender) -> NotifyHandle {
35 let actor = NotifyActor::new(sender);
36 let (sender, receiver) = unbounded::<Message>();
37 let thread = jod_thread::Builder::new()
38 .name("VfsLoader".to_owned())
39 .spawn(move || actor.run(receiver))
40 .expect("failed to spawn thread");
41 NotifyHandle { sender, _thread: thread }
43 fn set_config(&mut self, config: loader::Config) {
44 self.sender.send(Message::Config(config)).unwrap();
46 fn invalidate(&mut self, path: AbsPathBuf) {
47 self.sender.send(Message::Invalidate(path)).unwrap();
49 fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>> {
54 type NotifyEvent = notify::Result<notify::Event>;
57 sender: loader::Sender,
58 watched_entries: Vec<loader::Entry>,
59 // Drop order is significant.
60 watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
66 NotifyEvent(NotifyEvent),
70 fn new(sender: loader::Sender) -> NotifyActor {
71 NotifyActor { sender, watched_entries: Vec::new(), watcher: None }
73 fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
74 let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
76 recv(receiver) -> it => it.ok().map(Event::Message),
77 recv(watcher_receiver.unwrap_or(&never())) -> it => Some(Event::NotifyEvent(it.unwrap())),
80 fn run(mut self, inbox: Receiver<Message>) {
81 while let Some(event) = self.next_event(&inbox) {
82 tracing::debug!("vfs-notify event: {:?}", event);
84 Event::Message(msg) => match msg {
85 Message::Config(config) => {
87 if !config.watch.is_empty() {
88 let (watcher_sender, watcher_receiver) = unbounded();
89 let watcher = log_notify_error(RecommendedWatcher::new(move |event| {
90 watcher_sender.send(event).unwrap();
92 self.watcher = watcher.map(|it| (it, watcher_receiver));
95 let config_version = config.version;
97 let n_total = config.load.len();
98 self.send(loader::Message::Progress { n_total, n_done: 0, config_version });
100 self.watched_entries.clear();
102 for (i, entry) in config.load.into_iter().enumerate() {
103 let watch = config.watch.contains(&i);
105 self.watched_entries.push(entry.clone());
107 let files = self.load_entry(entry, watch);
108 self.send(loader::Message::Loaded { files });
109 self.send(loader::Message::Progress {
116 Message::Invalidate(path) => {
117 let contents = read(path.as_path());
118 let files = vec![(path, contents)];
119 self.send(loader::Message::Loaded { files });
122 Event::NotifyEvent(event) => {
123 if let Some(event) = log_notify_error(event) {
127 .map(|path| AbsPathBuf::try_from(path).unwrap())
129 let meta = fs::metadata(&path).ok()?;
130 if meta.file_type().is_dir()
134 .any(|entry| entry.contains_dir(&path))
140 if !meta.file_type().is_file() {
146 .any(|entry| entry.contains_file(&path))
151 let contents = read(&path);
152 Some((path, contents))
155 self.send(loader::Message::Loaded { files });
163 entry: loader::Entry,
165 ) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
167 loader::Entry::Files(files) => files
171 self.watch(file.clone());
173 let contents = read(file.as_path());
176 .collect::<Vec<_>>(),
177 loader::Entry::Directories(dirs) => {
178 let mut res = Vec::new();
180 for root in &dirs.include {
182 WalkDir::new(root).follow_links(true).into_iter().filter_entry(|entry| {
183 if !entry.file_type().is_dir() {
186 let path = AbsPath::assert(entry.path());
188 || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
191 let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
192 let is_dir = entry.file_type().is_dir();
193 let is_file = entry.file_type().is_file();
194 let abs_path = AbsPathBuf::assert(entry.into_path());
196 self.watch(abs_path.clone());
201 let ext = abs_path.extension().unwrap_or_default();
202 if dirs.extensions.iter().all(|it| it.as_str() != ext) {
208 res.extend(files.map(|file| {
209 let contents = read(file.as_path());
218 fn watch(&mut self, path: AbsPathBuf) {
219 if let Some((watcher, _)) = &mut self.watcher {
220 log_notify_error(watcher.watch(path.as_ref(), RecursiveMode::NonRecursive));
223 fn send(&mut self, msg: loader::Message) {
228 fn read(path: &AbsPath) -> Option<Vec<u8>> {
229 std::fs::read(path).ok()
232 fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
233 res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()