use std::{io, marker::PhantomData, path::{Path, PathBuf}, sync::Arc};
use ahash::{HashMap, HashMapExt};
use bytes::Bytes;
use crate::{
builder::EntryBuilderKind,
Asset, BuildError, Builder, DataSource, Modifier, ModifierContext, SplitGlob,
};
#[derive(Debug, Clone)]
pub(crate) struct AssetsInner(Arc<AssetsEvenMoreInner>);
#[derive(Debug, Clone)]
pub(crate) struct AssetsEvenMoreInner {
assets: HashMap<String, (DataSource, Modifier)>,
globs: Vec<DevGlobEntry>,
}
#[derive(Debug, Clone)]
struct DevGlobEntry {
http_prefix: String,
glob: SplitGlob,
modifier: Modifier,
base_path: &'static Path,
}
impl AssetsInner {
pub(crate) async fn build(builder: Builder<'_>) -> Result<Self, BuildError> {
let globs = builder.assets.iter().filter_map(|ab| {
if let EntryBuilderKind::Glob { http_prefix, glob, base_path, .. } = &ab.kind {
Some(DevGlobEntry {
http_prefix: http_prefix.clone().into_owned(),
glob: glob.clone(),
modifier: ab.modifier.clone(),
base_path: Path::new(*base_path),
})
} else {
None
}
}).collect();
let mut assets = HashMap::with_capacity(builder.assets.len());
for ab in builder.assets {
match ab.kind {
EntryBuilderKind::Single { http_path, source } => {
assets.insert(http_path.into_owned(), (source, ab.modifier));
}
EntryBuilderKind::Glob { http_prefix, files, .. } => {
for file in files {
assets.insert(
file.http_path(&http_prefix),
(file.source, ab.modifier.clone()),
);
}
}
}
}
Ok(Self(Arc::new(AssetsEvenMoreInner { assets, globs })))
}
pub(crate) fn get(&self, http_path: &str) -> Option<Asset> {
self.0.assets.get(http_path)
.cloned()
.or_else(|| {
self.0.match_globs(http_path)
.filter(|(path, _)| path.exists())
.map(|(path, modifier)| (DataSource::File(path), modifier))
})
.map(|(source, modifier)| Asset(AssetInner {
source,
modifier,
assets: self.0.clone(),
}))
}
pub(crate) fn len(&self) -> usize {
self.0.assets.len()
}
pub(crate) fn iter(&self) -> impl '_ + Iterator<Item = (&str, Asset)> {
self.0.assets.keys().flat_map(move |key| self.get(key).map(|a| (&**key, a)))
}
}
impl AssetsEvenMoreInner {
fn match_globs(&self, http_path: &str) -> Option<(PathBuf, Modifier)> {
self.globs.iter().find_map(|item| {
http_path.strip_prefix(&item.http_prefix)
.filter(|suffix| item.glob.suffix.matches(suffix))
.map(|suffix| (
item.base_path.join(item.glob.prefix).join(suffix),
item.modifier.clone(),
))
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct AssetInner {
source: DataSource,
modifier: Modifier,
assets: Arc<AssetsEvenMoreInner>,
}
impl AssetInner {
pub(crate) async fn content(&self) -> Result<Bytes, io::Error> {
let bytes = self.source.load().await.map_err(|(e, _)| e)?;
let modified = match &self.modifier {
Modifier::None => bytes,
Modifier::PathFixup(_) => bytes,
Modifier::Custom { f, deps } => f(bytes, ModifierContext {
declared_deps: &deps,
inner: ModifierContextInner {
assets: self.assets.clone(),
_dummy: PhantomData,
},
}),
};
Ok(modified)
}
pub(crate) fn is_filename_hashed(&self) -> bool {
false
}
}
#[derive(Debug)]
pub(crate) struct ModifierContextInner<'a> {
assets: Arc<AssetsEvenMoreInner>,
_dummy: PhantomData<&'a ()>,
}
impl<'a> ModifierContextInner<'a> {
pub(crate) fn resolve_path<'b>(&'b self, path: &'b str) -> Option<&'b str> {
if self.assets.assets.contains_key(path) || self.assets.match_globs(path).is_some() {
Some(path)
} else {
None
}
}
}