use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct LiveProject {
root: PathBuf,
storage: PathBuf,
}
impl LiveProject {
pub fn resolve(raw: &str) -> std::io::Result<Self> {
let root = Path::new(raw).canonicalize()?;
let storage = crate::cli::leindex::resolve_existing_storage_path(&root)
.unwrap_or_else(|| root.join(".leindex"));
Ok(Self { root, storage })
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn storage(&self) -> &Path {
&self.storage
}
pub fn active_storage(&self) -> PathBuf {
let Ok(raw) = std::fs::read_to_string(self.storage.join("CURRENT")) else {
return self.storage.clone();
};
let value = raw.trim();
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return self.storage.clone();
}
let generation = self.storage.join("generations").join(value);
if generation.join("leindex.db").is_file() && generation.join("index-state.json").is_file()
{
generation
} else {
self.storage.clone()
}
}
pub fn file(&self, raw: &str) -> std::io::Result<PathBuf> {
let candidate = if Path::new(raw).is_absolute() {
PathBuf::from(raw)
} else {
self.root.join(raw)
};
let canonical = candidate.canonicalize()?;
if canonical.starts_with(&self.root) {
Ok(canonical)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
format!(
"{} is outside the project boundary {}",
canonical.display(),
self.root.display()
),
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn active_storage_accepts_only_completed_decimal_generation() {
let temp = tempfile::tempdir().expect("live project tempdir");
let storage = temp.path().join(".leindex");
let generation = storage.join("generations/7");
std::fs::create_dir_all(&generation).expect("generation directory");
std::fs::write(storage.join("CURRENT"), b"7\n").expect("current marker");
std::fs::write(generation.join("leindex.db"), b"db").expect("catalog marker");
std::fs::write(generation.join("index-state.json"), b"{}").expect("health marker");
let live = LiveProject {
root: temp.path().to_path_buf(),
storage: storage.clone(),
};
assert_eq!(live.active_storage(), generation);
std::fs::write(storage.join("CURRENT"), b"../7\n").expect("unsafe marker");
assert_eq!(live.active_storage(), storage);
}
}