use crate::module::traits::ModuleError;
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub type ModuleHash = [u8; 32];
pub struct ContentAddressableStorage {
storage_dir: PathBuf,
index: HashMap<ModuleHash, PathBuf>, }
impl ContentAddressableStorage {
pub fn new<P: AsRef<Path>>(storage_dir: P) -> Result<Self, ModuleError> {
let storage_dir = storage_dir.as_ref().to_path_buf();
std::fs::create_dir_all(&storage_dir)
.map_err(|e| ModuleError::op_err("Failed to create CAS directory", e))?;
let index = Self::load_index(&storage_dir)?;
Ok(Self { storage_dir, index })
}
pub fn store(&mut self, content: &[u8]) -> Result<ModuleHash, ModuleError> {
let hash = Self::hash_content(content);
let path = self.storage_dir.join(hex::encode(hash));
std::fs::write(&path, content)
.map_err(|e| ModuleError::op_err("Failed to write CAS file", e))?;
self.index.insert(hash, path);
self.save_index()?;
Ok(hash)
}
pub fn get(&self, hash: &ModuleHash) -> Result<Vec<u8>, ModuleError> {
if let Some(path) = self.index.get(hash) {
return std::fs::read(path)
.map_err(|e| ModuleError::op_err("Failed to read CAS file", e));
}
let path = self.storage_dir.join(hex::encode(hash));
if path.exists() {
return std::fs::read(&path)
.map_err(|e| ModuleError::op_err("Failed to read CAS file", e));
}
Err(ModuleError::ModuleNotFound(format!(
"Content not found for hash: {}",
hex::encode(hash)
)))
}
pub fn hash_content(content: &[u8]) -> ModuleHash {
let mut hasher = Sha256::new();
hasher.update(content);
hasher.finalize().into()
}
pub fn verify(&self, content: &[u8], expected_hash: &ModuleHash) -> bool {
let actual_hash = Self::hash_content(content);
actual_hash == *expected_hash
}
pub fn has(&self, hash: &ModuleHash) -> bool {
self.index.contains_key(hash) || {
let path = self.storage_dir.join(hex::encode(hash));
path.exists()
}
}
fn load_index(storage_dir: &Path) -> Result<HashMap<ModuleHash, PathBuf>, ModuleError> {
let index_file = storage_dir.join("index.json");
if !index_file.exists() {
return Ok(HashMap::new());
}
let contents = std::fs::read_to_string(&index_file)
.map_err(|e| ModuleError::op_err("Failed to read index file", e))?;
let index_data: HashMap<String, String> = serde_json::from_str(&contents)
.map_err(|e| ModuleError::op_err("Failed to parse index file", e))?;
let mut index = HashMap::new();
for (hash_hex, path_str) in index_data {
let hash_bytes = hex::decode(&hash_hex)
.map_err(|e| ModuleError::op_err("Invalid hash in index", e))?;
if hash_bytes.len() != 32 {
continue;
}
let mut hash = [0u8; 32];
hash.copy_from_slice(&hash_bytes);
index.insert(hash, PathBuf::from(path_str));
}
Ok(index)
}
fn save_index(&self) -> Result<(), ModuleError> {
let index_file = self.storage_dir.join("index.json");
let index_data: HashMap<String, String> = self
.index
.iter()
.map(|(hash, path)| (hex::encode(hash), path.to_string_lossy().to_string()))
.collect();
let contents = serde_json::to_string_pretty(&index_data)
.map_err(|e| ModuleError::op_err("Failed to serialize index", e))?;
std::fs::write(&index_file, contents)
.map_err(|e| ModuleError::op_err("Failed to write index file", e))?;
Ok(())
}
}