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}