Add is_watch_enabled method to VFS and enhance prefetch cache tests

- Introduced `is_watch_enabled` method to check the status of automatic file watching in the VFS.
- Added a new test for prefetch cache to validate metadata retrieval and error handling for walked roots and external paths.
- Updated the `watch` method in `StdBackend` for cleaner logic.
- Enhanced the `dir_init` field in `PrefetchCache` to store resolved init-file information for directories, improving prefetch operations.
This commit is contained in:
Joe 2026-03-06 21:51:22 -06:00
parent 8b18dc451a
commit 5275d45adc
6 changed files with 91 additions and 9 deletions

View file

@ -419,6 +419,11 @@ impl Vfs {
}
}
/// Returns whether automatic file watching is currently enabled.
pub fn is_watch_enabled(&self) -> bool {
self.inner.lock().unwrap().watch_enabled
}
/// Turns automatic file watching on or off. Enabled by default.
///
/// Turning off file watching may be useful for single-use cases, especially
@ -1008,6 +1013,63 @@ mod test {
}
}
#[test]
fn prefetch_cache_walked_roots_fast_reject() {
let mut imfs = InMemoryFs::new();
imfs.load_snapshot(
"/root",
VfsSnapshot::dir(HashMap::from([
("known.txt", VfsSnapshot::file("hello")),
(
"subdir",
VfsSnapshot::dir(HashMap::from([("nested.txt", VfsSnapshot::file("world"))])),
),
])),
)
.unwrap();
imfs.load_snapshot("/outside/other.txt", VfsSnapshot::file("external"))
.unwrap();
let vfs = Vfs::new(imfs);
let mut is_file = HashMap::new();
is_file.insert(PathBuf::from("/root"), false);
is_file.insert(PathBuf::from("/root/known.txt"), true);
is_file.insert(PathBuf::from("/root/subdir"), false);
vfs.set_prefetch_cache(PrefetchCache {
files: HashMap::new(),
is_file,
children: HashMap::new(),
dir_init: HashMap::new(),
walked_roots: vec![PathBuf::from("/root")],
});
let known = vfs.metadata("/root/known.txt").unwrap();
assert!(
known.is_file(),
"cached entry should return correct metadata"
);
let subdir = vfs.metadata("/root/subdir").unwrap();
assert!(
subdir.is_dir(),
"cached directory should return correct metadata"
);
let missing = vfs.metadata("/root/subdir/nested.txt");
assert!(
missing.is_err(),
"path under walked root but not in cache should return NotFound"
);
assert_eq!(missing.unwrap_err().kind(), io::ErrorKind::NotFound);
let outside = vfs.metadata("/outside/other.txt").unwrap();
assert!(
outside.is_file(),
"path outside walked roots should fall through to backend"
);
}
#[test]
fn prefetch_cache_read_after_write_ignores_cache() {
let mut imfs = InMemoryFs::new();

View file

@ -331,12 +331,8 @@ impl VfsBackend for StdBackend {
}
fn watch(&mut self, path: &Path, recursive: bool) -> io::Result<()> {
if self.watches.contains(path) {
if !recursive || self.recursive_watches.contains(path) {
return Ok(());
}
// Upgrade: path was watched non-recursively, now needs recursive.
// Fall through to re-issue the watch with RecursiveMode::Recursive.
if self.watches.contains(path) && (!recursive || self.recursive_watches.contains(path)) {
return Ok(());
}
if path
.ancestors()

View file

@ -39,6 +39,6 @@ pub use snapshot::{
InstanceContext, InstanceMetadata, InstanceSnapshot, InstanceWithMeta, InstanceWithMetaMut,
RojoDescendants, RojoTree,
};
pub use snapshot_middleware::{snapshot_from_vfs, Middleware, ScriptType};
pub use snapshot_middleware::{snapshot_from_vfs, Middleware, ScriptType, INIT_FILE_PRIORITY};
pub use syncback::{syncback_loop, FsSnapshot, SyncbackData, SyncbackResult, SyncbackSnapshot};
pub use web::interface as web_api;

View file

@ -349,9 +349,10 @@ impl ServeSession {
let snap_start = Instant::now();
log::trace!("Generating snapshot of instances from VFS");
let was_watch_enabled = vfs.is_watch_enabled();
vfs.set_watch_enabled(false);
let snapshot = snapshot_from_vfs(&instance_context, vfs, start_path)?;
vfs.set_watch_enabled(true);
vfs.set_watch_enabled(was_watch_enabled);
log::debug!("Snapshot built in {:.1?}", snap_start.elapsed());
vfs.clear_prefetch_cache();

View file

@ -544,6 +544,12 @@ mod test {
"DEFAULT_PROJECT_NAMES entry {project_name:?} missing from INIT_FILE_PRIORITY"
);
}
for &name in &priority_names {
assert!(
DEFAULT_PROJECT_NAMES.contains(&name),
"INIT_FILE_PRIORITY has Project entry {name:?} not in DEFAULT_PROJECT_NAMES"
);
}
}
#[test]

View file

@ -263,12 +263,29 @@ fn parallel_snapshot_with_prefetch_cache() {
children.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
}
let mut dir_init: HashMap<std::path::PathBuf, Option<(String, std::path::PathBuf)>> =
HashMap::new();
for (dir_path, child_paths) in &children_map {
let child_names: std::collections::HashSet<&str> = child_paths
.iter()
.filter_map(|p| p.file_name().and_then(|n| n.to_str()))
.collect();
let mut found = None;
for &(_, init_name) in librojo::INIT_FILE_PRIORITY {
if child_names.contains(init_name) {
found = Some((init_name.to_string(), dir_path.join(init_name)));
break;
}
}
dir_init.insert(dir_path.clone(), found);
}
let vfs_cached = memofs::Vfs::new_default();
vfs_cached.set_prefetch_cache(memofs::PrefetchCache {
files,
is_file,
children: children_map,
dir_init: std::collections::HashMap::new(),
dir_init,
walked_roots: Vec::new(),
});