diff options
Diffstat (limited to 'crates/ra_vfs/src/watcher.rs')
-rw-r--r-- | crates/ra_vfs/src/watcher.rs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/crates/ra_vfs/src/watcher.rs b/crates/ra_vfs/src/watcher.rs new file mode 100644 index 000000000..cc05f949e --- /dev/null +++ b/crates/ra_vfs/src/watcher.rs | |||
@@ -0,0 +1,96 @@ | |||
1 | use std::{ | ||
2 | path::{Path, PathBuf}, | ||
3 | sync::mpsc, | ||
4 | thread, | ||
5 | time::Duration, | ||
6 | }; | ||
7 | |||
8 | use crossbeam_channel::Receiver; | ||
9 | use drop_bomb::DropBomb; | ||
10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; | ||
11 | |||
12 | pub struct Watcher { | ||
13 | receiver: Receiver<WatcherChange>, | ||
14 | watcher: RecommendedWatcher, | ||
15 | thread: thread::JoinHandle<()>, | ||
16 | bomb: DropBomb, | ||
17 | } | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | pub enum WatcherChange { | ||
21 | Create(PathBuf), | ||
22 | Write(PathBuf), | ||
23 | Remove(PathBuf), | ||
24 | Rename(PathBuf, PathBuf), | ||
25 | } | ||
26 | |||
27 | impl WatcherChange { | ||
28 | fn from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> { | ||
29 | match ev { | ||
30 | DebouncedEvent::NoticeWrite(_) | ||
31 | | DebouncedEvent::NoticeRemove(_) | ||
32 | | DebouncedEvent::Chmod(_) | ||
33 | | DebouncedEvent::Rescan => { | ||
34 | // ignore | ||
35 | None | ||
36 | } | ||
37 | DebouncedEvent::Create(path) => Some(WatcherChange::Create(path)), | ||
38 | DebouncedEvent::Write(path) => Some(WatcherChange::Write(path)), | ||
39 | DebouncedEvent::Remove(path) => Some(WatcherChange::Remove(path)), | ||
40 | DebouncedEvent::Rename(src, dst) => Some(WatcherChange::Rename(src, dst)), | ||
41 | DebouncedEvent::Error(err, path) => { | ||
42 | // TODO | ||
43 | log::warn!("watch error {}, {:?}", err, path); | ||
44 | None | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | } | ||
49 | |||
50 | impl Watcher { | ||
51 | pub fn new() -> Result<Watcher, Box<std::error::Error>> { | ||
52 | let (input_sender, input_receiver) = mpsc::channel(); | ||
53 | let watcher = notify::watcher(input_sender, Duration::from_millis(250))?; | ||
54 | let (output_sender, output_receiver) = crossbeam_channel::unbounded(); | ||
55 | let thread = thread::spawn(move || loop { | ||
56 | match input_receiver.recv() { | ||
57 | Ok(ev) => { | ||
58 | // forward relevant events only | ||
59 | if let Some(change) = WatcherChange::from_debounced_event(ev) { | ||
60 | output_sender.send(change).unwrap(); | ||
61 | } | ||
62 | } | ||
63 | Err(err) => { | ||
64 | log::debug!("Watcher stopped ({})", err); | ||
65 | break; | ||
66 | } | ||
67 | } | ||
68 | }); | ||
69 | Ok(Watcher { | ||
70 | receiver: output_receiver, | ||
71 | watcher, | ||
72 | thread, | ||
73 | bomb: DropBomb::new(format!("Watcher was not shutdown")), | ||
74 | }) | ||
75 | } | ||
76 | |||
77 | pub fn watch(&mut self, root: impl AsRef<Path>) -> Result<(), Box<std::error::Error>> { | ||
78 | self.watcher.watch(root, RecursiveMode::Recursive)?; | ||
79 | Ok(()) | ||
80 | } | ||
81 | |||
82 | pub fn change_receiver(&self) -> &Receiver<WatcherChange> { | ||
83 | &self.receiver | ||
84 | } | ||
85 | |||
86 | pub fn shutdown(mut self) -> thread::Result<()> { | ||
87 | self.bomb.defuse(); | ||
88 | drop(self.watcher); | ||
89 | let res = self.thread.join(); | ||
90 | match &res { | ||
91 | Ok(()) => log::info!("... Watcher terminated with ok"), | ||
92 | Err(_) => log::error!("... Watcher terminated with err"), | ||
93 | } | ||
94 | res | ||
95 | } | ||
96 | } | ||