feat: add automatic config reloading
This commit is contained in:
parent
62ad1eebdf
commit
fccd90df5c
3 changed files with 177 additions and 33 deletions
13
README.md
13
README.md
|
@ -16,8 +16,15 @@ has a unix socket (`/tmp/hinoirisetr.sock`) that accepts four commands
|
||||||
- [x] добавить логгинг нормальный
|
- [x] добавить логгинг нормальный
|
||||||
- [x] избавиться от env_logger и написать свой собственный
|
- [x] избавиться от env_logger и написать свой собственный
|
||||||
- [x] сделать файл конфига а не блять компайлтайм конфиг(suckless🤢🤢🤮)
|
- [x] сделать файл конфига а не блять компайлтайм конфиг(suckless🤢🤢🤮)
|
||||||
- [ ] сделать свой парсер для конфига после serde
|
- [x] сделать свой парсер для конфига после serde
|
||||||
- [ ] заебашить автоматический хотрелоад конфига
|
- [x] заебашить автоматический хотрелоад конфига
|
||||||
- [ ] вместо юзания notify-rust напрямую пользоваться libnotify.so и грузить её через dlopen(чтоб бинарник был маленький ваще)
|
- [x] вместо юзания notify-rust напрямую пользоваться libnotify.so и грузить её через dlopen(чтоб бинарник был маленький ваще)
|
||||||
- [x] переползти с chrono на time потому что мне похуй не нужна суперточность(а мб и на std::time)
|
- [x] переползти с chrono на time потому что мне похуй не нужна суперточность(а мб и на std::time)
|
||||||
- [ ] fix `status` command crash
|
- [ ] fix `status` command crash
|
||||||
|
|
||||||
|
Notify: Replaced tokio::sync::Notify with smol::channel::unbounded. The Sender is used to signal, and Receiver is polled in the main loop.
|
||||||
|
UnixListener: Switched to smol::net::UnixListener.
|
||||||
|
Signal Handling: Used signal-hook with smol::spawn for async signal processing.
|
||||||
|
Timers: Replaced tokio::time::sleep with smol::Timer.
|
||||||
|
Main Loop: Used smol::future::or to combine timer and notification events, replacing tokio::select!.
|
||||||
|
Executor: Switched from #[tokio::main] to smol::block_on.
|
||||||
|
|
92
src/lib.rs
92
src/lib.rs
|
@ -9,8 +9,8 @@ use std::process::Command;
|
||||||
use time::Time;
|
use time::Time;
|
||||||
|
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod time;
|
|
||||||
pub mod notify;
|
pub mod notify;
|
||||||
|
pub mod time;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -44,10 +44,29 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
IoError(std::io::Error),
|
||||||
|
ParseIntError(std::num::ParseIntError),
|
||||||
|
InvalidTemperature(String),
|
||||||
|
InvalidTime(String),
|
||||||
|
InvalidGamma(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for ConfigError {
|
||||||
|
fn from(err: std::io::Error) -> Self {
|
||||||
|
ConfigError::IoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for ConfigError {
|
||||||
|
fn from(err: std::num::ParseIntError) -> Self {
|
||||||
|
ConfigError::ParseIntError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load<P: AsRef<Path> + std::fmt::Debug>(
|
pub fn load<P: AsRef<Path> + std::fmt::Debug>(path: P) -> Result<Self, ConfigError> {
|
||||||
path: P,
|
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
|
||||||
trace!("Config::load({path:?})");
|
trace!("Config::load({path:?})");
|
||||||
let mut config = Self::default(); // Start with default values
|
let mut config = Self::default(); // Start with default values
|
||||||
|
|
||||||
|
@ -75,24 +94,73 @@ impl Config {
|
||||||
match current_section.as_str() {
|
match current_section.as_str() {
|
||||||
"" => {
|
"" => {
|
||||||
if key_trimmed == "notification_timeout" {
|
if key_trimmed == "notification_timeout" {
|
||||||
config.notification_timeout = value.parse()?;
|
config.notification_timeout = value.parse::<u32>()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"gamma" => match key_trimmed {
|
"gamma" => match key_trimmed {
|
||||||
"day" => config.gamma_day = value.parse()?,
|
"day" => {
|
||||||
|
let parsed = value.parse::<u16>()?;
|
||||||
|
if parsed <= 100 {
|
||||||
|
config.gamma_day = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidGamma(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
"night" => config.gamma_night = value.parse()?,
|
"night" => config.gamma_night = value.parse()?,
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
"temp" => match key_trimmed {
|
"temp" => match key_trimmed {
|
||||||
"day" => config.temp_day = value.parse()?,
|
"day" => {
|
||||||
"night" => config.temp_night = value.parse()?,
|
let parsed = value.parse::<u16>()?;
|
||||||
|
if (1000..=20000).contains(&parsed) {
|
||||||
|
config.temp_day = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidTemperature(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"night" => {
|
||||||
|
let parsed = value.parse::<u16>()?;
|
||||||
|
if (1000..=20000).contains(&parsed) {
|
||||||
|
config.temp_night = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidTemperature(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
"time" => match key_trimmed {
|
"time" => match key_trimmed {
|
||||||
"sunset_start" => config.sunset_start = value.parse()?,
|
"sunset_start" => {
|
||||||
"sunset_end" => config.sunset_end = value.parse()?,
|
let parsed = value.parse::<u8>()?;
|
||||||
"sunrise_start" => config.sunrise_start = value.parse()?,
|
if (0..=23).contains(&parsed) {
|
||||||
"sunrise_end" => config.sunrise_end = value.parse()?,
|
config.sunset_start = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidTime(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sunset_end" => {
|
||||||
|
let parsed = value.parse::<u8>()?;
|
||||||
|
if (0..=23).contains(&parsed) {
|
||||||
|
config.sunset_end = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidTime(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sunrise_start" => {
|
||||||
|
let parsed = value.parse::<u8>()?;
|
||||||
|
if (0..=23).contains(&parsed) {
|
||||||
|
config.sunrise_start = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidTime(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sunrise_end" => {
|
||||||
|
let parsed = value.parse::<u8>()?;
|
||||||
|
if (0..=23).contains(&parsed) {
|
||||||
|
config.sunrise_end = parsed
|
||||||
|
} else {
|
||||||
|
return Err(ConfigError::InvalidTime(value.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
105
src/main.rs
105
src/main.rs
|
@ -2,7 +2,7 @@ use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use std::time::Duration;
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
|
|
||||||
use hinoirisetr::notify::InitializedNotificationSystem;
|
use hinoirisetr::notify::InitializedNotificationSystem;
|
||||||
use hinoirisetr::time::Time;
|
use hinoirisetr::time::Time;
|
||||||
|
@ -16,17 +16,49 @@ use tokio::time::sleep;
|
||||||
const SOCKET_PATH: &str = "/tmp/hinoirisetr.sock";
|
const SOCKET_PATH: &str = "/tmp/hinoirisetr.sock";
|
||||||
|
|
||||||
static CONFIG: OnceLock<Arc<RwLock<Config>>> = OnceLock::new();
|
static CONFIG: OnceLock<Arc<RwLock<Config>>> = OnceLock::new();
|
||||||
|
static LAST_MODIFIED: OnceLock<Arc<RwLock<Option<u64>>>> = OnceLock::new();
|
||||||
|
|
||||||
enum NotifyState {
|
enum NotifyState {
|
||||||
Enabled(InitializedNotificationSystem),
|
Enabled(InitializedNotificationSystem),
|
||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn config_realoader(notify: Arc<Notify>) {
|
||||||
|
debug!("config reloader started");
|
||||||
|
// Config polling every 5 seconds
|
||||||
|
loop {
|
||||||
|
trace!("config poll tick");
|
||||||
|
let config_path = get_config_path();
|
||||||
|
if config_path.exists() {
|
||||||
|
let last_modified = LAST_MODIFIED.get().expect("last_modified not init");
|
||||||
|
let last_modified_guard = last_modified.read().await;
|
||||||
|
if let Ok(current_modified) = std::fs::metadata(&config_path)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.map(|t| t.duration_since(UNIX_EPOCH).unwrap().as_secs())
|
||||||
|
{
|
||||||
|
if let Some(last) = *last_modified_guard {
|
||||||
|
if current_modified > last {
|
||||||
|
trace!("{current_modified}");
|
||||||
|
trace!("{last}");
|
||||||
|
debug!("Config file modified, reloading...");
|
||||||
|
reload_config(Arc::clone(¬ify)).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// File is new, reload to capture initial state
|
||||||
|
debug!("Config file detected, reloading...");
|
||||||
|
reload_config(Arc::clone(¬ify)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
||||||
let listener = UnixListener::bind(SOCKET_PATH).expect("Failed to bind socket");
|
let listener = UnixListener::bind(SOCKET_PATH).expect("Failed to bind socket");
|
||||||
trace!("socket server bound");
|
trace!("socket server bound");
|
||||||
let notification: NotifyState =
|
let notification: NotifyState =
|
||||||
match { hinoirisetr::notify::InitializedNotificationSystem::new("hinoirisetr") } {
|
match hinoirisetr::notify::InitializedNotificationSystem::new("hinoirisetr") {
|
||||||
Ok(not) => NotifyState::Enabled(not),
|
Ok(not) => NotifyState::Enabled(not),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
info!("libnotify not found, disabling 'status_notify' command");
|
info!("libnotify not found, disabling 'status_notify' command");
|
||||||
|
@ -80,19 +112,7 @@ async fn socket_server(disabled: Arc<AtomicBool>, notify: Arc<Notify>) {
|
||||||
}
|
}
|
||||||
"reload" => {
|
"reload" => {
|
||||||
trace!("reload dispatched");
|
trace!("reload dispatched");
|
||||||
let config_handle = config_handle();
|
reload_config(notify.clone()).await;
|
||||||
let mut config = config_handle.write().await;
|
|
||||||
match Config::load(get_config_path()) {
|
|
||||||
Ok(cfg) => {
|
|
||||||
debug!("Config file found, loading...");
|
|
||||||
*config = cfg;
|
|
||||||
notify.notify_one();
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("Failed to load config: {err}");
|
|
||||||
warn!("Using default config.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"status_notify" => {
|
"status_notify" => {
|
||||||
trace!("status_notify dispatched");
|
trace!("status_notify dispatched");
|
||||||
|
@ -156,12 +176,22 @@ async fn main() {
|
||||||
let disabled = Arc::new(AtomicBool::new(false));
|
let disabled = Arc::new(AtomicBool::new(false));
|
||||||
let notify = Arc::new(Notify::new());
|
let notify = Arc::new(Notify::new());
|
||||||
|
|
||||||
let cfg: Config = if get_config_path().exists() {
|
// load config
|
||||||
|
let config_path = get_config_path();
|
||||||
|
let cfg: Config = if config_path.exists() {
|
||||||
debug!("Config file found, loading...");
|
debug!("Config file found, loading...");
|
||||||
match Config::load(get_config_path()) {
|
LAST_MODIFIED
|
||||||
|
.set(Arc::new(RwLock::new(
|
||||||
|
std::fs::metadata(&config_path)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.map(|t| t.duration_since(UNIX_EPOCH).unwrap().as_secs())
|
||||||
|
.ok(),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
match Config::load(&config_path) {
|
||||||
Ok(cfg) => cfg,
|
Ok(cfg) => cfg,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed to load config: {err}");
|
error!("Failed to load config: {err:?}");
|
||||||
warn!("Using default config.");
|
warn!("Using default config.");
|
||||||
Config::default()
|
Config::default()
|
||||||
}
|
}
|
||||||
|
@ -196,6 +226,14 @@ async fn main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spawn config reloader
|
||||||
|
{
|
||||||
|
let notify = Arc::clone(¬ify);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
config_realoader(notify).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Signal handling
|
// Signal handling
|
||||||
let mut sigint = signal(SignalKind::interrupt()).unwrap();
|
let mut sigint = signal(SignalKind::interrupt()).unwrap();
|
||||||
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
||||||
|
@ -243,6 +281,37 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to handle config reloading
|
||||||
|
async fn reload_config(notify: Arc<Notify>) {
|
||||||
|
trace!("reload_config called");
|
||||||
|
let config_handle = config_handle();
|
||||||
|
let mut config = config_handle.write().await;
|
||||||
|
let config_path = get_config_path();
|
||||||
|
match Config::load(&config_path) {
|
||||||
|
Ok(cfg) => {
|
||||||
|
debug!("Config file reloaded successfully");
|
||||||
|
*config = cfg;
|
||||||
|
trace!("new config: {:#?}", config);
|
||||||
|
|
||||||
|
let last_modified = LAST_MODIFIED.get().expect("last_modified not init");
|
||||||
|
let mut last_modified = last_modified.write().await;
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let new_modified = std::fs::metadata(&config_path)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.map(|t| t.duration_since(UNIX_EPOCH).unwrap().as_secs())
|
||||||
|
.ok();
|
||||||
|
trace!("last_modified: {last_modified:?}");
|
||||||
|
trace!("new_modified: {new_modified:?}");
|
||||||
|
*last_modified = new_modified;
|
||||||
|
});
|
||||||
|
notify.notify_one();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to reload config: {err:?}");
|
||||||
|
warn!("Retaining current config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fn is_binary_available(binary_name: &str) -> bool {
|
fn is_binary_available(binary_name: &str) -> bool {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
if let Ok(paths) = env::var("PATH") {
|
if let Ok(paths) = env::var("PATH") {
|
||||||
|
|
Loading…
Reference in a new issue