#![cfg(not(target_arch = "wasm32"))]
use std::fs;
use std::path::{Path, PathBuf};
use serde::Deserialize;
pub const MEDIA_TYPE_GGUF: &str = "application/vnd.ollama.image.model";
pub const MEDIA_TYPE_TEMPLATE: &str = "application/vnd.ollama.image.template";
pub const MEDIA_TYPE_TOKENIZER: &str = "application/vnd.ollama.image.tokenizer";
pub const MEDIA_TYPE_PARAMS: &str = "application/vnd.ollama.image.params";
#[derive(Debug, Deserialize)]
struct OciManifest {
#[serde(default)]
layers: Vec<OciLayer>,
}
#[derive(Debug, Deserialize)]
struct OciLayer {
#[serde(rename = "mediaType")]
media_type: String,
digest: String,
#[serde(default)]
size: u64,
}
#[derive(Debug, Clone)]
pub struct OllamaCachedFile {
pub media_type: String,
pub digest: String,
pub size: u64,
pub blob_path: PathBuf,
}
impl OllamaCachedFile {
pub fn is_weights(&self) -> bool {
self.media_type == MEDIA_TYPE_GGUF
}
pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
fs::read(&self.blob_path)
}
}
pub fn ollama_models_dir() -> Option<PathBuf> {
if let Ok(custom) = std::env::var("OLLAMA_MODELS") {
let p = PathBuf::from(custom);
if p.is_dir() {
return Some(p);
}
}
let home = dirs::home_dir()?;
let p = home.join(".ollama").join("models");
if p.is_dir() { Some(p) } else { None }
}
pub fn manifest_path(models_dir: &Path, name: &str, tag: &str) -> PathBuf {
let canonical = if name.contains('/') {
name.to_string()
} else {
format!("library/{name}")
};
models_dir
.join("manifests")
.join("registry.ollama.ai")
.join(canonical)
.join(tag)
}
pub fn blob_path(models_dir: &Path, digest: &str) -> PathBuf {
let normalized = digest.replacen("sha256:", "sha256-", 1);
models_dir.join("blobs").join(normalized)
}
pub fn lookup(name: &str, tag: &str) -> std::io::Result<Option<Vec<OllamaCachedFile>>> {
let models_dir = match ollama_models_dir() {
Some(d) => d,
None => return Ok(None),
};
let manifest_p = manifest_path(&models_dir, name, tag);
if !manifest_p.is_file() {
return Ok(None);
}
let manifest_bytes = fs::read(&manifest_p)?;
let manifest: OciManifest = serde_json::from_slice(&manifest_bytes)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let mut out = Vec::with_capacity(manifest.layers.len());
for layer in manifest.layers {
let blob_p = blob_path(&models_dir, &layer.digest);
if !blob_p.is_file() {
return Ok(None);
}
out.push(OllamaCachedFile {
media_type: layer.media_type,
digest: layer.digest,
size: layer.size,
blob_path: blob_p,
});
}
Ok(Some(out))
}
pub fn read_weights(name: &str, tag: &str) -> std::io::Result<Option<Vec<u8>>> {
let layers = match lookup(name, tag)? {
Some(l) => l,
None => return Ok(None),
};
for layer in layers {
if layer.is_weights() {
return Ok(Some(layer.read_bytes()?));
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manifest_path_library_default() {
let dir = PathBuf::from("/tmp/ollama");
let p = manifest_path(&dir, "gemma4", "e2b");
assert_eq!(
p,
PathBuf::from("/tmp/ollama/manifests/registry.ollama.ai/library/gemma4/e2b"),
);
}
#[test]
fn manifest_path_user_namespace() {
let dir = PathBuf::from("/tmp/ollama");
let p = manifest_path(&dir, "myorg/mymodel", "v1");
assert_eq!(
p,
PathBuf::from("/tmp/ollama/manifests/registry.ollama.ai/myorg/mymodel/v1"),
);
}
#[test]
fn blob_path_translates_colon_to_dash() {
let dir = PathBuf::from("/tmp/ollama");
let p = blob_path(&dir, "sha256:abc123");
assert_eq!(p, PathBuf::from("/tmp/ollama/blobs/sha256-abc123"));
}
}