mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
config: modularize; add config watcher and move error variants
This commit is contained in:
parent
dde938b638
commit
9f7d86ff01
10 changed files with 349 additions and 126 deletions
76
src/config/load.rs
Normal file
76
src/config/load.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Configuration loading functionality
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig, ProfileConfig};
|
||||
|
||||
// The primary function to load application configuration.
|
||||
// It tries user-specific and then system-wide TOML files.
|
||||
// Falls back to default settings if no file is found or if parsing fails.
|
||||
pub fn load_config() -> Result<AppConfig, ConfigError> {
|
||||
let mut config_paths: Vec<PathBuf> = Vec::new();
|
||||
|
||||
// User-specific path
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml");
|
||||
config_paths.push(user_config_path);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Warning: Could not determine home directory. User-specific config will not be loaded."
|
||||
);
|
||||
}
|
||||
|
||||
// System-wide path
|
||||
let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml");
|
||||
config_paths.push(system_config_path);
|
||||
|
||||
for path in config_paths {
|
||||
if path.exists() {
|
||||
println!("Attempting to load config from: {}", path.display());
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(contents) => {
|
||||
match toml::from_str::<AppConfigToml>(&contents) {
|
||||
Ok(toml_app_config) => {
|
||||
// Convert AppConfigToml to AppConfig
|
||||
let app_config = AppConfig {
|
||||
charger: ProfileConfig::from(toml_app_config.charger),
|
||||
battery: ProfileConfig::from(toml_app_config.battery),
|
||||
battery_charge_thresholds: toml_app_config.battery_charge_thresholds,
|
||||
ignored_power_supplies: toml_app_config.ignored_power_supplies,
|
||||
poll_interval_sec: toml_app_config.poll_interval_sec,
|
||||
daemon: DaemonConfig {
|
||||
poll_interval_sec: toml_app_config.daemon.poll_interval_sec,
|
||||
adaptive_interval: toml_app_config.daemon.adaptive_interval,
|
||||
min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec,
|
||||
max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec,
|
||||
throttle_on_battery: toml_app_config.daemon.throttle_on_battery,
|
||||
log_level: toml_app_config.daemon.log_level,
|
||||
stats_file_path: toml_app_config.daemon.stats_file_path,
|
||||
},
|
||||
};
|
||||
return Ok(app_config);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing config file {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading config file {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("No configuration file found or all failed to parse. Using default configuration.");
|
||||
// Construct default AppConfig by converting default AppConfigToml
|
||||
let default_toml_config = AppConfigToml::default();
|
||||
Ok(AppConfig {
|
||||
charger: ProfileConfig::from(default_toml_config.charger),
|
||||
battery: ProfileConfig::from(default_toml_config.battery),
|
||||
battery_charge_thresholds: default_toml_config.battery_charge_thresholds,
|
||||
ignored_power_supplies: default_toml_config.ignored_power_supplies,
|
||||
poll_interval_sec: default_toml_config.poll_interval_sec,
|
||||
daemon: DaemonConfig::default(),
|
||||
})
|
||||
}
|
9
src/config/mod.rs
Normal file
9
src/config/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub mod watcher;
|
||||
|
||||
// Re-export all configuration types and functions
|
||||
pub use self::types::*;
|
||||
pub use self::load::*;
|
||||
|
||||
// Internal organization of config submodules
|
||||
mod types;
|
||||
mod load;
|
|
@ -1,7 +1,6 @@
|
|||
// Configuration types and structures for superfreq
|
||||
use crate::core::TurboSetting;
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Structs for configuration using serde::Deserialize
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
|
@ -83,82 +82,6 @@ impl std::fmt::Display for ConfigError {
|
|||
|
||||
impl std::error::Error for ConfigError {}
|
||||
|
||||
// The primary function to load application configuration.
|
||||
// It tries user-specific and then system-wide TOML files.
|
||||
// Falls back to default settings if no file is found or if parsing fails.
|
||||
pub fn load_config() -> Result<AppConfig, ConfigError> {
|
||||
let mut config_paths: Vec<PathBuf> = Vec::new();
|
||||
|
||||
// User-specific path
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml");
|
||||
config_paths.push(user_config_path);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Warning: Could not determine home directory. User-specific config will not be loaded."
|
||||
);
|
||||
}
|
||||
|
||||
// System-wide path
|
||||
let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml");
|
||||
config_paths.push(system_config_path);
|
||||
|
||||
for path in config_paths {
|
||||
if path.exists() {
|
||||
println!("Attempting to load config from: {}", path.display());
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(contents) => {
|
||||
match toml::from_str::<AppConfigToml>(&contents) {
|
||||
Ok(toml_app_config) => {
|
||||
// Convert AppConfigToml to AppConfig
|
||||
let app_config = AppConfig {
|
||||
charger: ProfileConfig::from(toml_app_config.charger),
|
||||
battery: ProfileConfig::from(toml_app_config.battery),
|
||||
battery_charge_thresholds: toml_app_config
|
||||
.battery_charge_thresholds,
|
||||
ignored_power_supplies: toml_app_config.ignored_power_supplies,
|
||||
poll_interval_sec: toml_app_config.poll_interval_sec,
|
||||
daemon: DaemonConfig {
|
||||
poll_interval_sec: toml_app_config.daemon.poll_interval_sec,
|
||||
adaptive_interval: toml_app_config.daemon.adaptive_interval,
|
||||
min_poll_interval_sec: toml_app_config
|
||||
.daemon
|
||||
.min_poll_interval_sec,
|
||||
max_poll_interval_sec: toml_app_config
|
||||
.daemon
|
||||
.max_poll_interval_sec,
|
||||
throttle_on_battery: toml_app_config.daemon.throttle_on_battery,
|
||||
log_level: toml_app_config.daemon.log_level,
|
||||
stats_file_path: toml_app_config.daemon.stats_file_path,
|
||||
},
|
||||
};
|
||||
return Ok(app_config);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing config file {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading config file {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("No configuration file found or all failed to parse. Using default configuration.");
|
||||
// Construct default AppConfig by converting default AppConfigToml
|
||||
let default_toml_config = AppConfigToml::default();
|
||||
Ok(AppConfig {
|
||||
charger: ProfileConfig::from(default_toml_config.charger),
|
||||
battery: ProfileConfig::from(default_toml_config.battery),
|
||||
battery_charge_thresholds: default_toml_config.battery_charge_thresholds,
|
||||
ignored_power_supplies: default_toml_config.ignored_power_supplies,
|
||||
poll_interval_sec: default_toml_config.poll_interval_sec,
|
||||
daemon: DaemonConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
// Intermediate structs for TOML parsing
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct ProfileConfigToml {
|
||||
|
@ -350,4 +273,4 @@ impl Default for DaemonConfigToml {
|
|||
stats_file_path: default_stats_file_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,6 @@ use crate::util::error::ControlError;
|
|||
use core::str;
|
||||
use std::{fs, io, path::Path, string::ToString};
|
||||
|
||||
impl std::error::Error for ControlError {}
|
||||
|
||||
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
|
||||
|
||||
// Write a value to a sysfs file
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::config::{AppConfig, LogLevel};
|
||||
use crate::config::watcher::ConfigWatcher;
|
||||
use crate::conflict;
|
||||
use crate::core::SystemReport;
|
||||
use crate::engine;
|
||||
|
@ -10,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Run the daemon
|
||||
pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Set effective log level based on config and verbose flag
|
||||
let effective_log_level = if verbose {
|
||||
LogLevel::Debug
|
||||
|
@ -63,6 +64,34 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::e
|
|||
);
|
||||
}
|
||||
|
||||
// Initialize config file watcher if a path is available
|
||||
let config_file_path = if let Ok(path) = std::env::var("SUPERFREQ_CONFIG") { Some(path) } else {
|
||||
let default_paths = [
|
||||
"/etc/superfreq/config.toml",
|
||||
"/etc/superfreq.toml",
|
||||
];
|
||||
|
||||
default_paths.iter()
|
||||
.find(|&path| std::path::Path::new(path).exists())
|
||||
.map(|path| (*path).to_string())
|
||||
};
|
||||
|
||||
let config_watcher: Option<ConfigWatcher> = match config_file_path {
|
||||
Some(path) => {
|
||||
match ConfigWatcher::new(&path) {
|
||||
Ok(watcher) => {
|
||||
println!("Watching config file: {path}");
|
||||
Some(watcher)
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to initialize config file watcher: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
// Variables for adaptive polling
|
||||
let mut current_poll_interval = config.daemon.poll_interval_sec;
|
||||
let mut last_settings_change = Instant::now();
|
||||
|
@ -72,6 +101,24 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::e
|
|||
while running.load(Ordering::SeqCst) {
|
||||
let start_time = Instant::now();
|
||||
|
||||
// Check for configuration changes
|
||||
if let Some(watcher) = &config_watcher {
|
||||
if let Some(config_result) = watcher.check_for_changes() {
|
||||
match config_result {
|
||||
Ok(new_config) => {
|
||||
if verbose {
|
||||
println!("Config file changed, updating configuration");
|
||||
}
|
||||
config = new_config;
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error loading new configuration: {e}");
|
||||
// Continue with existing config
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match monitor::collect_system_report(&config) {
|
||||
Ok(report) => {
|
||||
log_message(
|
||||
|
|
|
@ -1,37 +1,7 @@
|
|||
use crate::config::{AppConfig, ProfileConfig};
|
||||
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
||||
use crate::cpu::{self};
|
||||
use crate::util::error::ControlError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EngineError {
|
||||
ControlError(ControlError),
|
||||
ConfigurationError(String),
|
||||
}
|
||||
|
||||
impl From<ControlError> for EngineError {
|
||||
fn from(err: ControlError) -> Self {
|
||||
Self::ControlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EngineError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ControlError(e) => write!(f, "CPU control error: {e}"),
|
||||
Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EngineError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::ControlError(e) => Some(e),
|
||||
Self::ConfigurationError(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::util::error::EngineError;
|
||||
|
||||
/// Determines the appropriate CPU profile based on power status or forced mode,
|
||||
/// and applies the settings using functions from the `cpu` module.
|
||||
|
@ -158,6 +128,13 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<()
|
|||
}
|
||||
};
|
||||
|
||||
// Validate the configuration to ensure it's usable
|
||||
if turbo_settings.load_threshold_high <= turbo_settings.load_threshold_low {
|
||||
return Err(EngineError::ConfigurationError(
|
||||
"Invalid turbo auto settings: high threshold must be greater than low threshold".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
// Decision logic for enabling/disabling turbo
|
||||
let enable_turbo = match (cpu_temp, avg_cpu_usage) {
|
||||
// If temperature is too high, disable turbo regardless of load
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use crate::config::AppConfig;
|
||||
use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport};
|
||||
use crate::cpu::{get_logical_core_count, get_platform_profiles};
|
||||
use crate::util::error::ControlError;
|
||||
use crate::cpu::get_logical_core_count;
|
||||
use crate::util::error::SysMonitorError;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs, io,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
thread,
|
||||
|
@ -13,8 +12,6 @@ use std::{
|
|||
time::SystemTime,
|
||||
};
|
||||
|
||||
impl std::error::Error for SysMonitorError {}
|
||||
|
||||
pub type Result<T, E = SysMonitorError> = std::result::Result<T, E>;
|
||||
|
||||
// Read a sysfs file to a string, trimming whitespace
|
||||
|
@ -417,8 +414,8 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
|
|||
None
|
||||
};
|
||||
|
||||
let available_governors = if cpufreq_base.join("scaling_available_governors").exists() {
|
||||
read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else(
|
||||
let available_governors = if cpufreq_base_path.join("scaling_available_governors").exists() {
|
||||
read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_available_governors")).map_or_else(
|
||||
|_| vec![],
|
||||
|s| s.split_whitespace().map(String::from).collect(),
|
||||
)
|
||||
|
|
|
@ -39,6 +39,8 @@ impl std::fmt::Display for ControlError {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ControlError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SysMonitorError {
|
||||
Io(io::Error),
|
||||
|
@ -67,3 +69,35 @@ impl std::fmt::Display for SysMonitorError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SysMonitorError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EngineError {
|
||||
ControlError(ControlError),
|
||||
ConfigurationError(String),
|
||||
}
|
||||
|
||||
impl From<ControlError> for EngineError {
|
||||
fn from(err: ControlError) -> Self {
|
||||
Self::ControlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EngineError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ControlError(e) => write!(f, "CPU control error: {e}"),
|
||||
Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EngineError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::ControlError(e) => Some(e),
|
||||
Self::ConfigurationError(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue