Skip to content

Commit ccac2b3

Browse files
committed
Cache information about file type
This commit adds a cache that remembers whether a given path is a file or a directory, based on the results of `std::fs::read_dir`. This reduces the number of executed syscalls and improves the performance of the library.
1 parent 7b29204 commit ccac2b3

File tree

1 file changed

+61
-19
lines changed

1 file changed

+61
-19
lines changed

src/lib.rs

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ use std::cmp;
7373
use std::error::Error;
7474
use std::fmt;
7575
use std::fs;
76+
use std::fs::DirEntry;
7677
use std::io;
78+
use std::ops::Deref;
7779
use std::path::{self, Component, Path, PathBuf};
7880
use std::str::FromStr;
7981

@@ -96,8 +98,8 @@ pub struct Paths {
9698
dir_patterns: Vec<Pattern>,
9799
require_dir: bool,
98100
options: MatchOptions,
99-
todo: Vec<Result<(PathBuf, usize), GlobError>>,
100-
scope: Option<PathBuf>,
101+
todo: Vec<Result<(PathWrapper, usize), GlobError>>,
102+
scope: Option<PathWrapper>,
101103
}
102104

103105
/// Return an iterator that produces all the `Path`s that match the given
@@ -242,6 +244,7 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternE
242244
}
243245

244246
let scope = root.map_or_else(|| PathBuf::from("."), to_scope);
247+
let scope = PathWrapper::from_path(scope);
245248

246249
let mut dir_patterns = Vec::new();
247250
let components =
@@ -323,8 +326,44 @@ impl fmt::Display for GlobError {
323326
}
324327
}
325328

326-
fn is_dir(p: &Path) -> bool {
327-
fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
329+
#[derive(Debug)]
330+
struct PathWrapper {
331+
path: PathBuf,
332+
is_directory: bool,
333+
}
334+
335+
impl PathWrapper {
336+
fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self {
337+
let is_directory = e
338+
.file_type()
339+
.ok()
340+
.map(|file_type| file_type.is_dir())
341+
.or_else(|| fs::metadata(&path).map(|m| m.is_dir()).ok())
342+
.unwrap_or(false);
343+
Self { path, is_directory }
344+
}
345+
fn from_path(path: PathBuf) -> Self {
346+
let is_directory = fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false);
347+
Self { path, is_directory }
348+
}
349+
350+
fn into_path(self) -> PathBuf {
351+
self.path
352+
}
353+
}
354+
355+
impl Deref for PathWrapper {
356+
type Target = Path;
357+
358+
fn deref(&self) -> &Self::Target {
359+
self.path.deref()
360+
}
361+
}
362+
363+
impl AsRef<Path> for PathWrapper {
364+
fn as_ref(&self) -> &Path {
365+
self.path.as_ref()
366+
}
328367
}
329368

330369
/// An alias for a glob iteration result.
@@ -363,10 +402,10 @@ impl Iterator for Paths {
363402
// idx -1: was already checked by fill_todo, maybe path was '.' or
364403
// '..' that we can't match here because of normalization.
365404
if idx == !0 as usize {
366-
if self.require_dir && !is_dir(&path) {
405+
if self.require_dir && !path.is_directory {
367406
continue;
368407
}
369-
return Some(Ok(path));
408+
return Some(Ok(path.into_path()));
370409
}
371410

372411
if self.dir_patterns[idx].is_recursive {
@@ -379,7 +418,7 @@ impl Iterator for Paths {
379418
next += 1;
380419
}
381420

382-
if is_dir(&path) {
421+
if path.is_directory {
383422
// the path is a directory, so it's a match
384423

385424
// push this directory's contents
@@ -394,7 +433,7 @@ impl Iterator for Paths {
394433
if next == self.dir_patterns.len() - 1 {
395434
// pattern ends in recursive pattern, so return this
396435
// directory as a result
397-
return Some(Ok(path));
436+
return Some(Ok(path.into_path()));
398437
} else {
399438
// advanced to the next pattern for this path
400439
idx = next + 1;
@@ -427,8 +466,8 @@ impl Iterator for Paths {
427466
// *AND* its children so we don't need to check the
428467
// children
429468

430-
if !self.require_dir || is_dir(&path) {
431-
return Some(Ok(path));
469+
if !self.require_dir || path.is_directory {
470+
return Some(Ok(path.into_path()));
432471
}
433472
} else {
434473
fill_todo(
@@ -817,10 +856,10 @@ impl Pattern {
817856
// special-casing patterns to match `.` and `..`, and avoiding `readdir()`
818857
// calls when there are no metacharacters in the pattern.
819858
fn fill_todo(
820-
todo: &mut Vec<Result<(PathBuf, usize), GlobError>>,
859+
todo: &mut Vec<Result<(PathWrapper, usize), GlobError>>,
821860
patterns: &[Pattern],
822861
idx: usize,
823-
path: &Path,
862+
path: &PathWrapper,
824863
options: MatchOptions,
825864
) {
826865
// convert a pattern that's just many Char(_) to a string
@@ -836,7 +875,7 @@ fn fill_todo(
836875
Some(s)
837876
}
838877

839-
let add = |todo: &mut Vec<_>, next_path: PathBuf| {
878+
let add = |todo: &mut Vec<_>, next_path: PathWrapper| {
840879
if idx + 1 == patterns.len() {
841880
// We know it's good, so don't make the iterator match this path
842881
// against the pattern again. In particular, it can't match
@@ -848,8 +887,8 @@ fn fill_todo(
848887
};
849888

850889
let pattern = &patterns[idx];
851-
let is_dir = is_dir(path);
852-
let curdir = path == Path::new(".");
890+
let is_dir = path.is_directory;
891+
let curdir = path.as_ref() == Path::new(".");
853892
match pattern_as_str(pattern) {
854893
Some(s) => {
855894
// This pattern component doesn't have any metacharacters, so we
@@ -863,6 +902,7 @@ fn fill_todo(
863902
} else {
864903
path.join(&s)
865904
};
905+
let next_path = PathWrapper::from_path(next_path);
866906
if (special && is_dir)
867907
|| (!special
868908
&& (fs::metadata(&next_path).is_ok()
@@ -875,19 +915,21 @@ fn fill_todo(
875915
let dirs = fs::read_dir(path).and_then(|d| {
876916
d.map(|e| {
877917
e.map(|e| {
878-
if curdir {
918+
let path = if curdir {
879919
PathBuf::from(e.path().file_name().unwrap())
880920
} else {
881921
e.path()
882-
}
922+
};
923+
PathWrapper::from_dir_entry(path, e)
883924
})
884925
})
885926
.collect::<Result<Vec<_>, _>>()
886927
});
887928
match dirs {
888929
Ok(mut children) => {
889930
if options.require_literal_leading_dot {
890-
children.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
931+
children
932+
.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
891933
}
892934
children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name()));
893935
todo.extend(children.into_iter().map(|x| Ok((x, idx))));
@@ -900,7 +942,7 @@ fn fill_todo(
900942
if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
901943
for &special in &[".", ".."] {
902944
if pattern.matches_with(special, options) {
903-
add(todo, path.join(special));
945+
add(todo, PathWrapper::from_path(path.join(special)));
904946
}
905947
}
906948
}

0 commit comments

Comments
 (0)