Skip to main content

trailcache_core/
config.rs

1//! Application configuration management.
2//!
3//! This module handles loading and saving the application configuration,
4//! which includes the organization GUID, unit name, and last used username.
5//!
6//! Configuration is stored at `~/.config/trailcache/config.json`.
7
8use std::path::PathBuf;
9
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12
13/// Application name used for config/cache directory paths
14const APP_NAME: &str = "trailcache";
15
16/// Config file name
17const CONFIG_FILE: &str = "config.json";
18
19#[derive(Debug, Clone, Serialize, Deserialize, Default)]
20pub struct Config {
21    pub organization_guid: Option<String>,
22    pub unit_name: Option<String>,
23    pub last_username: Option<String>,
24    #[serde(default)]
25    pub offline_mode: bool,
26    /// Explicit config directory override (for mobile platforms where `dirs` doesn't work).
27    #[serde(skip)]
28    pub config_dir_override: Option<PathBuf>,
29    /// Explicit cache directory override (for mobile platforms).
30    #[serde(skip)]
31    pub cache_dir_override: Option<PathBuf>,
32}
33
34impl Config {
35    pub fn load() -> Result<Self> {
36        let path = Self::config_path()?;
37        if path.exists() {
38            let contents = std::fs::read_to_string(&path)?;
39            Ok(serde_json::from_str(&contents)?)
40        } else {
41            Ok(Self::default())
42        }
43    }
44
45    /// Load config from an explicit directory (for mobile where `dirs` doesn't work).
46    pub fn load_from(config_dir: PathBuf) -> Result<Self> {
47        let path = config_dir.join(APP_NAME).join(CONFIG_FILE);
48        if path.exists() {
49            let contents = std::fs::read_to_string(&path)?;
50            let mut config: Config = serde_json::from_str(&contents)?;
51            config.config_dir_override = Some(config_dir);
52            Ok(config)
53        } else {
54            Ok(Self {
55                config_dir_override: Some(config_dir),
56                ..Self::default()
57            })
58        }
59    }
60
61    pub fn save(&self) -> Result<()> {
62        let path = self.resolved_config_path()?;
63        if let Some(parent) = path.parent() {
64            std::fs::create_dir_all(parent)?;
65        }
66        let contents = serde_json::to_string_pretty(self)?;
67        std::fs::write(path, contents)?;
68        Ok(())
69    }
70
71    fn config_path() -> Result<PathBuf> {
72        let config_dir = dirs::config_dir()
73            .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?;
74        Ok(config_dir.join(APP_NAME).join(CONFIG_FILE))
75    }
76
77    fn resolved_config_path(&self) -> Result<PathBuf> {
78        if let Some(ref dir) = self.config_dir_override {
79            Ok(dir.join(APP_NAME).join(CONFIG_FILE))
80        } else {
81            Self::config_path()
82        }
83    }
84
85    pub fn cache_dir(&self) -> Result<PathBuf> {
86        let base = if let Some(ref dir) = self.cache_dir_override {
87            dir.clone()
88        } else {
89            dirs::cache_dir()
90                .ok_or_else(|| anyhow::anyhow!("Could not find cache directory"))?
91        };
92
93        let mut path = base.join(APP_NAME);
94        if let Some(ref org) = self.organization_guid {
95            path = path.join(org);
96        }
97        Ok(path)
98    }
99
100    /// Set an explicit cache directory (for mobile).
101    pub fn set_cache_dir(&mut self, dir: PathBuf) {
102        self.cache_dir_override = Some(dir);
103    }
104}