aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-05-05 21:12:32 +0200
committersalmonik <raisersoft@proton.me>2026-05-05 21:12:32 +0200
commitbd29978b2a0d0d6def486fcdaa63bc8934d68045 (patch)
tree5df0efb17d350ae7f095eb9c0940b1bbf568de96 /src/config.rs
downloadrsplayer-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.rs180
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 @@
1use directories::ProjectDirs;
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::PathBuf;
5
6#[derive(Debug, Serialize, Deserialize, Default)]
7pub 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
21impl 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}