vfs/async_vfs/impls/
physical.rs

1//! An async implementation of a "physical" file system implementation using the underlying OS file system
2use crate::async_vfs::{AsyncFileSystem, SeekAndRead};
3use crate::error::VfsErrorKind;
4use crate::path::VfsFileType;
5use crate::{VfsError, VfsMetadata, VfsResult};
6
7use async_std::fs::{File, OpenOptions};
8use async_std::io::{ErrorKind, Write};
9use async_std::path::{Path, PathBuf};
10use async_trait::async_trait;
11use filetime::FileTime;
12use futures::stream::{Stream, StreamExt};
13use std::pin::Pin;
14use std::time::SystemTime;
15use tokio::runtime::Handle;
16
17/// A physical filesystem implementation using the underlying OS file system
18#[derive(Debug)]
19pub struct AsyncPhysicalFS {
20    root: Pin<PathBuf>,
21}
22
23impl AsyncPhysicalFS {
24    /// Create a new physical filesystem rooted in `root`
25    pub fn new<T: AsRef<Path>>(root: T) -> Self {
26        AsyncPhysicalFS {
27            root: Pin::new(root.as_ref().to_path_buf()),
28        }
29    }
30
31    fn get_path(&self, mut path: &str) -> PathBuf {
32        if path.starts_with('/') {
33            path = &path[1..];
34        }
35        self.root.join(path)
36    }
37}
38
39/// Runs normal blocking io on a tokio thread.
40/// Requires a tokio runtime.
41async fn blocking_io<F>(f: F) -> Result<(), VfsError>
42where
43    F: FnOnce() -> std::io::Result<()> + Send + 'static,
44{
45    if Handle::try_current().is_ok() {
46        let result = tokio::task::spawn_blocking(f).await;
47
48        match result {
49            Ok(val) => val,
50            Err(err) => {
51                return Err(VfsError::from(VfsErrorKind::Other(format!(
52                    "Tokio Concurrency Error: {}",
53                    err
54                ))));
55            }
56        }?;
57
58        Ok(())
59    } else {
60        Err(VfsError::from(VfsErrorKind::NotSupported))
61    }
62}
63
64#[async_trait]
65impl AsyncFileSystem for AsyncPhysicalFS {
66    async fn read_dir(
67        &self,
68        path: &str,
69    ) -> VfsResult<Box<dyn Unpin + Stream<Item = String> + Send>> {
70        let entries = Box::new(
71            self.get_path(path)
72                .read_dir()
73                .await?
74                .map(|entry| entry.unwrap().file_name().into_string().unwrap()),
75        );
76        Ok(entries)
77    }
78
79    async fn create_dir(&self, path: &str) -> VfsResult<()> {
80        let fs_path = self.get_path(path);
81        match async_std::fs::create_dir(&fs_path).await {
82            Ok(()) => Ok(()),
83            Err(e) => match e.kind() {
84                ErrorKind::AlreadyExists => {
85                    let metadata = async_std::fs::metadata(&fs_path).await.unwrap();
86                    if metadata.is_dir() {
87                        return Err(VfsError::from(VfsErrorKind::DirectoryExists));
88                    }
89                    Err(VfsError::from(VfsErrorKind::FileExists))
90                }
91                _ => Err(e.into()),
92            },
93        }
94    }
95
96    async fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send + Unpin>> {
97        Ok(Box::new(File::open(self.get_path(path)).await?))
98    }
99
100    async fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>> {
101        Ok(Box::new(File::create(self.get_path(path)).await?))
102    }
103
104    async fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>> {
105        Ok(Box::new(
106            OpenOptions::new()
107                .write(true)
108                .append(true)
109                .open(self.get_path(path))
110                .await?,
111        ))
112    }
113
114    async fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
115        let metadata = self.get_path(path).metadata().await?;
116        Ok(if metadata.is_dir() {
117            VfsMetadata {
118                file_type: VfsFileType::Directory,
119                len: 0,
120                modified: metadata.modified().ok(),
121                created: metadata.created().ok(),
122                accessed: metadata.accessed().ok(),
123            }
124        } else {
125            VfsMetadata {
126                file_type: VfsFileType::File,
127                len: metadata.len(),
128                modified: metadata.modified().ok(),
129                created: metadata.created().ok(),
130                accessed: metadata.accessed().ok(),
131            }
132        })
133    }
134
135    async fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
136        let path = self.get_path(path);
137
138        blocking_io(move || filetime::set_file_mtime(path, FileTime::from(time))).await?;
139
140        Ok(())
141    }
142
143    async fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
144        let path = self.get_path(path);
145
146        blocking_io(move || filetime::set_file_atime(path, FileTime::from(time))).await?;
147
148        Ok(())
149    }
150
151    async fn exists(&self, path: &str) -> VfsResult<bool> {
152        Ok(self.get_path(path).exists().await)
153    }
154
155    async fn remove_file(&self, path: &str) -> VfsResult<()> {
156        async_std::fs::remove_file(self.get_path(path)).await?;
157        Ok(())
158    }
159
160    async fn remove_dir(&self, path: &str) -> VfsResult<()> {
161        async_std::fs::remove_dir(self.get_path(path)).await?;
162        Ok(())
163    }
164
165    async fn copy_file(&self, src: &str, dest: &str) -> VfsResult<()> {
166        async_std::fs::copy(self.get_path(src), self.get_path(dest)).await?;
167        Ok(())
168    }
169
170    async fn move_file(&self, src: &str, dest: &str) -> VfsResult<()> {
171        async_std::fs::rename(self.get_path(src), self.get_path(dest)).await?;
172
173        Ok(())
174    }
175
176    async fn move_dir(&self, src: &str, dest: &str) -> VfsResult<()> {
177        let result = async_std::fs::rename(self.get_path(src), self.get_path(dest)).await;
178        if result.is_err() {
179            // Error possibly due to different filesystems, return not supported and let the fallback handle it
180            return Err(VfsErrorKind::NotSupported.into());
181        }
182        Ok(())
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::async_vfs::AsyncVfsPath;
190
191    use async_std::io::ReadExt;
192    use async_std::io::WriteExt;
193    use async_std::path::Path;
194    use futures::stream::StreamExt;
195
196    test_async_vfs!(futures::executor::block_on(async {
197        let temp_dir = std::env::temp_dir();
198        let dir = temp_dir.join(uuid::Uuid::new_v4().to_string());
199        async_std::fs::create_dir_all(&dir).await.unwrap();
200        AsyncPhysicalFS::new(dir)
201    }));
202    test_async_vfs_readonly!({ AsyncPhysicalFS::new("test/test_directory") });
203
204    fn create_root() -> AsyncVfsPath {
205        AsyncPhysicalFS::new(std::env::current_dir().unwrap()).into()
206    }
207
208    #[tokio::test]
209    async fn open_file() {
210        let expected = async_std::fs::read_to_string("Cargo.toml").await.unwrap();
211        let root = create_root();
212        let mut string = String::new();
213        root.join("Cargo.toml")
214            .unwrap()
215            .open_file()
216            .await
217            .unwrap()
218            .read_to_string(&mut string)
219            .await
220            .unwrap();
221        assert_eq!(string, expected);
222    }
223
224    #[tokio::test]
225    async fn create_file() {
226        let root = create_root();
227        let _string = String::new();
228        let _ = async_std::fs::remove_file("target/test.txt").await;
229        root.join("target/test.txt")
230            .unwrap()
231            .create_file()
232            .await
233            .unwrap()
234            .write_all(b"Testing only")
235            .await
236            .unwrap();
237        let read = std::fs::read_to_string("target/test.txt").unwrap();
238        assert_eq!(read, "Testing only");
239    }
240
241    #[tokio::test]
242    async fn append_file() {
243        let root = create_root();
244        let _string = String::new();
245        let _ = async_std::fs::remove_file("target/test_append.txt").await;
246        let path = Box::pin(root.join("target/test_append.txt").unwrap());
247        path.create_file()
248            .await
249            .unwrap()
250            .write_all(b"Testing 1")
251            .await
252            .unwrap();
253        path.append_file()
254            .await
255            .unwrap()
256            .write_all(b"Testing 2")
257            .await
258            .unwrap();
259        let read = async_std::fs::read_to_string("target/test_append.txt")
260            .await
261            .unwrap();
262        assert_eq!(read, "Testing 1Testing 2");
263    }
264
265    #[tokio::test]
266    async fn read_dir() {
267        let _expected = async_std::fs::read_to_string("Cargo.toml").await.unwrap();
268        let root = create_root();
269        let entries: Vec<_> = root.read_dir().await.unwrap().collect().await;
270        let map: Vec<_> = entries
271            .iter()
272            .map(|path: &AsyncVfsPath| path.as_str())
273            .filter(|x| x.ends_with(".toml"))
274            .collect();
275        assert_eq!(&["/Cargo.toml"], &map[..]);
276    }
277
278    #[tokio::test]
279    async fn create_dir() {
280        let _ = async_std::fs::remove_dir("target/fs_test").await;
281        let root = create_root();
282        root.join("target/fs_test")
283            .unwrap()
284            .create_dir()
285            .await
286            .unwrap();
287        let path = Path::new("target/fs_test");
288        assert!(path.exists().await, "Path was not created");
289        assert!(path.is_dir().await, "Path is not a directory");
290        async_std::fs::remove_dir("target/fs_test").await.unwrap();
291    }
292
293    #[tokio::test]
294    async fn file_metadata() {
295        let expected = async_std::fs::read_to_string("Cargo.toml").await.unwrap();
296        let root = create_root();
297        let metadata = root.join("Cargo.toml").unwrap().metadata().await.unwrap();
298        assert_eq!(metadata.len, expected.len() as u64);
299        assert_eq!(metadata.file_type, VfsFileType::File);
300    }
301
302    #[tokio::test]
303    async fn dir_metadata() {
304        let root = create_root();
305        let metadata = root.metadata().await.unwrap();
306        assert_eq!(metadata.len, 0);
307        assert_eq!(metadata.file_type, VfsFileType::Directory);
308        let metadata = root.join("src").unwrap().metadata().await.unwrap();
309        assert_eq!(metadata.len, 0);
310        assert_eq!(metadata.file_type, VfsFileType::Directory);
311    }
312}