1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use std::{
ffi::{OsStr, OsString},
path::{Path, PathBuf},
};
use crate::{
core::Error,
load::{DirData, Loader, LoaderOptions, get_ftag_path},
};
#[derive(PartialEq, Eq, Copy, Clone)]
pub(crate) enum DirEntryType {
File,
Dir,
}
/// Entry found during recursive traversal. `depth` 1 corresponds to
/// the root of the recursive traversal, and subsequent depths
/// indicate the level of nesting.
pub(crate) struct DirEntry {
depth: usize,
entry_type: DirEntryType,
name: OsString,
}
impl DirEntry {
pub fn name(&self) -> &OsStr {
&self.name
}
}
/// Recursively walk directories, while caching useful information
/// about the contents of the directory. The traversal is depth first.
pub(crate) struct DirTree {
abs_dir_path: PathBuf,
rel_dir_path: PathBuf,
stack: Vec<DirEntry>,
cur_depth: usize,
num_children: usize,
loader: Loader,
}
pub(crate) enum MetaData<'a> {
Ok(&'a DirData<'a>),
NotFound,
FailedToLoad(Error),
}
pub(crate) struct VisitedDir<'a> {
pub(crate) traverse_depth: usize,
pub(crate) abs_dir_path: &'a Path,
pub(crate) rel_dir_path: &'a Path,
pub(crate) files: &'a [DirEntry],
pub(crate) metadata: MetaData<'a>,
}
fn ignore_file(file: &OsStr) -> bool {
file.to_string_lossy().starts_with(".")
}
impl DirTree {
pub fn new(rootdir: PathBuf, options: LoaderOptions) -> Result<Self, Error> {
if !rootdir.is_dir() {
return Err(Error::InvalidPath(rootdir));
}
let mut stack = Vec::with_capacity(32);
stack.push(DirEntry {
depth: 1,
entry_type: DirEntryType::Dir,
name: OsString::new(),
});
Ok(DirTree {
abs_dir_path: rootdir,
rel_dir_path: PathBuf::new(),
stack,
cur_depth: 0,
num_children: 0,
loader: Loader::new(options),
})
}
/// Move on to the next directory. Returns a tuple containing the depth of
/// the directory, its absolute path, its path relative to the root of the
/// walk, and a slice containing info about the files in this directory.
pub fn walk(&'_ mut self) -> Option<VisitedDir<'_>> {
while let Some(DirEntry {
depth,
entry_type,
name,
}) = self.stack.pop()
{
match entry_type {
DirEntryType::File => continue,
DirEntryType::Dir => {
while self.cur_depth > depth - 1 {
self.abs_dir_path.pop();
self.rel_dir_path.pop();
self.cur_depth -= 1;
}
self.abs_dir_path.push(name.clone());
self.rel_dir_path.push(name);
self.cur_depth += 1;
// Push all children.
let mut numfiles = 0;
let before = self.stack.len();
if let Ok(entries) = std::fs::read_dir(&self.abs_dir_path) {
for child in entries.flatten() {
match (child.file_name(), child.file_type()) {
(cname, _) if ignore_file(&cname) => continue,
(cname, Ok(ctype)) if ctype.is_dir() => self.stack.push(DirEntry {
depth: depth + 1,
entry_type: DirEntryType::Dir,
name: cname,
}),
(cname, Ok(ctype)) if ctype.is_file() => {
self.stack.push(DirEntry {
depth: depth + 1,
entry_type: DirEntryType::File,
name: cname,
});
numfiles += 1;
}
_ => continue,
}
}
}
self.num_children = self.stack.len() - before;
// Sort the contents of this folder to move all the files to the end of the stack.
self.stack[before..].sort_unstable_by(|a, b| {
match (a.entry_type, b.entry_type) {
(DirEntryType::File, DirEntryType::File) => a.name.cmp(&b.name),
(DirEntryType::File, DirEntryType::Dir) => std::cmp::Ordering::Greater,
(DirEntryType::Dir, DirEntryType::File) => std::cmp::Ordering::Less,
(DirEntryType::Dir, DirEntryType::Dir) => std::cmp::Ordering::Equal,
}
});
return Some(VisitedDir {
traverse_depth: depth,
abs_dir_path: &self.abs_dir_path,
rel_dir_path: &self.rel_dir_path,
files: &self.stack[(self.stack.len() - numfiles)..], // Files are sorted to the end of the stack.
metadata: match get_ftag_path::<true>(&self.abs_dir_path) {
Some(fpath) => match self.loader.load(&fpath) {
Ok(data) => MetaData::Ok(data),
Err(e) => MetaData::FailedToLoad(e),
},
None => MetaData::NotFound,
},
});
}
}
}
None
}
}