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
use crate::util::{component_iter, make_relative, normalize_path, not_found};
use parking_lot::Mutex;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// A directory in tree-based filesystem.
pub type Directory<T> = HashMap<String, Entry<T>>;
/// A directory node in the file tree.
pub enum Entry<T> {
Directory(HashMap<String, Entry<T>>),
UserData(T),
}
impl<T> Default for Entry<T> {
fn default() -> Self {
Self::Directory(HashMap::default())
}
}
/// A tree-based filesystem with directories and other data.
pub struct FilesystemTree<T> {
root: Mutex<Entry<T>>,
}
impl<T> FilesystemTree<T> {
/// Creates all directories specified in `path`, including the trailing path. Calls `f` with the resulting
/// directory on success.
///
/// # Arguments
/// `path`: The path to create all of the directories for.
/// `f`: The function.
pub fn create_dir_all<R, P: AsRef<Path>, F: FnOnce(&mut Directory<T>) -> R>(
&self,
path: P,
f: F,
) -> crate::Result<R> {
// specialize this method so we don't turn this into O(n^2) searching for each subcomponent
let mut entry = self.root.lock();
let mut entry = &mut *entry;
for component in component_iter(&normalize_and_relativize(path)) {
let Entry::Directory(dir) = entry else {
return Err(not_found());
};
entry = dir
.entry(component.to_owned())
.or_insert_with(|| Entry::Directory(HashMap::default()));
}
// make sure the last entry was also a directory
if let Entry::Directory(dir) = entry {
Ok(f(dir))
} else {
Err(not_found())
}
}
/// Calls `f` with the directory at the specified path, only if it is located.
///
/// # Arguments
/// `path`: The directory to fetch the entry for.
pub fn with_directory<R, P: AsRef<Path>, F: FnOnce(&mut Directory<T>) -> R>(
&self,
path: P,
f: F,
) -> crate::Result<R> {
self.with_entry(path, |entry| entry.map(f).map_err(|_| not_found()))
}
/// Calls `f` with the entry at `path`, or the last found entry and remaining path.
///
/// # Arguments
/// `normalized_path`: The normalized path as
pub fn with_entry<
R,
P: AsRef<Path>,
F: FnOnce(Result<&mut Directory<T>, (&mut T, &Path)>) -> crate::Result<R>,
>(
&self,
path: P,
f: F,
) -> crate::Result<R> {
// normalize the path
let normalized_path = normalize_and_relativize(path);
let components: Vec<&str> = component_iter(&normalized_path).collect();
// iterate through each component until we hit a filesystem
let mut entry = self.root.lock();
let mut entry = &mut *entry;
for (i, component) in components.iter().enumerate() {
match entry {
Entry::Directory(directory) => {
// traverse into the directory
entry = directory.get_mut(*component).ok_or_else(not_found)?;
}
Entry::UserData(ud) => {
// there can't be a valid component after resolving a file
let remaining: PathBuf = components[i..].iter().collect();
return f(Err((ud, &remaining)));
}
}
}
// entry has to be a directory unless the root is a filesystem
match entry {
Entry::Directory(dir) => f(Ok(dir)),
Entry::UserData(ud) => f(Err((ud, Path::new("")))),
}
}
}
impl<T> Default for FilesystemTree<T> {
fn default() -> Self {
Self {
root: Mutex::default(),
}
}
}
/// Normalizes a path by making it relative and resolving any backtracking.
pub(crate) fn normalize_and_relativize<P: AsRef<Path>>(p: P) -> PathBuf {
// treat every path as relative from the root
normalize_path(make_relative(p))
}