forked from zed-industries/zed
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlinux_watcher.rs
165 lines (148 loc) · 6.33 KB
/
linux_watcher.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use notify::EventKind;
use parking_lot::Mutex;
use std::sync::{Arc, OnceLock};
use util::ResultExt;
use crate::{PathEvent, PathEventKind, Watcher};
pub struct LinuxWatcher {
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
}
impl LinuxWatcher {
pub fn new(
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
) -> Self {
Self {
tx,
pending_path_events,
}
}
}
impl Watcher for LinuxWatcher {
fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
let root_path = path.to_path_buf();
// Canonicalize the root path to handle cases where it's a symlink or below one
let target_path = std::fs::canonicalize(&path).ok();
let is_canonical = target_path == Some(root_path.clone());
let tx = self.tx.clone();
let pending_paths = self.pending_path_events.clone();
use notify::Watcher;
global({
|g| {
g.add(move |event: ¬ify::Event| {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let mut path_events = event
.paths
.iter()
.filter_map(|event_path| {
// we canonicalize the parent and join with file name to handle cases
// where the file doesn't exist anymore
if let Some(parent) = event_path.parent() {
if event_path.clone().starts_with(parent) {
if !is_canonical {
if let Ok(canonical_parent) = std::fs::canonicalize(&parent)
{
if let Some(file_name) = event_path.file_name() {
return Some(PathEvent {
path: canonical_parent.join(file_name),
kind,
});
}
}
} else {
return Some(PathEvent {
path: event_path.clone(),
kind,
});
}
} else {
if let Ok(canonical_parent) = std::fs::canonicalize(&parent) {
if event_path.starts_with(canonical_parent.clone()) {
if !is_canonical {
if let Some(file_name) = event_path.file_name() {
return Some(PathEvent {
path: canonical_parent.join(file_name),
kind,
});
}
} else {
return Some(PathEvent {
path: event_path.clone(),
kind,
});
}
}
}
}
}
None
})
.collect::<Vec<_>>();
if !path_events.is_empty() {
path_events.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(
&mut *pending_paths,
path_events,
usize::MAX,
|a, b| a.path.cmp(&b.path),
);
}
})
}
})?;
global(|g| {
g.inotify
.lock()
.watch(path, notify::RecursiveMode::NonRecursive)
})??;
Ok(())
}
fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
use notify::Watcher;
Ok(global(|w| w.inotify.lock().unwatch(path))??)
}
}
pub struct GlobalWatcher {
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
#[cfg(target_os = "linux")]
pub(super) inotify: Mutex<notify::INotifyWatcher>,
#[cfg(target_os = "freebsd")]
pub(super) inotify: Mutex<notify::KqueueWatcher>,
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
}
impl GlobalWatcher {
pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
self.watchers.lock().push(Box::new(cb))
}
}
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
fn handle_event(event: Result<notify::Event, notify::Error>) {
let Some(event) = event.log_err() else { return };
global::<()>(move |watcher| {
for f in watcher.watchers.lock().iter() {
f(&event)
}
})
.log_err();
}
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
let result = INOTIFY_INSTANCE.get_or_init(|| {
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
inotify: Mutex::new(file_watcher),
watchers: Default::default(),
})
});
match result {
Ok(g) => Ok(f(g)),
Err(e) => Err(anyhow::anyhow!("{}", e)),
}
}