aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-02 13:22:44 +0100
committersalmonik <salmonik@tuta.io>2026-11-02 13:22:44 +0100
commit2c044de38f6adcfbd3cc64eb9fa55e67d61e6f94 (patch)
tree24a7a55a70b2235d115d9861d24385a61725e7b8
parentseek: jump within the current track from the CLI and over MPRIS (diff)
downloadrsplayer-2c044de38f6adcfbd3cc64eb9fa55e67d61e6f94.tar.gz
rsplayer-2c044de38f6adcfbd3cc64eb9fa55e67d61e6f94.zip
config: parse the config once and reuse it until the file changes
Every command, status line and notification went through Config::load, which reads *and* rewrites config.toml, so `rsp status` alone touched the file on each call. The daemon now keeps a parsed copy and only stats the file to notice outside edits, so `rsp settings set` still takes effect immediately. Config::save refreshes the cached copy too, and Config::config_path is shared by the loader and the cache.
-rw-r--r--src/config.rs107
-rw-r--r--src/daemon.rs54
-rw-r--r--src/mpris.rs12
3 files changed, 124 insertions, 49 deletions
diff --git a/src/config.rs b/src/config.rs
index f20fb3a..a08f01f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -9,8 +9,10 @@ use directories::ProjectDirs;
9use serde::{Deserialize, Serialize}; 9use serde::{Deserialize, Serialize};
10use std::fs; 10use std::fs;
11use std::path::PathBuf; 11use std::path::PathBuf;
12use std::sync::{LazyLock, RwLock};
13use std::time::SystemTime;
12 14
13#[derive(Debug, Serialize, Deserialize, Default)] 15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct Config { 16pub struct Config {
15 #[serde(skip)] 17 #[serde(skip)]
16 pub config_path: PathBuf, 18 pub config_path: PathBuf,
@@ -25,24 +27,29 @@ pub struct Config {
25 pub display_format: Option<String>, 27 pub display_format: Option<String>,
26} 28}
27 29
30/// Identity of the config file on disk, used to tell whether a cached copy is
31/// still current. `modified` alone is not enough: several filesystems only
32/// store whole seconds, so a quick edit could keep the same timestamp.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34struct FileStamp {
35 modified: Option<SystemTime>,
36 len: u64,
37}
38
39struct CachedConfig {
40 config: Config,
41 stamp: FileStamp,
42}
43
44static CACHE: LazyLock<RwLock<Option<CachedConfig>>> = LazyLock::new(|| RwLock::new(None));
45
28impl Config { 46impl Config {
29 pub fn load() -> Result<Self, Box<dyn std::error::Error>> { 47 pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
30 let config_dir = if cfg!(debug_assertions) { 48 let config_path = Self::config_path()?;
31 std::env::current_exe()? 49 let config_dir = config_path
32 .parent() 50 .parent()
33 .map(|p| p.to_path_buf()) 51 .map(|p| p.to_path_buf())
34 .unwrap_or_else(|| PathBuf::from(".")) 52 .unwrap_or_else(|| PathBuf::from("."));
35 } else {
36 let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer")
37 .ok_or("Failed to get project directories")?;
38 if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
39 PathBuf::from(xdg).join("rsplayer")
40 } else {
41 proj_dirs.config_dir().to_path_buf()
42 }
43 };
44
45 let config_path = config_dir.join("config.toml");
46 53
47 if !config_path.exists() { 54 if !config_path.exists() {
48 fs::create_dir_all(&config_dir)?; 55 fs::create_dir_all(&config_dir)?;
@@ -76,6 +83,64 @@ impl Config {
76 Ok(config) 83 Ok(config)
77 } 84 }
78 85
86 /// Where the config lives: next to the binary for debug builds (so a
87 /// checkout does not touch the installed config), XDG config otherwise.
88 pub fn config_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
89 if cfg!(debug_assertions) {
90 return Ok(std::env::current_exe()?
91 .parent()
92 .map(|p| p.to_path_buf())
93 .unwrap_or_else(|| PathBuf::from("."))
94 .join("config.toml"));
95 }
96 let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer")
97 .ok_or("Failed to get project directories")?;
98 let config_dir = if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
99 PathBuf::from(xdg).join("rsplayer")
100 } else {
101 proj_dirs.config_dir().to_path_buf()
102 };
103 Ok(config_dir.join("config.toml"))
104 }
105
106 fn file_stamp() -> FileStamp {
107 let stamp = Self::config_path()
108 .ok()
109 .and_then(|path| fs::metadata(path).ok());
110 FileStamp {
111 modified: stamp.as_ref().and_then(|m| m.modified().ok()),
112 len: stamp.as_ref().map(|m| m.len()).unwrap_or(0),
113 }
114 }
115
116 /// The config, parsed once and reused until the file changes on disk.
117 ///
118 /// The daemon reads the config for every command, status line and
119 /// notification, and [`Config::load`] parses the TOML *and* rewrites the
120 /// file. Checking a stat instead keeps that off the hot paths while
121 /// `rsp settings set` still takes effect immediately.
122 pub fn cached() -> Result<Self, Box<dyn std::error::Error>> {
123 let stamp = Self::file_stamp();
124 if let Ok(guard) = CACHE.read()
125 && let Some(cached) = guard.as_ref()
126 && cached.stamp == stamp
127 {
128 return Ok(cached.config.clone());
129 }
130
131 let config = Self::load()?;
132 // `load` normalizes and rewrites the file, so take the stamp afterwards:
133 // reading it before would make our own write look like an outside edit.
134 let stamp = Self::file_stamp();
135 if let Ok(mut guard) = CACHE.write() {
136 *guard = Some(CachedConfig {
137 config: config.clone(),
138 stamp,
139 });
140 }
141 Ok(config)
142 }
143
79 pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { 144 pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
80 let mut output = String::new(); 145 let mut output = String::new();
81 let paths_str = self 146 let paths_str = self
@@ -131,6 +196,14 @@ impl Config {
131 fs::create_dir_all(parent)?; 196 fs::create_dir_all(parent)?;
132 } 197 }
133 fs::write(&self.config_path, output)?; 198 fs::write(&self.config_path, output)?;
199 // Keep the parsed cache in step with what was just written, so this
200 // write does not make the next `cached()` re-read the file.
201 if let Ok(mut guard) = CACHE.write()
202 && let Some(cached) = guard.as_mut()
203 {
204 cached.stamp = Self::file_stamp();
205 cached.config = self.clone();
206 }
134 Ok(()) 207 Ok(())
135 } 208 }
136 209
diff --git a/src/daemon.rs b/src/daemon.rs
index 4f95820..eaae64f 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -190,7 +190,7 @@ pub fn spawn_try_scrobble_current(
190} 190}
191 191
192pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { 192pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> {
193 let config = Config::load()?; 193 let config = Config::cached()?;
194 let socket_path = Config::get_socket_path(); 194 let socket_path = Config::get_socket_path();
195 let pid_path = get_pid_path(&config); 195 let pid_path = get_pid_path(&config);
196 196
@@ -365,7 +365,7 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> {
365 if let Ok(mut s) = scrobble_state.lock() { 365 if let Ok(mut s) = scrobble_state.lock() {
366 s.start(track.clone()); 366 s.start(track.clone());
367 } 367 }
368 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 368 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
369 notification::notify_track(&track, fmt.as_deref()); 369 notification::notify_track(&track, fmt.as_deref());
370 break; 370 break;
371 } 371 }
@@ -448,7 +448,7 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> {
448} 448}
449 449
450pub fn stop_daemon() -> Result<(), Box<dyn std::error::Error>> { 450pub fn stop_daemon() -> Result<(), Box<dyn std::error::Error>> {
451 let config = Config::load()?; 451 let config = Config::cached()?;
452 let pid_path = get_pid_path(&config); 452 let pid_path = get_pid_path(&config);
453 453
454 match check_running_daemon(&pid_path) { 454 match check_running_daemon(&pid_path) {
@@ -708,7 +708,7 @@ enum LoadedLyrics {
708 708
709/// Build the "Now playing" header shown above a track's lyrics. 709/// Build the "Now playing" header shown above a track's lyrics.
710fn lyrics_header(track: &Track) -> String { 710fn lyrics_header(track: &Track) -> String {
711 let name = match Config::load() 711 let name = match Config::cached()
712 .ok() 712 .ok()
713 .and_then(|c| c.display_format.clone()) 713 .and_then(|c| c.display_format.clone())
714 .as_deref() 714 .as_deref()
@@ -1022,7 +1022,7 @@ fn process_command(
1022 "Paused" 1022 "Paused"
1023 }; 1023 };
1024 let track = Track::from_path(track_path); 1024 let track = Track::from_path(track_path);
1025 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 1025 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
1026 let track_str = match fmt.as_deref() { 1026 let track_str = match fmt.as_deref() {
1027 Some(f) => track.format(f), 1027 Some(f) => track.format(f),
1028 None => track.display_string(0, 1), 1028 None => track.display_string(0, 1),
@@ -1094,7 +1094,7 @@ fn process_command(
1094 "Shutting down\n".to_string() 1094 "Shutting down\n".to_string()
1095 } 1095 }
1096 "reload" | "validate" => { 1096 "reload" | "validate" => {
1097 if let Ok(config) = Config::load() { 1097 if let Ok(config) = Config::cached() {
1098 let temp_scrobbler = Scrobbler::new( 1098 let temp_scrobbler = Scrobbler::new(
1099 config.lastfm_api_key.clone(), 1099 config.lastfm_api_key.clone(),
1100 config.lastfm_api_secret.clone(), 1100 config.lastfm_api_secret.clone(),
@@ -1137,7 +1137,7 @@ fn process_command(
1137 if let Ok(mut s) = scrobble_state.lock() { 1137 if let Ok(mut s) = scrobble_state.lock() {
1138 s.start(track.clone()); 1138 s.start(track.clone());
1139 } 1139 }
1140 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 1140 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
1141 notification::notify_track(&track, fmt.as_deref()); 1141 notification::notify_track(&track, fmt.as_deref());
1142 "Playing next track\n".to_string() 1142 "Playing next track\n".to_string()
1143 } else { 1143 } else {
@@ -1160,7 +1160,7 @@ fn process_command(
1160 if let Ok(mut s) = scrobble_state.lock() { 1160 if let Ok(mut s) = scrobble_state.lock() {
1161 s.start(track.clone()); 1161 s.start(track.clone());
1162 } 1162 }
1163 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 1163 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
1164 notification::notify_track(&track, fmt.as_deref()); 1164 notification::notify_track(&track, fmt.as_deref());
1165 "Playing previous track\n".to_string() 1165 "Playing previous track\n".to_string()
1166 } else { 1166 } else {
@@ -1202,7 +1202,7 @@ fn process_command(
1202 "Usage: shuffle [on|off|album|albums]\n".to_string() 1202 "Usage: shuffle [on|off|album|albums]\n".to_string()
1203 }; 1203 };
1204 // Save to config 1204 // Save to config
1205 if let Ok(mut config) = Config::load() { 1205 if let Ok(mut config) = Config::cached() {
1206 if mode_str == "off" || mode_str == "none" { 1206 if mode_str == "off" || mode_str == "none" {
1207 config.shuffle = None; 1207 config.shuffle = None;
1208 } else { 1208 } else {
@@ -1242,7 +1242,7 @@ fn process_command(
1242 let (album_tracks, display_name): (Vec<PathBuf>, String) = if path.is_file() { 1242 let (album_tracks, display_name): (Vec<PathBuf>, String) = if path.is_file() {
1243 let canon = std::fs::canonicalize(&path).unwrap_or_else(|_| path.clone()); 1243 let canon = std::fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
1244 let (artist, album, _, _, _) = crate::database::extract_metadata(&canon); 1244 let (artist, album, _, _, _) = crate::database::extract_metadata(&canon);
1245 let full: Vec<PathBuf> = match Config::load() { 1245 let full: Vec<PathBuf> = match Config::cached() {
1246 Ok(config) => match crate::database::Database::open(&config) { 1246 Ok(config) => match crate::database::Database::open(&config) {
1247 Ok(db) => db 1247 Ok(db) => db
1248 .find_tracks_by_album(&artist, &album) 1248 .find_tracks_by_album(&artist, &album)
@@ -1267,7 +1267,7 @@ fn process_command(
1267 .to_string(); 1267 .to_string();
1268 (rotated, name) 1268 (rotated, name)
1269 } else { 1269 } else {
1270 match Config::load() { 1270 match Config::cached() {
1271 Ok(config) => match crate::database::Database::open(&config) { 1271 Ok(config) => match crate::database::Database::open(&config) {
1272 Ok(db) => { 1272 Ok(db) => {
1273 let tracks = match db.resolve_path(query_str) { 1273 let tracks = match db.resolve_path(query_str) {
@@ -1352,7 +1352,7 @@ fn process_command(
1352 if let Ok(mut s) = scrobble_state.lock() { 1352 if let Ok(mut s) = scrobble_state.lock() {
1353 s.start(track.clone()); 1353 s.start(track.clone());
1354 } 1354 }
1355 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 1355 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
1356 notification::notify_track(track, fmt.as_deref()); 1356 notification::notify_track(track, fmt.as_deref());
1357 } 1357 }
1358 format!( 1358 format!(
@@ -1464,7 +1464,7 @@ fn process_command(
1464 s.start(track.clone()); 1464 s.start(track.clone());
1465 } 1465 }
1466 let fmt = 1466 let fmt =
1467 Config::load().ok().and_then(|c| c.display_format.clone()); 1467 Config::cached().ok().and_then(|c| c.display_format.clone());
1468 notification::notify_track(track, fmt.as_deref()); 1468 notification::notify_track(track, fmt.as_deref());
1469 } 1469 }
1470 format!("Playing now: {}\n", path_str) 1470 format!("Playing now: {}\n", path_str)
@@ -1486,7 +1486,7 @@ fn process_command(
1486 s.start(track.clone()); 1486 s.start(track.clone());
1487 } 1487 }
1488 let fmt = 1488 let fmt =
1489 Config::load().ok().and_then(|c| c.display_format.clone()); 1489 Config::cached().ok().and_then(|c| c.display_format.clone());
1490 notification::notify_track(track, fmt.as_deref()); 1490 notification::notify_track(track, fmt.as_deref());
1491 } 1491 }
1492 format!("Queued: {}\n", path_str) 1492 format!("Queued: {}\n", path_str)
@@ -1514,8 +1514,9 @@ fn process_command(
1514 if let Ok(mut s) = scrobble_state.lock() { 1514 if let Ok(mut s) = scrobble_state.lock() {
1515 s.start(track.clone()); 1515 s.start(track.clone());
1516 } 1516 }
1517 let fmt = 1517 let fmt = Config::cached()
1518 Config::load().ok().and_then(|c| c.display_format.clone()); 1518 .ok()
1519 .and_then(|c| c.display_format.clone());
1519 notification::notify_track(track, fmt.as_deref()); 1520 notification::notify_track(track, fmt.as_deref());
1520 } 1521 }
1521 format!("Playing now: {}\n", path_str) 1522 format!("Playing now: {}\n", path_str)
@@ -1535,8 +1536,9 @@ fn process_command(
1535 if let Ok(mut s) = scrobble_state.lock() { 1536 if let Ok(mut s) = scrobble_state.lock() {
1536 s.start(track.clone()); 1537 s.start(track.clone());
1537 } 1538 }
1538 let fmt = 1539 let fmt = Config::cached()
1539 Config::load().ok().and_then(|c| c.display_format.clone()); 1540 .ok()
1541 .and_then(|c| c.display_format.clone());
1540 notification::notify_track(&track, fmt.as_deref()); 1542 notification::notify_track(&track, fmt.as_deref());
1541 format!("Playing: {}\n", path_str) 1543 format!("Playing: {}\n", path_str)
1542 } else { 1544 } else {
@@ -1568,7 +1570,7 @@ fn process_command(
1568 if let Ok(mut s) = scrobble_state.lock() { 1570 if let Ok(mut s) = scrobble_state.lock() {
1569 s.start(track.clone()); 1571 s.start(track.clone());
1570 } 1572 }
1571 let fmt = Config::load() 1573 let fmt = Config::cached()
1572 .ok() 1574 .ok()
1573 .and_then(|c| c.display_format.clone()); 1575 .and_then(|c| c.display_format.clone());
1574 notification::notify_track(track, fmt.as_deref()); 1576 notification::notify_track(track, fmt.as_deref());
@@ -1602,7 +1604,7 @@ fn process_command(
1602 if let Ok(mut s) = scrobble_state.lock() { 1604 if let Ok(mut s) = scrobble_state.lock() {
1603 s.start(track.clone()); 1605 s.start(track.clone());
1604 } 1606 }
1605 let fmt = Config::load() 1607 let fmt = Config::cached()
1606 .ok() 1608 .ok()
1607 .and_then(|c| c.display_format.clone()); 1609 .and_then(|c| c.display_format.clone());
1608 notification::notify_track(track, fmt.as_deref()); 1610 notification::notify_track(track, fmt.as_deref());
@@ -1622,7 +1624,7 @@ fn process_command(
1622 _ => format!("Error: No audio files found in '{}'\n", path_str), 1624 _ => format!("Error: No audio files found in '{}'\n", path_str),
1623 } 1625 }
1624 } else { 1626 } else {
1625 match Config::load() { 1627 match Config::cached() {
1626 Ok(config) => match crate::database::Database::open(&config) { 1628 Ok(config) => match crate::database::Database::open(&config) {
1627 Ok(db) => { 1629 Ok(db) => {
1628 // Collect tracks: try hierarchical path first, fallback to search 1630 // Collect tracks: try hierarchical path first, fallback to search
@@ -1666,7 +1668,7 @@ fn process_command(
1666 if let Ok(mut s) = scrobble_state.lock() { 1668 if let Ok(mut s) = scrobble_state.lock() {
1667 s.start(track.clone()); 1669 s.start(track.clone());
1668 } 1670 }
1669 let fmt = Config::load() 1671 let fmt = Config::cached()
1670 .ok() 1672 .ok()
1671 .and_then(|c| c.display_format.clone()); 1673 .and_then(|c| c.display_format.clone());
1672 notification::notify_track(track, fmt.as_deref()); 1674 notification::notify_track(track, fmt.as_deref());
@@ -1710,7 +1712,7 @@ fn process_command(
1710 if let Ok(mut s) = scrobble_state.lock() { 1712 if let Ok(mut s) = scrobble_state.lock() {
1711 s.start(track.clone()); 1713 s.start(track.clone());
1712 } 1714 }
1713 let fmt = Config::load() 1715 let fmt = Config::cached()
1714 .ok() 1716 .ok()
1715 .and_then(|c| c.display_format.clone()); 1717 .and_then(|c| c.display_format.clone());
1716 notification::notify_track( 1718 notification::notify_track(
@@ -1772,7 +1774,7 @@ fn process_command(
1772 let current = audio.get_volume() as i8; 1774 let current = audio.get_volume() as i8;
1773 let new_level = (current + sign * delta).clamp(0, 100) as u8; 1775 let new_level = (current + sign * delta).clamp(0, 100) as u8;
1774 audio.set_volume(new_level as f32 / 100.0); 1776 audio.set_volume(new_level as f32 / 100.0);
1775 if let Ok(mut config) = Config::load() { 1777 if let Ok(mut config) = Config::cached() {
1776 config.volume = Some(new_level); 1778 config.volume = Some(new_level);
1777 let _ = config.save(); 1779 let _ = config.save();
1778 } 1780 }
@@ -1781,7 +1783,7 @@ fn process_command(
1781 // Try absolute value 1783 // Try absolute value
1782 if let Ok(level) = arg.parse::<u8>() { 1784 if let Ok(level) = arg.parse::<u8>() {
1783 audio.set_volume(level as f32 / 100.0); 1785 audio.set_volume(level as f32 / 100.0);
1784 if let Ok(mut config) = Config::load() { 1786 if let Ok(mut config) = Config::cached() {
1785 config.volume = Some(level); 1787 config.volume = Some(level);
1786 let _ = config.save(); 1788 let _ = config.save();
1787 } 1789 }
@@ -1811,7 +1813,7 @@ fn process_command(
1811 q.shuffled_indices().to_vec() 1813 q.shuffled_indices().to_vec()
1812 }; 1814 };
1813 let is_playing = audio.is_playing(); 1815 let is_playing = audio.is_playing();
1814 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 1816 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
1815 for (display_idx, &track_idx) in indices.iter().enumerate() { 1817 for (display_idx, &track_idx) in indices.iter().enumerate() {
1816 if let Some(track) = q.track_at(track_idx) { 1818 if let Some(track) = q.track_at(track_idx) {
1817 let track_str = match fmt.as_deref() { 1819 let track_str = match fmt.as_deref() {
diff --git a/src/mpris.rs b/src/mpris.rs
index de19113..9daf516 100644
--- a/src/mpris.rs
+++ b/src/mpris.rs
@@ -204,7 +204,7 @@ impl MprisPlayer {
204 if let Ok(mut s) = self.scrobble_state.lock() { 204 if let Ok(mut s) = self.scrobble_state.lock() {
205 s.start(track.clone()); 205 s.start(track.clone());
206 } 206 }
207 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 207 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
208 notification::notify_track(&track, fmt.as_deref()); 208 notification::notify_track(&track, fmt.as_deref());
209 } else { 209 } else {
210 self.audio.stop(); 210 self.audio.stop();
@@ -226,7 +226,7 @@ impl MprisPlayer {
226 if let Ok(mut s) = self.scrobble_state.lock() { 226 if let Ok(mut s) = self.scrobble_state.lock() {
227 s.start(track.clone()); 227 s.start(track.clone());
228 } 228 }
229 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 229 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
230 notification::notify_track(&track, fmt.as_deref()); 230 notification::notify_track(&track, fmt.as_deref());
231 } 231 }
232 } 232 }
@@ -271,7 +271,7 @@ impl MprisPlayer {
271 if let Ok(mut s) = self.scrobble_state.lock() { 271 if let Ok(mut s) = self.scrobble_state.lock() {
272 s.start(track.clone()); 272 s.start(track.clone());
273 } 273 }
274 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 274 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
275 notification::notify_track(track, fmt.as_deref()); 275 notification::notify_track(track, fmt.as_deref());
276 } 276 }
277 } 277 }
@@ -319,7 +319,7 @@ impl MprisPlayer {
319 if let Ok(mut s) = self.scrobble_state.lock() { 319 if let Ok(mut s) = self.scrobble_state.lock() {
320 s.start(t.clone()); 320 s.start(t.clone());
321 } 321 }
322 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 322 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
323 notification::notify_track(t, fmt.as_deref()); 323 notification::notify_track(t, fmt.as_deref());
324 } 324 }
325 } else if path.is_file() { 325 } else if path.is_file() {
@@ -331,7 +331,7 @@ impl MprisPlayer {
331 if let Ok(mut s) = self.scrobble_state.lock() { 331 if let Ok(mut s) = self.scrobble_state.lock() {
332 s.start(track.clone()); 332 s.start(track.clone());
333 } 333 }
334 let fmt = Config::load().ok().and_then(|c| c.display_format.clone()); 334 let fmt = Config::cached().ok().and_then(|c| c.display_format.clone());
335 notification::notify_track(&track, fmt.as_deref()); 335 notification::notify_track(&track, fmt.as_deref());
336 } 336 }
337 } 337 }
@@ -417,7 +417,7 @@ impl MprisPlayer {
417 fn set_volume(&self, value: f64) { 417 fn set_volume(&self, value: f64) {
418 let level = (value * 100.0).clamp(0.0, 100.0) as u8; 418 let level = (value * 100.0).clamp(0.0, 100.0) as u8;
419 self.audio.set_volume(level as f32 / 100.0); 419 self.audio.set_volume(level as f32 / 100.0);
420 if let Ok(mut config) = Config::load() { 420 if let Ok(mut config) = Config::cached() {
421 config.volume = Some(level); 421 config.volume = Some(level);
422 let _ = config.save(); 422 let _ = config.save();
423 } 423 }