Skip to main content

lyrics_next/
config.rs

1use anyhow::{Context, Result};
2use serde::Deserialize;
3use std::{
4    fs,
5    path::PathBuf,
6    sync::{OnceLock, RwLock},
7};
8use tracing::debug;
9
10use crate::{error::LyricsError, utils::ensure_parent_dir};
11
12/// config
13static CONFIG: OnceLock<RwLock<Config>> = OnceLock::new();
14
15pub fn get_config() -> &'static RwLock<Config> {
16    CONFIG.get_or_init(|| RwLock::new(Config::default()))
17}
18
19#[derive(Debug, Deserialize, Default)]
20#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
21pub struct Config {
22    pub player_filter: PlayerFilter,
23    pub ui: Ui,
24    pub sources: Sources,
25}
26
27#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
28#[serde(rename_all = "lowercase")]
29pub enum PlayerProtocol {
30    #[default]
31    Auto,
32    Mpris,
33    Mpd,
34}
35
36#[derive(Debug, Deserialize)]
37pub struct PlayerFilter {
38    #[serde(default)]
39    pub protocol: PlayerProtocol,
40    #[serde(default = "Vec::new")]
41    pub only: Vec<String>,
42    #[serde(default = "default_player_except")]
43    pub except: Vec<String>,
44    #[serde(default = "default_mpd_host")]
45    pub mpd_host: String,
46    #[serde(default = "default_mpd_port")]
47    pub mpd_port: u16,
48}
49
50fn default_player_except() -> Vec<String> {
51    vec![
52        "browser".to_string(),
53        "video".to_string(),
54        "screen-cast".to_string(),
55        "chromium".to_string(),
56        "firefox".to_string(),
57    ]
58}
59
60fn default_mpd_host() -> String {
61    "127.0.0.1".to_string()
62}
63
64fn default_mpd_port() -> u16 {
65    6600
66}
67
68impl Default for PlayerFilter {
69    fn default() -> Self {
70        PlayerFilter {
71            protocol: PlayerProtocol::Mpris,
72            only: vec![],
73            except: default_player_except(),
74            mpd_host: default_mpd_host(),
75            mpd_port: default_mpd_port(),
76        }
77    }
78}
79
80#[derive(Debug, Deserialize)]
81pub struct Ui {
82    #[serde(default = "default_true")]
83    pub title: bool,
84    #[serde(default)]
85    pub time: bool,
86    #[serde(default = "default_true")]
87    pub progress_bar: bool,
88    #[serde(default)]
89    pub text_center: bool,
90}
91
92impl Default for Ui {
93    fn default() -> Self {
94        Self {
95            title: true,
96            time: false,
97            progress_bar: true,
98            text_center: false,
99        }
100    }
101}
102
103#[derive(Debug, Deserialize)]
104pub struct Sources {
105    #[serde(default = "default_true")]
106    pub netease: bool,
107    #[serde(default = "default_true")]
108    pub qq: bool,
109    #[serde(default = "default_true")]
110    pub kugou: bool,
111}
112
113impl Default for Sources {
114    fn default() -> Self {
115        Sources {
116            netease: true,
117            qq: true,
118            kugou: true,
119        }
120    }
121}
122
123fn default_true() -> bool {
124    true
125}
126
127impl Config {
128    pub fn load_or_default(path: Option<PathBuf>) -> Result<(), LyricsError> {
129        let config_path = match path {
130            Some(p) => p,
131            None => config_path(),
132        };
133
134        let config: Config = if config_path.exists() {
135            let config_content = fs::read_to_string(&config_path).with_context(|| {
136                format!("Failed to read config file: {}", config_path.display())
137            })?;
138
139            toml::from_str(&config_content).with_context(|| {
140                format!("Failed to parse config file: {}", config_path.display())
141            })?
142        } else {
143            Self::default()
144        };
145
146        debug!("config: {:?}", config);
147
148        let mut c = get_config().write().expect("Get config failed.");
149        *c = config;
150
151        Ok(())
152    }
153}
154
155const CONFIG_PATH: &str = "lyrics";
156
157pub fn config_path() -> PathBuf {
158    let config_dir = dirs::home_dir()
159        .unwrap_or_else(|| PathBuf::from("."))
160        .join(".config")
161        .join(CONFIG_PATH)
162        .join("config.toml");
163    ensure_parent_dir(&config_dir);
164    config_dir
165}
166
167pub fn log_path() -> PathBuf {
168    let log_file = dirs::home_dir()
169        .unwrap_or_else(|| PathBuf::from("."))
170        .join(".cache")
171        .join(CONFIG_PATH);
172    // .join("lyrics.log");
173    ensure_parent_dir(&log_file);
174    log_file
175}
176
177pub fn lyrics_path() -> PathBuf {
178    let cache_dir = dirs::home_dir()
179        .unwrap_or_else(|| PathBuf::from("."))
180        .join(".local")
181        .join("share")
182        .join(CONFIG_PATH);
183    ensure_parent_dir(&cache_dir);
184    cache_dir
185}