Files
leaguerecorder/record-daemon/src/config/persistence.rs

177 lines
5.7 KiB
Rust

//! Configuration persistence - loading and saving settings.
use std::fs;
use std::io::{self, Read, Write};
use std::path::PathBuf;
use tracing::{debug, info, warn};
use super::{get_config_dir, Settings, CONFIG_FILE_NAME};
use crate::error::{ConfigError, Result};
/// Configuration persistence handler.
pub struct ConfigPersistence {
config_path: PathBuf,
}
impl ConfigPersistence {
/// Create a new persistence handler with the given config path.
pub fn new(config_path: PathBuf) -> Self {
Self { config_path }
}
/// Create a persistence handler using the default config location.
pub fn default_location() -> io::Result<Self> {
let config_dir = get_config_dir()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Config directory not found"))?;
Ok(Self::new(config_dir.join(CONFIG_FILE_NAME)))
}
/// Get the configuration file path.
pub fn config_path(&self) -> &PathBuf {
&self.config_path
}
/// Check if the configuration file exists.
pub fn exists(&self) -> bool {
self.config_path.exists()
}
/// Load settings from the configuration file.
pub fn load(&self) -> Result<Settings> {
if !self.exists() {
debug!("Config file not found, using defaults");
return Ok(Settings::default());
}
let mut file = fs::File::open(&self.config_path).map_err(ConfigError::ReadError)?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(ConfigError::ReadError)?;
let settings: Settings = toml::from_str(&contents).map_err(ConfigError::from)?;
info!("Loaded configuration from {:?}", self.config_path);
Ok(settings)
}
/// Save settings to the configuration file.
pub fn save(&self, settings: &Settings) -> Result<()> {
// Ensure parent directory exists
if let Some(parent) = self.config_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent).map_err(ConfigError::WriteError)?;
debug!("Created config directory: {:?}", parent);
}
}
let contents = toml::to_string_pretty(settings)
.map_err(|e| ConfigError::InvalidConfig(e.to_string()))?;
let mut file = fs::File::create(&self.config_path).map_err(ConfigError::WriteError)?;
file.write_all(contents.as_bytes())
.map_err(ConfigError::WriteError)?;
info!("Saved configuration to {:?}", self.config_path);
Ok(())
}
/// Validate settings and apply any necessary migrations.
pub fn validate_and_migrate(&self, settings: &mut Settings) -> Result<()> {
// Ensure output directory exists
if !settings.output.path.exists() {
fs::create_dir_all(&settings.output.path).map_err(|e| {
ConfigError::InvalidConfig(format!("Cannot create output directory: {}", e))
})?;
debug!("Created output directory: {:?}", settings.output.path);
}
// Validate frame rate
if settings.video.frame_rate == 0 || settings.video.frame_rate > 240 {
warn!(
"Invalid frame rate {}, defaulting to 60",
settings.video.frame_rate
);
settings.video.frame_rate = 60;
}
// Validate container format
let valid_containers = ["mp4", "mkv", "mov", "flv"];
if !valid_containers.contains(&settings.output.container.as_str()) {
warn!(
"Invalid container format '{}', defaulting to mp4",
settings.output.container
);
settings.output.container = "mp4".to_string();
}
// Validate log level
let valid_levels = ["trace", "debug", "info", "warn", "error"];
if !valid_levels.contains(&settings.daemon.log_level.as_str()) {
warn!(
"Invalid log level '{}', defaulting to info",
settings.daemon.log_level
);
settings.daemon.log_level = "info".to_string();
}
Ok(())
}
}
/// Load configuration from the default location.
pub fn load_config() -> Result<Settings> {
let persistence = ConfigPersistence::default_location().map_err(|e| {
ConfigError::InvalidConfig(format!("Cannot access config directory: {}", e))
})?;
let mut settings = persistence.load()?;
persistence.validate_and_migrate(&mut settings)?;
Ok(settings)
}
/// Save configuration to the default location.
pub fn save_config(settings: &Settings) -> Result<()> {
let persistence = ConfigPersistence::default_location().map_err(|e| {
ConfigError::InvalidConfig(format!("Cannot access config directory: {}", e))
})?;
persistence.save(settings)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_save_and_load_config() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("config.toml");
let persistence = ConfigPersistence::new(config_path);
let settings = Settings::default();
persistence.save(&settings).unwrap();
assert!(persistence.exists());
let loaded = persistence.load().unwrap();
assert_eq!(settings.video.frame_rate, loaded.video.frame_rate);
assert_eq!(settings.daemon.auto_record, loaded.daemon.auto_record);
}
#[test]
fn test_load_nonexistent_config() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("nonexistent.toml");
let persistence = ConfigPersistence::new(config_path);
let settings = persistence.load().unwrap();
assert_eq!(settings.video.frame_rate, 60);
}
}