use anyhow::{Context as _, Result}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct LibraryFilters { #[serde(default)] pub hide_featured_only: bool, } impl LibraryFilters { pub fn is_active(&self) -> bool { self.hide_featured_only } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AppSettings { #[serde(default = "default_volume")] pub volume: u8, #[serde(default)] pub library: LibraryFilters, } impl Default for AppSettings { fn default() -> Self { Self { volume: default_volume(), library: LibraryFilters::default(), } } } impl AppSettings { pub fn normalized(mut self) -> Self { self.volume = self.volume.min(100); self } } fn default_volume() -> u8 { 80 } pub fn load() -> (AppSettings, Option) { let Some(path) = settings_path() else { return (AppSettings::default(), None); }; match std::fs::read_to_string(&path) { Ok(text) => match toml::from_str::(&text) { Ok(settings) => (settings.normalized(), None), Err(err) => ( AppSettings::default(), Some(format!("settings.toml is malformed: {err}")), ), }, Err(err) if err.kind() == std::io::ErrorKind::NotFound => (AppSettings::default(), None), Err(err) => ( AppSettings::default(), Some(format!("settings.toml could not be read: {err}")), ), } } pub fn save(settings: &AppSettings) -> Result<()> { let path = settings_path().context("cannot determine the config directory")?; if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } std::fs::write( &path, toml::to_string_pretty(&settings.clone().normalized())?, ) .with_context(|| format!("writing {}", path.display()))?; Ok(()) } fn settings_path() -> Option { crate::config::project_dirs().map(|dirs| dirs.config_dir().join("settings.toml")) } #[cfg(test)] mod tests { use super::*; #[test] fn settings_parse_and_normalize() { let settings: AppSettings = toml::from_str( r#" volume = 150 [library] hide_featured_only = true "#, ) .unwrap(); let settings = settings.normalized(); assert_eq!(settings.volume, 100); assert!(settings.library.hide_featured_only); assert!(settings.library.is_active()); } }