Skip to main content

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