vfs/async_vfs/path.rs
1//! Virtual filesystem path
2//!
3//! The virtual file system abstraction generalizes over file systems and allow using
4//! different VirtualFileSystem implementations (i.e. an in memory implementation for unit tests)
5
6use crate::async_vfs::AsyncFileSystem;
7use crate::error::{VfsError, VfsErrorKind};
8use crate::path::PathLike;
9use crate::path::VfsFileType;
10use crate::{VfsMetadata, VfsResult};
11
12use async_recursion::async_recursion;
13use async_std::io::{Read, ReadExt, Seek, Write};
14use async_std::sync::Arc;
15use async_std::task::{Context, Poll};
16use futures::{future::BoxFuture, FutureExt, Stream, StreamExt};
17use std::pin::Pin;
18use std::time::SystemTime;
19
20/// Trait combining Seek and Read, return value for opening files
21pub trait SeekAndRead: Seek + Read {}
22
23impl<T> SeekAndRead for T where T: Seek + Read {}
24
25#[derive(Debug)]
26struct AsyncVFS {
27 fs: Box<dyn AsyncFileSystem>,
28}
29
30/// A virtual filesystem path, identifying a single file or directory in this virtual filesystem
31#[derive(Clone, Debug)]
32pub struct AsyncVfsPath {
33 path: String,
34 fs: Arc<AsyncVFS>,
35}
36
37impl PathLike for AsyncVfsPath {
38 fn get_path(&self) -> String {
39 self.path.clone()
40 }
41}
42
43impl PartialEq for AsyncVfsPath {
44 fn eq(&self, other: &Self) -> bool {
45 self.path == other.path && Arc::ptr_eq(&self.fs, &other.fs)
46 }
47}
48
49impl Eq for AsyncVfsPath {}
50
51impl AsyncVfsPath {
52 /// Creates a root path for the given filesystem
53 ///
54 /// ```
55 /// # use vfs::async_vfs::{AsyncPhysicalFS, AsyncVfsPath};
56 /// let path = AsyncVfsPath::new(AsyncPhysicalFS::new("."));
57 /// ````
58 pub fn new<T: AsyncFileSystem>(filesystem: T) -> Self {
59 AsyncVfsPath {
60 path: "".to_string(),
61 fs: Arc::new(AsyncVFS {
62 fs: Box::new(filesystem),
63 }),
64 }
65 }
66
67 /// Returns the string representation of this path
68 ///
69 /// ```
70 /// # use vfs::async_vfs::{AsyncPhysicalFS, AsyncVfsPath};
71 /// # use vfs::VfsError;
72 /// let path = AsyncVfsPath::new(AsyncPhysicalFS::new("."));
73 ///
74 /// assert_eq!(path.as_str(), "");
75 /// assert_eq!(path.join("foo.txt")?.as_str(), "/foo.txt");
76 /// # Ok::<(), VfsError>(())
77 /// ````
78 pub fn as_str(&self) -> &str {
79 &self.path
80 }
81
82 /// Appends a path segment to this path, returning the result
83 ///
84 /// ```
85 /// # use vfs::async_vfs::{AsyncPhysicalFS, AsyncVfsPath};
86 /// # use vfs::VfsError;
87 /// let path = AsyncVfsPath::new(AsyncPhysicalFS::new("."));
88 ///
89 /// assert_eq!(path.join("foo.txt")?.as_str(), "/foo.txt");
90 /// assert_eq!(path.join("foo/bar.txt")?.as_str(), "/foo/bar.txt");
91 ///
92 /// let foo = path.join("foo")?;
93 ///
94 /// assert_eq!(path.join("foo/bar.txt")?, foo.join("bar.txt")?);
95 /// assert_eq!(path, foo.join("..")?);
96 /// # Ok::<(), VfsError>(())
97 /// ```
98 pub fn join(&self, path: impl AsRef<str>) -> VfsResult<Self> {
99 let new_path = self.join_internal(&self.path, path.as_ref())?;
100 Ok(Self {
101 path: new_path,
102 fs: self.fs.clone(),
103 })
104 }
105
106 /// Returns the root path of this filesystem
107 ///
108 /// ```
109 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
110 /// # use vfs::{VfsError, VfsFileType};
111 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
112 /// let directory = path.join("foo/bar")?;
113 ///
114 /// assert_eq!(directory.root(), path);
115 /// # Ok::<(), VfsError>(())
116 /// ```
117 pub fn root(&self) -> AsyncVfsPath {
118 AsyncVfsPath {
119 path: "".to_string(),
120 fs: self.fs.clone(),
121 }
122 }
123
124 /// Returns true if this is the root path
125 ///
126 /// ```
127 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
128 /// # use vfs::{VfsError, VfsFileType};
129 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
130 /// assert!(path.is_root());
131 /// let path = path.join("foo/bar")?;
132 /// assert!(! path.is_root());
133 /// # Ok::<(), VfsError>(())
134 /// ```
135 pub fn is_root(&self) -> bool {
136 self.path.is_empty()
137 }
138
139 /// Creates the directory at this path
140 ///
141 /// Note that the parent directory must exist, while the given path must not exist.
142 ///
143 /// Returns VfsErrorKind::FileExists if a file already exists at the given path
144 /// Returns VfsErrorKind::DirectoryExists if a directory already exists at the given path
145 ///
146 /// ```
147 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
148 /// # use vfs::{VfsError, VfsFileType};
149 /// # tokio_test::block_on(async {
150 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
151 /// let directory = path.join("foo")?;
152 ///
153 /// directory.create_dir().await?;
154 ///
155 /// assert!(directory.exists().await?);
156 /// assert_eq!(directory.metadata().await?.file_type, VfsFileType::Directory);
157 /// # Ok::<(), VfsError>(())
158 /// # });
159 /// ```
160 pub async fn create_dir(&self) -> VfsResult<()> {
161 self.get_parent("create directory").await?;
162 self.fs.fs.create_dir(&self.path).await.map_err(|err| {
163 err.with_path(&self.path)
164 .with_context(|| "Could not create directory")
165 })
166 }
167
168 /// Creates the directory at this path, also creating parent directories as necessary
169 ///
170 /// ```
171 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
172 /// # use vfs::{VfsError, VfsFileType};
173 /// # tokio_test::block_on(async {
174 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
175 /// let directory = path.join("foo/bar")?;
176 ///
177 /// directory.create_dir_all().await?;
178 ///
179 /// assert!(directory.exists().await?);
180 /// assert_eq!(directory.metadata().await?.file_type, VfsFileType::Directory);
181 /// let parent = path.join("foo")?;
182 /// assert!(parent.exists().await?);
183 /// assert_eq!(parent.metadata().await?.file_type, VfsFileType::Directory);
184 /// # Ok::<(), VfsError>(())
185 /// # });
186 /// ```
187 pub async fn create_dir_all(&self) -> VfsResult<()> {
188 let mut pos = 1;
189 let path = &self.path;
190 if path.is_empty() {
191 // root exists always
192 return Ok(());
193 }
194 loop {
195 // Iterate over path segments
196 let end = path[pos..]
197 .find('/')
198 .map(|it| it + pos)
199 .unwrap_or_else(|| path.len());
200 let directory = &path[..end];
201 if let Err(error) = self.fs.fs.create_dir(directory).await {
202 match error.kind() {
203 VfsErrorKind::DirectoryExists => {}
204 _ => {
205 return Err(error.with_path(directory).with_context(|| {
206 format!("Could not create directories at '{}'", path)
207 }))
208 }
209 }
210 }
211 if end == path.len() {
212 break;
213 }
214 pos = end + 1;
215 }
216 Ok(())
217 }
218
219 /// Iterates over all entries of this directory path
220 ///
221 /// ```
222 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
223 /// # use vfs::VfsError;
224 /// use futures::stream::Collect;
225 /// use futures::stream::StreamExt;
226 /// # tokio_test::block_on(async {
227 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
228 /// path.join("foo")?.create_dir().await?;
229 /// path.join("bar")?.create_dir().await?;
230 ///
231 /// let mut directories: Vec<_> = path.read_dir().await?.collect().await;
232 ///
233 /// directories.sort_by_key(|path| path.as_str().to_string());
234 /// assert_eq!(directories, vec![path.join("bar")?, path.join("foo")?]);
235 /// # Ok::<(), VfsError>(())
236 /// # });
237 /// ```
238 pub async fn read_dir(&self) -> VfsResult<Box<dyn Unpin + Stream<Item = AsyncVfsPath> + Send>> {
239 let parent = self.path.clone();
240 let fs = self.fs.clone();
241 Ok(Box::new(
242 self.fs
243 .fs
244 .read_dir(&self.path)
245 .await
246 .map_err(|err| {
247 err.with_path(&self.path)
248 .with_context(|| "Could not read directory")
249 })?
250 .map(move |path| {
251 println!("{:?}", path);
252 AsyncVfsPath {
253 path: format!("{}/{}", parent, path),
254 fs: fs.clone(),
255 }
256 }),
257 ))
258 }
259
260 /// Creates a file at this path for writing, overwriting any existing file
261 ///
262 /// ```
263 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
264 /// # use vfs::VfsError;
265 /// use async_std::io:: {ReadExt, WriteExt};
266 /// # tokio_test::block_on(async {
267 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
268 /// let file = path.join("foo.txt")?;
269 ///
270 /// write!(file.create_file().await?, "Hello, world!").await?;
271 ///
272 /// let mut result = String::new();
273 /// file.open_file().await?.read_to_string(&mut result).await?;
274 /// assert_eq!(&result, "Hello, world!");
275 /// # Ok::<(), VfsError>(())
276 /// # });
277 /// ```
278 pub async fn create_file(&self) -> VfsResult<Box<dyn Write + Send + Unpin>> {
279 self.get_parent("create file").await?;
280 self.fs.fs.create_file(&self.path).await.map_err(|err| {
281 err.with_path(&self.path)
282 .with_context(|| "Could not create file")
283 })
284 }
285
286 /// Opens the file at this path for reading
287 ///
288 /// ```
289 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
290 /// # use vfs::VfsError;
291 /// use async_std::io:: {ReadExt, WriteExt};
292 /// # tokio_test::block_on(async {
293 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
294 /// let file = path.join("foo.txt")?;
295 /// write!(file.create_file().await?, "Hello, world!").await?;
296 /// let mut result = String::new();
297 ///
298 /// file.open_file().await?.read_to_string(&mut result).await?;
299 ///
300 /// assert_eq!(&result, "Hello, world!");
301 /// # Ok::<(), VfsError>(())
302 /// # });
303 /// ```
304 pub async fn open_file(&self) -> VfsResult<Box<dyn SeekAndRead + Send + Unpin>> {
305 self.fs.fs.open_file(&self.path).await.map_err(|err| {
306 err.with_path(&self.path)
307 .with_context(|| "Could not open file")
308 })
309 }
310
311 /// Checks whether parent is a directory
312 async fn get_parent(&self, action: &str) -> VfsResult<()> {
313 let parent = self.parent();
314 if !parent.exists().await? {
315 return Err(VfsError::from(VfsErrorKind::Other(format!(
316 "Could not {}, parent directory does not exist",
317 action
318 )))
319 .with_path(&self.path));
320 }
321 let metadata = parent.metadata().await?;
322 if metadata.file_type != VfsFileType::Directory {
323 return Err(VfsError::from(VfsErrorKind::Other(format!(
324 "Could not {}, parent path is not a directory",
325 action
326 )))
327 .with_path(&self.path));
328 }
329 Ok(())
330 }
331
332 /// Opens the file at this path for appending
333 ///
334 /// ```
335 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
336 /// # use vfs::VfsError;
337 /// use async_std::io:: {ReadExt, WriteExt};
338 /// # tokio_test::block_on(async {
339 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
340 /// let file = path.join("foo.txt")?;
341 /// write!(file.create_file().await?, "Hello, ").await?;
342 /// write!(file.append_file().await?, "world!").await?;
343 /// let mut result = String::new();
344 /// file.open_file().await?.read_to_string(&mut result).await?;
345 /// assert_eq!(&result, "Hello, world!");
346 /// # Ok::<(), VfsError>(())
347 /// # });
348 /// ```
349 pub async fn append_file(&self) -> VfsResult<Box<dyn Write + Send + Unpin>> {
350 self.fs.fs.append_file(&self.path).await.map_err(|err| {
351 err.with_path(&self.path)
352 .with_context(|| "Could not open file for appending")
353 })
354 }
355
356 /// Removes the file at this path
357 ///
358 /// ```
359 /// use async_std::io:: {ReadExt, WriteExt};
360 /// # use vfs::async_vfs::{AsyncMemoryFS , AsyncVfsPath};
361 /// # use vfs::VfsError;
362 /// # tokio_test::block_on(async {
363 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
364 /// let file = path.join("foo.txt")?;
365 /// write!(file.create_file().await?, "Hello, ").await?;
366 /// assert!(file.exists().await?);
367 ///
368 /// file.remove_file().await?;
369 ///
370 /// assert!(!file.exists().await?);
371 /// # Ok::<(), VfsError>(())
372 /// # });
373 /// ```
374 pub async fn remove_file(&self) -> VfsResult<()> {
375 self.fs.fs.remove_file(&self.path).await.map_err(|err| {
376 err.with_path(&self.path)
377 .with_context(|| "Could not remove file")
378 })
379 }
380
381 /// Removes the directory at this path
382 ///
383 /// The directory must be empty.
384 ///
385 /// ```
386 /// # tokio_test::block_on(async {
387 /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
388 /// use vfs::VfsError;
389 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
390 /// let directory = path.join("foo")?;
391 /// directory.create_dir().await;
392 /// assert!(directory.exists().await?);
393 ///
394 /// directory.remove_dir().await?;
395 ///
396 /// assert!(!directory.exists().await?);
397 /// # Ok::<(), VfsError>(())
398 /// # });
399 /// ```
400 pub async fn remove_dir(&self) -> VfsResult<()> {
401 self.fs.fs.remove_dir(&self.path).await.map_err(|err| {
402 err.with_path(&self.path)
403 .with_context(|| "Could not remove directory")
404 })
405 }
406
407 /// Ensures that the directory at this path is removed, recursively deleting all contents if necessary
408 ///
409 /// Returns successfully if directory does not exist
410 ///
411 /// ```
412 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
413 /// # use vfs::VfsError;
414 /// # tokio_test::block_on(async {
415 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
416 /// let directory = path.join("foo")?;
417 /// directory.join("bar")?.create_dir_all().await?;
418 /// assert!(directory.exists().await?);
419 ///
420 /// directory.remove_dir_all().await?;
421 ///
422 /// assert!(!directory.exists().await?);
423 /// # Ok::<(), VfsError>(())
424 /// # });
425 /// ```
426 #[async_recursion]
427 pub async fn remove_dir_all(&self) -> VfsResult<()> {
428 if !self.exists().await? {
429 return Ok(());
430 }
431 let mut path_stream = self.read_dir().await?;
432 while let Some(child) = path_stream.next().await {
433 let metadata = child.metadata().await?;
434 match metadata.file_type {
435 VfsFileType::File => child.remove_file().await?,
436 VfsFileType::Directory => child.remove_dir_all().await?,
437 }
438 }
439 self.remove_dir().await?;
440 Ok(())
441 }
442
443 /// Returns the file metadata for the file at this path
444 ///
445 /// ```
446 /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
447 /// use vfs::{VfsError, VfsFileType, VfsMetadata};
448 /// use async_std::io::WriteExt;
449 /// # tokio_test::block_on(async {
450 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
451 /// let directory = path.join("foo")?;
452 /// directory.create_dir().await?;
453 ///
454 /// assert_eq!(directory.metadata().await?.len, 0);
455 /// assert_eq!(directory.metadata().await?.file_type, VfsFileType::Directory);
456 ///
457 /// let file = path.join("bar.txt")?;
458 /// write!(file.create_file().await?, "Hello, world!").await?;
459 ///
460 /// assert_eq!(file.metadata().await?.len, 13);
461 /// assert_eq!(file.metadata().await?.file_type, VfsFileType::File);
462 /// # Ok::<(), VfsError>(())
463 /// # });
464 pub async fn metadata(&self) -> VfsResult<VfsMetadata> {
465 self.fs.fs.metadata(&self.path).await.map_err(|err| {
466 err.with_path(&self.path)
467 .with_context(|| "Could not get metadata")
468 })
469 }
470
471 /// Sets the files creation timestamp at this path
472 ///
473 /// ```
474 /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
475 /// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath};
476 /// use async_std::io::WriteExt;
477 /// # tokio_test::block_on(async {
478 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
479 /// let file = path.join("foo.txt")?;
480 /// file.create_file();
481 ///
482 /// let time = std::time::SystemTime::now();
483 /// file.set_creation_time(time).await?;
484 ///
485 /// assert_eq!(file.metadata().await?.len, 0);
486 /// assert_eq!(file.metadata().await?.created, Some(time));
487 ///
488 /// # Ok::<(), VfsError>(())
489 /// # });
490 pub async fn set_creation_time(&self, time: SystemTime) -> VfsResult<()> {
491 self.fs
492 .fs
493 .set_creation_time(&self.path, time)
494 .await
495 .map_err(|err| {
496 err.with_path(&*self.path)
497 .with_context(|| "Could not set creation timestamp.")
498 })
499 }
500
501 /// Sets the files modification timestamp at this path
502 ///
503 /// ```
504 /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
505 /// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath};
506 /// use async_std::io::WriteExt;
507 /// # tokio_test::block_on(async {
508 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
509 /// let file = path.join("foo.txt")?;
510 /// file.create_file();
511 ///
512 /// let time = std::time::SystemTime::now();
513 /// file.set_modification_time(time).await?;
514 ///
515 /// assert_eq!(file.metadata().await?.len, 0);
516 /// assert_eq!(file.metadata().await?.modified, Some(time));
517 ///
518 /// # Ok::<(), VfsError>(())
519 /// # });
520 pub async fn set_modification_time(&self, time: SystemTime) -> VfsResult<()> {
521 self.fs
522 .fs
523 .set_modification_time(&self.path, time)
524 .await
525 .map_err(|err| {
526 err.with_path(&*self.path)
527 .with_context(|| "Could not set modification timestamp.")
528 })
529 }
530
531 /// Sets the files access timestamp at this path
532 ///
533 /// ```
534 /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
535 /// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath};
536 /// use async_std::io::WriteExt;
537 /// # tokio_test::block_on(async {
538 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
539 /// let file = path.join("foo.txt")?;
540 /// file.create_file();
541 ///
542 /// let time = std::time::SystemTime::now();
543 /// file.set_access_time(time).await?;
544 ///
545 /// assert_eq!(file.metadata().await?.len, 0);
546 /// assert_eq!(file.metadata().await?.accessed, Some(time));
547 ///
548 /// # Ok::<(), VfsError>(())
549 /// # });
550 pub async fn set_access_time(&self, time: SystemTime) -> VfsResult<()> {
551 self.fs
552 .fs
553 .set_access_time(&self.path, time)
554 .await
555 .map_err(|err| {
556 err.with_path(&*self.path)
557 .with_context(|| "Could not set access timestamp.")
558 })
559 }
560
561 /// Returns `true` if the path exists and is pointing at a regular file, otherwise returns `false`.
562 ///
563 /// Note that this call may fail if the file's existence cannot be determined or the metadata can not be retrieved
564 ///
565 /// ```
566 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
567 /// # use vfs::{VfsError, VfsFileType, VfsMetadata};
568 /// # tokio_test::block_on(async {
569 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
570 /// let directory = path.join("foo")?;
571 /// directory.create_dir().await?;
572 /// let file = path.join("foo.txt")?;
573 /// file.create_file().await?;
574 ///
575 /// assert!(!directory.is_file().await?);
576 /// assert!(file.is_file().await?);
577 /// # Ok::<(), VfsError>(())
578 /// # });
579 pub async fn is_file(&self) -> VfsResult<bool> {
580 if !self.exists().await? {
581 return Ok(false);
582 }
583 let metadata = self.metadata().await?;
584 Ok(metadata.file_type == VfsFileType::File)
585 }
586
587 /// Returns `true` if the path exists and is pointing at a directory, otherwise returns `false`.
588 ///
589 /// Note that this call may fail if the directory's existence cannot be determined or the metadata can not be retrieved
590 ///
591 /// ```
592 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
593 /// # use vfs::{VfsError, VfsFileType, VfsMetadata};
594 /// # tokio_test::block_on(async {
595 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
596 /// let directory = path.join("foo")?;
597 /// directory.create_dir().await?;
598 /// let file = path.join("foo.txt")?;
599 /// file.create_file().await?;
600 ///
601 /// assert!(directory.is_dir().await?);
602 /// assert!(!file.is_dir().await?);
603 /// # Ok::<(), VfsError>(())
604 /// # });
605 pub async fn is_dir(&self) -> VfsResult<bool> {
606 if !self.exists().await? {
607 return Ok(false);
608 }
609 let metadata = self.metadata().await?;
610 Ok(metadata.file_type == VfsFileType::Directory)
611 }
612
613 /// Returns true if a file or directory exists at this path, false otherwise
614 ///
615 /// ```
616 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
617 /// # use vfs::{VfsError, VfsFileType, VfsMetadata};
618 /// # tokio_test::block_on(async {
619 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
620 /// let directory = path.join("foo")?;
621 ///
622 /// assert!(!directory.exists().await?);
623 ///
624 /// directory.create_dir().await?;
625 ///
626 /// assert!(directory.exists().await?);
627 /// # Ok::<(), VfsError>(())
628 /// # });
629 pub async fn exists(&self) -> VfsResult<bool> {
630 self.fs.fs.exists(&self.path).await
631 }
632
633 /// Returns the filename portion of this path
634 ///
635 /// ```
636 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
637 /// # use vfs::{VfsError, VfsFileType, VfsMetadata};
638 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
639 /// let file = path.join("foo/bar.txt")?;
640 ///
641 /// assert_eq!(&file.filename(), "bar.txt");
642 ///
643 /// # Ok::<(), VfsError>(())
644 pub fn filename(&self) -> String {
645 self.filename_internal()
646 }
647
648 /// Returns the extension portion of this path
649 ///
650 /// ```
651 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
652 /// # use vfs::{VfsError, VfsFileType, VfsMetadata};
653 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
654 ///
655 /// assert_eq!(path.join("foo/bar.txt")?.extension(), Some("txt".to_string()));
656 /// assert_eq!(path.join("foo/bar.txt.zip")?.extension(), Some("zip".to_string()));
657 /// assert_eq!(path.join("foo/bar")?.extension(), None);
658 ///
659 /// # Ok::<(), VfsError>(())
660 pub fn extension(&self) -> Option<String> {
661 self.extension_internal()
662 }
663
664 /// Returns the parent path of this portion of this path
665 ///
666 /// Root will return itself.
667 ///
668 /// ```
669 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
670 /// # use vfs::{VfsError, VfsFileType, VfsMetadata};
671 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
672 ///
673 /// assert_eq!(path.parent(), path.root());
674 /// assert_eq!(path.join("foo/bar")?.parent(), path.join("foo")?);
675 /// assert_eq!(path.join("foo")?.parent(), path);
676 ///
677 /// # Ok::<(), VfsError>(())
678 pub fn parent(&self) -> Self {
679 let parent_path = self.parent_internal(&self.path);
680 Self {
681 path: parent_path,
682 fs: self.fs.clone(),
683 }
684 }
685
686 /// Recursively iterates over all the directories and files at this path
687 ///
688 /// Directories are visited before their children
689 ///
690 /// Note that the iterator items can contain errors, usually when directories are removed during the iteration.
691 /// The returned paths may also point to non-existant files if there is concurrent removal.
692 ///
693 /// Also note that loops in the file system hierarchy may cause this iterator to never terminate.
694 ///
695 /// ```
696 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
697 /// # use vfs::{VfsError, VfsResult};
698 /// use futures::stream::StreamExt;
699 /// # tokio_test::block_on(async {
700 /// let root = AsyncVfsPath::new(AsyncMemoryFS::new());
701 /// root.join("foo/bar")?.create_dir_all().await?;
702 /// root.join("fizz/buzz")?.create_dir_all().await?;
703 /// root.join("foo/bar/baz")?.create_file().await?;
704 ///
705 /// let mut directories = root.walk_dir().await?.map(|res| res.unwrap()).collect::<Vec<_>>().await;
706 ///
707 /// directories.sort_by_key(|path| path.as_str().to_string());
708 /// let expected = vec!["fizz", "fizz/buzz", "foo", "foo/bar", "foo/bar/baz"].iter().map(|path| root.join(path)).collect::<VfsResult<Vec<_>>>()?;
709 /// assert_eq!(directories, expected);
710 /// # Ok::<(), VfsError>(())
711 /// # });
712 /// ```
713 pub async fn walk_dir(&self) -> VfsResult<WalkDirIterator> {
714 Ok(WalkDirIterator {
715 inner: self.read_dir().await?,
716 todo: vec![],
717 prev_result: None,
718 metadata_fut: None,
719 read_dir_fut: None,
720 })
721 }
722
723 /// Reads a complete file to a string
724 ///
725 /// Returns an error if the file does not exist or is not valid UTF-8
726 ///
727 /// ```
728 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
729 /// # use vfs::VfsError;
730 /// use async_std::io::{ReadExt, WriteExt};
731 /// # tokio_test::block_on(async {
732 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
733 /// let file = path.join("foo.txt")?;
734 /// write!(file.create_file().await?, "Hello, world!").await?;
735 ///
736 /// let result = file.read_to_string().await?;
737 ///
738 /// assert_eq!(&result, "Hello, world!");
739 /// # Ok::<(), VfsError>(())
740 /// # });
741 /// ```
742 pub async fn read_to_string(&self) -> VfsResult<String> {
743 let metadata = self.metadata().await?;
744 if metadata.file_type != VfsFileType::File {
745 return Err(
746 VfsError::from(VfsErrorKind::Other("Path is a directory".into()))
747 .with_path(&self.path)
748 .with_context(|| "Could not read path"),
749 );
750 }
751 let mut result = String::with_capacity(metadata.len as usize);
752 self.open_file()
753 .await?
754 .read_to_string(&mut result)
755 .await
756 .map_err(|source| {
757 VfsError::from(source)
758 .with_path(&self.path)
759 .with_context(|| "Could not read path")
760 })?;
761 Ok(result)
762 }
763
764 /// Copies a file to a new destination
765 ///
766 /// The destination must not exist, but its parent directory must
767 ///
768 /// ```
769 /// use async_std::io::{ReadExt, WriteExt};
770 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
771 /// # use vfs::VfsError;
772 /// # tokio_test::block_on(async {
773 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
774 /// let src = path.join("foo.txt")?;
775 /// write!(src.create_file().await?, "Hello, world!").await?;
776 /// let dest = path.join("bar.txt")?;
777 ///
778 /// src.copy_file(&dest).await?;
779 ///
780 /// assert_eq!(dest.read_to_string().await?, "Hello, world!");
781 /// # Ok::<(), VfsError>(())
782 /// # });
783 /// ```
784 pub async fn copy_file(&self, destination: &AsyncVfsPath) -> VfsResult<()> {
785 async {
786 if destination.exists().await? {
787 return Err(VfsError::from(VfsErrorKind::Other(
788 "Destination exists already".into(),
789 ))
790 .with_path(&self.path));
791 }
792 if Arc::ptr_eq(&self.fs, &destination.fs) {
793 let result = self.fs.fs.copy_file(&self.path, &destination.path).await;
794 match result {
795 Err(err) => match err.kind() {
796 VfsErrorKind::NotSupported => {
797 // continue
798 }
799 _ => return Err(err),
800 },
801 other => return other,
802 }
803 }
804 let mut src = self.open_file().await?;
805 let mut dest = destination.create_file().await?;
806 async_std::io::copy(&mut src, &mut dest)
807 .await
808 .map_err(|source| {
809 VfsError::from(source)
810 .with_path(&self.path)
811 .with_context(|| "Could not read path")
812 })?;
813 Ok(())
814 }
815 .await
816 .map_err(|err| {
817 err.with_path(&self.path).with_context(|| {
818 format!(
819 "Could not copy '{}' to '{}'",
820 self.as_str(),
821 destination.as_str()
822 )
823 })
824 })?;
825 Ok(())
826 }
827
828 /// Moves or renames a file to a new destination
829 ///
830 /// The destination must not exist, but its parent directory must
831 ///
832 /// ```
833 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
834 /// # use vfs::VfsError;
835 /// use async_std::io::{ReadExt, WriteExt};
836 /// # tokio_test::block_on(async {
837 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
838 /// let src = path.join("foo.txt")?;
839 /// write!(src.create_file().await?, "Hello, world!").await?;
840 /// let dest = path.join("bar.txt")?;
841 ///
842 /// src.move_file(&dest).await?;
843 ///
844 /// assert_eq!(dest.read_to_string().await?, "Hello, world!");
845 /// assert!(!src.exists().await?);
846 /// # Ok::<(), VfsError>(())
847 /// # });
848 /// ```
849 pub async fn move_file(&self, destination: &AsyncVfsPath) -> VfsResult<()> {
850 async {
851 if destination.exists().await? {
852 return Err(VfsError::from(VfsErrorKind::Other(
853 "Destination exists already".into(),
854 ))
855 .with_path(&destination.path));
856 }
857 if Arc::ptr_eq(&self.fs, &destination.fs) {
858 let result = self.fs.fs.move_file(&self.path, &destination.path);
859 match result.await {
860 Err(err) => match err.kind() {
861 VfsErrorKind::NotSupported => {
862 // continue
863 }
864 _ => return Err(err),
865 },
866 other => return other,
867 }
868 }
869 let mut src = self.open_file().await?;
870 let mut dest = destination.create_file().await?;
871 async_std::io::copy(&mut src, &mut dest)
872 .await
873 .map_err(|source| {
874 VfsError::from(source)
875 .with_path(&self.path)
876 .with_context(|| "Could not read path")
877 })?;
878 self.remove_file().await?;
879 Ok(())
880 }
881 .await
882 .map_err(|err| {
883 err.with_path(&self.path).with_context(|| {
884 format!(
885 "Could not move '{}' to '{}'",
886 self.as_str(),
887 destination.as_str()
888 )
889 })
890 })?;
891 Ok(())
892 }
893
894 /// Copies a directory to a new destination, recursively
895 ///
896 /// The destination must not exist, but the parent directory must
897 ///
898 /// Returns the number of files copied
899 ///
900 /// ```
901 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
902 /// # use vfs::VfsError;
903 /// # tokio_test::block_on(async {
904 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
905 /// let src = path.join("foo")?;
906 /// src.join("dir")?.create_dir_all().await?;
907 /// let dest = path.join("bar.txt")?;
908 ///
909 /// src.copy_dir(&dest).await?;
910 ///
911 /// assert!(dest.join("dir")?.exists().await?);
912 /// # Ok::<(), VfsError>(())
913 /// # });
914 /// ```
915 pub async fn copy_dir(&self, destination: &AsyncVfsPath) -> VfsResult<u64> {
916 let files_copied = async {
917 let mut files_copied = 0u64;
918 if destination.exists().await? {
919 return Err(VfsError::from(VfsErrorKind::Other(
920 "Destination exists already".into(),
921 ))
922 .with_path(&destination.path));
923 }
924 destination.create_dir().await?;
925 let prefix = self.path.as_str();
926 let prefix_len = prefix.len();
927 let mut path_stream = self.walk_dir().await?;
928 while let Some(file) = path_stream.next().await {
929 let src_path: AsyncVfsPath = file?;
930 let dest_path = destination.join(&src_path.as_str()[prefix_len + 1..])?;
931 match src_path.metadata().await?.file_type {
932 VfsFileType::Directory => dest_path.create_dir().await?,
933 VfsFileType::File => src_path.copy_file(&dest_path).await?,
934 }
935 files_copied += 1;
936 }
937 Ok(files_copied)
938 }
939 .await
940 .map_err(|err| {
941 err.with_path(&self.path).with_context(|| {
942 format!(
943 "Could not copy directory '{}' to '{}'",
944 self.as_str(),
945 destination.as_str()
946 )
947 })
948 })?;
949 Ok(files_copied)
950 }
951
952 /// Moves a directory to a new destination, including subdirectories and files
953 ///
954 /// The destination must not exist, but its parent directory must
955 ///
956 /// ```
957 /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
958 /// # use vfs::VfsError;
959 /// # tokio_test::block_on(async {
960 /// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
961 /// let src = path.join("foo")?;
962 /// src.join("dir")?.create_dir_all().await?;
963 /// let dest = path.join("bar.txt")?;
964 ///
965 /// src.move_dir(&dest).await?;
966 ///
967 /// assert!(dest.join("dir")?.exists().await?);
968 /// assert!(!src.join("dir")?.exists().await?);
969 /// # Ok::<(), VfsError>(())
970 /// # });
971 /// ```
972 pub async fn move_dir(&self, destination: &AsyncVfsPath) -> VfsResult<()> {
973 async {
974 if destination.exists().await? {
975 return Err(VfsError::from(VfsErrorKind::Other(
976 "Destination exists already".into(),
977 ))
978 .with_path(&destination.path));
979 }
980 if Arc::ptr_eq(&self.fs, &destination.fs) {
981 let result = self.fs.fs.move_dir(&self.path, &destination.path).await;
982 match result {
983 Err(err) => match err.kind() {
984 VfsErrorKind::NotSupported => {
985 // continue
986 }
987 _ => return Err(err),
988 },
989 other => return other,
990 }
991 }
992 destination.create_dir().await?;
993 let prefix = self.path.as_str();
994 let prefix_len = prefix.len();
995 let mut path_stream = self.walk_dir().await?;
996 while let Some(file) = path_stream.next().await {
997 let src_path: AsyncVfsPath = file?;
998 let dest_path = destination.join(&src_path.as_str()[prefix_len + 1..])?;
999 match src_path.metadata().await?.file_type {
1000 VfsFileType::Directory => dest_path.create_dir().await?,
1001 VfsFileType::File => src_path.copy_file(&dest_path).await?,
1002 }
1003 }
1004 self.remove_dir_all().await?;
1005 Ok(())
1006 }
1007 .await
1008 .map_err(|err| {
1009 err.with_path(&self.path).with_context(|| {
1010 format!(
1011 "Could not move directory '{}' to '{}'",
1012 self.as_str(),
1013 destination.as_str()
1014 )
1015 })
1016 })?;
1017 Ok(())
1018 }
1019}
1020
1021/// An iterator for recursively walking a file hierarchy
1022pub struct WalkDirIterator {
1023 /// the path iterator of the current directory
1024 inner: Box<dyn Stream<Item = AsyncVfsPath> + Send + Unpin>,
1025 /// stack of subdirectories still to walk
1026 todo: Vec<AsyncVfsPath>,
1027 /// used to store the previous yield of the todo stream,
1028 /// which would otherwise get dropped if path.metadata() is pending
1029 prev_result: Option<AsyncVfsPath>,
1030 // Used to store futures when poll_next returns pending
1031 // this ensures a new future is not spawned on each poll.
1032 read_dir_fut: Option<
1033 BoxFuture<'static, Result<Box<(dyn Stream<Item = AsyncVfsPath> + Send + Unpin)>, VfsError>>,
1034 >,
1035 metadata_fut: Option<BoxFuture<'static, Result<VfsMetadata, VfsError>>>,
1036}
1037
1038impl std::fmt::Debug for WalkDirIterator {
1039 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1040 f.write_str("WalkDirIterator")?;
1041 self.todo.fmt(f)
1042 }
1043}
1044
1045impl Stream for WalkDirIterator {
1046 type Item = VfsResult<AsyncVfsPath>;
1047
1048 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1049 let this = self.get_mut();
1050 // Check if we have a previously stored result from last call
1051 // that we could not utilize due to pending path.metadata() call
1052 let result = if this.prev_result.is_none() {
1053 loop {
1054 match this.inner.poll_next_unpin(cx) {
1055 Poll::Pending => return Poll::Pending,
1056 Poll::Ready(Some(path)) => break Ok(path),
1057 Poll::Ready(None) => {
1058 let directory = if this.todo.is_empty() {
1059 return Poll::Ready(None);
1060 } else {
1061 this.todo[this.todo.len() - 1].clone()
1062 };
1063 let mut read_dir_fut = if this.read_dir_fut.is_some() {
1064 this.read_dir_fut.take().unwrap()
1065 } else {
1066 Box::pin(async move { directory.read_dir().await })
1067 };
1068 match read_dir_fut.poll_unpin(cx) {
1069 Poll::Pending => {
1070 this.read_dir_fut = Some(read_dir_fut);
1071 return Poll::Pending;
1072 }
1073 Poll::Ready(Err(err)) => {
1074 let _ = this.todo.pop();
1075 break Err(err);
1076 }
1077 Poll::Ready(Ok(iterator)) => {
1078 let _ = this.todo.pop();
1079 this.inner = iterator;
1080 }
1081 }
1082 }
1083 }
1084 }
1085 } else {
1086 Ok(this.prev_result.take().unwrap())
1087 };
1088 if let Ok(path) = &result {
1089 let mut metadata_fut = if this.metadata_fut.is_some() {
1090 this.metadata_fut.take().unwrap()
1091 } else {
1092 let path_clone = path.clone();
1093 Box::pin(async move { path_clone.metadata().await })
1094 };
1095 match metadata_fut.poll_unpin(cx) {
1096 Poll::Pending => {
1097 this.prev_result = Some(path.clone());
1098 this.metadata_fut = Some(metadata_fut);
1099 return Poll::Pending;
1100 }
1101 Poll::Ready(Ok(meta)) => {
1102 if meta.file_type == VfsFileType::Directory {
1103 this.todo.push(path.clone());
1104 }
1105 }
1106 Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))),
1107 }
1108 }
1109 Poll::Ready(Some(result))
1110 }
1111}