goods_fs/
lib.rs

1use {
2    futures_core::future::BoxFuture,
3    goods::{AssetNotFound, AutoLocalSource, Source},
4    std::{future::ready, path::PathBuf},
5};
6
7/// Asset source that treats asset key as relative file path,
8/// joins it with root path and loads asset data from file.
9#[derive(Debug)]
10pub struct FileSource {
11    root: PathBuf,
12}
13
14impl AutoLocalSource for FileSource {}
15
16impl FileSource {
17    /// Create new source with specified root path
18    pub fn new(root: PathBuf) -> Self {
19        #[cfg(feature = "tracing")]
20        tracing::info!("New file asset source. Root: {}", root.display());
21        FileSource { root }
22    }
23}
24
25impl<P> Source<P> for FileSource
26where
27    P: AsRef<str>,
28{
29    fn read(&self, path_or_url: &P) -> BoxFuture<'static, eyre::Result<Box<[u8]>>> {
30        let path_or_url: &str = path_or_url.as_ref();
31
32        let path = if let Some(stripped) = path_or_url.strip_prefix("file://") {
33            let path = if let Some(file_path) = stripped.strip_prefix('/') {
34                file_path
35            } else if let Some(localhost_path) = stripped.strip_prefix("localhost/") {
36                localhost_path
37            } else {
38                return Box::pin(ready(Err(AssetNotFound.into())));
39            };
40            #[cfg(feature = "urlencoding")]
41            {
42                match urlencoding::decode(path) {
43                    Ok(decoded) => self.root.join(&decoded),
44                    Err(err) => {
45                        return Box::pin(ready(Err(SourceError::Error(Arc::new(err)))));
46                    }
47                }
48            }
49            #[cfg(not(feature = "urlencoding"))]
50            {
51                self.root.join(path)
52            }
53        } else {
54            self.root.join(path_or_url)
55        };
56        let path = self.root.join(path);
57
58        #[cfg(feature = "tracing")]
59        tracing::debug!("Fetching asset file at {}", path.display());
60        let result = match std::fs::read(path) {
61            Ok(bytes) => {
62                #[cfg(feature = "tracing")]
63                tracing::trace!("File loaded. {} bytes", bytes.len());
64                Ok(bytes.into_boxed_slice())
65            }
66            Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
67                #[cfg(feature = "tracing")]
68                tracing::debug!("File not found");
69                Err(AssetNotFound.into())
70            }
71            Err(err) => {
72                #[cfg(feature = "tracing")]
73                tracing::debug!("File loading error: {}", err);
74                Err(err.into())
75            }
76        };
77
78        Box::pin(ready(result))
79    }
80}