use std::path::{Path, PathBuf};
use ui_core::{AssetCache, AssetKey};
pub struct DiskCache {
root: PathBuf,
}
impl DiskCache {
pub fn new(root: impl Into<PathBuf>) -> Self {
Self { root: root.into() }
}
pub fn root(&self) -> &Path {
&self.root
}
fn path(&self, key: &AssetKey) -> PathBuf {
self.root.join(file_name(key.kind)).join(file_name(&key.id))
}
}
impl AssetCache for DiskCache {
fn get(&self, key: &AssetKey) -> Option<Vec<u8>> {
std::fs::read(self.path(key)).ok()
}
fn put(&self, key: &AssetKey, bytes: &[u8]) {
let path = self.path(key);
let Some(dir) = path.parent() else {
return;
};
if let Err(e) = std::fs::create_dir_all(dir) {
tracing::warn!("could not create asset cache {}: {e}", dir.display());
return;
}
if let Err(e) = std::fs::write(&path, bytes) {
tracing::warn!("could not cache asset {}: {e}", path.display());
}
}
}
fn file_name(name: &str) -> String {
let simple = !name.is_empty()
&& name.len() <= 32
&& name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-');
if simple {
return name.to_string();
}
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut hasher);
let hash = hasher.finish();
let sanitized: String = name
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
c
} else {
'_'
}
})
.take(32)
.collect();
format!("{sanitized}_{hash:x}")
}
#[cfg(test)]
#[path = "disk_cache_test.rs"]
mod tests;