use crate::db::ProcessedImages;
use camino::Utf8Path;
use canopydb::Database;
use rapidhash::fast::RapidHasher;
use std::fs;
use std::hash::Hasher;
use std::path::Path;
use std::sync::OnceLock;
static IMAGE_CACHE: OnceLock<Database> = OnceLock::new();
pub struct ContentStore {
db: Database,
}
impl ContentStore {
pub fn open(path: &Utf8Path) -> color_eyre::Result<Self> {
fs::create_dir_all(path)?;
let db = Database::new(path.as_std_path())?;
Ok(Self { db })
}
fn hash(content: &[u8]) -> u64 {
let mut hasher = RapidHasher::default();
hasher.write(content);
hasher.finish()
}
pub fn write_if_changed(&self, path: &Utf8Path, content: &[u8]) -> color_eyre::Result<bool> {
let hash = Self::hash(content);
let hash_bytes = hash.to_le_bytes();
let path_key = path.as_str().as_bytes();
let unchanged = {
let rx = self.db.begin_read()?;
if let Some(tree) = rx.get_tree(b"hashes")? {
if let Some(stored) = tree.get(path_key)? {
stored.as_ref() == hash_bytes
} else {
false
}
} else {
false
}
};
if unchanged {
return Ok(false);
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, content)?;
let tx = self.db.begin_write()?;
let mut tree = tx.get_or_create_tree(b"hashes")?;
tree.insert(path_key, &hash_bytes)?;
drop(tree);
tx.commit()?;
Ok(true)
}
}
pub fn init_image_cache(cache_dir: &Path) -> color_eyre::Result<()> {
let db_path = cache_dir.join("images.canopy");
fs::create_dir_all(&db_path)?;
let db = Database::new(&db_path)?;
let _ = IMAGE_CACHE.set(db);
tracing::info!("Image cache initialized at {:?}", db_path);
Ok(())
}
pub const IMAGE_PIPELINE_VERSION: u64 = 1;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InputHash(pub [u8; 32]);
#[derive(Hash)]
pub struct ImageVariantKey {
pub input_hash: InputHash,
pub format: crate::image::OutputFormat,
pub width: u32,
}