noosphere_into/write/
native.rs

1use anyhow::{anyhow, Result};
2use async_trait::async_trait;
3use std::path::{Path, PathBuf};
4use tokio::fs::{create_dir_all, File};
5use tokio::io::{copy, AsyncRead, AsyncWriteExt};
6
7use super::target::WriteTarget;
8use super::WriteTargetConditionalSend;
9
10/// A generalized file system-backed implementation of WriteTarget. It roots
11/// all writes to the configured `root`, making it suitable for rendering
12/// Noosphere content to a target directory.
13#[derive(Clone)]
14pub struct NativeFs {
15    pub root: PathBuf,
16}
17
18impl NativeFs {
19    fn assert_relative(path: &Path) -> Result<()> {
20        if path.is_absolute() || path.starts_with("..") {
21            Err(anyhow!(
22                "Only relative sub-paths allowed, but received: {:?}",
23                path
24            ))
25        } else {
26            Ok(())
27        }
28    }
29}
30
31#[async_trait]
32impl WriteTarget for NativeFs {
33    async fn exists(&self, path: &Path) -> Result<bool> {
34        Ok(self.root.join(path).exists())
35    }
36
37    async fn write<R>(&self, path: &Path, mut contents: R) -> Result<()>
38    where
39        R: AsyncRead + Unpin + WriteTargetConditionalSend,
40    {
41        NativeFs::assert_relative(path)?;
42
43        if let Some(parent) = path.parent() {
44            create_dir_all(self.root.join(parent)).await?;
45        }
46
47        let path = self.root.join(path);
48        let mut file = File::create(path).await?;
49
50        copy(&mut contents, &mut file).await?;
51
52        file.flush().await?;
53
54        Ok(())
55    }
56
57    async fn symlink(&self, src: &Path, dst: &Path) -> Result<()> {
58        NativeFs::assert_relative(src)?;
59        NativeFs::assert_relative(dst)?;
60
61        #[cfg(not(windows))]
62        let result = tokio::fs::symlink(self.root.join(src), self.root.join(dst)).await?;
63        #[cfg(windows)]
64        let result = tokio::fs::symlink_file(self.root.join(src), self.root.join(dst)).await?;
65
66        Ok(result)
67    }
68
69    async fn spawn<F>(future: F) -> Result<()>
70    where
71        F: futures::Future<Output = Result<()>> + WriteTargetConditionalSend + 'static,
72    {
73        tokio::spawn(future).await??;
74        Ok(())
75    }
76}