Skip to main content

rusty_stack/
config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use serde_json::{json, Value};
4use std::fs;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct InstallerConfig {
9    pub scripts_dir: String,
10    pub log_dir: String,
11    pub install_path: String,
12    pub batch_mode: bool,
13    pub install_method: String,
14    pub auto_confirm: bool,
15    pub star_repos: bool,
16    pub force_reinstall: bool,
17    pub theme: String,
18    pub performance_profile: String,
19    #[serde(skip)]
20    pub config_path: PathBuf,
21}
22
23impl InstallerConfig {
24    pub fn load_or_default(scripts_dir: &str) -> Result<Self> {
25        let config_path = config_file_path()?;
26        let log_dir = config_path
27            .parent()
28            .and_then(|p| p.parent())
29            .map(|p| p.join("logs"))
30            .unwrap_or_else(|| PathBuf::from("/tmp/mlstack/logs"));
31
32        let mut config = InstallerConfig::default_with_paths(
33            scripts_dir,
34            log_dir.to_string_lossy().to_string(),
35            config_path.clone(),
36        );
37
38        if config_path.exists() {
39            let raw = fs::read_to_string(&config_path).context("Failed to read config file")?;
40            let value: Value = serde_json::from_str(&raw).unwrap_or_else(|_| json!({}));
41            config.apply_from_value(&value);
42        } else {
43            // Only try to save if we can, but don't crash if we can't
44            let _ = config.save(None);
45        }
46
47        Ok(config)
48    }
49
50    pub fn save(&self, existing: Option<Value>) -> Result<()> {
51        if let Some(parent) = self.config_path.parent() {
52            fs::create_dir_all(parent).context("Failed to create config directory")?;
53        }
54        let mut value = existing.unwrap_or_else(|| json!({}));
55
56        value["scripts_dir"] = json!(self.scripts_dir);
57        value["log_dir"] = json!(self.log_dir);
58        value["batch_mode"] = json!(self.batch_mode);
59        value["install_method"] = json!(self.install_method);
60
61        let user_prefs = value
62            .get("user_preferences")
63            .cloned()
64            .unwrap_or_else(|| json!({}));
65        let mut user_prefs = user_prefs;
66        user_prefs["installation_path"] = json!(self.install_path);
67        user_prefs["install_method"] = json!(self.install_method);
68        user_prefs["auto_confirm"] = json!(self.auto_confirm);
69        user_prefs["star_repos"] = json!(self.star_repos);
70        user_prefs["force_reinstall"] = json!(self.force_reinstall);
71        user_prefs["theme"] = json!(self.theme);
72        user_prefs["performance_profile"] = json!(self.performance_profile);
73        value["user_preferences"] = user_prefs;
74
75        fs::write(&self.config_path, serde_json::to_string_pretty(&value)?)
76            .context("Failed to save config")?;
77
78        Ok(())
79    }
80
81    fn apply_from_value(&mut self, value: &Value) {
82        if let Some(dir) = value.get("scripts_dir").and_then(|v| v.as_str()) {
83            self.scripts_dir = dir.to_string();
84        }
85        if let Some(dir) = value.get("log_dir").and_then(|v| v.as_str()) {
86            self.log_dir = dir.to_string();
87        }
88        if let Some(batch) = value.get("batch_mode").and_then(|v| v.as_bool()) {
89            self.batch_mode = batch;
90        }
91        if let Some(method) = value.get("install_method").and_then(|v| v.as_str()) {
92            self.install_method = normalize_install_method(method);
93        }
94
95        if let Some(prefs) = value.get("user_preferences") {
96            if let Some(path) = prefs.get("installation_path").and_then(|v| v.as_str()) {
97                self.install_path = path.to_string();
98            }
99            if let Some(method) = prefs.get("install_method").and_then(|v| v.as_str()) {
100                self.install_method = normalize_install_method(method);
101            }
102            if let Some(auto) = prefs.get("auto_confirm").and_then(|v| v.as_bool()) {
103                self.auto_confirm = auto;
104            }
105            if let Some(star) = prefs.get("star_repos").and_then(|v| v.as_bool()) {
106                self.star_repos = star;
107            }
108            if let Some(force) = prefs.get("force_reinstall").and_then(|v| v.as_bool()) {
109                self.force_reinstall = force;
110            }
111            if let Some(theme) = prefs.get("theme").and_then(|v| v.as_str()) {
112                self.theme = theme.to_string();
113            }
114            if let Some(profile) = prefs.get("performance_profile").and_then(|v| v.as_str()) {
115                self.performance_profile = profile.to_string();
116            }
117        }
118    }
119
120    pub fn default_with_paths(scripts_dir: &str, log_dir: String, config_path: PathBuf) -> Self {
121        Self {
122            scripts_dir: scripts_dir.to_string(),
123            log_dir,
124            install_path: "/opt/rocm".into(),
125            batch_mode: std::env::var("MLSTACK_BATCH_MODE").unwrap_or_default() == "1",
126            install_method: normalize_install_method(
127                &std::env::var("MLSTACK_INSTALL_METHOD").unwrap_or_else(|_| "auto".into()),
128            ),
129            auto_confirm: false,
130            star_repos: true,
131            force_reinstall: false,
132            theme: "dark".into(),
133            performance_profile: "balanced".into(),
134            config_path,
135        }
136    }
137}
138
139fn normalize_install_method(input: &str) -> String {
140    match input.trim().to_ascii_lowercase().as_str() {
141        "global" => "global".into(),
142        "venv" => "venv".into(),
143        _ => "auto".into(),
144    }
145}
146
147pub fn config_file_path() -> Result<PathBuf> {
148    let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
149    let config_dir = PathBuf::from(home).join(".mlstack").join("config");
150    fs::create_dir_all(&config_dir).context("Failed to create config directory")?;
151    Ok(config_dir.join("config.json"))
152}