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}