record-daemon: initial commit
This commit is contained in:
176
record-daemon/src/config/persistence.rs
Normal file
176
record-daemon/src/config/persistence.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user