use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::{env, fs, path::PathBuf};
use tracing::{debug, info, warn};
use super::{
error::{ConfigError, Result},
input::ControlsConfig,
window::WindowConfig,
zoom::ZoomConfig,
CacheConfig,
HelpMenuConfig,
IndicatorConfig,
CONFIG_VERSION,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FerriteConfig {
version: String,
pub window: WindowConfig,
pub zoom: ZoomConfig,
pub controls: ControlsConfig,
pub indicator: IndicatorConfig,
pub help_menu: HelpMenuConfig,
pub cache: CacheConfig,
pub log_file: Option<PathBuf>,
}
impl Default for FerriteConfig {
fn default() -> Self {
info!("Creating default configuration");
let default_log_file_path =
ProjectDirs::from("com", "ferrite", "ferrite").map(|proj_dirs| {
let log_dir = proj_dirs.data_local_dir().join("logs"); log_dir.join("ferrite.log")
});
let log_file = default_log_file_path.or_else(|| {
warn!(
"Could not determine platform-specific log directory. \
Defaulting log file to 'ferrite.log' in CWD or next to \
config."
);
Some(PathBuf::from("ferrite.log")) });
Self {
version: CONFIG_VERSION.to_string(),
window: WindowConfig::default(),
zoom: ZoomConfig::default(),
controls: ControlsConfig::default(),
indicator: IndicatorConfig::default(),
help_menu: HelpMenuConfig::default(),
cache: CacheConfig::default(),
log_file, }
}
}
impl FerriteConfig {
pub fn resolve_config_path() -> Result<PathBuf> {
if let Ok(env_path) = env::var("FERRITE_CONF") {
let path = PathBuf::from(env_path);
if let Some(parent) = path.parent() {
if !parent.exists() {
return Err(ConfigError::InvalidPath(format!(
"Directory {} from FERRITE_CONF does not exist",
parent.display()
)));
}
}
return Ok(path);
}
Self::get_default_path()
}
pub fn load() -> Result<Self> {
let config_path = Self::resolve_config_path()?;
if !config_path.exists() {
info!(
"No configuration file found at {:?}, using defaults",
config_path
);
return Ok(Self::default());
}
info!("Loading configuration from {:?}", config_path);
Self::load_from_path(&config_path)
}
pub fn load_from_path(path: &PathBuf) -> Result<Self> {
if !path.exists() {
debug!("No config file found at {:?}, using defaults", path);
return Ok(Self::default());
}
info!("Loading configuration from {:?}", path);
let content = fs::read_to_string(path)?;
let config: Self = toml::from_str(&content)?;
if config.version != CONFIG_VERSION {
return Err(ConfigError::VersionError {
found: config.version.clone(),
supported: CONFIG_VERSION.to_string(),
});
}
config.validate()?;
Ok(config)
}
pub fn save_to_path(&self, path: &PathBuf) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
self.validate()?;
let content = toml::to_string_pretty(self)?;
fs::write(path, content)?;
info!("Saved configuration to {:?}", path);
Ok(())
}
pub fn get_default_path() -> Result<PathBuf> {
ProjectDirs::from("com", "ferrite", "ferrite")
.map(|proj_dirs| proj_dirs.config_dir().join("config.toml"))
.ok_or_else(|| ConfigError::DirectoryError(PathBuf::from(".")))
}
pub fn validate(&self) -> Result<()> {
self.window.validate()?;
self.zoom.validate()?;
self.controls.validate()?;
self.indicator.validate()?;
Ok(())
}
pub fn with_modifications<F>(&self, modify_fn: F) -> Result<Self>
where
F: FnOnce(&mut Self),
{
let mut new_config = self.clone();
modify_fn(&mut new_config);
new_config.validate()?;
Ok(new_config)
}
}