1use {
2 futures_core::future::BoxFuture,
3 goods::{AssetNotFound, AutoLocalSource, Source},
4 std::{future::ready, path::PathBuf},
5};
6
7#[derive(Debug)]
10pub struct FileSource {
11 root: PathBuf,
12}
13
14impl AutoLocalSource for FileSource {}
15
16impl FileSource {
17 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}