selene-daemon 0.5.1

Official music player daemon for Selene
Documentation
use std::{
    path::{Path, PathBuf},
    sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
};

use lunar_lib::{
    config::{Config, ConfigError},
    debug,
};
use selene_core::runtime_dir;
use serde::{Deserialize, Serialize};

pub static DAEMON_CONFIG: OnceLock<RwLock<DaemonConfig>> = OnceLock::new();
pub fn initialize_daemon_config() -> Result<(), ConfigError> {
    if DAEMON_CONFIG.get().is_some() {
        return Ok(());
    }

    let config = DaemonConfig::load()?;
    let _ = DAEMON_CONFIG.set(RwLock::new(config));

    Ok(())
}

/// Returns a read guard to the current [`DaemonConfig`]
///
/// # Panics
///
/// Panics if the config hasn't been intialized or the internal [`RwLock`] is poisoned
pub fn daemon_config() -> RwLockReadGuard<'static, DaemonConfig> {
    DAEMON_CONFIG
        .get()
        .expect("Config wasn't intialized")
        .read()
        .unwrap()
}

/// Returns a write guard to the current [`DaemonConfig`]
///
/// # Panics
///
/// Panics if the config hasn't been intialized or the internal [`RwLock`] is poisoned
pub fn daemon_config_mut() -> RwLockWriteGuard<'static, DaemonConfig> {
    DAEMON_CONFIG
        .get()
        .expect("Config wasn't initialized")
        .write()
        .unwrap()
}

#[derive(Debug, Serialize, Deserialize, Default)]
pub struct DaemonConfig {
    pub main: MainSettings,
    pub playback: PlaybackSettings,
}

impl Config for DaemonConfig {
    const CONFIG_FILE_NAME: &'static str = "daemon";

    fn config_dir() -> Option<&'static Path> {
        Some(selene_core::config_dir())
    }
}

impl DaemonConfig {
    /// Reloads the current config
    pub fn reload() -> Result<(), ConfigError> {
        debug!("Reloading daemon config");
        initialize_daemon_config()?;

        let mut current_config = daemon_config_mut();
        let loaded_config = Self::load()?;

        *current_config = loaded_config;

        Ok(())
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MainSettings {
    pub socket_path: PathBuf,
}

impl Default for MainSettings {
    fn default() -> Self {
        Self {
            socket_path: runtime_dir().join("selene.sock"),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PlaybackSettings {
    /// Size of the audio buffer in kibibytes (1 = 1024 bytes)
    pub audio_buffer_size: usize,

    /// How far into a song you must be for 'previous' to restart the current song
    pub restart_on_previous_thresh: Option<f64>,
}

impl Default for PlaybackSettings {
    fn default() -> Self {
        Self {
            audio_buffer_size: 16,
            restart_on_previous_thresh: Some(5.0),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NotificationSettings {
    pub enabled: bool,
    pub body_format: String,
    pub header_format: String,
}

impl Default for NotificationSettings {
    fn default() -> Self {
        Self {
            enabled: true,
            header_format: "Now playing: {$title?UNKNOWN TITLE}".to_owned(),
            body_format: "by {$main_track_artist?UNKNOWN_ARTIST} {$feat_track_artists< (feat. >)}"
                .to_owned(),
        }
    }
}