aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-05-11 16:25:49 +0200
committersalmonik <raisersoft@proton.me>2026-05-11 16:25:49 +0200
commit455ec54613230f4004a24f8d2798fb0084e00cb4 (patch)
tree5ffb7606a11ebf33ea940f068ed046bfda0f0c54 /src/config.rs
parentFix shuffle to not shuffle current track - only shuffle tracks after current ... (diff)
downloadrsplayer-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 '')
-rw-r--r--src/config.rs186
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
21impl Config { 21impl 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}