Skip to main content

selene_daemon/
config.rs

1use std::{
2    path::PathBuf,
3    sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
4};
5
6use lunar_lib::{
7    config::{Config, ConfigError},
8    debug,
9};
10use selene_core::runtime_dir;
11use serde::{Deserialize, Serialize};
12
13pub static DAEMON_CONFIG: OnceLock<RwLock<DaemonConfig>> = OnceLock::new();
14pub fn initialize_daemon_config() -> Result<(), ConfigError> {
15    if DAEMON_CONFIG.get().is_some() {
16        return Ok(());
17    }
18
19    let config = DaemonConfig::load()?;
20    let _ = DAEMON_CONFIG.set(RwLock::new(config));
21
22    Ok(())
23}
24
25/// Returns a read guard to the current [`DaemonConfig`]
26///
27/// # Panics
28///
29/// Panics if the config hasn't been intialized or the internal [`RwLock`] is poisoned
30pub fn daemon_config() -> RwLockReadGuard<'static, DaemonConfig> {
31    DAEMON_CONFIG
32        .get()
33        .expect("Config wasn't intialized")
34        .read()
35        .unwrap()
36}
37
38/// Returns a write guard to the current [`DaemonConfig`]
39///
40/// # Panics
41///
42/// Panics if the config hasn't been intialized or the internal [`RwLock`] is poisoned
43pub fn daemon_config_mut() -> RwLockWriteGuard<'static, DaemonConfig> {
44    DAEMON_CONFIG
45        .get()
46        .expect("Config wasn't initialized")
47        .write()
48        .unwrap()
49}
50
51#[derive(Debug, Serialize, Deserialize, Default)]
52pub struct DaemonConfig {
53    pub main: MainSettings,
54    pub playback: PlaybackSettings,
55}
56
57impl Config for DaemonConfig {
58    const CONFIG_FILE_NAME: &'static str = "daemon";
59
60    fn config_dir() -> PathBuf {
61        selene_core::config_dir().to_owned()
62    }
63}
64
65impl DaemonConfig {
66    /// Reloads the current config
67    pub fn reload() -> Result<(), ConfigError> {
68        debug!("Reloading daemon config");
69        initialize_daemon_config()?;
70
71        let mut current_config = daemon_config_mut();
72        let loaded_config = Self::load()?;
73
74        *current_config = loaded_config;
75
76        Ok(())
77    }
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81pub struct MainSettings {
82    /// If scrobbling is enabled
83    pub scrobbling: bool,
84
85    /// Socket file path
86    pub socket_path: PathBuf,
87}
88
89impl Default for MainSettings {
90    fn default() -> Self {
91        Self {
92            scrobbling: true,
93            socket_path: runtime_dir().join("selene.sock"),
94        }
95    }
96}
97
98#[derive(Debug, Serialize, Deserialize)]
99pub struct PlaybackSettings {
100    /// Size of the audio buffer in kibibytes (1 = 1024 bytes)
101    pub audio_buffer_size: usize,
102
103    /// How far into a song you must be for 'previous' to restart the current song
104    pub previous_restart_thresh: Option<f64>,
105}
106
107impl Default for PlaybackSettings {
108    fn default() -> Self {
109        Self {
110            audio_buffer_size: 16,
111            previous_restart_thresh: Some(5.0),
112        }
113    }
114}
115
116#[derive(Debug, Serialize, Deserialize)]
117pub struct NotificationSettings {
118    pub enabled: bool,
119    pub body_format: String,
120    pub header_format: String,
121}
122
123impl Default for NotificationSettings {
124    fn default() -> Self {
125        Self {
126            enabled: true,
127            header_format: "Now playing: {$title?UNKNOWN TITLE}".to_owned(),
128            body_format: "by {$main_track_artist?UNKNOWN_ARTIST} {$feat_track_artists< (feat. >)}"
129                .to_owned(),
130        }
131    }
132}