mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-22 04:51:47 +00:00
Avoid watchers for one-shot builds and preserve watcher errors (AI-assisted)
This commit is contained in:
parent
7e60406373
commit
691efad330
7 changed files with 160 additions and 14 deletions
|
|
@ -262,6 +262,16 @@ impl Vfs {
|
|||
Ok(Self::new(StdBackend::new()?))
|
||||
}
|
||||
|
||||
/// Creates a `Vfs` with `StdBackend` and automatic file watching disabled.
|
||||
///
|
||||
/// No filesystem watcher is allocated unless automatic watching is later
|
||||
/// enabled and a file or directory is read.
|
||||
pub fn new_default_without_watcher() -> Self {
|
||||
let vfs = Self::new(StdBackend::new_without_watcher());
|
||||
vfs.set_watch_enabled(false);
|
||||
vfs
|
||||
}
|
||||
|
||||
/// Creates a new `Vfs` with the given backend.
|
||||
pub fn new<B: VfsBackend>(backend: B) -> Self {
|
||||
let lock = VfsInner {
|
||||
|
|
@ -588,6 +598,30 @@ mod test {
|
|||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn watching_can_be_enabled_after_starting_without_watcher(
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let dir_path = dunce::canonicalize(dir.path())?;
|
||||
let vfs = Vfs::new_default_without_watcher();
|
||||
let receiver = vfs.event_receiver();
|
||||
|
||||
assert_eq!(vfs.read_dir(&dir_path)?.count(), 0);
|
||||
vfs.set_watch_enabled(true);
|
||||
assert_eq!(vfs.read_dir(&dir_path)?.count(), 0);
|
||||
|
||||
let file_path = dir_path.join("new.txt");
|
||||
fs_err::write(&file_path, "created after watching was enabled")?;
|
||||
let event = receiver.recv_timeout(std::time::Duration::from_secs(5))?;
|
||||
assert!(
|
||||
matches!(&event, crate::VfsEvent::Create(path) if path == &file_path),
|
||||
"expected creation of {}, got {:?}",
|
||||
file_path.display(),
|
||||
event
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// https://github.com/rojo-rbx/rojo/issues/899
|
||||
#[test]
|
||||
fn read_to_string_lf_normalized_keeps_trailing_newline() {
|
||||
|
|
|
|||
|
|
@ -4,24 +4,45 @@ use std::thread;
|
|||
use std::time::Duration;
|
||||
use std::{collections::HashSet, io};
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||
|
||||
/// `VfsBackend` that uses `std::fs` and the `notify` crate.
|
||||
pub struct StdBackend {
|
||||
watcher: RecommendedWatcher,
|
||||
watcher: Option<RecommendedWatcher>,
|
||||
watcher_sender: Sender<VfsEvent>,
|
||||
watcher_receiver: Receiver<VfsEvent>,
|
||||
watches: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl StdBackend {
|
||||
/// Creates a filesystem backend with an initialized file watcher.
|
||||
///
|
||||
/// Returns an error if the platform's watcher cannot be created.
|
||||
pub fn new() -> io::Result<StdBackend> {
|
||||
let (notify_tx, notify_rx) = mpsc::channel();
|
||||
let watcher = watcher(notify_tx, Duration::from_millis(50)).map_err(io::Error::other)?;
|
||||
let mut backend = Self::new_without_watcher();
|
||||
backend.watcher = Some(Self::create_watcher(backend.watcher_sender.clone())?);
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
/// Creates a filesystem backend without allocating a file watcher.
|
||||
///
|
||||
/// A watcher is initialized only if `watch` is subsequently called.
|
||||
pub fn new_without_watcher() -> StdBackend {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
Self {
|
||||
watcher: None,
|
||||
watcher_sender: tx,
|
||||
watcher_receiver: rx,
|
||||
watches: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_watcher(tx: Sender<VfsEvent>) -> io::Result<RecommendedWatcher> {
|
||||
let (notify_tx, notify_rx) = mpsc::channel();
|
||||
let watcher = watcher(notify_tx, Duration::from_millis(50)).map_err(notify_error_to_io)?;
|
||||
|
||||
thread::spawn(move || {
|
||||
for event in notify_rx {
|
||||
|
|
@ -46,11 +67,7 @@ impl StdBackend {
|
|||
Result::<(), crossbeam_channel::SendError<VfsEvent>>::Ok(())
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
watcher,
|
||||
watcher_receiver: rx,
|
||||
watches: HashSet::new(),
|
||||
})
|
||||
Ok(watcher)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,15 +139,47 @@ impl VfsBackend for StdBackend {
|
|||
{
|
||||
Ok(())
|
||||
} else {
|
||||
let watcher = match &mut self.watcher {
|
||||
Some(watcher) => watcher,
|
||||
slot @ None => slot.insert(Self::create_watcher(self.watcher_sender.clone())?),
|
||||
};
|
||||
self.watches.insert(path.to_path_buf());
|
||||
self.watcher
|
||||
watcher
|
||||
.watch(path, RecursiveMode::Recursive)
|
||||
.map_err(io::Error::other)
|
||||
.map_err(notify_error_to_io)
|
||||
}
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||
self.watches.remove(path);
|
||||
self.watcher.unwatch(path).map_err(io::Error::other)
|
||||
match &mut self.watcher {
|
||||
Some(watcher) => watcher.unwatch(path).map_err(notify_error_to_io),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_error_to_io(error: notify::Error) -> io::Error {
|
||||
match error {
|
||||
// notify 4 formats I/O errors using the deprecated Error::description,
|
||||
// which can hide the actual operating system error.
|
||||
notify::Error::Io(error) => error,
|
||||
error => io::Error::other(error),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::notify_error_to_io;
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn notify_io_error_preserves_message_and_kind() {
|
||||
let message = "filesystem watcher permission denied";
|
||||
let source = io::Error::new(io::ErrorKind::PermissionDenied, message);
|
||||
let error = notify_error_to_io(notify::Error::Io(source));
|
||||
|
||||
assert_eq!(error.to_string(), message);
|
||||
assert_eq!(error.kind(), io::ErrorKind::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue