rust/crates/vfs-notify/src/lib.rs

228 lines
8.5 KiB
Rust
Raw Normal View History

2020-06-11 04:04:09 -05:00
//! An implementation of `loader::Handle`, based on `walkdir` and `notify`.
//!
//! The file watching bits here are untested and quite probably buggy. For this
//! reason, by default we don't watch files and rely on editor's file watching
//! capabilities.
//!
//! Hopefully, one day a reliable file watching/walking crate appears on
//! crates.io, and we can reduce this to trivial glue code.
2020-07-18 09:40:10 -05:00
use std::convert::TryFrom;
2020-06-11 04:04:09 -05:00
2020-07-06 02:28:17 -05:00
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
2020-06-11 04:04:09 -05:00
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use paths::{AbsPath, AbsPathBuf};
use vfs::loader;
use walkdir::WalkDir;
#[derive(Debug)]
2020-06-25 01:59:55 -05:00
pub struct NotifyHandle {
2020-06-11 04:04:09 -05:00
// Relative order of fields below is significant.
2020-06-28 15:35:18 -05:00
sender: Sender<Message>,
thread: jod_thread::JoinHandle,
2020-06-11 04:04:09 -05:00
}
#[derive(Debug)]
enum Message {
Config(loader::Config),
Invalidate(AbsPathBuf),
}
2020-06-25 01:59:55 -05:00
impl loader::Handle for NotifyHandle {
fn spawn(sender: loader::Sender) -> NotifyHandle {
let actor = NotifyActor::new(sender);
2020-06-11 04:04:09 -05:00
let (sender, receiver) = unbounded::<Message>();
let thread = jod_thread::spawn(move || actor.run(receiver));
2020-06-28 15:35:18 -05:00
NotifyHandle { sender, thread }
2020-06-11 04:04:09 -05:00
}
fn set_config(&mut self, config: loader::Config) {
self.sender.send(Message::Config(config)).unwrap()
}
fn invalidate(&mut self, path: AbsPathBuf) {
self.sender.send(Message::Invalidate(path)).unwrap();
}
2020-06-24 08:52:07 -05:00
fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>> {
2020-06-11 04:04:09 -05:00
read(path)
}
}
type NotifyEvent = notify::Result<notify::Event>;
2020-06-25 01:59:55 -05:00
struct NotifyActor {
sender: loader::Sender,
2020-07-18 09:40:10 -05:00
watched_entries: Vec<loader::Entry>,
2020-07-06 02:28:17 -05:00
// Drop order is significant.
watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
2020-06-11 04:04:09 -05:00
}
#[derive(Debug)]
enum Event {
Message(Message),
NotifyEvent(NotifyEvent),
}
2020-06-25 01:59:55 -05:00
impl NotifyActor {
fn new(sender: loader::Sender) -> NotifyActor {
2020-07-18 09:40:10 -05:00
NotifyActor { sender, watched_entries: Vec::new(), watcher: None }
2020-06-11 04:04:09 -05:00
}
2020-06-25 10:14:11 -05:00
fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
2020-07-06 02:28:17 -05:00
let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
2020-06-25 10:14:11 -05:00
select! {
recv(receiver) -> it => it.ok().map(Event::Message),
2020-07-06 02:28:17 -05:00
recv(watcher_receiver.unwrap_or(&never())) -> it => Some(Event::NotifyEvent(it.unwrap())),
2020-06-25 10:14:11 -05:00
}
}
2020-06-25 06:47:22 -05:00
fn run(mut self, inbox: Receiver<Message>) {
while let Some(event) = self.next_event(&inbox) {
2020-06-11 04:04:09 -05:00
log::debug!("vfs-notify event: {:?}", event);
match event {
Event::Message(msg) => match msg {
Message::Config(config) => {
2020-07-06 02:28:17 -05:00
self.watcher = None;
2020-07-10 16:39:25 -05:00
if !config.watch.is_empty() {
let (watcher_sender, watcher_receiver) = unbounded();
let watcher = log_notify_error(Watcher::new_immediate(move |event| {
watcher_sender.send(event).unwrap()
}));
self.watcher = watcher.map(|it| (it, watcher_receiver));
}
2020-07-06 02:28:17 -05:00
let config_version = config.version;
2020-06-24 09:58:49 -05:00
let n_total = config.load.len();
self.send(loader::Message::Progress { n_total, n_done: 0, config_version });
2020-06-11 04:04:09 -05:00
2020-07-18 09:40:10 -05:00
self.watched_entries.clear();
2020-06-11 04:04:09 -05:00
for (i, entry) in config.load.into_iter().enumerate() {
let watch = config.watch.contains(&i);
2020-07-18 09:40:10 -05:00
if watch {
self.watched_entries.push(entry.clone())
}
2020-06-11 04:04:09 -05:00
let files = self.load_entry(entry, watch);
self.send(loader::Message::Loaded { files });
self.send(loader::Message::Progress {
n_total,
n_done: i + 1,
config_version,
});
2020-06-11 04:04:09 -05:00
}
}
Message::Invalidate(path) => {
let contents = read(path.as_path());
let files = vec![(path, contents)];
self.send(loader::Message::Loaded { files });
}
},
Event::NotifyEvent(event) => {
if let Some(event) = log_notify_error(event) {
let files = event
.paths
.into_iter()
.map(|path| AbsPathBuf::try_from(path).unwrap())
.filter_map(|path| {
2020-07-18 09:40:10 -05:00
if path.is_dir()
&& self
.watched_entries
.iter()
.any(|entry| entry.contains_dir(&path))
{
self.watch(path);
return None;
2020-06-11 04:04:09 -05:00
}
2020-07-18 09:40:10 -05:00
if !path.is_file() {
2020-06-11 04:04:09 -05:00
return None;
}
2020-07-18 09:40:10 -05:00
if !self
.watched_entries
.iter()
.any(|entry| entry.contains_file(&path))
{
2020-06-11 04:04:09 -05:00
return None;
}
2020-07-18 09:40:10 -05:00
2020-06-11 04:04:09 -05:00
let contents = read(&path);
Some((path, contents))
})
.collect();
self.send(loader::Message::Loaded { files })
}
}
}
}
}
fn load_entry(
&mut self,
entry: loader::Entry,
watch: bool,
) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
match entry {
loader::Entry::Files(files) => files
.into_iter()
.map(|file| {
if watch {
self.watch(file.clone())
}
let contents = read(file.as_path());
(file, contents)
})
.collect::<Vec<_>>(),
2020-07-18 09:40:10 -05:00
loader::Entry::Directories(dirs) => {
let mut res = Vec::new();
for root in dirs.include.iter() {
let walkdir =
WalkDir::new(root).follow_links(true).into_iter().filter_entry(|entry| {
if !entry.file_type().is_dir() {
return true;
}
let path = AbsPath::assert(entry.path());
root == path
|| dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
});
2020-07-18 09:40:10 -05:00
let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
2020-06-11 04:04:09 -05:00
let is_dir = entry.file_type().is_dir();
let is_file = entry.file_type().is_file();
2020-07-18 09:40:10 -05:00
let abs_path = AbsPathBuf::assert(entry.into_path());
2020-06-24 10:11:07 -05:00
if is_dir && watch {
2020-06-11 04:04:09 -05:00
self.watch(abs_path.clone());
}
2020-07-18 09:40:10 -05:00
if !is_file {
return None;
}
let ext = abs_path.extension().unwrap_or_default();
if dirs.extensions.iter().all(|it| it.as_str() != ext) {
return None;
2020-06-11 04:04:09 -05:00
}
2020-07-18 09:40:10 -05:00
Some(abs_path)
2020-06-11 04:04:09 -05:00
});
2020-07-18 09:40:10 -05:00
res.extend(files.map(|file| {
2020-06-11 04:04:09 -05:00
let contents = read(file.as_path());
(file, contents)
2020-07-18 09:40:10 -05:00
}));
}
res
2020-06-11 04:04:09 -05:00
}
}
}
fn watch(&mut self, path: AbsPathBuf) {
2020-07-06 02:28:17 -05:00
if let Some((watcher, _)) = &mut self.watcher {
2020-06-11 04:04:09 -05:00
log_notify_error(watcher.watch(&path, RecursiveMode::NonRecursive));
}
}
fn send(&mut self, msg: loader::Message) {
(self.sender)(msg)
}
}
fn read(path: &AbsPath) -> Option<Vec<u8>> {
std::fs::read(path).ok()
}
fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
res.map_err(|err| log::warn!("notify error: {}", err)).ok()
}