gglib_core/paths/
models.rs

1//! Models directory resolution.
2//!
3//! Provides utilities for resolving the models directory from explicit paths,
4//! environment variables, or platform defaults.
5
6use std::env;
7use std::path::PathBuf;
8
9use super::error::PathError;
10use super::platform::normalize_user_path;
11
12/// Default relative location for downloaded models under the user's home directory.
13pub const DEFAULT_MODELS_DIR_RELATIVE: &str = ".local/share/llama_models";
14
15/// How the models directory was derived.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ModelsDirSource {
18    /// The user passed an explicit path (e.g., CLI flag or GUI form).
19    Explicit,
20    /// The path came from environment variables / `.env`.
21    EnvVar,
22    /// Fallback default (`~/.local/share/llama_models`).
23    Default,
24}
25
26/// Resolution result for the models directory.
27#[derive(Debug, Clone)]
28pub struct ModelsDirResolution {
29    /// The resolved path to the models directory.
30    pub path: PathBuf,
31    /// How the path was determined.
32    pub source: ModelsDirSource,
33}
34
35/// Return the platform-specific default models directory.
36///
37/// Defaults to `~/.local/share/llama_models`.
38pub fn default_models_dir() -> Result<PathBuf, PathError> {
39    let home = dirs::home_dir().ok_or(PathError::NoHomeDir)?;
40    Ok(home.join(DEFAULT_MODELS_DIR_RELATIVE))
41}
42
43/// Resolve the models directory from an explicit override, env var, or default.
44///
45/// Resolution order:
46/// 1. Explicit path provided by caller (highest priority)
47/// 2. `GGLIB_MODELS_DIR` environment variable
48/// 3. Default models directory (`~/.local/share/llama_models`)
49pub fn resolve_models_dir(explicit: Option<&str>) -> Result<ModelsDirResolution, PathError> {
50    if let Some(path_str) = explicit {
51        return Ok(ModelsDirResolution {
52            path: normalize_user_path(path_str)?,
53            source: ModelsDirSource::Explicit,
54        });
55    }
56
57    if let Ok(env_path) = env::var("GGLIB_MODELS_DIR") {
58        if !env_path.trim().is_empty() {
59            return Ok(ModelsDirResolution {
60                path: normalize_user_path(&env_path)?,
61                source: ModelsDirSource::EnvVar,
62            });
63        }
64    }
65
66    Ok(ModelsDirResolution {
67        path: default_models_dir()?,
68        source: ModelsDirSource::Default,
69    })
70}
71
72#[cfg(test)]
73#[allow(unsafe_code)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn test_default_models_dir_contains_relative() {
79        let dir = default_models_dir().unwrap();
80        assert!(dir.to_string_lossy().contains(DEFAULT_MODELS_DIR_RELATIVE));
81    }
82
83    #[test]
84    fn test_resolve_models_dir_prefers_explicit() {
85        let prev = env::var("GGLIB_MODELS_DIR").ok();
86        unsafe {
87            env::set_var("GGLIB_MODELS_DIR", "/tmp/env-value");
88        }
89        let resolved = resolve_models_dir(Some("/tmp/explicit")).unwrap();
90        assert_eq!(resolved.source, ModelsDirSource::Explicit);
91        assert!(resolved.path.ends_with("explicit"));
92        restore_env("GGLIB_MODELS_DIR", prev);
93    }
94
95    #[test]
96    fn test_resolve_models_dir_env_value() {
97        let prev = env::var("GGLIB_MODELS_DIR").ok();
98        unsafe {
99            env::set_var("GGLIB_MODELS_DIR", "/tmp/from-env");
100        }
101        let resolved = resolve_models_dir(None).unwrap();
102        assert_eq!(resolved.source, ModelsDirSource::EnvVar);
103        assert!(resolved.path.ends_with("from-env"));
104        restore_env("GGLIB_MODELS_DIR", prev);
105    }
106
107    fn restore_env(key: &str, previous: Option<String>) {
108        if let Some(value) = previous {
109            unsafe {
110                env::set_var(key, value);
111            }
112        } else {
113            unsafe {
114                env::remove_var(key);
115            }
116        }
117    }
118}