over_there/core/server/fs/
dir.rs1use std::io;
2use std::path::{Path, PathBuf};
3use tokio::fs;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct LocalDirEntry {
7 pub path: PathBuf,
8 pub is_file: bool,
9 pub is_dir: bool,
10 pub is_symlink: bool,
11}
12
13impl LocalDirEntry {
14 pub fn path_to_string(&self) -> String {
15 self.path.to_string_lossy().to_string()
16 }
17}
18
19pub async fn entries(path: impl AsRef<Path>) -> io::Result<Vec<LocalDirEntry>> {
20 let mut entries = Vec::new();
21 let mut dir_stream = fs::read_dir(path).await?;
22 while let Some(entry) = dir_stream.next_entry().await? {
23 let file_type = entry.file_type().await?;
24 entries.push(LocalDirEntry {
25 path: entry.path(),
26 is_file: file_type.is_file(),
27 is_dir: file_type.is_dir(),
28 is_symlink: file_type.is_symlink(),
29 });
30 }
31 Ok(entries)
32}
33
34pub async fn rename(
35 from: impl AsRef<Path>,
36 to: impl AsRef<Path>,
37) -> io::Result<()> {
38 let metadata = fs::metadata(from.as_ref()).await?;
39
40 if metadata.is_dir() {
41 fs::rename(from, to).await
42 } else {
43 Err(io::Error::new(io::ErrorKind::Other, "Not a directory"))
44 }
45}
46
47pub async fn create(
48 path: impl AsRef<Path>,
49 create_components: bool,
50) -> io::Result<()> {
51 if create_components {
52 fs::create_dir_all(path).await
53 } else {
54 fs::create_dir(path).await
55 }
56}
57
58pub async fn remove(path: impl AsRef<Path>, non_empty: bool) -> io::Result<()> {
59 if non_empty {
60 fs::remove_dir_all(path).await
61 } else {
62 fs::remove_dir(path).await
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[tokio::test]
71 async fn entries_should_yield_error_if_not_a_directory() {
72 let result = {
73 let file = tempfile::NamedTempFile::new().unwrap();
74 entries(file.as_ref()).await
75 };
76
77 match result {
78 Err(x) if x.kind() == io::ErrorKind::Other => (),
79 x => panic!("Unexpected result: {:?}", x),
80 }
81 }
82
83 #[tokio::test]
84 async fn entries_should_return_immediate_entries_within_dir() {
85 let (dir_path, result) = {
86 let dir = tempfile::tempdir().unwrap();
87
88 fs::File::create(dir.as_ref().join("test-file"))
89 .await
90 .expect("Failed to create file");
91
92 fs::create_dir(dir.as_ref().join("test-dir"))
93 .await
94 .expect("Failed to create dir");
95
96 let result = entries(dir.as_ref()).await;
97
98 (dir.into_path(), result)
99 };
100
101 match result {
102 Ok(entries) => {
103 assert_eq!(entries.len(), 2, "Unexpected number of entries");
104
105 assert!(
106 entries.contains(&LocalDirEntry {
107 path: dir_path.join("test-file"),
108 is_file: true,
109 is_dir: false,
110 is_symlink: false,
111 }),
112 "No test-file found"
113 );
114
115 assert!(
116 entries.contains(&LocalDirEntry {
117 path: dir_path.join("test-dir"),
118 is_file: false,
119 is_dir: true,
120 is_symlink: false,
121 }),
122 "No test-dir found"
123 );
124 }
125 x => panic!("Unexpected result: {:?}", x),
126 }
127 }
128
129 #[tokio::test]
130 async fn rename_should_yield_error_if_not_a_directory() {
131 let result = {
132 let from_file = tempfile::NamedTempFile::new().unwrap();
133 let from = from_file.as_ref();
134 let to_dir = tempfile::tempdir().unwrap();
135 let to = to_dir.as_ref();
136
137 rename(from, to).await
138 };
139
140 match result {
141 Err(x) if x.kind() == io::ErrorKind::Other => (),
142 x => panic!("Unexpected result: {:?}", x),
143 }
144 }
145
146 #[tokio::test]
147 async fn rename_should_return_success_if_able_to_rename_directory() {
148 let result = {
149 let from_dir = tempfile::tempdir().unwrap();
150 let from = from_dir.as_ref();
151 let to_dir = tempfile::tempdir().unwrap();
152 let to = to_dir.as_ref();
153
154 rename(from, to).await
155 };
156
157 match result {
158 Ok(_) => (),
159 x => panic!("Unexpected result: {:?}", x),
160 }
161 }
162
163 #[tokio::test]
164 async fn create_should_return_success_if_able_to_make_an_empty_directory() {
165 let result = {
166 let parent_dir = tempfile::tempdir().unwrap();
167
168 create(parent_dir.as_ref().join("test-dir"), false).await
169 };
170
171 assert!(result.is_ok(), "Failed unexpectedly: {:?}", result);
172
173 let result = {
174 let parent_dir = tempfile::tempdir().unwrap();
175
176 create(parent_dir.as_ref().join("test-dir"), true).await
177 };
178
179 assert!(result.is_ok(), "Failed unexpectedly: {:?}", result);
180 }
181
182 #[tokio::test]
183 async fn create_should_yield_error_if_some_components_dont_exist_and_flag_not_set(
184 ) {
185 let result = {
186 let parent_dir = tempfile::tempdir().unwrap();
187 let new_dir = parent_dir.as_ref().join(
188 ["does", "not", "exist"]
189 .iter()
190 .collect::<PathBuf>()
191 .as_path(),
192 );
193
194 create(new_dir, false).await
195 };
196
197 assert!(result.is_err(), "Unexpectedly succeeded: {:?}", result);
198 }
199
200 #[tokio::test]
201 async fn create_should_return_success_if_able_to_make_nested_empty_directory(
202 ) {
203 let parent_dir = tempfile::tempdir().unwrap();
204
205 create(parent_dir.as_ref().join("test-dir"), false)
206 .await
207 .expect("Failed to create directory");
208
209 create(parent_dir.as_ref().join("test-dir"), true)
210 .await
211 .expect("Failed to create directory");
212 }
213
214 #[tokio::test]
215 async fn remove_should_yield_error_if_not_a_directory() {
216 let result = {
217 let file = tempfile::NamedTempFile::new().unwrap();
218 remove(file.as_ref(), false).await
219 };
220
221 match result {
222 Err(x) if x.kind() == io::ErrorKind::Other => (),
223 x => panic!("Unexpected result: {:?}", x),
224 }
225 }
226
227 #[tokio::test]
228 async fn remove_should_return_success_if_able_to_remove_empty_directory() {
229 let result = {
231 let dir = tempfile::tempdir().unwrap();
232 remove(dir.as_ref(), false).await
233 };
234
235 match result {
236 Ok(_) => (),
237 x => panic!("Unexpected result: {:?}", x),
238 }
239
240 let result = {
242 let dir = tempfile::tempdir().unwrap();
243 remove(dir.as_ref(), true).await
244 };
245
246 match result {
247 Ok(_) => (),
248 x => panic!("Unexpected result: {:?}", x),
249 }
250 }
251
252 #[tokio::test]
253 async fn remove_should_yield_error_if_removing_nonempty_directory_and_flag_not_set(
254 ) {
255 let result = {
256 let dir = tempfile::tempdir().unwrap();
257
258 fs::File::create(dir.as_ref().join("test-file"))
259 .await
260 .expect("Failed to create file");
261
262 remove(dir.as_ref(), false).await
263 };
264
265 match result {
266 Err(x) if x.kind() == io::ErrorKind::Other => (),
267 x => panic!("Unexpected result: {:?}", x),
268 }
269 }
270
271 #[tokio::test]
272 async fn remove_should_return_success_if_able_to_remove_nonempty_directory_if_flag_set(
273 ) {
274 let result = {
275 let dir = tempfile::tempdir().unwrap();
276
277 fs::File::create(dir.as_ref().join("test-file"))
278 .await
279 .expect("Failed to create file");
280
281 remove(dir.as_ref(), true).await
282 };
283
284 match result {
285 Ok(_) => (),
286 x => panic!("Unexpected result: {:?}", x),
287 }
288 }
289}