spotify_client/
config.rs

1use anyhow::Result;
2use config_parser2::*;
3use librespot_core::config::SessionConfig;
4use reqwest::Url;
5use serde::{Deserialize, Serialize};
6use std::{
7    path::Path,
8    sync::OnceLock,
9};
10
11static CONFIGS: OnceLock<Config> = OnceLock::new();
12
13#[derive(Debug)]
14pub struct Config {
15    pub app_config: AppConfig,
16    pub login_info: (String, String),
17}
18
19impl Config {
20    pub fn from_pass<T: Into<String>>(username: T, password: T) -> Self {
21        Self {
22            app_config: AppConfig::default(),
23            login_info: (username.into(), password.into()),
24        }
25    }
26}
27
28
29impl Config {
30    pub fn new<P, T>(config_folder: P, username: T, password: T) -> Result<Self>
31        where
32            P: AsRef<Path>,
33            T: Into<String>
34    {
35        Ok(Self {
36            app_config: AppConfig::new(config_folder)?,
37            login_info: (username.into(), password.into())
38        })
39    }
40
41    // <P: AsRef<Path>>
42    #[cfg(feature = "env-file")]
43    pub fn from_env() -> Result<Self> {
44        use std::env::var;
45        dotenvy::dotenv().ok();
46
47        let config_path = var("SPOTIFY_CONFIG_PATH").unwrap_or(".config/spotify-player".to_string());
48        let username = var("SPOTIFY_USERNAME")?;
49        let password = var("SPOTIFY_PASSWORD")?;
50
51        Self::new(config_path, username, password)
52    }
53}
54
55#[derive(Debug, Deserialize, Serialize, ConfigParse)]
56/// Application configurations
57pub struct AppConfig {
58    pub client_id: String,
59    pub client_port: u16,
60
61    // session configs
62    pub proxy: Option<String>,
63    pub ap_port: Option<u16>,
64}
65
66impl Default for AppConfig {
67    fn default() -> Self {
68        Self {
69            // official Spotify web app's client id
70            client_id: "65b708073fc0480ea92a077233ca87bd".to_string(),
71            client_port: 8080,
72            proxy: None,
73            ap_port: None,
74        }
75    }
76}
77
78
79impl AppConfig {
80    #[cfg(feature = "file")]
81    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
82        let mut config = Self::default();
83        if !config.parse_config_file(path.as_ref())? {
84            config.write_config_file(path.as_ref())?
85        }
86
87        Ok(config)
88    }
89
90    #[cfg(not(feature = "file"))]
91    pub fn new(_: impl AsRef<Path>) -> Result<Self> {
92        let config = Self::default();
93        Ok(config)
94    }
95
96    // parses configurations from an application config file in `path` folder,
97    // then updates the current configurations accordingly.
98    // returns false if no config file found and true otherwise
99    #[cfg(feature = "file")]
100    fn parse_config_file<P: AsRef<Path>>(&mut self, path: P) -> Result<bool> {
101        let file_path = path.as_ref().join(APP_CONFIG_FILE);
102        match std::fs::read_to_string(file_path) {
103            Ok(content) => self
104                .parse(toml::from_str::<toml::Value>(&content)?)
105                .map(|_| true),
106            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
107            Err(error) => Err(error.into()),
108        }
109    }
110
111    #[cfg(feature = "file")]
112    fn write_config_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
113        toml::to_string_pretty(&self)
114            .map_err(From::from)
115            .and_then(|content| {
116                std::fs::write(path.as_ref().join(APP_CONFIG_FILE), content)
117                    .map_err(From::from)
118            })
119    }
120
121    pub fn session_config(&self) -> SessionConfig {
122        let proxy = self
123            .proxy
124            .as_ref()
125            .and_then(|proxy| match Url::parse(proxy) {
126                Err(err) => {
127                    tracing::warn!("failed to parse proxy url {proxy}: {err:#}");
128                    None
129                }
130                Ok(url) => Some(url),
131            });
132        SessionConfig {
133            proxy,
134            ap_port: self.ap_port,
135            ..Default::default()
136        }
137    }
138}
139
140/// gets the application's configuration folder path
141#[cfg(feature = "file")]
142pub fn get_config_folder_path() -> Result<PathBuf> {
143    match dirs_next::home_dir() {
144        Some(home) => Ok(format!("./{}", DEFAULT_CONFIG_FOLDER).into()),
145        None => Err(anyhow!("cannot find the folder")),
146    }
147}
148
149#[cfg(feature = "file")]
150/// gets the application's cache folder path
151pub fn get_cache_folder_path() -> Result<PathBuf> {
152    match dirs_next::home_dir() {
153        Some(home) =>  Ok(format!("./{}", DEFAULT_CACHE_FOLDER).into()),
154        None => Err(anyhow!("cannot find the folder")),
155    }
156}
157
158
159#[inline(always)]
160pub fn get_config() -> &'static Config {
161    CONFIGS.get().expect("configs is already initialized")
162}
163
164pub fn set_config(configs: Config) {
165    CONFIGS
166        .set(configs)
167        .expect("configs should be initialized only once")
168}