use std::path::{Path, PathBuf};
pub const HOME_ENV: &str = "MODELSHELF_HOME";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShelfPaths {
home: PathBuf,
}
impl ShelfPaths {
pub fn resolve(explicit: Option<&Path>) -> Option<Self> {
if let Some(p) = explicit {
return Some(Self::at(p));
}
if let Some(env) = std::env::var_os(HOME_ENV) {
if !env.is_empty() {
return Some(Self::at(PathBuf::from(env)));
}
}
dirs::home_dir().map(|h| Self::at(h.join(".modelshelf")))
}
pub fn at(home: impl Into<PathBuf>) -> Self {
Self { home: home.into() }
}
pub fn home(&self) -> &Path {
&self.home
}
pub fn registry_json(&self) -> PathBuf {
self.home.join("registry.json")
}
pub fn lock_file(&self) -> PathBuf {
self.home.join("registry.lock")
}
pub fn blobs_dir(&self) -> PathBuf {
self.home.join("blobs")
}
pub fn blob_file(&self, sha256_hex: &str) -> PathBuf {
self.blobs_dir().join(format!("sha256-{sha256_hex}"))
}
pub fn downloads_dir(&self) -> PathBuf {
self.home.join("downloads")
}
pub fn journal_dir(&self) -> PathBuf {
self.home.join("journal")
}
pub fn config_json(&self) -> PathBuf {
self.home.join("config.json")
}
pub fn catalog_json(&self) -> PathBuf {
self.home.join("catalog.json")
}
pub fn ensure_layout(&self) -> crate::Result<()> {
for dir in [
self.home.clone(),
self.blobs_dir(),
self.downloads_dir(),
self.journal_dir(),
] {
std::fs::create_dir_all(&dir).map_err(|e| crate::Error::io(&dir, e))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_path_wins() {
let p = ShelfPaths::resolve(Some(Path::new("/x/y"))).unwrap();
assert_eq!(p.home(), Path::new("/x/y"));
assert_eq!(p.registry_json(), Path::new("/x/y/registry.json"));
assert_eq!(p.blob_file("ab12"), Path::new("/x/y/blobs/sha256-ab12"));
}
#[test]
fn ensure_layout_creates_subdirs() {
let tmp = tempfile::tempdir().unwrap();
let p = ShelfPaths::at(tmp.path().join("shelf"));
p.ensure_layout().unwrap();
assert!(p.blobs_dir().is_dir());
assert!(p.downloads_dir().is_dir());
assert!(p.journal_dir().is_dir());
}
}