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
|
use std::path::PathBuf;
use std::sync::mpsc::{self, RecvTimeoutError};
use std::time::{Duration, Instant};
use log::{debug, error};
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use winit::event_loop::EventLoopProxy;
use alacritty_terminal::thread;
use crate::event::{Event, EventType};
const DEBOUNCE_DELAY: Duration = Duration::from_millis(10);
/// The fallback for `RecommendedWatcher` polling.
const FALLBACK_POLLING_TIMEOUT: Duration = Duration::from_secs(1);
pub fn watch(mut paths: Vec<PathBuf>, event_proxy: EventLoopProxy<Event>) {
// Don't monitor config if there is no path to watch.
if paths.is_empty() {
return;
}
// Exclude char devices like `/dev/null`, sockets, and so on, by checking that file type is a
// regular file.
paths.retain(|path| {
// Call `metadata` to resolve symbolic links.
path.metadata().map_or(false, |metadata| metadata.file_type().is_file())
});
// Canonicalize paths, keeping the base paths for symlinks.
for i in 0..paths.len() {
if let Ok(canonical_path) = paths[i].canonicalize() {
match paths[i].symlink_metadata() {
Ok(metadata) if metadata.file_type().is_symlink() => paths.push(canonical_path),
_ => paths[i] = canonical_path,
}
}
}
// The Duration argument is a debouncing period.
let (tx, rx) = mpsc::channel();
let mut watcher = match RecommendedWatcher::new(
tx,
Config::default().with_poll_interval(FALLBACK_POLLING_TIMEOUT),
) {
Ok(watcher) => watcher,
Err(err) => {
error!("Unable to watch config file: {}", err);
return;
},
};
thread::spawn_named("config watcher", move || {
// Get all unique parent directories.
let mut parents = paths
.iter()
.map(|path| {
let mut path = path.clone();
path.pop();
path
})
.collect::<Vec<PathBuf>>();
parents.sort_unstable();
parents.dedup();
// Watch all configuration file directories.
for parent in &parents {
if let Err(err) = watcher.watch(parent, RecursiveMode::NonRecursive) {
debug!("Unable to watch config directory {:?}: {}", parent, err);
}
}
// The current debouncing time.
let mut debouncing_deadline: Option<Instant> = None;
// The events accumulated during the debounce period.
let mut received_events = Vec::new();
loop {
// We use `recv_timeout` to debounce the events coming from the watcher and reduce
// the amount of config reloads.
let event = match debouncing_deadline.as_ref() {
Some(debouncing_deadline) => {
rx.recv_timeout(debouncing_deadline.saturating_duration_since(Instant::now()))
},
None => {
let event = rx.recv().map_err(Into::into);
// Set the debouncing deadline after receiving the event.
debouncing_deadline = Some(Instant::now() + DEBOUNCE_DELAY);
event
},
};
match event {
Ok(Ok(event)) => match event.kind {
EventKind::Any
| EventKind::Create(_)
| EventKind::Modify(_)
| EventKind::Other => {
received_events.push(event);
},
_ => (),
},
Err(RecvTimeoutError::Timeout) => {
// Go back to polling the events.
debouncing_deadline = None;
if received_events
.drain(..)
.flat_map(|event| event.paths.into_iter())
.any(|path| paths.contains(&path))
{
// Always reload the primary configuration file.
let event = Event::new(EventType::ConfigReload(paths[0].clone()), None);
let _ = event_proxy.send_event(event);
}
},
Ok(Err(err)) => {
debug!("Config watcher errors: {:?}", err);
},
Err(err) => {
debug!("Config watcher channel dropped unexpectedly: {}", err);
break;
},
};
}
});
}
|