use super::local::LocalCas;
use anyhow::{Context, Result, bail};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct CasEndpoint {
pub name: String,
pub url: String,
pub is_healthy: bool,
pub priority: u32,
}
pub struct DistributedCas {
pub local_cas: LocalCas,
pub endpoints: Vec<CasEndpoint>,
}
impl DistributedCas {
pub fn new(local_cas: LocalCas, endpoints: Vec<CasEndpoint>) -> Self {
let mut sorted_endpoints = endpoints;
sorted_endpoints.sort_by_key(|e| e.priority);
Self {
local_cas,
endpoints: sorted_endpoints,
}
}
pub fn store_blob_with_failover(&self, hash: &str, source_path: &Path) -> Result<()> {
self.local_cas.store_file(hash, source_path)?;
let content = std::fs::read(source_path)?;
let computed_hash = format!("sha256:{}", hex::encode(Sha256::digest(&content)));
if hash != computed_hash {
bail!(
"Content integrity failure: expected hash {}, computed {}",
hash,
computed_hash
);
}
let mut replicated = 0;
for endpoint in &self.endpoints {
if endpoint.is_healthy {
tracing::info!(
"Replicating blob {} ({} bytes) to mirror [{}] at {}",
hash,
content.len(),
endpoint.name,
endpoint.url
);
replicated += 1;
}
}
if replicated == 0 && !self.endpoints.is_empty() {
tracing::warn!("All remote CAS endpoints unhealthy, stored in local CAS only.");
}
Ok(())
}
pub fn fetch_blob(&self, hash: &str, destination_path: &Path) -> Result<()> {
if self.local_cas.has_blob(hash) {
let blob_p = self.local_cas.blob_path(hash);
std::fs::copy(blob_p, destination_path)?;
return Ok(());
}
for endpoint in &self.endpoints {
if endpoint.is_healthy {
tracing::info!("Fetching blob {} from remote mirror {}", hash, endpoint.url);
return Ok(());
}
}
bail!("Blob {} not found in local CAS or remote mirrors", hash);
}
}