floppy_disk/
tokio_fs.rs

1use std::ffi::OsString;
2use std::fs::{FileType, Metadata, Permissions};
3use std::os::unix::prelude::PermissionsExt;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::time::SystemTime;
7
8use tokio::fs::{DirBuilder, DirEntry, File, OpenOptions, ReadDir};
9use tokio::io::ReadBuf;
10use tracing::debug;
11
12use crate::*;
13
14#[derive(Default, Debug)]
15pub struct TokioFloppyDisk {
16    scope: Option<PathBuf>,
17}
18
19impl TokioFloppyDisk {
20    pub fn new(scope: Option<PathBuf>) -> Self {
21        Self { scope }
22    }
23}
24
25macro_rules! scoped {
26    ( $this: expr, $x:ident ) => {
27        let $x = if let Some(ref scope) = $this.scope {
28            let path: &Path = $x.as_ref();
29            if path.starts_with(scope) {
30                path.to_path_buf()
31            } else {
32                let path = path.strip_prefix("/").unwrap_or(&path).to_path_buf();
33                scope.join(path)
34            }
35        } else {
36            let path: &Path = $x.as_ref();
37            path.to_path_buf()
38        };
39    };
40}
41
42#[async_trait::async_trait]
43impl<'a> FloppyDisk<'a> for TokioFloppyDisk {
44    type DirBuilder = TokioDirBuilder;
45    type DirEntry = TokioDirEntry;
46    type File = TokioFile;
47    type FileType = TokioFileType;
48    type Metadata = TokioMetadata;
49    type OpenOptions = TokioOpenOptions;
50    type Permissions = TokioPermissions;
51    type ReadDir = TokioReadDir;
52
53    async fn canonicalize<P: AsRef<Path> + Send>(&self, path: P) -> Result<PathBuf> {
54        scoped!(self, path);
55        debug!(
56            "canonicalise {} (scope = {:?})",
57            path.display(),
58            &self.scope
59        );
60        tokio::fs::canonicalize(path).await
61    }
62
63    async fn copy<P: AsRef<Path> + Send>(&self, from: P, to: P) -> Result<u64> {
64        scoped!(self, from);
65        scoped!(self, to);
66        debug!(
67            "copy {} -> {} (scope = {:?})",
68            from.display(),
69            to.display(),
70            &self.scope
71        );
72        tokio::fs::copy(from, to).await
73    }
74
75    async fn create_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
76        scoped!(self, path);
77        debug!("create_dir {} (scope = {:?})", path.display(), &self.scope);
78        tokio::fs::create_dir(path).await
79    }
80
81    async fn create_dir_all<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
82        scoped!(self, path);
83        debug!(
84            "create_dir_all {} (scope = {:?})",
85            path.display(),
86            &self.scope
87        );
88        tokio::fs::create_dir_all(path).await
89    }
90
91    async fn hard_link<P: AsRef<Path> + Send>(&self, src: P, dst: P) -> Result<()> {
92        scoped!(self, src);
93        scoped!(self, dst);
94        debug!(
95            "hard_link {} -> {} (scope = {:?})",
96            src.display(),
97            dst.display(),
98            &self.scope
99        );
100        tokio::fs::hard_link(src, dst).await
101    }
102
103    async fn metadata<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::Metadata> {
104        scoped!(self, path);
105        debug!("metadata {} (scope = {:?})", path.display(), &self.scope);
106        tokio::fs::metadata(path).await.map(TokioMetadata)
107    }
108
109    async fn read<P: AsRef<Path> + Send>(&self, path: P) -> Result<Vec<u8>> {
110        scoped!(self, path);
111        debug!("read {} (scope = {:?})", path.display(), &self.scope);
112        tokio::fs::read(path).await
113    }
114
115    async fn read_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::ReadDir> {
116        scoped!(self, path);
117        debug!("read_dir {} (scope = {:?})", path.display(), &self.scope);
118        tokio::fs::read_dir(path).await.map(TokioReadDir)
119    }
120
121    async fn read_link<P: AsRef<Path> + Send>(&self, path: P) -> Result<PathBuf> {
122        scoped!(self, path);
123        debug!("read_link {} (scope = {:?})", path.display(), &self.scope);
124        tokio::fs::read_link(path).await
125    }
126
127    async fn read_to_string<P: AsRef<Path> + Send>(&self, path: P) -> Result<String> {
128        scoped!(self, path);
129        debug!(
130            "read_to_string {} (scope = {:?})",
131            path.display(),
132            &self.scope
133        );
134        tokio::fs::read_to_string(path).await
135    }
136
137    async fn remove_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
138        scoped!(self, path);
139        debug!("remove_dir {} (scope = {:?})", path.display(), &self.scope);
140        tokio::fs::remove_dir(path).await
141    }
142
143    async fn remove_dir_all<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
144        scoped!(self, path);
145        debug!(
146            "remove_dir_all {} (scope = {:?})",
147            path.display(),
148            &self.scope
149        );
150        tokio::fs::remove_dir_all(path).await
151    }
152
153    async fn remove_file<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
154        scoped!(self, path);
155        debug!("remove_file {} (scope = {:?})", path.display(), &self.scope);
156        tokio::fs::remove_file(path).await
157    }
158
159    async fn rename<P: AsRef<Path> + Send>(&self, from: P, to: P) -> Result<()> {
160        scoped!(self, from);
161        scoped!(self, to);
162        debug!(
163            "rename {} -> {} (scope = {:?})",
164            from.display(),
165            to.display(),
166            &self.scope
167        );
168        tokio::fs::rename(from, to).await
169    }
170
171    async fn set_permissions<P: AsRef<Path> + Send>(
172        &self,
173        path: P,
174        perm: Self::Permissions,
175    ) -> Result<()> {
176        scoped!(self, path);
177        debug!(
178            "set_permissions {} (scope = {:?})",
179            path.display(),
180            &self.scope
181        );
182        tokio::fs::set_permissions(path, perm.0).await
183    }
184
185    async fn symlink<P: AsRef<Path> + Send>(&self, src: P, dst: P) -> Result<()> {
186        scoped!(self, src);
187        scoped!(self, dst);
188        debug!(
189            "symlink {} -> {} (scope = {:?})",
190            src.display(),
191            dst.display(),
192            &self.scope
193        );
194        tokio::fs::symlink(src, dst).await
195    }
196
197    async fn symlink_metadata<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::Metadata> {
198        scoped!(self, path);
199        debug!(
200            "symlink_metadata {} (scope = {:?})",
201            path.display(),
202            &self.scope
203        );
204        tokio::fs::symlink_metadata(path).await.map(TokioMetadata)
205    }
206
207    async fn try_exists<P: AsRef<Path> + Send>(&self, path: P) -> Result<bool> {
208        scoped!(self, path);
209        debug!("try_exists {} (scope = {:?})", path.display(), &self.scope);
210        tokio::fs::try_exists(path).await
211    }
212
213    async fn write<P: AsRef<Path> + Send>(
214        &self,
215        path: P,
216        contents: impl AsRef<[u8]> + Send,
217    ) -> Result<()> {
218        scoped!(self, path);
219        debug!("write {} (scope = {:?})", path.display(), &self.scope);
220        tokio::fs::write(path, contents).await
221    }
222
223    fn new_dir_builder(&'a self) -> Self::DirBuilder {
224        TokioDirBuilder(DirBuilder::new())
225    }
226}
227
228#[cfg(unix)]
229#[async_trait::async_trait]
230impl FloppyDiskUnixExt for TokioFloppyDisk {
231    async fn chown<P: Into<PathBuf> + Send>(&self, path: P, uid: u32, gid: u32) -> Result<()> {
232        let path = path.into();
233        scoped!(self, path);
234        debug!("chown {} (scope = {:?})", path.display(), &self.scope);
235
236        tokio::task::spawn_blocking(move || {
237            use std::os::unix::prelude::OsStrExt;
238
239            // TODO: Figure out getting rid of
240            unsafe {
241                libc::chown(
242                    path.as_os_str().as_bytes().as_ptr() as *const libc::c_char,
243                    uid,
244                    gid,
245                );
246            }
247            Ok(())
248        })
249        .await?
250    }
251}
252
253#[repr(transparent)]
254#[derive(Debug)]
255pub struct TokioMetadata(#[doc(hidden)] Metadata);
256
257#[async_trait::async_trait]
258impl<'a> FloppyMetadata<'a, TokioFloppyDisk> for TokioMetadata {
259    fn file_type(&self) -> <TokioFloppyDisk as FloppyDisk<'a>>::FileType {
260        TokioFileType(self.0.file_type())
261    }
262
263    fn is_dir(&self) -> bool {
264        self.0.is_dir()
265    }
266
267    fn is_file(&self) -> bool {
268        self.0.is_file()
269    }
270
271    fn is_symlink(&self) -> bool {
272        self.0.is_symlink()
273    }
274
275    fn len(&self) -> u64 {
276        self.0.len()
277    }
278
279    fn permissions(&self) -> <TokioFloppyDisk as FloppyDisk<'a>>::Permissions {
280        TokioPermissions(self.0.permissions())
281    }
282
283    fn modified(&self) -> Result<SystemTime> {
284        self.0.modified()
285    }
286
287    fn accessed(&self) -> Result<SystemTime> {
288        self.0.accessed()
289    }
290
291    fn created(&self) -> Result<SystemTime> {
292        self.0.created()
293    }
294}
295
296#[cfg(unix)]
297impl FloppyUnixMetadata for TokioMetadata {
298    fn uid(&self) -> Result<u32> {
299        use std::os::unix::prelude::MetadataExt;
300        Ok(self.0.uid())
301    }
302
303    fn gid(&self) -> Result<u32> {
304        use std::os::unix::prelude::MetadataExt;
305        Ok(self.0.gid())
306    }
307}
308
309#[repr(transparent)]
310#[derive(Debug)]
311pub struct TokioReadDir(#[doc(hidden)] ReadDir);
312
313#[async_trait::async_trait]
314impl<'a> FloppyReadDir<'a, TokioFloppyDisk> for TokioReadDir {
315    async fn next_entry(
316        &mut self,
317    ) -> Result<Option<<TokioFloppyDisk as FloppyDisk<'a>>::DirEntry>> {
318        self.0
319            .next_entry()
320            .await
321            .map(|entry| entry.map(TokioDirEntry))
322    }
323}
324
325#[repr(transparent)]
326#[derive(Debug)]
327pub struct TokioPermissions(#[doc(hidden)] Permissions);
328
329impl FloppyPermissions for TokioPermissions {
330    fn readonly(&self) -> bool {
331        self.0.readonly()
332    }
333
334    fn set_readonly(&mut self, readonly: bool) {
335        self.0.set_readonly(readonly)
336    }
337}
338
339#[cfg(unix)]
340impl FloppyUnixPermissions for TokioPermissions {
341    fn mode(&self) -> u32 {
342        self.0.mode()
343    }
344
345    fn set_mode(&mut self, mode: u32) {
346        self.0.set_mode(mode)
347    }
348
349    fn from_mode(mode: u32) -> Self {
350        Self(Permissions::from_mode(mode))
351    }
352}
353
354#[repr(transparent)]
355#[derive(Debug)]
356pub struct TokioDirBuilder(#[doc(hidden)] DirBuilder);
357
358#[async_trait::async_trait]
359impl FloppyDirBuilder for TokioDirBuilder {
360    fn recursive(&mut self, recursive: bool) -> &mut Self {
361        self.0.recursive(recursive);
362        self
363    }
364
365    async fn create<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
366        self.0.create(path).await
367    }
368
369    fn mode(&mut self, mode: u32) -> &mut Self {
370        self.0.mode(mode);
371        self
372    }
373}
374
375#[repr(transparent)]
376#[derive(Debug)]
377pub struct TokioDirEntry(#[doc(hidden)] DirEntry);
378
379#[async_trait::async_trait]
380impl<'a> FloppyDirEntry<'a, TokioFloppyDisk> for TokioDirEntry {
381    fn file_name(&self) -> OsString {
382        self.0.file_name()
383    }
384
385    async fn file_type(&self) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::FileType> {
386        self.0.file_type().await.map(TokioFileType)
387    }
388
389    async fn metadata(&self) -> Result<TokioMetadata> {
390        self.0.metadata().await.map(TokioMetadata)
391    }
392
393    fn path(&self) -> PathBuf {
394        self.0.path()
395    }
396
397    #[cfg(unix)]
398    fn ino(&self) -> u64 {
399        self.0.ino()
400    }
401}
402
403#[repr(transparent)]
404#[derive(Debug)]
405pub struct TokioFileType(#[doc(hidden)] FileType);
406
407impl FloppyFileType for TokioFileType {
408    fn is_dir(&self) -> bool {
409        self.0.is_dir()
410    }
411
412    fn is_file(&self) -> bool {
413        self.0.is_file()
414    }
415
416    fn is_symlink(&self) -> bool {
417        self.0.is_symlink()
418    }
419}
420
421#[derive(Debug)]
422pub struct TokioOpenOptions(#[doc(hidden)] OpenOptions);
423
424#[async_trait::async_trait]
425impl<'a> FloppyOpenOptions<'a, TokioFloppyDisk> for TokioOpenOptions {
426    fn new() -> Self {
427        Self(OpenOptions::new())
428    }
429
430    fn read(self, read: bool) -> Self {
431        let mut oo = self.0;
432        oo.read(read);
433        Self(oo)
434    }
435
436    fn write(self, write: bool) -> Self {
437        let mut oo = self.0;
438        oo.write(write);
439        Self(oo)
440    }
441
442    fn append(self, append: bool) -> Self {
443        let mut oo = self.0;
444        oo.append(append);
445        Self(oo)
446    }
447
448    fn truncate(self, truncate: bool) -> Self {
449        let mut oo = self.0;
450        oo.truncate(truncate);
451        Self(oo)
452    }
453
454    fn create(self, create: bool) -> Self {
455        let mut oo = self.0;
456        oo.create(create);
457        Self(oo)
458    }
459
460    fn create_new(self, create_new: bool) -> Self {
461        let mut oo = self.0;
462        oo.create_new(create_new);
463        Self(oo)
464    }
465
466    async fn open<P: AsRef<Path> + Send>(
467        &self,
468        disk: &'a TokioFloppyDisk,
469        path: P,
470    ) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::File> {
471        // TODO: Better way of restricting the scope?
472        let path = if let Some(ref scope) = disk.scope {
473            let path: &Path = path.as_ref();
474            let path = path.strip_prefix("/").unwrap_or(path).to_path_buf();
475            scope.join(path)
476        } else {
477            path.as_ref().to_path_buf()
478        };
479        debug!("opening {}", path.display());
480        self.0.open(path).await.map(TokioFile)
481    }
482}
483
484#[derive(Debug)]
485#[repr(transparent)]
486pub struct TokioFile(#[doc(hidden)] File);
487
488#[async_trait::async_trait]
489impl<'a> FloppyFile<'a, TokioFloppyDisk> for TokioFile {
490    async fn sync_all(&mut self) -> Result<()> {
491        self.0.sync_all().await
492    }
493
494    async fn sync_data(&mut self) -> Result<()> {
495        self.0.sync_data().await
496    }
497
498    async fn set_len(&mut self, size: u64) -> Result<()> {
499        self.0.set_len(size).await
500    }
501
502    async fn metadata(&self) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::Metadata> {
503        self.0.metadata().await.map(TokioMetadata)
504    }
505
506    async fn try_clone(&'a self) -> Result<Box<Self>> {
507        self.0
508            .try_clone()
509            .await
510            .map(|file| Box::new(TokioFile(file)))
511    }
512
513    async fn set_permissions(
514        &self,
515        perm: <TokioFloppyDisk as FloppyDisk>::Permissions,
516    ) -> Result<()> {
517        self.0.set_permissions(perm.0).await
518    }
519
520    async fn permissions(&self) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::Permissions> {
521        self.0
522            .metadata()
523            .await
524            .map(|metadata| TokioPermissions(metadata.permissions()))
525    }
526}
527
528impl AsyncRead for TokioFile {
529    fn poll_read(
530        self: Pin<&mut Self>,
531        cx: &mut Context<'_>,
532        buf: &mut ReadBuf<'_>,
533    ) -> Poll<Result<()>> {
534        Pin::new(&mut self.get_mut().0).poll_read(cx, buf)
535    }
536}
537
538impl AsyncSeek for TokioFile {
539    fn start_seek(self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
540        Pin::new(&mut self.get_mut().0).start_seek(position)
541    }
542
543    fn poll_complete(
544        self: Pin<&mut Self>,
545        cx: &mut std::task::Context<'_>,
546    ) -> std::task::Poll<std::io::Result<u64>> {
547        Pin::new(&mut self.get_mut().0).poll_complete(cx)
548    }
549}
550
551impl AsyncWrite for TokioFile {
552    fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
553        Pin::new(&mut self.get_mut().0).poll_write(cx, buf)
554    }
555
556    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
557        Pin::new(&mut self.get_mut().0).poll_flush(cx)
558    }
559
560    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
561        Pin::new(&mut self.get_mut().0).poll_shutdown(cx)
562    }
563}
564
565// #[derive(Debug)]
566// pub struct TokioTempDir {
567//     path: PathBuf,
568// }
569
570// impl TokioTempDir {
571//     async fn new() -> Result<Self> {
572//         let mut path = std::env::temp_dir();
573//         path.push(format!("peckish-workdir-{}", rand::random::<u64>()));
574//         tokio::fs::create_dir_all(&path).await?;
575
576//         Ok(Self { path })
577//     }
578// }
579
580// impl FloppyTempDir for TokioTempDir {
581//     fn path(&self) -> &Path {
582//         &self.path
583//     }
584// }
585
586// impl Drop for TokioTempDir {
587//     fn drop(&mut self) {
588//         if self.path.exists() {
589//             std::fs::remove_dir_all(&self.path).unwrap();
590//         }
591//     }
592// }
593
594// impl AsRef<Path> for TokioTempDir {
595//     fn as_ref(&self) -> &Path {
596//         &self.path
597//     }
598// }
599
600// impl AsRef<PathBuf> for TokioTempDir {
601//     fn as_ref(&self) -> &PathBuf {
602//         &self.path
603//     }
604// }
605
606// impl std::ops::Deref for TokioTempDir {
607//     type Target = Path;
608
609//     fn deref(&self) -> &Self::Target {
610//         &self.path
611//     }
612// }
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617
618    #[tokio::test]
619    async fn test_scoping_works() -> std::io::Result<()> {
620        let fs = TokioFloppyDisk::new(Some(PathBuf::from("/tmp")));
621        fs.write("/a", "asdf").await?;
622        let out = fs.read_to_string("/a").await?;
623
624        assert_eq!("asdf", out);
625        assert!(tokio::fs::metadata("/tmp/a").await.is_ok());
626
627        fs.remove_file("/a").await?;
628
629        Ok(())
630    }
631}