1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![feature(doc_cfg)]
4
5mod daemon_thread;
6mod music_lib;
7pub mod playback;
8#[cfg(test)]
9mod tests;
10
11use std::{
12 env::{home_dir, temp_dir},
13 fs::remove_file,
14 path::PathBuf,
15 sync::{
16 Arc, LazyLock, RwLock,
17 mpsc::{self, channel},
18 },
19 thread,
20};
21
22use interprocess::local_socket::{Listener, ListenerOptions, Stream, traits::ListenerExt};
23use log::{debug, error, info, trace, warn};
24
25use chilen_ipc::{Command, DEFAULT_SOCKET_NAME, Event, Response, send_command};
26use serde::{Deserialize, Serialize};
27
28use crate::music_lib::{CACHE_DIR, covers::LoadMode};
29
30pub type SocketType = chilen_ipc::SocketType;
32
33static EVENT_SENDER: LazyLock<Arc<RwLock<Option<mpsc::Sender<Event>>>>> =
34 LazyLock::new(|| Arc::new(RwLock::new(None)));
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum Error {
38 SocketError,
40 AddrInUse,
42 EventChannelInitialized,
46 DaemonNotRunning,
48 NoLibrary,
49 LibraryNotAccessible,
50 CacheDirError(String),
51 DataDirError(String),
52 ConfigNotInitialized,
53}
54
55impl std::fmt::Display for Error {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 Self::SocketError => write!(f, "Socket creation/connection failed"),
59 Self::AddrInUse => write!(f, "The socket address is already in use"),
60 Self::EventChannelInitialized => {
61 write!(f, "The event channel for the daemon is already initialized")
62 }
63 Self::DaemonNotRunning => write!(f, "The daemon doesn't seem to be running"),
64 Self::NoLibrary => {
65 write!(f, "The provided music library directory does not exist")
66 }
67 Self::LibraryNotAccessible => write!(
68 f,
69 "Could not access the music library due to a permission issue"
70 ),
71 Self::CacheDirError(e) => write!(f, "Could not initialize the cache directory: {e}"),
72 Self::DataDirError(e) => write!(f, "Could not initialize the data directory: {e}"),
73 Self::ConfigNotInitialized => write!(f, "The daemon configuration is not set"),
74 }
75 }
76}
77
78#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub enum AddrClaimMode {
83 DoNotClaim,
85 #[default]
88 ClaimIfUnresponsive,
89 ForceClaim,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
95pub enum ConfigError {
96 InvalidBusNameSuffix,
98 HomeError,
100}
101
102impl std::fmt::Display for ConfigError {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 match self {
105 Self::InvalidBusNameSuffix => write!(f, "The bus name suffix provided was invalid"),
106 Self::HomeError => write!(f, "Could not get the home directory path"),
107 }
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
115pub struct Config {
116 pub cache_dir: PathBuf,
118 pub data_dir: PathBuf,
120 pub music_dir: PathBuf,
122 pub socket_name: String,
127 pub addr_claim_mode: AddrClaimMode,
129 pub socket_type: SocketType,
131 pub playback_config: playback::Config,
133}
134
135impl Config {
136 pub fn try_default() -> Result<Self, ConfigError> {
147 Self::try_from_name("my-player", DEFAULT_SOCKET_NAME)
148 }
149
150 pub fn try_from_name(name: &str, socket_name: &str) -> Result<Self, ConfigError> {
163 let home_dir = match home_dir() {
164 Some(home) => home,
165 None => {
166 return Err(ConfigError::HomeError);
167 }
168 };
169
170 let mut cache_dir = home_dir.clone();
171 cache_dir.push(".cache");
172 cache_dir.push(name);
173
174 let mut data_dir = home_dir.clone();
175 data_dir.push(".local/share");
176 data_dir.push(name);
177
178 let mut music_dir = home_dir.clone();
179 music_dir.push("Music");
180
181 #[cfg(feature = "mpris")]
182 let bus_name_suffix = String::from("com.dev.") + name;
183 #[cfg(feature = "mpris")]
184 if !bus_name_suffix.is_ascii() {
185 return Err(ConfigError::InvalidBusNameSuffix);
186 }
187
188 Ok(Config {
189 cache_dir,
190 music_dir,
191 data_dir,
192 socket_name: socket_name.to_string(),
193 addr_claim_mode: AddrClaimMode::default(),
194 socket_type: SocketType::default(),
195 playback_config: playback::Config {
196 #[cfg(feature = "mpris")]
197 identity: name.to_string(),
198 #[cfg(feature = "mpris")]
199 bus_name_suffix,
200 allow_rate_modification: false,
201 #[cfg(feature = "mpris")]
202 can_raise: false,
203 },
204 })
205 }
206}
207
208fn handle_error(conn: std::io::Result<Stream>) -> Option<Stream> {
209 match conn {
210 Ok(c) => Some(c),
211 Err(e) => {
212 warn!("Incoming connection failed: {e}");
213 None
214 }
215 }
216}
217
218fn get_fs_socket_path(socket_name: &str) -> PathBuf {
220 let mut temp_dir = temp_dir();
221 temp_dir.push(socket_name);
222 temp_dir
223}
224
225fn get_listener(
236 socket_name: &str,
237 socket_type: &SocketType,
238 claim_mode: &AddrClaimMode,
239) -> Result<Listener, Error> {
240 let socket = match chilen_ipc::get_socket(socket_name, socket_type) {
241 Ok(sock) => sock,
242 Err(e) => {
243 error!("Could not obtain the socket: {e}");
244 return Err(Error::SocketError);
245 }
246 };
247
248 let opts = ListenerOptions::new().name(socket.clone());
249 if socket.is_namespaced() {
250 trace!(
251 "Creating a listener on \"{}\" (namespaced socket)",
252 socket_name
253 );
254 } else {
255 trace!(
256 "Creating a listener on \"{}\" (filesystem socket)",
257 socket_name
258 );
259 }
260
261 match opts.create_sync() {
262 Ok(listener) => Ok(listener),
263 Err(e) => {
264 if e.kind() == std::io::ErrorKind::AddrInUse
265 && socket.is_path()
266 && !socket.is_namespaced()
267 {
268 warn!("The socket address is already in use");
269
270 match claim_mode {
271 AddrClaimMode::DoNotClaim => {
272 info!(
273 "The daemon is configured not to reclaim the socket address, aborting"
274 );
275 Err(Error::AddrInUse)
276 }
277 AddrClaimMode::ClaimIfUnresponsive => {
278 info!("Attempting to claim the socket address");
279 match send_command(Command::Ping, socket_name, socket_type) {
280 Ok(response) => {
281 if response == Response::Pong {
282 error!(
283 "The other daemon responded to the pong command, aborting"
284 );
285 return Err(Error::AddrInUse);
286 } else {
287 error!(
288 "Got an unexpected response from the daemon: {response:?}"
289 );
290 return Err(Error::AddrInUse);
291 }
292 }
293 Err(e) => {
294 if e == chilen_ipc::Error::ConnectionError {
295 info!(
296 "The other daemon is either dead or unresponsive, claiming the address"
297 );
298 } else {
299 error!(
300 "Got an unexpected error while sending a ping command: {e}"
301 );
302 return Err(Error::AddrInUse);
303 }
304 }
305 }
306
307 if let Err(e) = remove_file(get_fs_socket_path(socket_name)) {
308 error!("Could not remove the old socket: {e}");
309 return Err(Error::SocketError);
310 }
311 let opts = ListenerOptions::new().name(socket.clone());
312 match opts.create_sync() {
313 Ok(listener) => {
314 info!("Successfully claimed the address");
315 Ok(listener)
316 }
317 Err(e) => {
318 error!("Could not create a listener: {e}");
319 Err(Error::SocketError)
320 }
321 }
322 }
323 AddrClaimMode::ForceClaim => {
324 info!("Force claiming the socket address");
325 if let Err(e) = remove_file(get_fs_socket_path(socket_name)) {
326 error!("Could not remove the old socket: {e}");
327 return Err(Error::SocketError);
328 }
329 let opts = ListenerOptions::new().name(socket.clone());
330 match opts.create_sync() {
331 Ok(listener) => {
332 info!("Successfully claimed the address");
333 Ok(listener)
334 }
335 Err(e) => {
336 error!(
337 "Could not create a listener despite claiming the socket: {e}"
338 );
339 Err(Error::SocketError)
340 }
341 }
342 }
343 }
344 } else {
345 error!("Failed creating a listener for the daemon socket: {e}");
346 Err(Error::SocketError)
347 }
348 }
349 }
350}
351
352pub(crate) fn send_event(event: Event) -> Result<(), String> {
356 match EVENT_SENDER.read().as_mut() {
357 Ok(guard) => match guard.clone() {
358 Some(guard) => match guard.send(event) {
359 Ok(_) => Ok(()),
360 Err(e) => {
361 error!("Could not send the event to the daemon: {e}");
362 Err(e.to_string())
363 }
364 },
365 None => Err(String::from(
366 "Could not obtain the event channel, this is expected during testing",
367 )),
368 },
369 Err(e) => Err(e.to_string()),
370 }
371}
372
373#[cfg(any(feature = "mpris", doc))]
377pub fn set_can_raise(can_raise: bool) -> Result<(), Error> {
378 use crate::playback::CONFIG;
379
380 let mut conf_guard = CONFIG.write().unwrap();
381 let conf = match conf_guard.as_mut() {
382 Some(conf) => conf,
383 None => return Err(Error::ConfigNotInitialized),
384 };
385 conf.can_raise = can_raise;
386 Ok(())
387}
388
389pub fn stop() -> Result<(), Error> {
396 if send_event(Event::Shutdown).is_err() {
397 error!("The daemon doesn't seem to be running");
398 Err(Error::DaemonNotRunning)
399 } else {
400 Ok(())
401 }
402}
403
404pub fn start(config: Config) -> Result<(), Error> {
425 debug!("Starting daemon on \"{}\"", config.socket_name);
426
427 if let Err(e) = music_lib::set_dirs(config.clone()) {
428 error!("Could not set the paths: {e}");
429 return Err(e);
430 }
431
432 if config.socket_name == chilen_ipc::DEFAULT_SOCKET_NAME {
433 warn!(
434 "Using the default IPC socket name. Please use a unique name outside of just testing"
435 );
436 }
437
438 let listener = get_listener(
439 &config.socket_name,
440 &config.socket_type,
441 &config.addr_claim_mode,
442 )?;
443
444 thread::spawn(move || {
445 let _ = music_lib::state::load(LoadMode::Load);
446 playback::init(config.playback_config);
447 });
448
449 let senders = Arc::new(RwLock::new(Vec::new()));
450
451 thread::spawn({
452 let senders_clone = senders.clone();
453 info!("Listening for incoming connections");
454 move || {
455 for (index, conn) in listener.incoming().filter_map(handle_error).enumerate() {
456 let (ttx, trx) = channel();
457 senders_clone.clone().write().unwrap().push(ttx);
458 daemon_thread::spawn(conn, trx, u64::try_from(index).unwrap());
462 }
463 }
464 });
465
466 let mut guard = EVENT_SENDER.write().unwrap();
467 if guard.as_ref().is_some() {
469 error!("Could not start the daemon because the event channel is already initialized");
470 info!("(Did you try to start a second daemon in the same context?)");
471 return Err(Error::EventChannelInitialized);
472 }
473 let (event_sender, event_receiver) = mpsc::channel();
474 *guard = Some(event_sender);
475 drop(guard);
476
477 loop {
478 let event = event_receiver.recv().unwrap();
481 let mut guard = senders.write().unwrap();
482 let mut dead = Vec::new();
483 for (i, sender) in guard.iter().enumerate() {
484 if sender.send(event.clone()).is_err() {
485 debug!("Removing dead connection at {}", i);
486 dead.push(i);
487 }
488 }
489 for &ded in dead.iter().rev() {
490 guard.remove(ded);
491 }
492
493 match event {
494 Event::Shutdown => {
495 trace!("Received shutdown event");
496 let mut guard = EVENT_SENDER.write().unwrap();
497 *guard = None;
498 info!("Stopped.");
499 std::process::exit(0);
500 }
501 Event::ConnectionClosed => {
502 trace!("Connection with a client closed")
503 }
504 _ => {}
505 }
506 }
507}