use directories::{ProjectDirs, UserDirs}; use serde::{Deserialize, Serialize}; use std::{collections::HashSet, fs}; use expanduser::expanduser; #[derive(Serialize, Deserialize)] pub struct Config { pub music_root: String, pub audio_exts: HashSet, pub skip_window_secs: u64, pub temperature: f32, pub low_weight: f32, pub mid_weight: f32, pub high_weight: f32, pub max_weight: f32, pub lookback_window_halflife_in_entries: u16, } fn default_config_path() -> String { let name = "mood.json"; ProjectDirs::from("qualifier", "organisation", "mood") .and_then(|pd| { pd.config_dir() .join(name) .to_str() .map(std::string::ToString::to_string) }) .unwrap_or(format!("~/.config/mood/{name}")) } impl Config { pub fn try_load() -> Result { expanduser(default_config_path()) .map_err(|e| e.to_string()) .and_then(|p| fs::read_to_string(p).map_err(|e| e.to_string())) .and_then(|contents| serde_json::from_str(&contents).map_err(|e| e.to_string())) } pub fn try_save(&self) -> Result<(), String> { let path = expanduser(default_config_path()).map_err(|e| e.to_string())?; _ = path.parent().map(std::fs::create_dir_all); let serialised = serde_json::to_string_pretty(self) .map_err(|e| format!("Failed to serialize config: {e}"))?; fs::write(&path, serialised).map_err(|e| format!("Failed to write config file: {e}"))?; Ok(()) } } impl Default for Config { fn default() -> Config { Config { music_root: UserDirs::new() .and_then(|u| { u.audio_dir() .and_then(|p| p.to_str().map(std::string::ToString::to_string)) }) .unwrap_or("~/Music".to_string()), audio_exts: ["mp3", "flac", "wav", "m4a", "ogg", "vorbis", "mp4"] .into_iter() .map(String::from) .collect(), skip_window_secs: 60, temperature: 0.6, low_weight: 0.25, mid_weight: 0.5, high_weight: 0.75, max_weight: 1.0, lookback_window_halflife_in_entries: 128, } } }