diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-11 16:25:49 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-11 16:25:49 +0200 |
| commit | 455ec54613230f4004a24f8d2798fb0084e00cb4 (patch) | |
| tree | 5ffb7606a11ebf33ea940f068ed046bfda0f0c54 /src/config.rs | |
| parent | Fix shuffle to not shuffle current track - only shuffle tracks after current ... (diff) | |
| download | rsplayer-455ec54613230f4004a24f8d2798fb0084e00cb4.tar.gz rsplayer-455ec54613230f4004a24f8d2798fb0084e00cb4.zip | |
Add display_format config, fix restart command, clean up config code
- Add display_format option with get/set/list/unset for customizing track display
- Add Track::format() supporting $ARTIST, $ALBUM, $TRACK variables
- Update queue, status, notifications to use display_format
- Fix restart command using wrong subcommand 'start' -> 'start-daemon'
- Remove socket_path from config (now computed at runtime from XDG_RUNTIME_DIR)
- Consolidate duplicate save() and save_all() into single save()
- Fix redundant .clone().clone() in config.rs
- Add todo item for caching config in daemon hot paths
Diffstat (limited to 'src/config.rs')
| -rw-r--r-- | src/config.rs | 186 |
1 files changed, 152 insertions, 34 deletions
diff --git a/src/config.rs b/src/config.rs index 42573ed..5fd2f1b 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -9,13 +9,13 @@ pub struct Config { | |||
| 9 | pub config_path: PathBuf, | 9 | pub config_path: PathBuf, |
| 10 | pub music_paths: Vec<PathBuf>, | 10 | pub music_paths: Vec<PathBuf>, |
| 11 | pub scan_interval: Option<u64>, | 11 | pub scan_interval: Option<u64>, |
| 12 | pub socket_path: Option<PathBuf>, | ||
| 13 | pub lastfm_api_key: Option<String>, | 12 | pub lastfm_api_key: Option<String>, |
| 14 | pub lastfm_api_secret: Option<String>, | 13 | pub lastfm_api_secret: Option<String>, |
| 15 | pub lastfm_session_key: Option<String>, | 14 | pub lastfm_session_key: Option<String>, |
| 16 | pub listenbrainz_token: Option<String>, | 15 | pub listenbrainz_token: Option<String>, |
| 17 | pub volume: Option<u8>, | 16 | pub volume: Option<u8>, |
| 18 | pub shuffle: Option<String>, | 17 | pub shuffle: Option<String>, |
| 18 | pub display_format: Option<String>, | ||
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | impl Config { | 21 | impl Config { |
| @@ -43,8 +43,13 @@ impl Config { | |||
| 43 | config_path: config_path.clone(), | 43 | config_path: config_path.clone(), |
| 44 | music_paths: vec![dirs::audio_dir().unwrap_or_else(|| PathBuf::from("~/Music"))], | 44 | music_paths: vec![dirs::audio_dir().unwrap_or_else(|| PathBuf::from("~/Music"))], |
| 45 | scan_interval: Some(3600), | 45 | scan_interval: Some(3600), |
| 46 | socket_path: None, | 46 | lastfm_api_key: None, |
| 47 | ..Default::default() | 47 | lastfm_api_secret: None, |
| 48 | lastfm_session_key: None, | ||
| 49 | listenbrainz_token: None, | ||
| 50 | volume: Some(50), | ||
| 51 | shuffle: None, | ||
| 52 | display_format: Some(String::from("$ARTIST - $TRACK")), | ||
| 48 | }; | 53 | }; |
| 49 | default.save()?; | 54 | default.save()?; |
| 50 | return Ok(default); | 55 | return Ok(default); |
| @@ -53,15 +58,72 @@ impl Config { | |||
| 53 | let content = fs::read_to_string(&config_path)?; | 58 | let content = fs::read_to_string(&config_path)?; |
| 54 | let mut config: Self = toml::from_str(&content)?; | 59 | let mut config: Self = toml::from_str(&content)?; |
| 55 | config.config_path = config_path; | 60 | config.config_path = config_path; |
| 61 | if config.music_paths.is_empty() { | ||
| 62 | config.music_paths = | ||
| 63 | vec![dirs::audio_dir().unwrap_or_else(|| PathBuf::from("~/Music"))]; | ||
| 64 | } | ||
| 65 | if config.scan_interval.is_none() { | ||
| 66 | config.scan_interval = Some(3600); | ||
| 67 | } | ||
| 68 | config.save()?; | ||
| 56 | Ok(config) | 69 | Ok(config) |
| 57 | } | 70 | } |
| 58 | 71 | ||
| 59 | pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { | 72 | pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { |
| 60 | let content = toml::to_string_pretty(self)?; | 73 | let mut output = String::new(); |
| 74 | let paths_str = self | ||
| 75 | .music_paths | ||
| 76 | .iter() | ||
| 77 | .map(|p| format!("\"{}\"", p.display())) | ||
| 78 | .collect::<Vec<_>>() | ||
| 79 | .join(", "); | ||
| 80 | output.push_str(&format!("music_paths = [{}]\n", paths_str)); | ||
| 81 | output.push_str(&format!( | ||
| 82 | "scan_interval = {}\n", | ||
| 83 | self.scan_interval.unwrap_or(3600) | ||
| 84 | )); | ||
| 85 | let api_key = self | ||
| 86 | .lastfm_api_key | ||
| 87 | .as_ref() | ||
| 88 | .filter(|s| !s.is_empty()) | ||
| 89 | .map(|s| s.as_str()) | ||
| 90 | .unwrap_or(""); | ||
| 91 | output.push_str(&format!("lastfm_api_key = \"{}\"\n", api_key)); | ||
| 92 | let api_secret = self | ||
| 93 | .lastfm_api_secret | ||
| 94 | .as_ref() | ||
| 95 | .filter(|s| !s.is_empty()) | ||
| 96 | .map(|s| s.as_str()) | ||
| 97 | .unwrap_or(""); | ||
| 98 | output.push_str(&format!("lastfm_api_secret = \"{}\"\n", api_secret)); | ||
| 99 | let session_key = self | ||
| 100 | .lastfm_session_key | ||
| 101 | .as_ref() | ||
| 102 | .filter(|s| !s.is_empty()) | ||
| 103 | .map(|s| s.as_str()) | ||
| 104 | .unwrap_or(""); | ||
| 105 | output.push_str(&format!("lastfm_session_key = \"{}\"\n", session_key)); | ||
| 106 | let lb_token = self | ||
| 107 | .listenbrainz_token | ||
| 108 | .as_ref() | ||
| 109 | .filter(|s| !s.is_empty()) | ||
| 110 | .map(|s| s.as_str()) | ||
| 111 | .unwrap_or(""); | ||
| 112 | output.push_str(&format!("listenbrainz_token = \"{}\"\n", lb_token)); | ||
| 113 | output.push_str(&format!("volume = {}\n", self.volume.unwrap_or(0))); | ||
| 114 | let shuffle = self | ||
| 115 | .shuffle | ||
| 116 | .as_ref() | ||
| 117 | .filter(|s| !s.is_empty()) | ||
| 118 | .map(|s| s.as_str()) | ||
| 119 | .unwrap_or(""); | ||
| 120 | output.push_str(&format!("shuffle = \"{}\"\n", shuffle)); | ||
| 121 | let fmt = self.display_format.clone().unwrap_or_default(); | ||
| 122 | output.push_str(&format!("display_format = \"{}\"\n", fmt)); | ||
| 61 | if let Some(parent) = self.config_path.parent() { | 123 | if let Some(parent) = self.config_path.parent() { |
| 62 | fs::create_dir_all(parent)?; | 124 | fs::create_dir_all(parent)?; |
| 63 | } | 125 | } |
| 64 | fs::write(&self.config_path, content)?; | 126 | fs::write(&self.config_path, output)?; |
| 65 | Ok(()) | 127 | Ok(()) |
| 66 | } | 128 | } |
| 67 | 129 | ||
| @@ -77,32 +139,39 @@ impl Config { | |||
| 77 | .scan_interval | 139 | .scan_interval |
| 78 | .map(|i| i.to_string()) | 140 | .map(|i| i.to_string()) |
| 79 | .unwrap_or_else(|| "not set".to_string())), | 141 | .unwrap_or_else(|| "not set".to_string())), |
| 80 | "socket_path" => Ok(self | ||
| 81 | .socket_path | ||
| 82 | .as_ref() | ||
| 83 | .map(|p| p.display().to_string()) | ||
| 84 | .unwrap_or_else(|| "default".to_string())), | ||
| 85 | "lastfm_api_key" => Ok(self | 142 | "lastfm_api_key" => Ok(self |
| 86 | .lastfm_api_key | 143 | .lastfm_api_key |
| 87 | .clone() | 144 | .as_ref() |
| 145 | .filter(|s| !s.is_empty()) | ||
| 146 | .map(|_| "<set>".to_string()) | ||
| 88 | .unwrap_or_else(|| "not set".to_string())), | 147 | .unwrap_or_else(|| "not set".to_string())), |
| 89 | "lastfm_api_secret" => Ok(self | 148 | "lastfm_api_secret" => Ok(self |
| 90 | .lastfm_api_secret | 149 | .lastfm_api_secret |
| 91 | .clone() | 150 | .as_ref() |
| 151 | .filter(|s| !s.is_empty()) | ||
| 152 | .map(|_| "<set>".to_string()) | ||
| 92 | .unwrap_or_else(|| "not set".to_string())), | 153 | .unwrap_or_else(|| "not set".to_string())), |
| 93 | "lastfm_session_key" => Ok(self | 154 | "lastfm_session_key" => Ok(self |
| 94 | .lastfm_session_key | 155 | .lastfm_session_key |
| 95 | .clone() | 156 | .as_ref() |
| 157 | .filter(|s| !s.is_empty()) | ||
| 158 | .map(|_| "<set>".to_string()) | ||
| 96 | .unwrap_or_else(|| "not set".to_string())), | 159 | .unwrap_or_else(|| "not set".to_string())), |
| 97 | "listenbrainz_token" => Ok(self | 160 | "listenbrainz_token" => Ok(self |
| 98 | .listenbrainz_token | 161 | .listenbrainz_token |
| 99 | .clone() | 162 | .as_ref() |
| 163 | .filter(|s| !s.is_empty()) | ||
| 164 | .map(|_| "<set>".to_string()) | ||
| 100 | .unwrap_or_else(|| "not set".to_string())), | 165 | .unwrap_or_else(|| "not set".to_string())), |
| 101 | "volume" => Ok(self | 166 | "volume" => Ok(self |
| 102 | .volume | 167 | .volume |
| 103 | .map(|v| v.to_string()) | 168 | .map(|v| v.to_string()) |
| 104 | .unwrap_or_else(|| "not set".to_string())), | 169 | .unwrap_or_else(|| "not set".to_string())), |
| 105 | "shuffle" => Ok(self.shuffle.clone().unwrap_or_else(|| "off".to_string())), | 170 | "shuffle" => Ok(self.shuffle.clone().unwrap_or_else(|| "off".to_string())), |
| 171 | "display_format" => Ok(self | ||
| 172 | .display_format | ||
| 173 | .clone() | ||
| 174 | .unwrap_or_else(|| "not set".to_string())), | ||
| 106 | _ => Err(format!("Unknown setting: {}", key).into()), | 175 | _ => Err(format!("Unknown setting: {}", key).into()), |
| 107 | } | 176 | } |
| 108 | } | 177 | } |
| @@ -115,9 +184,6 @@ impl Config { | |||
| 115 | "scan_interval" => { | 184 | "scan_interval" => { |
| 116 | self.scan_interval = Some(value.parse()?); | 185 | self.scan_interval = Some(value.parse()?); |
| 117 | } | 186 | } |
| 118 | "socket_path" => { | ||
| 119 | self.socket_path = Some(PathBuf::from(value)); | ||
| 120 | } | ||
| 121 | "lastfm_api_key" => { | 187 | "lastfm_api_key" => { |
| 122 | self.lastfm_api_key = Some(value.to_string()); | 188 | self.lastfm_api_key = Some(value.to_string()); |
| 123 | } | 189 | } |
| @@ -142,45 +208,97 @@ impl Config { | |||
| 142 | return Err(format!("Invalid shuffle value: {}", value).into()); | 208 | return Err(format!("Invalid shuffle value: {}", value).into()); |
| 143 | } | 209 | } |
| 144 | } | 210 | } |
| 211 | "display_format" => { | ||
| 212 | self.display_format = Some(value.to_string()); | ||
| 213 | } | ||
| 145 | _ => return Err(format!("Unknown setting: {}", key).into()), | 214 | _ => return Err(format!("Unknown setting: {}", key).into()), |
| 146 | } | 215 | } |
| 147 | Ok(()) | 216 | Ok(()) |
| 148 | } | 217 | } |
| 149 | 218 | ||
| 150 | pub fn list(&self) { | 219 | pub fn list(&self) { |
| 151 | println!("music_paths = {:?}", self.music_paths); | 220 | println!( |
| 152 | println!("scan_interval = {:?}", self.scan_interval); | 221 | "music_paths = {}", |
| 153 | println!("socket_path = {:?}", self.socket_path); | 222 | self.music_paths |
| 154 | println!("lastfm_api_key = {:?}", self.lastfm_api_key); | 223 | .iter() |
| 155 | println!("lastfm_api_secret = {:?}", self.lastfm_api_secret); | 224 | .map(|p| p.display().to_string()) |
| 156 | println!("lastfm_session_key = {:?}", self.lastfm_session_key); | 225 | .collect::<Vec<_>>() |
| 157 | println!("listenbrainz_token = {:?}", self.listenbrainz_token); | 226 | .join(", ") |
| 158 | println!("volume = {:?}", self.volume); | 227 | ); |
| 159 | println!("shuffle = {:?}", self.shuffle); | 228 | println!( |
| 229 | "scan_interval = {}", | ||
| 230 | self.scan_interval | ||
| 231 | .map_or("not set".to_string(), |v| v.to_string()) | ||
| 232 | ); | ||
| 233 | println!( | ||
| 234 | "lastfm_api_key = {}", | ||
| 235 | self.lastfm_api_key | ||
| 236 | .as_ref() | ||
| 237 | .filter(|s| !s.is_empty()) | ||
| 238 | .map_or("not set".to_string(), |_| "<set>".to_string()) | ||
| 239 | ); | ||
| 240 | println!( | ||
| 241 | "lastfm_api_secret = {}", | ||
| 242 | self.lastfm_api_secret | ||
| 243 | .as_ref() | ||
| 244 | .filter(|s| !s.is_empty()) | ||
| 245 | .map_or("not set".to_string(), |_| "<set>".to_string()) | ||
| 246 | ); | ||
| 247 | println!( | ||
| 248 | "lastfm_session_key = {}", | ||
| 249 | self.lastfm_session_key | ||
| 250 | .as_ref() | ||
| 251 | .filter(|s| !s.is_empty()) | ||
| 252 | .map_or("not set".to_string(), |_| "<set>".to_string()) | ||
| 253 | ); | ||
| 254 | println!( | ||
| 255 | "listenbrainz_token = {}", | ||
| 256 | self.listenbrainz_token | ||
| 257 | .as_ref() | ||
| 258 | .filter(|s| !s.is_empty()) | ||
| 259 | .map_or("not set".to_string(), |_| "<set>".to_string()) | ||
| 260 | ); | ||
| 261 | println!( | ||
| 262 | "volume = {}", | ||
| 263 | self.volume | ||
| 264 | .filter(|&v| v != 0) | ||
| 265 | .map_or("not set".to_string(), |v| v.to_string()) | ||
| 266 | ); | ||
| 267 | println!( | ||
| 268 | "shuffle = {}", | ||
| 269 | self.shuffle | ||
| 270 | .clone() | ||
| 271 | .filter(|s| !s.is_empty()) | ||
| 272 | .unwrap_or_else(|| "off".to_string()) | ||
| 273 | ); | ||
| 274 | println!( | ||
| 275 | "display_format = {}", | ||
| 276 | self.display_format | ||
| 277 | .clone() | ||
| 278 | .unwrap_or_else(|| "not set".to_string()) | ||
| 279 | ); | ||
| 160 | } | 280 | } |
| 161 | 281 | ||
| 162 | pub fn unset(&mut self, key: &str) -> Result<(), Box<dyn std::error::Error>> { | 282 | pub fn unset(&mut self, key: &str) -> Result<(), Box<dyn std::error::Error>> { |
| 163 | match key { | 283 | match key { |
| 164 | "music_paths" => self.music_paths.clear(), | 284 | "music_paths" => self.music_paths.clear(), |
| 165 | "scan_interval" => self.scan_interval = None, | 285 | "scan_interval" => self.scan_interval = None, |
| 166 | "socket_path" => self.socket_path = None, | ||
| 167 | "lastfm_api_key" => self.lastfm_api_key = None, | 286 | "lastfm_api_key" => self.lastfm_api_key = None, |
| 168 | "lastfm_api_secret" => self.lastfm_api_secret = None, | 287 | "lastfm_api_secret" => self.lastfm_api_secret = None, |
| 169 | "lastfm_session_key" => self.lastfm_session_key = None, | 288 | "lastfm_session_key" => self.lastfm_session_key = None, |
| 170 | "listenbrainz_token" => self.listenbrainz_token = None, | 289 | "listenbrainz_token" => self.listenbrainz_token = None, |
| 171 | "volume" => self.volume = None, | 290 | "volume" => self.volume = None, |
| 172 | "shuffle" => self.shuffle = None, | 291 | "shuffle" => self.shuffle = None, |
| 292 | "display_format" => self.display_format = None, | ||
| 173 | _ => return Err(format!("Unknown setting: {}", key).into()), | 293 | _ => return Err(format!("Unknown setting: {}", key).into()), |
| 174 | } | 294 | } |
| 175 | Ok(()) | 295 | Ok(()) |
| 176 | } | 296 | } |
| 177 | 297 | ||
| 178 | pub fn get_socket_path(&self) -> PathBuf { | 298 | pub fn get_socket_path() -> PathBuf { |
| 179 | self.socket_path.clone().unwrap_or_else(|| { | 299 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") |
| 180 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") | 300 | .map(PathBuf::from) |
| 181 | .map(PathBuf::from) | 301 | .unwrap_or_else(|_| PathBuf::from("/tmp")); |
| 182 | .unwrap_or_else(|_| PathBuf::from("/tmp")); | 302 | runtime_dir.join("rsplayer").join("daemon.sock") |
| 183 | runtime_dir.join("rsplayer").join("daemon.sock") | ||
| 184 | }) | ||
| 185 | } | 303 | } |
| 186 | } | 304 | } |
