use std::path::{Path, PathBuf};
use anyhow::Result;
use futures::future::try_join_all;
use super::reveal_brand;
use crate::{AssetProvider, BrandSpec};
pub(super) async fn materialise_subtree(
provider: &dyn AssetProvider,
prefix: &str,
dest: &Path,
) -> Result<usize> {
let keys = provider.list(prefix).await?;
let written = try_join_all(keys.iter().map(|key| async move {
let Some(bytes) = provider.get(key).await? else {
return Ok(0usize);
};
let rel = key.strip_prefix(prefix).unwrap_or(key);
let path = dest.join(rel);
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
tokio::fs::write(&path, &bytes).await?;
Ok::<usize, anyhow::Error>(1)
}))
.await?;
Ok(written.into_iter().sum())
}
pub(super) async fn brand_style_file(
root: &Path,
brand: &BrandSpec,
assets: &dyn AssetProvider,
) -> Result<Option<PathBuf>> {
let mut body = reveal_brand::brand_css(brand).unwrap_or_default();
if let Some(bytes) = assets.get("revealjs/brand.css").await? {
match std::str::from_utf8(&bytes) {
Ok(raw) => body.push_str(raw),
Err(_) => tracing::warn!("revealjs/brand.css is not valid UTF-8; skipping"),
}
}
if body.is_empty() {
return Ok(None);
}
let path = root.join("brand.css.html");
tokio::fs::write(&path, format!("<style data-brand>\n{body}</style>\n")).await?;
Ok(Some(path))
}
pub(super) async fn brand_logo_file(
root: &Path,
brand: &BrandSpec,
assets: &dyn AssetProvider,
) -> Result<Option<PathBuf>> {
let Some(logo) = &brand.logo else {
return Ok(None);
};
let Some(bytes) = assets.get(&logo.key).await? else {
tracing::warn!(
key = logo.key.as_str(),
"brand logo not found in provider; skipping"
);
return Ok(None);
};
let mime = crate::image_format::mime_type(&bytes);
let html = reveal_brand::logo_html(logo, &bytes, mime);
let path = root.join("brand-logo.html");
tokio::fs::write(&path, html).await?;
Ok(Some(path))
}