diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-05 21:12:32 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-05 21:12:32 +0200 |
| commit | bd29978b2a0d0d6def486fcdaa63bc8934d68045 (patch) | |
| tree | 5df0efb17d350ae7f095eb9c0940b1bbf568de96 /src/config.rs | |
| download | rsplayer-bd29978b2a0d0d6def486fcdaa63bc8934d68045.tar.gz rsplayer-bd29978b2a0d0d6def486fcdaa63bc8934d68045.zip | |
fix(daemon): robust auto-advance; ensure next track starts even if playback fails
Diffstat (limited to '')
| -rw-r--r-- | src/config.rs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..cda59f4 --- /dev/null +++ b/src/config.rs | |||
| @@ -0,0 +1,180 @@ | |||
| 1 | use directories::ProjectDirs; | ||
| 2 | use serde::{Deserialize, Serialize}; | ||
| 3 | use std::fs; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | #[derive(Debug, Serialize, Deserialize, Default)] | ||
| 7 | pub struct Config { | ||
| 8 | #[serde(skip)] | ||
| 9 | pub config_path: PathBuf, | ||
| 10 | pub music_paths: Vec<PathBuf>, | ||
| 11 | pub scan_interval: Option<u64>, | ||
| 12 | pub socket_path: Option<PathBuf>, | ||
| 13 | pub lastfm_api_key: Option<String>, | ||
| 14 | pub lastfm_api_secret: Option<String>, | ||
| 15 | pub lastfm_session_key: Option<String>, | ||
| 16 | pub listenbrainz_token: Option<String>, | ||
| 17 | pub volume: Option<u8>, | ||
| 18 | pub shuffle: Option<String>, | ||
| 19 | } | ||
| 20 | |||
| 21 | impl Config { | ||
| 22 | pub fn load() -> Result<Self, Box<dyn std::error::Error>> { | ||
| 23 | let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer") | ||
| 24 | .ok_or("Failed to get project directories")?; | ||
| 25 | |||
| 26 | let config_dir = if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { | ||
| 27 | PathBuf::from(xdg).join("rsplayer") | ||
| 28 | } else { | ||
| 29 | proj_dirs.config_dir().to_path_buf() | ||
| 30 | }; | ||
| 31 | |||
| 32 | let config_path = config_dir.join("config.toml"); | ||
| 33 | |||
| 34 | if !config_path.exists() { | ||
| 35 | fs::create_dir_all(&config_dir)?; | ||
| 36 | let default = Self { | ||
| 37 | config_path: config_path.clone(), | ||
| 38 | music_paths: vec![dirs::audio_dir().unwrap_or_else(|| PathBuf::from("~/Music"))], | ||
| 39 | scan_interval: Some(3600), | ||
| 40 | socket_path: None, | ||
| 41 | ..Default::default() | ||
| 42 | }; | ||
| 43 | default.save()?; | ||
| 44 | return Ok(default); | ||
| 45 | } | ||
| 46 | |||
| 47 | let content = fs::read_to_string(&config_path)?; | ||
| 48 | let mut config: Self = toml::from_str(&content)?; | ||
| 49 | config.config_path = config_path; | ||
| 50 | Ok(config) | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { | ||
| 54 | let content = toml::to_string_pretty(self)?; | ||
| 55 | if let Some(parent) = self.config_path.parent() { | ||
| 56 | fs::create_dir_all(parent)?; | ||
| 57 | } | ||
| 58 | fs::write(&self.config_path, content)?; | ||
| 59 | Ok(()) | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn get(&self, key: &str) -> Result<String, Box<dyn std::error::Error>> { | ||
| 63 | match key { | ||
| 64 | "music_paths" => Ok(self | ||
| 65 | .music_paths | ||
| 66 | .iter() | ||
| 67 | .map(|p| p.display().to_string()) | ||
| 68 | .collect::<Vec<_>>() | ||
| 69 | .join(", ")), | ||
| 70 | "scan_interval" => Ok(self | ||
| 71 | .scan_interval | ||
| 72 | .map(|i| i.to_string()) | ||
| 73 | .unwrap_or_else(|| "not set".to_string())), | ||
| 74 | "socket_path" => Ok(self | ||
| 75 | .socket_path | ||
| 76 | .as_ref() | ||
| 77 | .map(|p| p.display().to_string()) | ||
| 78 | .unwrap_or_else(|| "default".to_string())), | ||
| 79 | "lastfm_api_key" => Ok(self | ||
| 80 | .lastfm_api_key | ||
| 81 | .clone() | ||
| 82 | .unwrap_or_else(|| "not set".to_string())), | ||
| 83 | "lastfm_api_secret" => Ok(self | ||
| 84 | .lastfm_api_secret | ||
| 85 | .clone() | ||
| 86 | .unwrap_or_else(|| "not set".to_string())), | ||
| 87 | "lastfm_session_key" => Ok(self | ||
| 88 | .lastfm_session_key | ||
| 89 | .clone() | ||
| 90 | .unwrap_or_else(|| "not set".to_string())), | ||
| 91 | "listenbrainz_token" => Ok(self | ||
| 92 | .listenbrainz_token | ||
| 93 | .clone() | ||
| 94 | .unwrap_or_else(|| "not set".to_string())), | ||
| 95 | "volume" => Ok(self | ||
| 96 | .volume | ||
| 97 | .map(|v| v.to_string()) | ||
| 98 | .unwrap_or_else(|| "not set".to_string())), | ||
| 99 | "shuffle" => Ok(self.shuffle.clone().unwrap_or_else(|| "off".to_string())), | ||
| 100 | _ => Err(format!("Unknown setting: {}", key).into()), | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | pub fn set(&mut self, key: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> { | ||
| 105 | match key { | ||
| 106 | "music_paths" => { | ||
| 107 | self.music_paths = value.split(',').map(|p| PathBuf::from(p.trim())).collect(); | ||
| 108 | } | ||
| 109 | "scan_interval" => { | ||
| 110 | self.scan_interval = Some(value.parse()?); | ||
| 111 | } | ||
| 112 | "socket_path" => { | ||
| 113 | self.socket_path = Some(PathBuf::from(value)); | ||
| 114 | } | ||
| 115 | "lastfm_api_key" => { | ||
| 116 | self.lastfm_api_key = Some(value.to_string()); | ||
| 117 | } | ||
| 118 | "lastfm_api_secret" => { | ||
| 119 | self.lastfm_api_secret = Some(value.to_string()); | ||
| 120 | } | ||
| 121 | "lastfm_session_key" => { | ||
| 122 | self.lastfm_session_key = Some(value.to_string()); | ||
| 123 | } | ||
| 124 | "listenbrainz_token" => { | ||
| 125 | self.listenbrainz_token = Some(value.to_string()); | ||
| 126 | } | ||
| 127 | "volume" => { | ||
| 128 | self.volume = Some(value.parse()?); | ||
| 129 | } | ||
| 130 | "shuffle" => { | ||
| 131 | if value == "on" || value == "all" || value == "album" { | ||
| 132 | self.shuffle = Some(value.to_string()); | ||
| 133 | } else if value == "off" || value == "none" { | ||
| 134 | self.shuffle = None; | ||
| 135 | } else { | ||
| 136 | return Err(format!("Invalid shuffle value: {}", value).into()); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | _ => return Err(format!("Unknown setting: {}", key).into()), | ||
| 140 | } | ||
| 141 | Ok(()) | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn list(&self) { | ||
| 145 | println!("music_paths = {:?}", self.music_paths); | ||
| 146 | println!("scan_interval = {:?}", self.scan_interval); | ||
| 147 | println!("socket_path = {:?}", self.socket_path); | ||
| 148 | println!("lastfm_api_key = {:?}", self.lastfm_api_key); | ||
| 149 | println!("lastfm_api_secret = {:?}", self.lastfm_api_secret); | ||
| 150 | println!("lastfm_session_key = {:?}", self.lastfm_session_key); | ||
| 151 | println!("listenbrainz_token = {:?}", self.listenbrainz_token); | ||
| 152 | println!("volume = {:?}", self.volume); | ||
| 153 | println!("shuffle = {:?}", self.shuffle); | ||
| 154 | } | ||
| 155 | |||
| 156 | pub fn unset(&mut self, key: &str) -> Result<(), Box<dyn std::error::Error>> { | ||
| 157 | match key { | ||
| 158 | "music_paths" => self.music_paths.clear(), | ||
| 159 | "scan_interval" => self.scan_interval = None, | ||
| 160 | "socket_path" => self.socket_path = None, | ||
| 161 | "lastfm_api_key" => self.lastfm_api_key = None, | ||
| 162 | "lastfm_api_secret" => self.lastfm_api_secret = None, | ||
| 163 | "lastfm_session_key" => self.lastfm_session_key = None, | ||
| 164 | "listenbrainz_token" => self.listenbrainz_token = None, | ||
| 165 | "volume" => self.volume = None, | ||
| 166 | "shuffle" => self.shuffle = None, | ||
| 167 | _ => return Err(format!("Unknown setting: {}", key).into()), | ||
| 168 | } | ||
| 169 | Ok(()) | ||
| 170 | } | ||
| 171 | |||
| 172 | pub fn get_socket_path(&self) -> PathBuf { | ||
| 173 | self.socket_path.clone().unwrap_or_else(|| { | ||
| 174 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") | ||
| 175 | .map(PathBuf::from) | ||
| 176 | .unwrap_or_else(|_| PathBuf::from("/tmp")); | ||
| 177 | runtime_dir.join("rsplayer").join("daemon.sock") | ||
| 178 | }) | ||
| 179 | } | ||
| 180 | } | ||
