use crate::constant::*;
use anyhow::{anyhow, Result};
use config_parser2::*;
use librespot_core::config::SessionConfig;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
sync::OnceLock,
};
static CONFIGS: OnceLock<Configs> = OnceLock::new();
#[derive(Debug)]
pub struct Configs {
pub app_config: AppConfig,
pub cache_folder: std::path::PathBuf,
pub username: String,
pub password: String
}
impl Configs {
pub fn new<P, T>(config_folder: P, cache_folder: P, username: T, password: T) -> Result<Self>
where
P: AsRef<Path>,
T: Into<String>
{
Ok(Self {
app_config: AppConfig::new(config_folder)?,
cache_folder: cache_folder.as_ref().to_path_buf(),
username: username.into(),
password: password.into()
})
}
pub fn from_env() -> Result<Self> {
use std::env::var;
dotenvy::dotenv().ok();
let config_path = var("SPOTIFY_CONFIG_PATH").unwrap_or(".config/spotify-player".to_string());
let cache_path = var("SPOTIFY_CACHE_PATH").unwrap_or(".cache/spotify-player".to_string());
let username = var("SPOTIFY_USERNAME")?;
let password = var("SPOTIFY_PASSWORD")?;
Self::new(config_path, cache_path, username, password)
}
}
#[derive(Debug, Deserialize, Serialize, ConfigParse)]
pub struct AppConfig {
pub client_id: String,
pub client_port: u16,
pub proxy: Option<String>,
pub ap_port: Option<u16>,
pub app_refresh_duration_in_ms: u64,
pub playback_refresh_duration_in_ms: u64,
pub enable_cover_image_cache: bool,
pub notify_streaming_only: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
client_id: "65b708073fc0480ea92a077233ca87bd".to_string(),
client_port: 8080,
proxy: None,
ap_port: None,
app_refresh_duration_in_ms: 32,
playback_refresh_duration_in_ms: 0,
enable_cover_image_cache: true,
notify_streaming_only: false,
}
}
}
impl AppConfig {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let mut config = Self::default();
if !config.parse_config_file(path.as_ref())? {
config.write_config_file(path.as_ref())?
}
Ok(config)
}
fn parse_config_file<P: AsRef<Path>>(&mut self, path: P) -> Result<bool> {
let file_path = path.as_ref().join(APP_CONFIG_FILE);
match std::fs::read_to_string(file_path) {
Ok(content) => self
.parse(toml::from_str::<toml::Value>(&content)?)
.map(|_| true),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error.into()),
}
}
fn write_config_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
toml::to_string_pretty(&self)
.map_err(From::from)
.and_then(|content| {
std::fs::write(path.as_ref().join(APP_CONFIG_FILE), content)
.map_err(From::from)
})
}
pub fn session_config(&self) -> SessionConfig {
let proxy = self
.proxy
.as_ref()
.and_then(|proxy| match Url::parse(proxy) {
Err(err) => {
tracing::warn!("failed to parse proxy url {proxy}: {err:#}");
None
}
Ok(url) => Some(url),
});
SessionConfig {
proxy,
ap_port: self.ap_port,
..Default::default()
}
}
}
pub fn get_config_folder_path() -> Result<PathBuf> {
match dirs_next::home_dir() {
Some(home) => Ok(format!("./{}", DEFAULT_CONFIG_FOLDER).into()),
None => Err(anyhow!("cannot find the folder")),
}
}
pub fn get_cache_folder_path() -> Result<PathBuf> {
match dirs_next::home_dir() {
Some(home) => Ok(format!("./{}", DEFAULT_CACHE_FOLDER).into()),
None => Err(anyhow!("cannot find the folder")),
}
}
#[inline(always)]
pub fn get_config() -> &'static Configs {
CONFIGS.get().expect("configs is already initialized")
}
pub fn set_config(configs: Configs) {
CONFIGS
.set(configs)
.expect("configs should be initialized only once")
}