use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use toml::from_str as from_toml_str;
use toml::to_string_pretty;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub download_dir: String,
pub concurrency: u32,
pub timeout: u64,
pub max_retries: u32,
pub user_agent: Option<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
download_dir: "downloads".to_string(),
concurrency: 4,
timeout: 300, max_retries: 3,
user_agent: None,
}
}
}
pub struct ConfigManager;
impl ConfigManager {
pub fn config_path() -> PathBuf {
let config_dir = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("vielpork");
config_dir.join("config.toml")
}
pub fn ensure_config_dir() -> std::io::Result<()> {
let config_path = Self::config_path();
let config_dir = config_path.parent().unwrap();
if !config_dir.exists() {
fs::create_dir_all(config_dir)?;
}
Ok(())
}
pub fn load() -> Result<Config, String> {
let config_path = Self::config_path();
if !config_path.exists() {
return Ok(Config::default());
}
let content = fs::read_to_string(&config_path)
.map_err(|e| format!("Failed to read config file: {}", e))?;
from_toml_str(&content).map_err(|e| format!("Failed to parse config: {}", e))
}
pub fn save(config: &Config) -> Result<(), String> {
Self::ensure_config_dir()
.map_err(|e| format!("Failed to create config directory: {}", e))?;
let config_path = Self::config_path();
let content =
to_string_pretty(config).map_err(|e| format!("Failed to serialize config: {}", e))?;
fs::write(&config_path, content)
.map_err(|e| format!("Failed to write config file: {}", e))?;
Ok(())
}
pub fn display(config: &Config) {
println!("📋 Current Configuration:");
println!(" Download Directory: {}", config.download_dir);
println!(" Concurrency: {}", config.concurrency);
println!(" Timeout: {}s", config.timeout);
println!(" Max Retries: {}", config.max_retries);
if let Some(ua) = &config.user_agent {
println!(" User Agent: {}", ua);
} else {
println!(" User Agent: (default)");
}
println!("\n📁 Config file: {}", Self::config_path().display());
}
pub fn get(config: &Config, key: &str) -> Option<String> {
match key {
"download_dir" => Some(config.download_dir.clone()),
"concurrency" => Some(config.concurrency.to_string()),
"timeout" => Some(config.timeout.to_string()),
"max_retries" => Some(config.max_retries.to_string()),
"user_agent" => config.user_agent.clone(),
_ => None,
}
}
pub fn set(config: &mut Config, key: &str, value: &str) -> Result<(), String> {
match key {
"download_dir" => {
config.download_dir = value.to_string();
}
"concurrency" => {
config.concurrency = value
.parse()
.map_err(|_| "Invalid concurrency value (must be number)".to_string())?;
}
"timeout" => {
config.timeout = value
.parse()
.map_err(|_| "Invalid timeout value (must be number)".to_string())?;
}
"max_retries" => {
config.max_retries = value
.parse()
.map_err(|_| "Invalid max_retries value (must be number)".to_string())?;
}
"user_agent" => {
config.user_agent = Some(value.to_string());
}
_ => {
return Err(format!("Unknown config key: {}", key));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.download_dir, "downloads");
assert_eq!(config.concurrency, 4);
assert_eq!(config.timeout, 30);
}
#[test]
fn test_config_serialization() {
let config = Config::default();
let toml_str = to_string_pretty(&config).unwrap();
let parsed: Config = from_toml_str(&toml_str).unwrap();
assert_eq!(config.download_dir, parsed.download_dir);
}
}