Skip to main content

synheart_sensor_agent/
config.rs

1//! Configuration for the Synheart Sensor Agent.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Main configuration for the sensor agent.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Config {
9    /// Which input sources to capture.
10    pub sources: SourceConfig,
11
12    /// Path for storing state and transparency logs.
13    pub data_path: PathBuf,
14
15    /// Whether collection is currently paused.
16    pub paused: bool,
17}
18
19impl Default for Config {
20    fn default() -> Self {
21        let data_dir = dirs::data_local_dir()
22            .unwrap_or_else(|| PathBuf::from("."))
23            .join("synheart-sensor-agent");
24
25        Self {
26            sources: SourceConfig::default(),
27            data_path: data_dir,
28            paused: false,
29        }
30    }
31}
32
33impl Config {
34    /// Load configuration from the default location.
35    pub fn load() -> Result<Self, ConfigError> {
36        let config_path = Self::config_path();
37
38        if config_path.exists() {
39            let content = std::fs::read_to_string(&config_path)
40                .map_err(|e| ConfigError::IoError(e.to_string()))?;
41            let config: Config = serde_json::from_str(&content)
42                .map_err(|e| ConfigError::ParseError(e.to_string()))?;
43            Ok(config)
44        } else {
45            Ok(Self::default())
46        }
47    }
48
49    /// Save configuration to the default location.
50    pub fn save(&self) -> Result<(), ConfigError> {
51        let config_path = Self::config_path();
52
53        if let Some(parent) = config_path.parent() {
54            std::fs::create_dir_all(parent).map_err(|e| ConfigError::IoError(e.to_string()))?;
55        }
56
57        let content = serde_json::to_string_pretty(self)
58            .map_err(|e| ConfigError::SerializeError(e.to_string()))?;
59
60        std::fs::write(&config_path, content).map_err(|e| ConfigError::IoError(e.to_string()))?;
61
62        Ok(())
63    }
64
65    /// Get the path to the configuration file.
66    pub fn config_path() -> PathBuf {
67        dirs::config_dir()
68            .unwrap_or_else(|| PathBuf::from("."))
69            .join("synheart-sensor-agent")
70            .join("config.json")
71    }
72
73    /// Ensure all required directories exist.
74    pub fn ensure_directories(&self) -> Result<(), ConfigError> {
75        std::fs::create_dir_all(&self.data_path)
76            .map_err(|e| ConfigError::IoError(e.to_string()))?;
77        Ok(())
78    }
79}
80
81/// Configuration for which input sources to capture.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct SourceConfig {
84    /// Capture keyboard events.
85    pub keyboard: bool,
86    /// Capture mouse events.
87    pub mouse: bool,
88}
89
90impl Default for SourceConfig {
91    fn default() -> Self {
92        Self {
93            keyboard: true,
94            mouse: true,
95        }
96    }
97}
98
99impl SourceConfig {
100    /// Parse source configuration from a comma-separated string.
101    pub fn from_csv(s: &str) -> Self {
102        let sources: Vec<String> = s.split(',').map(|s| s.trim().to_lowercase()).collect();
103
104        Self {
105            keyboard: sources.iter().any(|s| s == "keyboard" || s == "all"),
106            mouse: sources.iter().any(|s| s == "mouse" || s == "all"),
107        }
108    }
109
110    /// Check if at least one source is enabled.
111    pub fn any_enabled(&self) -> bool {
112        self.keyboard || self.mouse
113    }
114}
115
116/// Configuration errors.
117#[derive(Debug)]
118pub enum ConfigError {
119    /// File system I/O error.
120    IoError(String),
121    /// Failed to parse the configuration file.
122    ParseError(String),
123    /// Failed to serialize configuration.
124    SerializeError(String),
125}
126
127impl std::fmt::Display for ConfigError {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            ConfigError::IoError(e) => write!(f, "IO error: {e}"),
131            ConfigError::ParseError(e) => write!(f, "Parse error: {e}"),
132            ConfigError::SerializeError(e) => write!(f, "Serialize error: {e}"),
133        }
134    }
135}
136
137impl std::error::Error for ConfigError {}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_source_config_parsing() {
145        let config = SourceConfig::from_csv("keyboard,mouse");
146        assert!(config.keyboard);
147        assert!(config.mouse);
148
149        let config = SourceConfig::from_csv("keyboard");
150        assert!(config.keyboard);
151        assert!(!config.mouse);
152
153        let config = SourceConfig::from_csv("all");
154        assert!(config.keyboard);
155        assert!(config.mouse);
156    }
157
158    #[test]
159    fn test_default_config() {
160        let config = Config::default();
161        assert!(config.sources.keyboard);
162        assert!(config.sources.mouse);
163        assert!(!config.paused);
164    }
165}