gglib_core/paths/
models.rs1use std::env;
7use std::path::PathBuf;
8
9use super::error::PathError;
10use super::platform::normalize_user_path;
11
12#[cfg(not(target_os = "windows"))]
14pub const DEFAULT_MODELS_DIR_RELATIVE: &str = ".local/share/llama_models";
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ModelsDirSource {
19 Explicit,
21 EnvVar,
23 Default,
26}
27
28#[derive(Debug, Clone)]
30pub struct ModelsDirResolution {
31 pub path: PathBuf,
33 pub source: ModelsDirSource,
35}
36
37pub fn default_models_dir() -> Result<PathBuf, PathError> {
42 #[cfg(target_os = "windows")]
43 {
44 let local_app_data = dirs::data_local_dir().ok_or(PathError::NoDataDir)?;
45 Ok(local_app_data.join("llama_models"))
46 }
47 #[cfg(not(target_os = "windows"))]
48 {
49 let home = dirs::home_dir().ok_or(PathError::NoHomeDir)?;
50 Ok(home.join(DEFAULT_MODELS_DIR_RELATIVE))
51 }
52}
53
54pub fn resolve_models_dir(explicit: Option<&str>) -> Result<ModelsDirResolution, PathError> {
61 if let Some(path_str) = explicit {
62 return Ok(ModelsDirResolution {
63 path: normalize_user_path(path_str)?,
64 source: ModelsDirSource::Explicit,
65 });
66 }
67
68 if let Ok(env_path) = env::var("GGLIB_MODELS_DIR")
69 && !env_path.trim().is_empty()
70 {
71 return Ok(ModelsDirResolution {
72 path: normalize_user_path(&env_path)?,
73 source: ModelsDirSource::EnvVar,
74 });
75 }
76
77 Ok(ModelsDirResolution {
78 path: default_models_dir()?,
79 source: ModelsDirSource::Default,
80 })
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::paths::test_utils::{ENV_LOCK, EnvVarGuard};
87
88 #[test]
89 fn test_default_models_dir_platform_path() {
90 let dir = default_models_dir().unwrap();
91 let path_str = dir.to_string_lossy();
92 #[cfg(target_os = "windows")]
95 {
96 assert!(
97 path_str.contains("llama_models"),
98 "Expected 'llama_models' in path: {path_str}"
99 );
100 assert!(
101 !path_str.contains('/'),
102 "Path must not contain forward slashes on Windows: {path_str}"
103 );
104 }
105 #[cfg(not(target_os = "windows"))]
107 assert!(
108 path_str.contains(DEFAULT_MODELS_DIR_RELATIVE),
109 "Expected '{DEFAULT_MODELS_DIR_RELATIVE}' in path: {path_str}"
110 );
111 }
112
113 #[test]
114 fn test_resolve_models_dir_prefers_explicit() {
115 let _guard = ENV_LOCK.lock().unwrap();
116 let _env = EnvVarGuard::set("GGLIB_MODELS_DIR", "/tmp/env-value");
117 let resolved = resolve_models_dir(Some("/tmp/explicit")).unwrap();
118 assert_eq!(resolved.source, ModelsDirSource::Explicit);
119 assert!(resolved.path.ends_with("explicit"));
120 }
121
122 #[test]
123 fn test_resolve_models_dir_env_value() {
124 let _guard = ENV_LOCK.lock().unwrap();
125 let _env = EnvVarGuard::set("GGLIB_MODELS_DIR", "/tmp/from-env");
126 let resolved = resolve_models_dir(None).unwrap();
127 assert_eq!(resolved.source, ModelsDirSource::EnvVar);
128 assert!(resolved.path.ends_with("from-env"));
129 }
130}