1use std::collections::HashMap;
8use std::fs;
9use std::path::PathBuf;
10
11use crate::hardware::rf;
12use log::Level;
13
14#[derive(Clone, Debug, Default)]
15pub struct Config {
16 pub dry_run: bool,
17 pub policy_path: Option<PathBuf>,
18 pub saving_cpu_freq: Option<String>,
19 pub hold_trigger_sec: Option<f32>,
20 pub toggle_wifi: bool,
21 pub wifi_rfkill_path: Option<PathBuf>,
22 pub toggle_bt: bool,
23 pub bt_rfkill_path: Option<PathBuf>,
24 pub log_level: Option<Level>,
25}
26
27fn parse_bool(s: &str) -> bool {
30 matches!(s.to_ascii_lowercase().as_str(), "1" | "true" | "yes")
31}
32
33fn parse_value_map(content: &str) -> HashMap<String, String> {
34 let mut map = HashMap::new();
35 for line in content.lines() {
36 let line = line.trim();
37 if line.is_empty() || line.starts_with('#') {
38 continue;
39 }
40 if let Some(eq) = line.find('=') {
41 let key = line[..eq].trim().to_string();
42 let val = line[eq + 1..].trim().to_string();
43 map.insert(key, val);
44 }
45 }
46 map
47}
48
49impl Config {
50 pub fn load(path: Option<PathBuf>) -> Self {
54 let mut cfg = Config::default();
55
56 if let Ok(v) = std::env::var("DRY_RUN") {
58 cfg.dry_run = parse_bool(&v);
59 }
60 if let Ok(v) = std::env::var("POLICY_PATH") {
61 cfg.policy_path = Some(PathBuf::from(v));
62 }
63 if let Ok(v) = std::env::var("SAVING_CPU_FREQ") {
64 cfg.saving_cpu_freq = Some(v);
65 }
66 if let Ok(v) = std::env::var("HOLD_TRIGGER_SEC") {
67 cfg.hold_trigger_sec = v.parse::<f32>().ok();
68 }
69 if let Ok(v) = std::env::var("TOGGLE_WIFI") {
70 cfg.toggle_wifi = parse_bool(&v);
71 }
72 if let Ok(v) = std::env::var("WIFI_RFKILL") {
73 cfg.wifi_rfkill_path = Some(PathBuf::from(v));
74 }
75 if let Ok(v) = std::env::var("TOGGLE_BT") {
76 cfg.toggle_bt = parse_bool(&v);
77 }
78 if let Ok(v) = std::env::var("BT_RFKILL") {
79 cfg.bt_rfkill_path = Some(PathBuf::from(v));
80 }
81 if let Ok(v) = std::env::var("LOG_LEVEL")
82 && let Ok(l) = v.parse::<log::Level>()
83 {
84 cfg.log_level = Some(l);
85 }
86
87 let cfg_path = if let Some(p) = path {
89 p
90 } else if PathBuf::from("./etc/uconsole-sleep/config.default").exists() {
91 PathBuf::from("./etc/uconsole-sleep/config.default")
92 } else {
93 PathBuf::from("/etc/uconsole-sleep/config")
94 };
95
96 if let Ok(content) = fs::read_to_string(&cfg_path) {
97 let map = parse_value_map(&content);
98 if let Some(v) = map.get("DRY_RUN") {
99 cfg.dry_run = parse_bool(v);
100 }
101 if let Some(v) = map.get("POLICY_PATH") {
102 cfg.policy_path = Some(PathBuf::from(v));
103 }
104 if let Some(v) = map.get("SAVING_CPU_FREQ") {
105 cfg.saving_cpu_freq = Some(v.clone());
106 }
107 if let Some(v) = map.get("HOLD_TRIGGER_SEC") {
108 cfg.hold_trigger_sec = v.parse::<f32>().ok();
109 }
110 if let Some(v) = map.get("TOGGLE_WIFI") {
111 cfg.toggle_wifi = parse_bool(v);
112 }
113 if let Some(v) = map.get("WIFI_RFKILL") {
114 cfg.wifi_rfkill_path = Some(PathBuf::from(v));
115 }
116 if let Some(v) = map.get("TOGGLE_BT") {
117 cfg.toggle_bt = parse_bool(v);
118 }
119 if let Some(v) = map.get("BT_RFKILL") {
120 cfg.bt_rfkill_path = Some(PathBuf::from(v));
121 }
122 if let Some(v) = map.get("LOG_LEVEL")
123 && let Ok(l) = v.parse::<log::Level>()
124 {
125 cfg.log_level = Some(l);
126 }
127 }
128
129 if cfg.toggle_wifi && cfg.wifi_rfkill_path.is_none() {
131 cfg.wifi_rfkill_path = Some(PathBuf::from(rf::RFKILL_PATH_WIFI));
132 }
133 if cfg.toggle_bt && cfg.bt_rfkill_path.is_none() {
135 cfg.bt_rfkill_path = Some(PathBuf::from(rf::RFKILL_PATH_BT));
136 }
137
138 cfg
139 }
140
141 #[cfg(test)]
142 pub fn load_test_file(path: &std::path::Path) -> Self {
143 Config::load(Some(path.to_path_buf()))
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use crate::hardware::rf;
150
151 use super::*;
152 use std::env;
153 use std::fs;
154
155 #[test]
156 fn test_load_from_repo_default() {
157 let c = Config::load(None);
159 assert!(c.saving_cpu_freq.is_some());
160 assert_eq!(c.saving_cpu_freq.unwrap(), "100,600");
161 assert_eq!(c.hold_trigger_sec.unwrap(), 0.7_f32);
162 }
163
164 #[test]
165 fn test_wifi_default_rfkill() {
166 let tmp = env::temp_dir().join(format!(
167 "uconsole_cfg_{}",
168 std::time::SystemTime::now()
169 .duration_since(std::time::UNIX_EPOCH)
170 .unwrap()
171 .as_millis()
172 ));
173 let _ = fs::create_dir_all(&tmp);
174 let cfg_file = tmp.join("cfg");
175 fs::write(&cfg_file, "TOGGLE_WIFI=true\n").unwrap();
176 let cfg = Config::load(Some(cfg_file.clone()));
177 assert!(cfg.toggle_wifi);
178 assert_eq!(
179 cfg.wifi_rfkill_path.unwrap(),
180 PathBuf::from(rf::RFKILL_PATH_WIFI)
181 );
182 }
183
184 #[test]
185 fn test_bt_default_rfkill() {
186 let tmp = env::temp_dir().join(format!(
187 "uconsole_cfg_bt_{}",
188 std::time::SystemTime::now()
189 .duration_since(std::time::UNIX_EPOCH)
190 .unwrap()
191 .as_millis()
192 ));
193 let _ = fs::create_dir_all(&tmp);
194 let cfg_file = tmp.join("cfg_bt");
195 fs::write(&cfg_file, "TOGGLE_BT=true\n").unwrap();
196 let cfg = Config::load(Some(cfg_file.clone()));
197 assert!(cfg.toggle_bt);
198 assert_eq!(
199 cfg.bt_rfkill_path.unwrap(),
200 PathBuf::from(rf::RFKILL_PATH_BT)
201 );
202 }
203
204 #[test]
205 fn test_log_level_from_file() {
206 let tmp = env::temp_dir().join(format!(
207 "uconsole_cfg_{}",
208 std::time::SystemTime::now()
209 .duration_since(std::time::UNIX_EPOCH)
210 .unwrap()
211 .as_millis()
212 ));
213 let _ = fs::create_dir_all(&tmp);
214 let cfg_file = tmp.join("cfg_log");
215 fs::write(&cfg_file, "LOG_LEVEL=debug\n").unwrap();
216 let cfg = Config::load(Some(cfg_file.clone()));
217 assert_eq!(cfg.log_level, Some(log::Level::Debug));
218 }
219
220 }