vfs/async_vfs/impls/
physical.rs1use 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#[derive(Debug)]
19pub struct AsyncPhysicalFS {
20 root: Pin<PathBuf>,
21}
22
23impl AsyncPhysicalFS {
24 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
39async 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 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}