use anyhow::Result; use base64::{engine::general_purpose::STANDARD, Engine as _}; use serde::{Deserialize, Serialize}; use crate::pkce::DEFAULT_PORT; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Settings { #[serde(default = "default_download_path")] pub download_path: String, #[serde(default = "default_port")] pub auth_callback_port: u16, } impl Default for Settings { fn default() -> Self { Self { download_path: default_download_path(), auth_callback_port: DEFAULT_PORT, } } } fn default_download_path() -> String { if let Some(dir) = dirs::download_dir() { return dir.to_string_lossy().into_owned(); } if let Some(home) = dirs::home_dir() { if home.join("Downloads").is_dir() { return home.join("Downloads").to_string_lossy().into_owned(); } } "./download".to_string() } fn default_port() -> u16 { DEFAULT_PORT } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Token { #[serde(default)] pub userid: Option, #[serde(default, alias = "country_code")] pub country_code: Option, #[serde(default, alias = "access_token")] pub access_token: Option, #[serde(default, alias = "refresh_token")] pub refresh_token: Option, #[serde(default, alias = "expires_after")] pub expires_after: i64, } pub fn profile_path() -> std::path::PathBuf { crate::pkce::home_dir().join(".tidal-dl.json") } pub fn token_path() -> std::path::PathBuf { crate::pkce::home_dir().join(".tidal-dl.token.json") } pub fn load_settings() -> Settings { let path = profile_path(); let Ok(text) = std::fs::read_to_string(&path) else { return Settings::default(); }; let mut settings: Settings = serde_json::from_str(&text).unwrap_or_default(); if matches!(settings.download_path.as_str(), "./download/" | "./download" | "") { settings.download_path = default_download_path(); } settings } pub fn save_settings(settings: &Settings) -> Result<()> { let text = serde_json::to_string_pretty(settings)?; std::fs::write(profile_path(), text)?; Ok(()) } pub fn load_token() -> Token { let path = token_path(); let Ok(raw) = std::fs::read(&path) else { return Token::default(); }; let text = STANDARD .decode(&raw) .ok() .and_then(|b| String::from_utf8(b).ok()) .unwrap_or_else(|| String::from_utf8_lossy(&raw).into_owned()); serde_json::from_str(&text).unwrap_or_default() } pub fn save_token(token: &Token) -> Result<()> { let text = serde_json::to_string(token)?; std::fs::write(token_path(), STANDARD.encode(text))?; Ok(()) } pub fn clear_token() -> Result<()> { save_token(&Token::default()) }