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