use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use crate::hardware::rf;
use log::Level;
#[derive(Clone, Debug, Default)]
pub struct Config {
pub dry_run: bool,
pub policy_path: Option<PathBuf>,
pub saving_cpu_freq: Option<String>,
pub hold_trigger_sec: Option<f32>,
pub toggle_wifi: bool,
pub wifi_rfkill_path: Option<PathBuf>,
pub toggle_bt: bool,
pub bt_rfkill_path: Option<PathBuf>,
pub log_level: Option<Level>,
}
fn parse_bool(s: &str) -> bool {
matches!(s.to_ascii_lowercase().as_str(), "1" | "true" | "yes")
}
fn parse_value_map(content: &str) -> HashMap<String, String> {
let mut map = HashMap::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(eq) = line.find('=') {
let key = line[..eq].trim().to_string();
let val = line[eq + 1..].trim().to_string();
map.insert(key, val);
}
}
map
}
impl Config {
pub fn load(path: Option<PathBuf>) -> Self {
let mut cfg = Config::default();
if let Ok(v) = std::env::var("DRY_RUN") {
cfg.dry_run = parse_bool(&v);
}
if let Ok(v) = std::env::var("POLICY_PATH") {
cfg.policy_path = Some(PathBuf::from(v));
}
if let Ok(v) = std::env::var("SAVING_CPU_FREQ") {
cfg.saving_cpu_freq = Some(v);
}
if let Ok(v) = std::env::var("HOLD_TRIGGER_SEC") {
cfg.hold_trigger_sec = v.parse::<f32>().ok();
}
if let Ok(v) = std::env::var("TOGGLE_WIFI") {
cfg.toggle_wifi = parse_bool(&v);
}
if let Ok(v) = std::env::var("WIFI_RFKILL") {
cfg.wifi_rfkill_path = Some(PathBuf::from(v));
}
if let Ok(v) = std::env::var("TOGGLE_BT") {
cfg.toggle_bt = parse_bool(&v);
}
if let Ok(v) = std::env::var("BT_RFKILL") {
cfg.bt_rfkill_path = Some(PathBuf::from(v));
}
if let Ok(v) = std::env::var("LOG_LEVEL")
&& let Ok(l) = v.parse::<log::Level>()
{
cfg.log_level = Some(l);
}
let cfg_path = if let Some(p) = path {
p
} else if PathBuf::from("./etc/uconsole-sleep/config.default").exists() {
PathBuf::from("./etc/uconsole-sleep/config.default")
} else {
PathBuf::from("/etc/uconsole-sleep/config")
};
if let Ok(content) = fs::read_to_string(&cfg_path) {
let map = parse_value_map(&content);
if let Some(v) = map.get("DRY_RUN") {
cfg.dry_run = parse_bool(v);
}
if let Some(v) = map.get("POLICY_PATH") {
cfg.policy_path = Some(PathBuf::from(v));
}
if let Some(v) = map.get("SAVING_CPU_FREQ") {
cfg.saving_cpu_freq = Some(v.clone());
}
if let Some(v) = map.get("HOLD_TRIGGER_SEC") {
cfg.hold_trigger_sec = v.parse::<f32>().ok();
}
if let Some(v) = map.get("TOGGLE_WIFI") {
cfg.toggle_wifi = parse_bool(v);
}
if let Some(v) = map.get("WIFI_RFKILL") {
cfg.wifi_rfkill_path = Some(PathBuf::from(v));
}
if let Some(v) = map.get("TOGGLE_BT") {
cfg.toggle_bt = parse_bool(v);
}
if let Some(v) = map.get("BT_RFKILL") {
cfg.bt_rfkill_path = Some(PathBuf::from(v));
}
if let Some(v) = map.get("LOG_LEVEL")
&& let Ok(l) = v.parse::<log::Level>()
{
cfg.log_level = Some(l);
}
}
if cfg.toggle_wifi && cfg.wifi_rfkill_path.is_none() {
cfg.wifi_rfkill_path = Some(PathBuf::from(rf::RFKILL_PATH_WIFI));
}
if cfg.toggle_bt && cfg.bt_rfkill_path.is_none() {
cfg.bt_rfkill_path = Some(PathBuf::from(rf::RFKILL_PATH_BT));
}
cfg
}
#[cfg(test)]
pub fn load_test_file(path: &std::path::Path) -> Self {
Config::load(Some(path.to_path_buf()))
}
}
#[cfg(test)]
mod tests {
use crate::hardware::rf;
use super::*;
use std::env;
use std::fs;
#[test]
fn test_load_from_repo_default() {
let c = Config::load(None);
assert!(c.saving_cpu_freq.is_some());
assert_eq!(c.saving_cpu_freq.unwrap(), "100,600");
assert_eq!(c.hold_trigger_sec.unwrap(), 0.7_f32);
}
#[test]
fn test_wifi_default_rfkill() {
let tmp = env::temp_dir().join(format!(
"uconsole_cfg_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let _ = fs::create_dir_all(&tmp);
let cfg_file = tmp.join("cfg");
fs::write(&cfg_file, "TOGGLE_WIFI=true\n").unwrap();
let cfg = Config::load(Some(cfg_file.clone()));
assert!(cfg.toggle_wifi);
assert_eq!(
cfg.wifi_rfkill_path.unwrap(),
PathBuf::from(rf::RFKILL_PATH_WIFI)
);
}
#[test]
fn test_bt_default_rfkill() {
let tmp = env::temp_dir().join(format!(
"uconsole_cfg_bt_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let _ = fs::create_dir_all(&tmp);
let cfg_file = tmp.join("cfg_bt");
fs::write(&cfg_file, "TOGGLE_BT=true\n").unwrap();
let cfg = Config::load(Some(cfg_file.clone()));
assert!(cfg.toggle_bt);
assert_eq!(
cfg.bt_rfkill_path.unwrap(),
PathBuf::from(rf::RFKILL_PATH_BT)
);
}
#[test]
fn test_log_level_from_file() {
let tmp = env::temp_dir().join(format!(
"uconsole_cfg_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
));
let _ = fs::create_dir_all(&tmp);
let cfg_file = tmp.join("cfg_log");
fs::write(&cfg_file, "LOG_LEVEL=debug\n").unwrap();
let cfg = Config::load(Some(cfg_file.clone()));
assert_eq!(cfg.log_level, Some(log::Level::Debug));
}
}