- Delete src/audio/queue.rs (321 lines, PlayQueue never used) - Remove #![allow(dead_code)] from audio, subsonic, and mpris module roots - Remove unused MpvEvent2 enum, playlist_clear, get_volume, get_path, is_eof, observe_property from mpv.rs - Remove unused DEFAULT_DEVICE_ID, is_available, get_effective_rate from pipewire.rs (and associated dead test) - Remove unused search(), get_cover_art_url() from subsonic client - Remove unused SearchResult3Data, SearchResult3 model structs - Move parse_song_id_from_url into #[cfg(test)] block (only used by tests) - Add targeted #[allow(dead_code)] on deserialization-only fields (MpvEvent, SubsonicResponseInner.version, ArtistIndex.name) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
174 lines
4.6 KiB
Rust
174 lines
4.6 KiB
Rust
//! Configuration loading and management
|
|
|
|
pub mod paths;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::Path;
|
|
use tracing::{debug, info, warn};
|
|
|
|
use crate::error::ConfigError;
|
|
|
|
/// Main application configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct Config {
|
|
/// Subsonic server base URL
|
|
#[serde(rename = "BaseURL", default)]
|
|
pub base_url: String,
|
|
|
|
/// Username for authentication
|
|
#[serde(rename = "Username", default)]
|
|
pub username: String,
|
|
|
|
/// Password for authentication
|
|
#[serde(rename = "Password", default)]
|
|
pub password: String,
|
|
|
|
/// UI Theme name
|
|
#[serde(rename = "Theme", default)]
|
|
pub theme: String,
|
|
|
|
/// Enable cava audio visualizer
|
|
#[serde(rename = "Cava", default)]
|
|
pub cava: bool,
|
|
|
|
/// Cava visualizer height percentage (10-80, step 5)
|
|
#[serde(rename = "CavaSize", default = "Config::default_cava_size")]
|
|
pub cava_size: u8,
|
|
}
|
|
|
|
impl Config {
|
|
fn default_cava_size() -> u8 {
|
|
40
|
|
}
|
|
|
|
/// Create a new empty config
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Load config from the default location
|
|
pub fn load_default() -> Result<Self, ConfigError> {
|
|
let path = paths::config_file().ok_or_else(|| ConfigError::NotFound {
|
|
path: "default config location".to_string(),
|
|
})?;
|
|
|
|
if path.exists() {
|
|
Self::load_from_file(&path)
|
|
} else {
|
|
info!("No config file found at {}, using defaults", path.display());
|
|
Ok(Self::new())
|
|
}
|
|
}
|
|
|
|
/// Load config from a specific file
|
|
pub fn load_from_file(path: &Path) -> Result<Self, ConfigError> {
|
|
debug!("Loading config from {}", path.display());
|
|
|
|
if !path.exists() {
|
|
return Err(ConfigError::NotFound {
|
|
path: path.display().to_string(),
|
|
});
|
|
}
|
|
|
|
let contents = std::fs::read_to_string(path)?;
|
|
let config: Config = toml::from_str(&contents)?;
|
|
|
|
debug!("Config loaded successfully");
|
|
Ok(config)
|
|
}
|
|
|
|
/// Save config to the default location
|
|
pub fn save_default(&self) -> Result<(), ConfigError> {
|
|
let path = paths::config_file().ok_or_else(|| ConfigError::NotFound {
|
|
path: "default config location".to_string(),
|
|
})?;
|
|
|
|
self.save_to_file(&path)
|
|
}
|
|
|
|
/// Save config to a specific file
|
|
pub fn save_to_file(&self, path: &Path) -> Result<(), ConfigError> {
|
|
debug!("Saving config to {}", path.display());
|
|
|
|
// Ensure parent directory exists
|
|
if let Some(parent) = path.parent() {
|
|
if !parent.exists() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
}
|
|
|
|
let contents = toml::to_string_pretty(self)?;
|
|
std::fs::write(path, contents)?;
|
|
|
|
info!("Config saved to {}", path.display());
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if the config has valid server settings
|
|
pub fn is_configured(&self) -> bool {
|
|
!self.base_url.is_empty() && !self.username.is_empty() && !self.password.is_empty()
|
|
}
|
|
|
|
/// Validate the config
|
|
#[allow(dead_code)]
|
|
pub fn validate(&self) -> Result<(), ConfigError> {
|
|
if self.base_url.is_empty() {
|
|
return Err(ConfigError::MissingField {
|
|
field: "BaseURL".to_string(),
|
|
});
|
|
}
|
|
|
|
// Validate URL format
|
|
if url::Url::parse(&self.base_url).is_err() {
|
|
return Err(ConfigError::InvalidUrl {
|
|
url: self.base_url.clone(),
|
|
});
|
|
}
|
|
|
|
if self.username.is_empty() {
|
|
warn!("Username is empty");
|
|
}
|
|
|
|
if self.password.is_empty() {
|
|
warn!("Password is empty");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use tempfile::NamedTempFile;
|
|
|
|
#[test]
|
|
fn test_config_parse() {
|
|
let toml_content = r#"
|
|
BaseURL = "https://example.com"
|
|
Username = "testuser"
|
|
Password = "testpass"
|
|
"#;
|
|
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
file.write_all(toml_content.as_bytes()).unwrap();
|
|
|
|
let config = Config::load_from_file(file.path()).unwrap();
|
|
assert_eq!(config.base_url, "https://example.com");
|
|
assert_eq!(config.username, "testuser");
|
|
assert_eq!(config.password, "testpass");
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_configured() {
|
|
let mut config = Config::new();
|
|
assert!(!config.is_configured());
|
|
|
|
config.base_url = "https://example.com".to_string();
|
|
config.username = "user".to_string();
|
|
config.password = "pass".to_string();
|
|
assert!(config.is_configured());
|
|
}
|
|
}
|