1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

Merge pull request #22 from NotAShelf/complete-config-watcher

config: streamline hotreload; don't search in `$XDG_CONFIG_HOME`
This commit is contained in:
raf 2025-05-16 21:11:06 +03:00 committed by GitHub
commit 39736f2925
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 28 deletions

View file

@ -32,22 +32,27 @@ pub fn load_config_from_path(specific_path: Option<&str>) -> Result<AppConfig, C
))); )));
} }
// Otherwise try the standard paths // Check for SUPERFREQ_CONFIG environment variable
let mut config_paths: Vec<PathBuf> = Vec::new(); if let Ok(env_path) = std::env::var("SUPERFREQ_CONFIG") {
let env_path = Path::new(&env_path);
// User-specific path if env_path.exists() {
if let Some(home_dir) = dirs::home_dir() { println!(
let user_config_path = home_dir.join(".config/superfreq/config.toml"); "Loading config from SUPERFREQ_CONFIG: {}",
config_paths.push(user_config_path); env_path.display()
} else { );
return load_and_parse_config(env_path);
}
eprintln!( eprintln!(
"Warning: Could not determine home directory. User-specific config will not be loaded." "Warning: Config file specified by SUPERFREQ_CONFIG not found: {}",
env_path.display()
); );
} }
// System-wide paths // System-wide paths
config_paths.push(PathBuf::from("/etc/xdg/superfreq/config.toml")); let config_paths = vec![
config_paths.push(PathBuf::from("/etc/superfreq.toml")); PathBuf::from("/etc/xdg/superfreq/config.toml"),
PathBuf::from("/etc/superfreq.toml"),
];
for path in config_paths { for path in config_paths {
if path.exists() { if path.exists() {

View file

@ -87,8 +87,6 @@ pub struct AppConfig {
pub enum ConfigError { pub enum ConfigError {
IoError(std::io::Error), IoError(std::io::Error),
TomlError(toml::de::Error), TomlError(toml::de::Error),
NoValidConfigFound,
HomeDirNotFound,
ValidationError(String), ValidationError(String),
} }
@ -109,8 +107,6 @@ impl std::fmt::Display for ConfigError {
match self { match self {
Self::IoError(e) => write!(f, "I/O error: {e}"), Self::IoError(e) => write!(f, "I/O error: {e}"),
Self::TomlError(e) => write!(f, "TOML parsing error: {e}"), Self::TomlError(e) => write!(f, "TOML parsing error: {e}"),
Self::NoValidConfigFound => write!(f, "No valid configuration file found."),
Self::HomeDirNotFound => write!(f, "Could not determine user home directory."),
Self::ValidationError(s) => write!(f, "Configuration validation error: {s}"), Self::ValidationError(s) => write!(f, "Configuration validation error: {s}"),
} }
} }

View file

@ -1,3 +1,4 @@
use log::{debug, error, warn};
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::error::Error; use std::error::Error;
use std::path::Path; use std::path::Path;
@ -26,6 +27,8 @@ impl ConfigWatcher {
// Start watching the config file // Start watching the config file
watcher.watch(Path::new(config_path), RecursiveMode::NonRecursive)?; watcher.watch(Path::new(config_path), RecursiveMode::NonRecursive)?;
debug!("Started watching config file: {config_path}");
Ok(Self { Ok(Self {
rx, rx,
_watcher: watcher, _watcher: watcher,
@ -46,15 +49,34 @@ impl ConfigWatcher {
loop { loop {
match self.rx.try_recv() { match self.rx.try_recv() {
Ok(Ok(event)) => { Ok(Ok(event)) => {
// Only process write/modify events // Process various file events that might indicate configuration changes
if matches!(event.kind, EventKind::Modify(_)) { match event.kind {
should_reload = true; EventKind::Modify(_) => {
debug!("Detected modification to config file: {}", self.config_path);
should_reload = true;
}
EventKind::Create(_) => {
debug!("Detected recreation of config file: {}", self.config_path);
should_reload = true;
}
EventKind::Remove(_) => {
// Some editors delete then recreate the file when saving
// Just log this event and wait for the create event
debug!(
"Detected removal of config file: {} - waiting for recreation",
self.config_path
);
}
_ => {} // Ignore other event types
}
if should_reload {
self.last_event_time = std::time::Instant::now(); self.last_event_time = std::time::Instant::now();
} }
} }
Ok(Err(e)) => { Ok(Err(e)) => {
// File watcher error, log but continue // File watcher error, log but continue
eprintln!("Error watching config file: {e}"); warn!("Error watching config file: {e}");
} }
Err(TryRecvError::Empty) => { Err(TryRecvError::Empty) => {
// No more events // No more events
@ -62,7 +84,7 @@ impl ConfigWatcher {
} }
Err(TryRecvError::Disconnected) => { Err(TryRecvError::Disconnected) => {
// Channel disconnected, watcher is dead // Channel disconnected, watcher is dead
eprintln!("Config watcher channel disconnected"); error!("Config watcher channel disconnected");
return None; return None;
} }
} }
@ -78,18 +100,31 @@ impl ConfigWatcher {
thread::sleep(debounce_time - time_since_last_event); thread::sleep(debounce_time - time_since_last_event);
} }
// Ensure the file exists before attempting to reload
let config_path = Path::new(&self.config_path);
if !config_path.exists() {
warn!(
"Config file does not exist after change events: {}",
self.config_path
);
return None;
}
debug!("Reloading configuration from {}", self.config_path);
// Attempt to reload the config from the specific path being watched // Attempt to reload the config from the specific path being watched
match load_config_from_path(Some(&self.config_path)) { match load_config_from_path(Some(&self.config_path)) {
Ok(config) => Some(Ok(config)), Ok(config) => {
Err(e) => Some(Err(Box::new(e))), debug!("Successfully reloaded configuration");
Some(Ok(config))
}
Err(e) => {
error!("Failed to reload configuration: {e}");
Some(Err(Box::new(e)))
}
} }
} else { } else {
None None
} }
} }
/// Get the path of the config file being watched
pub const fn config_path(&self) -> &String {
&self.config_path
}
} }

View file

@ -100,7 +100,7 @@ fn main() {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(e) => { Err(e) => {
error!("Error loading configuration: {e}. Using default values."); error!("Error loading configuration: {e}. Using default values.");
// Proceed with default config if loading fails, as per previous steps // Proceed with default config if loading fails
AppConfig::default() AppConfig::default()
} }
}; };