|
1 | 1 | // Copyright (c) Microsoft Corporation. |
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
4 | | -use std::{ |
5 | | - io::ErrorKind, |
6 | | - path::{Path, PathBuf}, |
7 | | -}; |
| 4 | +use std::path::{Path, PathBuf}; |
8 | 5 |
|
9 | | -use anyhow::{format_err, Result}; |
10 | | -use notify::{Event, EventKind, Watcher}; |
| 6 | +use anyhow::{format_err, Context, Result}; |
| 7 | +use notify::{ |
| 8 | + event::{CreateKind, ModifyKind, RenameMode}, |
| 9 | + Event, EventKind, Watcher, |
| 10 | +}; |
11 | 11 | use tokio::{ |
12 | 12 | fs, |
13 | 13 | sync::mpsc::{unbounded_channel, UnboundedReceiver}, |
@@ -41,12 +41,37 @@ impl DirectoryMonitor { |
41 | 41 | } |
42 | 42 |
|
43 | 43 | let (sender, notify_events) = unbounded_channel(); |
44 | | - let event_handler = move |event_or_err| { |
45 | | - // A send error only occurs when the channel is closed. No remedial |
46 | | - // action is needed (or possible), so ignore it. |
47 | | - let _ = sender.send(event_or_err); |
48 | | - }; |
49 | | - let mut watcher = notify::recommended_watcher(event_handler)?; |
| 44 | + let mut watcher = |
| 45 | + notify::recommended_watcher(move |event_or_err: notify::Result<Event>| { |
| 46 | + // pre-filter the events here |
| 47 | + let result = match event_or_err { |
| 48 | + Ok(ev) => match ev.kind { |
| 49 | + // we are interested in: |
| 50 | + // - create |
| 51 | + // - remove |
| 52 | + // - modify name |
| 53 | + EventKind::Create(_) |
| 54 | + | EventKind::Remove(_) |
| 55 | + | EventKind::Modify(ModifyKind::Name(_)) => Some(Ok(ev)), |
| 56 | + // we are not interested in: |
| 57 | + // - access |
| 58 | + // - modify something else (data, metadata) |
| 59 | + // - any other events |
| 60 | + EventKind::Access(_) |
| 61 | + | EventKind::Modify(_) |
| 62 | + | EventKind::Any |
| 63 | + | EventKind::Other => None, |
| 64 | + }, |
| 65 | + Err(err) => Some(Err(err)), |
| 66 | + }; |
| 67 | + |
| 68 | + if let Some(to_send) = result { |
| 69 | + // A send error only occurs when the channel is closed. No remedial |
| 70 | + // action is needed (or possible), so ignore it. |
| 71 | + let _ = sender.send(to_send); |
| 72 | + } |
| 73 | + })?; |
| 74 | + |
50 | 75 | watcher.watch(&dir, RecursiveMode::NonRecursive)?; |
51 | 76 |
|
52 | 77 | Ok(Self { |
@@ -89,56 +114,87 @@ impl DirectoryMonitor { |
89 | 114 | } |
90 | 115 | }; |
91 | 116 |
|
| 117 | + let mut paths = event.paths.into_iter(); |
| 118 | + |
92 | 119 | match event.kind { |
93 | | - EventKind::Create(..) => { |
94 | | - let path = event |
95 | | - .paths |
96 | | - .get(0) |
97 | | - .ok_or_else(|| format_err!("missing path for file create event"))? |
98 | | - .clone(); |
99 | | - |
100 | | - if self.report_directories { |
101 | | - return Ok(Some(path)); |
| 120 | + EventKind::Create(create_kind) => { |
| 121 | + let path = paths |
| 122 | + .next() |
| 123 | + .ok_or_else(|| format_err!("missing path for file create event"))?; |
| 124 | + |
| 125 | + match create_kind { |
| 126 | + CreateKind::File => { |
| 127 | + return Ok(Some(path)); |
| 128 | + } |
| 129 | + CreateKind::Folder => { |
| 130 | + if self.report_directories { |
| 131 | + return Ok(Some(path)); |
| 132 | + } |
| 133 | + } |
| 134 | + CreateKind::Any | CreateKind::Other => { |
| 135 | + if self.report_directories { |
| 136 | + return Ok(Some(path)); |
| 137 | + } |
| 138 | + |
| 139 | + // check if it is a file |
| 140 | + let metadata = fs::metadata(&path) |
| 141 | + .await |
| 142 | + .context("checking metadata for file")?; |
| 143 | + |
| 144 | + if metadata.is_file() { |
| 145 | + return Ok(Some(path)); |
| 146 | + } |
| 147 | + } |
102 | 148 | } |
| 149 | + } |
| 150 | + EventKind::Modify(ModifyKind::Name(rename_mode)) => { |
| 151 | + match rename_mode { |
| 152 | + RenameMode::To => { |
| 153 | + let path = paths.next().ok_or_else(|| { |
| 154 | + format_err!("missing 'to' path for file rename-to event") |
| 155 | + })?; |
103 | 156 |
|
104 | | - match fs::metadata(&path).await { |
105 | | - Ok(metadata) if metadata.is_file() => { |
106 | 157 | return Ok(Some(path)); |
107 | 158 | } |
108 | | - Ok(_) => { |
109 | | - // Ignore directories. |
110 | | - continue; |
| 159 | + RenameMode::Both => { |
| 160 | + let _from = paths.next().ok_or_else(|| { |
| 161 | + format_err!("missing 'from' path for file rename event") |
| 162 | + })?; |
| 163 | + |
| 164 | + let to = paths.next().ok_or_else(|| { |
| 165 | + format_err!("missing 'to' path for file rename event") |
| 166 | + })?; |
| 167 | + |
| 168 | + return Ok(Some(to)); |
111 | 169 | } |
112 | | - Err(err) if err.kind() == ErrorKind::NotFound => { |
113 | | - // Ignore if deleted. |
114 | | - continue; |
| 170 | + RenameMode::From => { |
| 171 | + // ignore rename-from |
115 | 172 | } |
116 | | - Err(err) => { |
117 | | - warn!( |
118 | | - "error checking metadata for file. path = {}, error = {}", |
119 | | - path.display(), |
120 | | - err |
| 173 | + RenameMode::Any | RenameMode::Other => { |
| 174 | + // something unusual, ignore |
| 175 | + info!( |
| 176 | + "unknown rename event: ignoring {:?} for path {:?}", |
| 177 | + rename_mode, |
| 178 | + paths.next() |
121 | 179 | ); |
122 | | - continue; |
123 | 180 | } |
124 | 181 | } |
125 | 182 | } |
126 | 183 | EventKind::Remove(..) => { |
127 | | - let path = event |
128 | | - .paths |
129 | | - .get(0) |
| 184 | + let path = paths |
| 185 | + .next() |
130 | 186 | .ok_or_else(|| format_err!("missing path for file remove event"))?; |
131 | 187 |
|
132 | | - if path == &self.dir { |
| 188 | + if path == self.dir { |
133 | 189 | // The directory we were watching was removed; we're done. |
134 | 190 | let _ = self.stop(); |
135 | 191 | return Ok(None); |
136 | 192 | } else { |
137 | 193 | // Some file _inside_ the watched directory was removed. Ignore. |
138 | 194 | } |
139 | 195 | } |
140 | | - _event_kind => { |
141 | | - // Other filesystem event. Ignore. |
| 196 | + EventKind::Access(_) | EventKind::Modify(_) | EventKind::Other | EventKind::Any => { |
| 197 | + unreachable!() // these events have already been filtered out |
142 | 198 | } |
143 | 199 | } |
144 | 200 | } |
|
0 commit comments