use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use eyre::Result;
use crate::file;
use crate::hash::file_hash_blake3;
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct PrepareState {
#[serde(default)]
pub providers: BTreeMap<String, BTreeMap<String, String>>,
}
impl PrepareState {
pub fn load(project_root: &Path) -> Self {
let path = state_path(project_root);
if !path.exists() {
return Self::default();
}
match file::read_to_string(&path) {
Ok(contents) => match toml::from_str(&contents) {
Ok(state) => state,
Err(e) => {
warn!("failed to parse {}: {e}", path.display());
Self::default()
}
},
Err(e) => {
warn!("failed to read {}: {e}", path.display());
Self::default()
}
}
}
pub fn save(&self, project_root: &Path) -> Result<()> {
let path = state_path(project_root);
file::create_dir_all(path.parent().unwrap())?;
let contents = toml::to_string_pretty(self)?;
file::write(&path, contents)?;
Ok(())
}
pub fn get_hashes(&self, provider_id: &str) -> Option<&BTreeMap<String, String>> {
self.providers.get(provider_id)
}
pub fn set_hashes(&mut self, provider_id: &str, hashes: BTreeMap<String, String>) {
self.providers.insert(provider_id.to_string(), hashes);
}
}
pub fn hash_sources(sources: &[PathBuf], project_root: &Path) -> Result<BTreeMap<String, String>> {
let mut hashes = BTreeMap::new();
for source in sources {
if !source.exists() {
continue;
}
if source.is_dir() {
hash_dir_files(&mut hashes, source, project_root, 3)?;
} else {
let hash = file_hash_blake3(source, None)?;
let rel = source
.strip_prefix(project_root)
.unwrap_or(source)
.to_string_lossy()
.to_string();
hashes.insert(rel, hash);
}
}
Ok(hashes)
}
fn hash_dir_files(
hashes: &mut BTreeMap<String, String>,
dir: &Path,
project_root: &Path,
max_depth: usize,
) -> Result<()> {
if max_depth == 0 {
return Ok(());
}
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
hash_dir_files(hashes, &path, project_root, max_depth - 1)?;
} else {
let hash = file_hash_blake3(&path, None)?;
let rel = path
.strip_prefix(project_root)
.unwrap_or(&path)
.to_string_lossy()
.to_string();
hashes.insert(rel, hash);
}
}
}
Ok(())
}
fn state_path(project_root: &Path) -> PathBuf {
project_root.join(".mise").join("prepare-state.toml")
}