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 #[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)]
56pub struct AppConfig {
58 pub client_id: String,
59 pub client_port: u16,
60
61 pub proxy: Option<String>,
63 pub ap_port: Option<u16>,
64}
65
66impl Default for AppConfig {
67 fn default() -> Self {
68 Self {
69 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 #[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#[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")]
150pub 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}