1#![allow(dead_code)] use std::{
5 collections::VecDeque,
6 path::{Path, PathBuf},
7};
8
9use futures::future::BoxFuture;
10use tokio::io::{AsyncReadExt, AsyncWriteExt};
11
12use crate::{DirEntry, FileSystem, FsError};
13
14pub fn exists<F>(fs: &F, path: impl AsRef<Path>) -> bool
16where
17 F: FileSystem + ?Sized,
18{
19 fs.metadata(path.as_ref()).is_ok()
20}
21
22pub fn is_dir<F>(fs: &F, path: impl AsRef<Path>) -> bool
24where
25 F: FileSystem + ?Sized,
26{
27 match fs.metadata(path.as_ref()) {
28 Ok(meta) => meta.is_dir(),
29 Err(_) => false,
30 }
31}
32
33pub fn is_file<F>(fs: &F, path: impl AsRef<Path>) -> bool
35where
36 F: FileSystem + ?Sized,
37{
38 match fs.metadata(path.as_ref()) {
39 Ok(meta) => meta.is_file(),
40 Err(_) => false,
41 }
42}
43
44pub fn create_dir_all<F>(fs: &F, path: impl AsRef<Path>) -> Result<(), FsError>
48where
49 F: FileSystem + ?Sized,
50{
51 let path = path.as_ref();
52 if let Some(parent) = path.parent() {
53 create_dir_all(fs, parent)?;
54 }
55
56 if let Ok(metadata) = fs.metadata(path) {
57 if metadata.is_dir() {
58 return Ok(());
59 }
60 if metadata.is_file() {
61 return Err(FsError::BaseNotDirectory);
62 }
63 }
64
65 fs.create_dir(path)
66}
67
68static WHITEOUT_PREFIX: &str = ".wh.";
69
70pub fn create_white_out<F>(fs: &F, path: impl AsRef<Path>) -> Result<(), FsError>
72where
73 F: FileSystem + ?Sized,
74{
75 if let Some(filename) = path.as_ref().file_name() {
76 let mut path = path.as_ref().to_owned();
77 path.set_file_name(format!("{}{}", WHITEOUT_PREFIX, filename.to_string_lossy()));
78
79 if let Some(parent) = path.parent() {
80 create_dir_all(fs, parent).ok();
81 }
82
83 fs.new_open_options()
84 .create_new(true)
85 .truncate(true)
86 .write(true)
87 .open(path)?;
88 Ok(())
89 } else {
90 Err(FsError::EntryNotFound)
91 }
92}
93
94pub fn remove_white_out<F>(fs: &F, path: impl AsRef<Path>)
96where
97 F: FileSystem + ?Sized,
98{
99 if let Some(filename) = path.as_ref().file_name() {
100 let mut path = path.as_ref().to_owned();
101 path.set_file_name(format!("{}{}", WHITEOUT_PREFIX, filename.to_string_lossy()));
102 fs.remove_file(&path).ok();
103 }
104}
105
106pub fn has_white_out<F>(fs: &F, path: impl AsRef<Path>) -> bool
108where
109 F: FileSystem + ?Sized,
110{
111 if let Some(filename) = path.as_ref().file_name() {
112 let mut path = path.as_ref().to_owned();
113 path.set_file_name(format!("{}{}", WHITEOUT_PREFIX, filename.to_string_lossy()));
114 fs.metadata(&path).is_ok()
115 } else {
116 false
117 }
118}
119
120pub fn is_white_out(path: impl AsRef<Path>) -> Option<PathBuf> {
122 if let Some(filename) = path.as_ref().file_name() {
123 if let Some(filename) = filename.to_string_lossy().strip_prefix(WHITEOUT_PREFIX) {
124 let mut path = path.as_ref().to_owned();
125 path.set_file_name(filename);
126 return Some(path);
127 }
128 }
129 None
130}
131
132pub fn copy_reference<'a>(
134 source: &'a (impl FileSystem + ?Sized),
135 destination: &'a (impl FileSystem + ?Sized),
136 path: &'a Path,
137) -> BoxFuture<'a, Result<(), std::io::Error>> {
138 Box::pin(async { copy_reference_ext(source, destination, path, path).await })
139}
140
141pub fn copy_reference_ext<'a>(
143 source: &'a (impl FileSystem + ?Sized),
144 destination: &'a (impl FileSystem + ?Sized),
145 from: &Path,
146 to: &Path,
147) -> BoxFuture<'a, Result<(), std::io::Error>> {
148 let from = from.to_owned();
149 let to = to.to_owned();
150 Box::pin(async move {
151 let src = source.new_open_options().read(true).open(from)?;
152 let mut dst = destination
153 .new_open_options()
154 .create(true)
155 .write(true)
156 .truncate(true)
157 .open(to)?;
158
159 dst.copy_reference(src).await?;
160 Ok(())
161 })
162}
163
164pub async fn write<F>(
168 fs: &F,
169 path: impl AsRef<Path> + Send,
170 data: impl AsRef<[u8]> + Send,
171) -> Result<(), FsError>
172where
173 F: FileSystem + ?Sized,
174{
175 let path = path.as_ref();
176 let data = data.as_ref();
177
178 let mut f = fs
179 .new_open_options()
180 .create(true)
181 .truncate(true)
182 .write(true)
183 .open(path)?;
184
185 f.write_all(data).await?;
186 f.flush().await?;
187
188 Ok(())
189}
190
191pub async fn read<F>(fs: &F, path: impl AsRef<Path> + Send) -> Result<Vec<u8>, FsError>
195where
196 F: FileSystem + ?Sized,
197{
198 let mut f = fs.new_open_options().read(true).open(path.as_ref())?;
199 let mut buffer = Vec::new();
200 f.read_to_end(&mut buffer).await?;
201
202 Ok(buffer)
203}
204
205pub async fn read_to_string<F>(fs: &F, path: impl AsRef<Path> + Send) -> Result<String, FsError>
209where
210 F: FileSystem + ?Sized,
211{
212 let mut f = fs.new_open_options().read(true).open(path.as_ref())?;
213 let mut buffer = String::new();
214 f.read_to_string(&mut buffer).await?;
215
216 Ok(buffer)
217}
218
219pub fn touch<F>(fs: &F, path: impl AsRef<Path> + Send) -> Result<(), FsError>
222where
223 F: FileSystem + ?Sized,
224{
225 let _ = fs.new_open_options().create(true).write(true).open(path)?;
226
227 Ok(())
228}
229
230pub fn walk<F>(fs: &F, path: impl AsRef<Path>) -> Box<dyn Iterator<Item = DirEntry> + '_>
233where
234 F: FileSystem + ?Sized,
235{
236 let path = path.as_ref();
237 let mut dirs_to_visit: VecDeque<_> = fs
238 .read_dir(path)
239 .ok()
240 .into_iter()
241 .flatten()
242 .filter_map(|result| result.ok())
243 .collect();
244
245 Box::new(std::iter::from_fn(move || {
246 let next = dirs_to_visit.pop_back()?;
247
248 if let Ok(children) = fs.read_dir(&next.path) {
249 dirs_to_visit.extend(children.flatten());
250 }
251
252 Some(next)
253 }))
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::mem_fs::FileSystem as MemFS;
260 use tokio::io::AsyncReadExt;
261
262 #[tokio::test]
263 async fn write() {
264 let fs = MemFS::default();
265
266 super::write(&fs, "/file.txt", b"Hello, World!")
267 .await
268 .unwrap();
269
270 let mut contents = String::new();
271 fs.new_open_options()
272 .read(true)
273 .open("/file.txt")
274 .unwrap()
275 .read_to_string(&mut contents)
276 .await
277 .unwrap();
278 assert_eq!(contents, "Hello, World!");
279 }
280
281 #[tokio::test]
282 async fn read() {
283 let fs = MemFS::default();
284 fs.new_open_options()
285 .create(true)
286 .write(true)
287 .open("/file.txt")
288 .unwrap()
289 .write_all(b"Hello, World!")
290 .await
291 .unwrap();
292
293 let contents = super::read_to_string(&fs, "/file.txt").await.unwrap();
294 assert_eq!(contents, "Hello, World!");
295
296 let contents = super::read(&fs, "/file.txt").await.unwrap();
297 assert_eq!(contents, b"Hello, World!");
298 }
299
300 #[tokio::test]
301 async fn create_dir_all() {
302 let fs = MemFS::default();
303 super::write(&fs, "/file.txt", b"").await.unwrap();
304
305 assert!(!super::exists(&fs, "/really/nested/directory"));
306 super::create_dir_all(&fs, "/really/nested/directory").unwrap();
307 assert!(super::exists(&fs, "/really/nested/directory"));
308
309 super::create_dir_all(&fs, "/really/nested/directory").unwrap();
311
312 assert_eq!(
314 super::create_dir_all(&fs, "/file.txt").unwrap_err(),
315 FsError::BaseNotDirectory
316 );
317 assert_eq!(
318 super::create_dir_all(&fs, "/file.txt/invalid/path").unwrap_err(),
319 FsError::BaseNotDirectory
320 );
321 }
322
323 #[tokio::test]
324 async fn touch() {
325 let fs = MemFS::default();
326
327 super::touch(&fs, "/file.txt").unwrap();
328
329 assert_eq!(super::read(&fs, "/file.txt").await.unwrap(), b"");
330 }
331}