use std::path::{Path, PathBuf};
use super::{FoundFile, ScanEnv, Scanner};
use crate::registry::{Ecosystem, Source};
pub(crate) struct LmStudioScanner;
impl Scanner for LmStudioScanner {
fn ecosystem(&self) -> Ecosystem {
Ecosystem::Lmstudio
}
fn detect_root(&self, env: &ScanEnv) -> Option<PathBuf> {
if let Some(root) = env.root_override(self.ecosystem()) {
return root.is_dir().then(|| root.to_path_buf());
}
let home = env.home()?;
[
home.join(".lmstudio").join("models"),
home.join(".cache").join("lm-studio").join("models"),
]
.into_iter()
.find(|c| c.is_dir())
}
fn scan(&self, root: &Path) -> Vec<FoundFile> {
let mut found = super::walk_gguf(root, Ecosystem::Lmstudio);
for f in &mut found {
f.source = source_from_layout(root, &f.path);
}
found
}
}
fn source_from_layout(root: &Path, file: &Path) -> Option<Source> {
let rel = file.strip_prefix(root).ok()?;
let mut parts = rel.components();
let publisher = parts.next()?.as_os_str().to_str()?.to_owned();
let repo = parts.next()?.as_os_str().to_str()?.to_owned();
let filename = file.file_name()?.to_str()?.to_owned();
parts.next()?;
Some(Source {
kind: "huggingface".into(),
repo: format!("{publisher}/{repo}"),
filename,
revision: None,
resolved_at: crate::time_util::now_rfc3339(),
extra: Default::default(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derives_hf_source_from_tree_layout() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let file = root.join("bartowski/SomeModel-GGUF/SomeModel-Q4_K_M.gguf");
crate::gguf::fixtures::write_gguf(&file, "Some Model", "llama", 15, 4096, b"A");
let scanner = LmStudioScanner;
let found = scanner.scan(root);
assert_eq!(found.len(), 1);
let src = found[0].source.as_ref().unwrap();
assert_eq!(src.repo, "bartowski/SomeModel-GGUF");
assert_eq!(src.filename, "SomeModel-Q4_K_M.gguf");
}
#[test]
fn file_directly_under_publisher_gets_no_source() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let file = root.join("loose/model.gguf");
crate::gguf::fixtures::write_gguf(&file, "Loose", "llama", 7, 2048, b"B");
let found = LmStudioScanner.scan(root);
assert_eq!(found.len(), 1);
assert!(found[0].source.is_none());
}
}