use directories::ProjectDirs; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize, Default)] pub struct Config { #[serde(skip)] pub config_path: PathBuf, pub music_paths: Vec, pub scan_interval: Option, pub socket_path: Option, pub lastfm_api_key: Option, pub lastfm_api_secret: Option, pub lastfm_session_key: Option, pub listenbrainz_token: Option, pub volume: Option, pub shuffle: Option, } impl Config { pub fn load() -> Result> { let config_dir = if cfg!(debug_assertions) { std::env::current_exe()? .parent() .map(|p| p.to_path_buf()) .unwrap_or_else(|| PathBuf::from(".")) } else { let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer") .ok_or("Failed to get project directories")?; if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { PathBuf::from(xdg).join("rsplayer") } else { proj_dirs.config_dir().to_path_buf() } }; let config_path = config_dir.join("config.toml"); if !config_path.exists() { fs::create_dir_all(&config_dir)?; let default = Self { config_path: config_path.clone(), music_paths: vec![dirs::audio_dir().unwrap_or_else(|| PathBuf::from("~/Music"))], scan_interval: Some(3600), socket_path: None, ..Default::default() }; default.save()?; return Ok(default); } let content = fs::read_to_string(&config_path)?; let mut config: Self = toml::from_str(&content)?; config.config_path = config_path; Ok(config) } pub fn save(&self) -> Result<(), Box> { let content = toml::to_string_pretty(self)?; if let Some(parent) = self.config_path.parent() { fs::create_dir_all(parent)?; } fs::write(&self.config_path, content)?; Ok(()) } pub fn get(&self, key: &str) -> Result> { match key { "music_paths" => Ok(self .music_paths .iter() .map(|p| p.display().to_string()) .collect::>() .join(", ")), "scan_interval" => Ok(self .scan_interval .map(|i| i.to_string()) .unwrap_or_else(|| "not set".to_string())), "socket_path" => Ok(self .socket_path .as_ref() .map(|p| p.display().to_string()) .unwrap_or_else(|| "default".to_string())), "lastfm_api_key" => Ok(self .lastfm_api_key .clone() .unwrap_or_else(|| "not set".to_string())), "lastfm_api_secret" => Ok(self .lastfm_api_secret .clone() .unwrap_or_else(|| "not set".to_string())), "lastfm_session_key" => Ok(self .lastfm_session_key .clone() .unwrap_or_else(|| "not set".to_string())), "listenbrainz_token" => Ok(self .listenbrainz_token .clone() .unwrap_or_else(|| "not set".to_string())), "volume" => Ok(self .volume .map(|v| v.to_string()) .unwrap_or_else(|| "not set".to_string())), "shuffle" => Ok(self.shuffle.clone().unwrap_or_else(|| "off".to_string())), _ => Err(format!("Unknown setting: {}", key).into()), } } pub fn set(&mut self, key: &str, value: &str) -> Result<(), Box> { match key { "music_paths" => { self.music_paths = value.split(',').map(|p| PathBuf::from(p.trim())).collect(); } "scan_interval" => { self.scan_interval = Some(value.parse()?); } "socket_path" => { self.socket_path = Some(PathBuf::from(value)); } "lastfm_api_key" => { self.lastfm_api_key = Some(value.to_string()); } "lastfm_api_secret" => { self.lastfm_api_secret = Some(value.to_string()); } "lastfm_session_key" => { self.lastfm_session_key = Some(value.to_string()); } "listenbrainz_token" => { self.listenbrainz_token = Some(value.to_string()); } "volume" => { self.volume = Some(value.parse()?); } "shuffle" => { if value == "on" || value == "all" || value == "album" { self.shuffle = Some(value.to_string()); } else if value == "off" || value == "none" { self.shuffle = None; } else { return Err(format!("Invalid shuffle value: {}", value).into()); } } _ => return Err(format!("Unknown setting: {}", key).into()), } Ok(()) } pub fn list(&self) { println!("music_paths = {:?}", self.music_paths); println!("scan_interval = {:?}", self.scan_interval); println!("socket_path = {:?}", self.socket_path); println!("lastfm_api_key = {:?}", self.lastfm_api_key); println!("lastfm_api_secret = {:?}", self.lastfm_api_secret); println!("lastfm_session_key = {:?}", self.lastfm_session_key); println!("listenbrainz_token = {:?}", self.listenbrainz_token); println!("volume = {:?}", self.volume); println!("shuffle = {:?}", self.shuffle); } pub fn unset(&mut self, key: &str) -> Result<(), Box> { match key { "music_paths" => self.music_paths.clear(), "scan_interval" => self.scan_interval = None, "socket_path" => self.socket_path = None, "lastfm_api_key" => self.lastfm_api_key = None, "lastfm_api_secret" => self.lastfm_api_secret = None, "lastfm_session_key" => self.lastfm_session_key = None, "listenbrainz_token" => self.listenbrainz_token = None, "volume" => self.volume = None, "shuffle" => self.shuffle = None, _ => return Err(format!("Unknown setting: {}", key).into()), } Ok(()) } pub fn get_socket_path(&self) -> PathBuf { self.socket_path.clone().unwrap_or_else(|| { let runtime_dir = std::env::var("XDG_RUNTIME_DIR") .map(PathBuf::from) .unwrap_or_else(|_| PathBuf::from("/tmp")); runtime_dir.join("rsplayer").join("daemon.sock") }) } }