Skip to main content

bias_vfs/impls/
physical.rs

1//! A "physical" file system implementation using the underlying OS file system
2
3use crate::error::VfsErrorKind;
4use crate::{FileSystem, SeekAndWrite, VfsMetadata};
5use crate::{SeekAndRead, VfsFileType};
6use crate::{VfsError, VfsResult};
7use filetime::FileTime;
8use std::fs::{File, OpenOptions};
9use std::io::ErrorKind;
10use std::path::{Path, PathBuf};
11use std::time::SystemTime;
12
13/// A physical filesystem implementation using the underlying OS file system
14#[derive(Debug)]
15pub struct PhysicalFS {
16    root: PathBuf,
17}
18
19impl PhysicalFS {
20    /// Create a new physical filesystem rooted in `root`
21    pub fn new<T: AsRef<Path>>(root: T) -> Self {
22        PhysicalFS {
23            root: root.as_ref().to_path_buf(),
24        }
25    }
26
27    fn get_path(&self, mut path: &str) -> PathBuf {
28        if path.starts_with('/') {
29            path = &path[1..];
30        }
31        self.root.join(path)
32    }
33}
34
35impl FileSystem for PhysicalFS {
36    fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String> + Send>> {
37        let entries = Box::new(
38            self.get_path(path)
39                .read_dir()?
40                .map(|entry| entry.unwrap().file_name().into_string().unwrap()),
41        );
42        Ok(entries)
43    }
44
45    fn create_dir(&self, path: &str) -> VfsResult<()> {
46        let fs_path = self.get_path(path);
47        std::fs::create_dir(&fs_path).map_err(|err| match err.kind() {
48            ErrorKind::AlreadyExists => {
49                let metadata = std::fs::metadata(&fs_path).unwrap();
50                if metadata.is_dir() {
51                    return VfsError::from(VfsErrorKind::DirectoryExists);
52                }
53                VfsError::from(VfsErrorKind::FileExists)
54            }
55            _ => err.into(),
56        })?;
57        Ok(())
58    }
59
60    fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send>> {
61        Ok(Box::new(File::open(self.get_path(path))?))
62    }
63
64    fn create_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
65        Ok(Box::new(File::create(self.get_path(path))?))
66    }
67
68    fn append_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
69        Ok(Box::new(
70            OpenOptions::new().append(true).open(self.get_path(path))?,
71        ))
72    }
73
74    fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
75        let metadata = self.get_path(path).metadata()?;
76        Ok(if metadata.is_dir() {
77            VfsMetadata {
78                file_type: VfsFileType::Directory,
79                len: 0,
80                modified: metadata.modified().ok(),
81                created: metadata.created().ok(),
82                accessed: metadata.accessed().ok(),
83            }
84        } else {
85            VfsMetadata {
86                file_type: VfsFileType::File,
87                len: metadata.len(),
88                modified: metadata.modified().ok(),
89                created: metadata.created().ok(),
90                accessed: metadata.accessed().ok(),
91            }
92        })
93    }
94
95    fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
96        filetime::set_file_mtime(self.get_path(path), FileTime::from(time))?;
97        Ok(())
98    }
99
100    fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
101        filetime::set_file_atime(self.get_path(path), FileTime::from(time))?;
102        Ok(())
103    }
104
105    fn exists(&self, path: &str) -> VfsResult<bool> {
106        Ok(self.get_path(path).exists())
107    }
108
109    fn remove_file(&self, path: &str) -> VfsResult<()> {
110        std::fs::remove_file(self.get_path(path))?;
111        Ok(())
112    }
113
114    fn remove_dir(&self, path: &str) -> VfsResult<()> {
115        std::fs::remove_dir(self.get_path(path))?;
116        Ok(())
117    }
118
119    fn copy_file(&self, src: &str, dest: &str) -> VfsResult<()> {
120        std::fs::copy(self.get_path(src), self.get_path(dest))?;
121        Ok(())
122    }
123
124    fn move_file(&self, src: &str, dest: &str) -> VfsResult<()> {
125        std::fs::rename(self.get_path(src), self.get_path(dest))?;
126
127        Ok(())
128    }
129
130    fn move_dir(&self, src: &str, dest: &str) -> VfsResult<()> {
131        let result = std::fs::rename(self.get_path(src), self.get_path(dest));
132        if result.is_err() {
133            // Error possibly due to different filesystems, return not supported and let the fallback handle it
134            return Err(VfsErrorKind::NotSupported.into());
135        }
136        Ok(())
137    }
138
139    fn real_path(&self, path: &str) -> VfsResult<PathBuf> {
140        let path = path.strip_prefix("/").unwrap_or(path);
141        let real_path = self.root.join(path);
142
143        Ok(real_path)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use std::path::Path;
150
151    use super::*;
152
153    use crate::VfsPath;
154    test_vfs!({
155        let temp_dir = std::env::temp_dir();
156        let dir = temp_dir.join(uuid::Uuid::new_v4().to_string());
157        std::fs::create_dir_all(&dir).unwrap();
158        PhysicalFS::new(dir)
159    });
160    test_vfs_readonly!({ PhysicalFS::new("test/test_directory") });
161
162    fn create_root() -> VfsPath {
163        PhysicalFS::new(std::env::current_dir().unwrap()).into()
164    }
165
166    #[test]
167    fn open_file() {
168        let expected = std::fs::read_to_string("Cargo.toml").unwrap();
169        let root = create_root();
170        let mut string = String::new();
171        root.join("Cargo.toml")
172            .unwrap()
173            .open_file()
174            .unwrap()
175            .read_to_string(&mut string)
176            .unwrap();
177        assert_eq!(string, expected);
178    }
179
180    #[test]
181    fn create_file() {
182        let root = create_root();
183        let _string = String::new();
184        let _ = std::fs::remove_file("target/test.txt");
185        root.join("target/test.txt")
186            .unwrap()
187            .create_file()
188            .unwrap()
189            .write_all(b"Testing only")
190            .unwrap();
191        let read = std::fs::read_to_string("target/test.txt").unwrap();
192        assert_eq!(read, "Testing only");
193    }
194
195    #[test]
196    fn append_file() {
197        let root = create_root();
198        let _string = String::new();
199        let _ = std::fs::remove_file("target/test_append.txt");
200        let path = root.join("target/test_append.txt").unwrap();
201        path.create_file().unwrap().write_all(b"Testing 1").unwrap();
202        path.append_file().unwrap().write_all(b"Testing 2").unwrap();
203        let read = std::fs::read_to_string("target/test_append.txt").unwrap();
204        assert_eq!(read, "Testing 1Testing 2");
205    }
206
207    #[test]
208    fn read_dir() {
209        let _expected = std::fs::read_to_string("Cargo.toml").unwrap();
210        let root = create_root();
211        let entries: Vec<_> = root.read_dir().unwrap().collect();
212        let map: Vec<_> = entries
213            .iter()
214            .map(|path: &VfsPath| path.as_str())
215            .filter(|x| x.ends_with(".toml"))
216            .collect();
217        assert_eq!(&["/Cargo.toml"], &map[..]);
218    }
219
220    #[test]
221    fn create_dir() {
222        let _ = std::fs::remove_dir("target/fs_test");
223        let root = create_root();
224        root.join("target/fs_test").unwrap().create_dir().unwrap();
225        let path = Path::new("target/fs_test");
226        assert!(path.exists(), "Path was not created");
227        assert!(path.is_dir(), "Path is not a directory");
228        std::fs::remove_dir("target/fs_test").unwrap();
229    }
230
231    #[test]
232    fn file_metadata() {
233        let expected = std::fs::read_to_string("Cargo.toml").unwrap();
234        let root = create_root();
235        let metadata = root.join("Cargo.toml").unwrap().metadata().unwrap();
236        assert_eq!(metadata.len, expected.len() as u64);
237        assert_eq!(metadata.file_type, VfsFileType::File);
238    }
239
240    #[test]
241    fn dir_metadata() {
242        let root = create_root();
243        let metadata = root.metadata().unwrap();
244        assert_eq!(metadata.len, 0);
245        assert_eq!(metadata.file_type, VfsFileType::Directory);
246        let metadata = root.join("src").unwrap().metadata().unwrap();
247        assert_eq!(metadata.len, 0);
248        assert_eq!(metadata.file_type, VfsFileType::Directory);
249    }
250}