synheart_sensor_agent/
config.rs1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Config {
9 pub sources: SourceConfig,
11
12 pub data_path: PathBuf,
14
15 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 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct SourceConfig {
84 pub keyboard: bool,
86 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 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 pub fn any_enabled(&self) -> bool {
112 self.keyboard || self.mouse
113 }
114}
115
116#[derive(Debug)]
118pub enum ConfigError {
119 IoError(String),
121 ParseError(String),
123 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}