use std::path::{Path, PathBuf};
pub(crate) const CACHE_DIR: &str = ".decdn-bundles";
pub(crate) fn cache_path(out_root: &Path, hash: [u8; 32]) -> PathBuf {
let name = blake3::Hash::from_bytes(hash).to_hex();
out_root.join(CACHE_DIR).join(format!("{name}.json"))
}
pub(crate) fn load(out_root: &Path, hash: [u8; 32]) -> anyhow::Result<Option<Vec<u8>>> {
use anyhow::Context as _;
let path = cache_path(out_root, hash);
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => {
return Err(e)
.with_context(|| format!("read cached bundle manifest {}", path.display()));
}
};
if *blake3::hash(&bytes).as_bytes() != hash {
anyhow::bail!(
"cached bundle manifest {} does not hash to the bundle",
path.display()
);
}
Ok(Some(bytes))
}
pub(crate) fn store(out_root: &Path, hash: [u8; 32], bytes: &[u8]) -> Option<String> {
try_store(out_root, hash, bytes)
.err()
.map(|e| format!("bundle manifest cache not written: {e:#}"))
}
fn try_store(out_root: &Path, hash: [u8; 32], bytes: &[u8]) -> anyhow::Result<()> {
use anyhow::Context as _;
let dest = cache_path(out_root, hash);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent).with_context(|| format!("create {}", parent.display()))?;
}
let shown = dest.display();
let mut tmp = super::engine::temp_in_parent(&dest).with_context(|| format!("stage {shown}"))?;
std::io::Write::write_all(tmp.as_file_mut(), bytes)
.with_context(|| format!("write {shown}"))?;
tmp.as_file()
.sync_all()
.with_context(|| format!("sync {shown}"))?;
tmp.persist(&dest)
.map_err(|e| e.error)
.with_context(|| format!("persist {shown}"))?;
Ok(())
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
mod tests;