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}