rust/crates/ra_vfs/src/io.rs

254 lines
7.8 KiB
Rust
Raw Normal View History

2019-01-26 13:11:47 +01:00
use std::{
fs,
2019-01-26 14:40:24 +01:00
thread,
2019-01-26 13:11:47 +01:00
path::{Path, PathBuf},
sync::{mpsc, Arc},
time::Duration,
};
2019-01-26 14:40:24 +01:00
use crossbeam_channel::{Receiver, Sender};
2018-12-18 14:38:05 +01:00
use relative_path::RelativePathBuf;
use thread_worker::WorkerHandle;
2019-01-21 19:11:39 +01:00
use walkdir::WalkDir;
2019-01-26 13:11:47 +01:00
use parking_lot::Mutex;
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
2018-12-18 11:35:05 +01:00
2019-01-26 13:11:47 +01:00
use crate::{RootConfig, Roots, VfsRoot};
2018-12-18 11:35:05 +01:00
2019-01-26 14:40:24 +01:00
type Result<T> = std::result::Result<T, crossbeam_channel::SendError<TaskResult>>;
pub(crate) enum Task {
AddRoot {
root: VfsRoot,
2019-01-26 13:11:47 +01:00
config: Arc<RootConfig>,
},
2018-12-18 11:18:55 +01:00
}
2018-12-18 10:29:14 +01:00
#[derive(Debug)]
pub enum TaskResult {
2019-01-25 18:39:35 +01:00
BulkLoadRoot {
root: VfsRoot,
files: Vec<(RelativePathBuf, String)>,
},
AddSingleFile {
root: VfsRoot,
path: RelativePathBuf,
text: String,
},
ChangeSingleFile {
root: VfsRoot,
path: RelativePathBuf,
text: String,
},
RemoveSingleFile {
root: VfsRoot,
path: RelativePathBuf,
},
2018-12-19 13:04:15 +01:00
}
2019-01-26 13:11:47 +01:00
#[derive(Debug)]
enum ChangeKind {
Create,
Write,
Remove,
}
const WATCHER_DELAY: Duration = Duration::from_millis(250);
pub(crate) struct Worker {
worker: thread_worker::Worker<Task, TaskResult>,
worker_handle: WorkerHandle,
}
impl Worker {
2019-01-25 18:39:35 +01:00
pub(crate) fn start(roots: Arc<Roots>) -> Worker {
let (worker, worker_handle) =
thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| {
2019-01-26 13:11:47 +01:00
let (notify_sender, notify_receiver) = mpsc::channel();
let watcher = notify::watcher(notify_sender, WATCHER_DELAY)
.map_err(|e| log::error!("failed to spawn notify {}", e))
.ok();
let ctx = WatcherCtx {
roots,
watcher: Arc::new(Mutex::new(watcher)),
sender: output_sender,
2019-01-25 18:39:35 +01:00
};
2019-01-26 13:11:47 +01:00
let thread = thread::spawn({
let ctx = ctx.clone();
move || {
let _ = notify_receiver
.into_iter()
// forward relevant events only
.try_for_each(|change| ctx.handle_debounced_event(change));
}
});
let res1 = input_receiver.into_iter().try_for_each(|t| match t {
Task::AddRoot { root, config } => watch_root(&ctx, root, Arc::clone(&config)),
});
drop(ctx.watcher.lock().take());
drop(ctx);
let res2 = thread.join();
match &res2 {
Ok(()) => log::info!("... Watcher terminated with ok"),
Err(_) => log::error!("... Watcher terminated with err"),
2019-01-25 18:39:35 +01:00
}
2019-01-26 13:11:47 +01:00
res1.unwrap();
res2.unwrap();
});
Worker {
worker,
worker_handle,
}
}
pub(crate) fn sender(&self) -> &Sender<Task> {
&self.worker.inp
}
pub(crate) fn receiver(&self) -> &Receiver<TaskResult> {
&self.worker.out
}
pub(crate) fn shutdown(self) -> thread::Result<()> {
2019-01-21 18:37:46 +01:00
let _ = self.worker.shutdown();
self.worker_handle.shutdown()
}
}
2018-12-18 11:35:05 +01:00
2019-01-26 14:40:24 +01:00
fn watch_root(woker: &WatcherCtx, root: VfsRoot, config: Arc<RootConfig>) -> Result<()> {
2019-01-26 13:11:47 +01:00
let mut guard = woker.watcher.lock();
log::debug!("loading {} ...", config.root.as_path().display());
let files = watch_recursive(guard.as_mut(), config.root.as_path(), &*config)
.into_iter()
.filter_map(|path| {
let abs_path = path.to_path(&config.root);
2019-01-26 13:19:24 +01:00
let text = read_to_string(&abs_path)?;
2019-01-26 13:11:47 +01:00
Some((path, text))
})
.collect();
woker
.sender
.send(TaskResult::BulkLoadRoot { root, files })?;
log::debug!("... loaded {}", config.root.as_path().display());
Ok(())
}
#[derive(Clone)]
struct WatcherCtx {
roots: Arc<Roots>,
watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
sender: Sender<TaskResult>,
}
impl WatcherCtx {
2019-01-26 14:40:24 +01:00
fn handle_debounced_event(&self, ev: DebouncedEvent) -> Result<()> {
2019-01-26 13:11:47 +01:00
match ev {
DebouncedEvent::NoticeWrite(_)
| DebouncedEvent::NoticeRemove(_)
| DebouncedEvent::Chmod(_) => {
// ignore
}
DebouncedEvent::Rescan => {
// TODO rescan all roots
}
DebouncedEvent::Create(path) => {
self.handle_change(path, ChangeKind::Create)?;
}
DebouncedEvent::Write(path) => {
self.handle_change(path, ChangeKind::Write)?;
2019-01-25 18:39:35 +01:00
}
2019-01-26 13:11:47 +01:00
DebouncedEvent::Remove(path) => {
self.handle_change(path, ChangeKind::Remove)?;
}
DebouncedEvent::Rename(src, dst) => {
self.handle_change(src, ChangeKind::Remove)?;
self.handle_change(dst, ChangeKind::Create)?;
}
DebouncedEvent::Error(err, path) => {
// TODO should we reload the file contents?
log::warn!("watcher error \"{}\", {:?}", err, path);
}
}
Ok(())
}
2019-01-26 14:40:24 +01:00
fn handle_change(&self, path: PathBuf, kind: ChangeKind) -> Result<()> {
2019-01-26 13:11:47 +01:00
let (root, rel_path) = match self.roots.find(&path) {
None => return Ok(()),
Some(it) => it,
};
let config = &self.roots[root];
match kind {
ChangeKind::Create => {
let mut paths = Vec::new();
if path.is_dir() {
let mut guard = self.watcher.lock();
paths.extend(watch_recursive(guard.as_mut(), &path, &config));
} else {
paths.push(rel_path);
}
paths
.into_iter()
.filter_map(|rel_path| {
let abs_path = rel_path.to_path(&config.root);
2019-01-26 13:19:24 +01:00
let text = read_to_string(&abs_path)?;
2019-01-26 13:11:47 +01:00
Some((rel_path, text))
})
.try_for_each(|(path, text)| {
self.sender
.send(TaskResult::AddSingleFile { root, path, text })
})?
}
2019-01-26 13:19:24 +01:00
ChangeKind::Write => {
if let Some(text) = read_to_string(&path) {
self.sender.send(TaskResult::ChangeSingleFile {
root,
path: rel_path,
text,
})?;
}
}
2019-01-26 13:11:47 +01:00
ChangeKind::Remove => self.sender.send(TaskResult::RemoveSingleFile {
root,
path: rel_path,
})?,
}
2019-01-26 13:11:47 +01:00
Ok(())
}
2018-12-18 11:18:55 +01:00
}
2018-12-18 10:29:14 +01:00
2019-01-26 13:11:47 +01:00
fn watch_recursive(
mut watcher: Option<&mut RecommendedWatcher>,
dir: &Path,
config: &RootConfig,
) -> Vec<RelativePathBuf> {
let mut files = Vec::new();
for entry in WalkDir::new(dir)
2019-01-25 18:39:35 +01:00
.into_iter()
2019-01-26 13:11:47 +01:00
.filter_entry(|it| config.contains(it.path()).is_some())
.filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok())
2019-01-25 18:39:35 +01:00
{
2019-01-26 13:11:47 +01:00
if entry.file_type().is_dir() {
if let Some(watcher) = &mut watcher {
watch_one(watcher, entry.path());
2018-12-18 11:18:55 +01:00
}
2019-01-26 13:11:47 +01:00
} else {
let path = config.contains(entry.path()).unwrap();
files.push(path.to_owned());
2018-12-18 11:18:55 +01:00
}
}
2019-01-26 13:11:47 +01:00
files
}
fn watch_one(watcher: &mut RecommendedWatcher, dir: &Path) {
match watcher.watch(dir, RecursiveMode::NonRecursive) {
Ok(()) => log::debug!("watching \"{}\"", dir.display()),
Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
}
2018-12-18 11:18:55 +01:00
}
2019-01-26 13:19:24 +01:00
fn read_to_string(path: &Path) -> Option<String> {
fs::read_to_string(&path)
.map_err(|e| log::warn!("failed to read file {}", e))
.ok()
}