use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use super::types::CacheEntry;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PublishStatusCache {
pub(crate) entries: HashMap<String, CacheEntry>,
#[serde(skip)]
pub(crate) cache_path: Option<PathBuf>,
}
impl PublishStatusCache {
pub(crate) fn default_cache_path() -> PathBuf {
dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("batuta")
.join("publish-status.json")
}
#[must_use]
pub fn load() -> Self {
let path = Self::default_cache_path();
Self::load_from(&path).unwrap_or_default()
}
pub fn load_from(path: &Path) -> Result<Self> {
if !path.exists() {
return Ok(Self::default());
}
let data = std::fs::read_to_string(path)?;
let mut cache: Self = serde_json::from_str(&data)?;
cache.cache_path = Some(path.to_path_buf());
Ok(cache)
}
pub fn save(&self) -> Result<()> {
let path = self.cache_path.clone().unwrap_or_else(Self::default_cache_path);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let data = serde_json::to_string_pretty(self)?;
std::fs::write(&path, data)?;
Ok(())
}
#[must_use]
pub fn get(&self, name: &str, cache_key: &str) -> Option<&CacheEntry> {
self.entries.get(name).filter(|e| e.cache_key == cache_key)
}
pub fn insert(&mut self, name: String, entry: CacheEntry) {
self.entries.insert(name, entry);
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
pub fn compute_cache_key(repo_path: &Path) -> Result<String> {
let cargo_toml = repo_path.join("Cargo.toml");
if !cargo_toml.exists() {
return Err(anyhow!("No Cargo.toml found at {:?}", repo_path));
}
let content = std::fs::read(&cargo_toml)?;
let mtime = std::fs::metadata(&cargo_toml)?
.modified()?
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let head_sha = get_git_head(repo_path).unwrap_or_else(|_| "no-git".to_string());
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
head_sha.hash(&mut hasher);
mtime.hash(&mut hasher);
Ok(format!("{:016x}", hasher.finish()))
}
pub(crate) fn get_git_head(repo_path: &Path) -> Result<String> {
let output = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.current_dir(repo_path)
.output()?;
if !output.status.success() {
return Err(anyhow!("git rev-parse failed"));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}