use std::fs;
use std::path::PathBuf;
use sha2::{Digest, Sha256};
use crate::diagnostics::{
codes, Diagnostic, DiagnosticCategory, DiagnosticReport, DiagnosticStage, Severity,
};
use crate::model::RegistryDocument;
use crate::parser::DocumentFormat;
use super::load;
pub const CACHE_DIR_NAME: &str = "dtcs/registries";
#[must_use]
pub fn cache_dir() -> PathBuf {
if let Ok(path) = std::env::var("DTCS_REGISTRY_CACHE") {
return PathBuf::from(path);
}
let home = std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."));
home.join(".cache").join(CACHE_DIR_NAME)
}
pub fn load_uri_cached(uri: &str) -> Result<RegistryDocument, DiagnosticReport> {
let path = cache_path_for_uri(uri);
if !path.exists() {
let mut report = DiagnosticReport::new();
report.push(
Diagnostic::new(
codes::INVALID_REGISTRY,
Severity::Error,
DiagnosticStage::Parse,
DiagnosticCategory::Syntax,
format!("registry URI '{uri}' is not present in the offline cache"),
)
.with_remediation(
"Cache the registry with registry::cache_store, or load from a local file path",
),
);
return Err(report);
}
let bytes = std::fs::read(&path).map_err(io_error)?;
match load::load_bytes(&bytes, DocumentFormat::Yaml) {
Ok(document) => Ok(document),
Err(yaml_error) => match load::load_bytes(&bytes, DocumentFormat::Json) {
Ok(document) => Ok(document),
Err(_json_error) => Err(yaml_error),
},
}
}
pub fn cache_store(
uri: &str,
content: &[u8],
format: DocumentFormat,
) -> Result<PathBuf, DiagnosticReport> {
let _document = load::load_bytes(content, format)?;
let path = cache_path_for_uri(uri);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(io_error)?;
}
fs::write(&path, content).map_err(io_error)?;
Ok(path)
}
#[must_use]
pub fn cache_path_for_uri(uri: &str) -> PathBuf {
let mut hasher = Sha256::new();
hasher.update(uri.as_bytes());
let digest = hex_encode(&hasher.finalize());
let ext = if uri.ends_with(".json") {
"json"
} else {
"yaml"
};
cache_dir().join(format!("{digest}.{ext}"))
}
#[must_use]
pub fn is_cached(uri: &str) -> bool {
cache_path_for_uri(uri).exists()
}
fn io_error(error: std::io::Error) -> DiagnosticReport {
let mut report = DiagnosticReport::new();
report.push(
Diagnostic::new(
codes::INVALID_REGISTRY,
Severity::Error,
DiagnosticStage::Parse,
DiagnosticCategory::Syntax,
format!("registry cache I/O failed: {error}"),
)
.with_remediation("Check cache directory permissions"),
);
report
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
pub fn cache_remove(uri: &str) -> std::io::Result<()> {
let path = cache_path_for_uri(uri);
if path.exists() {
fs::remove_file(path)?;
}
Ok(())
}