use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::config_manager::ConfigModule;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YoutubeConfig {
pub enabled: bool,
pub cache_directory: PathBuf,
pub max_cache_size_mb: u64,
pub max_cache_age_days: u64,
pub preferred_quality: String,
pub audio_only: bool,
pub ytdlp_path: Option<PathBuf>,
pub concurrent_downloads: u32,
pub retry_attempts: u32,
}
impl ConfigModule for YoutubeConfig {
fn module_name() -> &'static str {
"youtube"
}
fn default() -> Self {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let cache_dir = home_dir.join(".bckplayer").join("youtube_cache");
Self {
enabled: true,
cache_directory: cache_dir,
max_cache_size_mb: 1024, max_cache_age_days: 30,
preferred_quality: "best[height<=720]".to_string(),
audio_only: false,
ytdlp_path: None, concurrent_downloads: 2,
retry_attempts: 3,
}
}
fn validate(&self) -> anyhow::Result<()> {
if self.max_cache_size_mb == 0 {
return Err(anyhow::anyhow!("Cache size must be greater than 0"));
}
if self.max_cache_size_mb > 100_000 {
return Err(anyhow::anyhow!(
"Cache size cannot exceed 100000 MB (100 GB)"
));
}
if self.max_cache_age_days == 0 {
return Err(anyhow::anyhow!("Cache age must be greater than 0"));
}
if self.concurrent_downloads == 0 {
return Err(anyhow::anyhow!("Concurrent downloads must be greater than 0"));
}
if self.retry_attempts > 10 {
return Err(anyhow::anyhow!("Retry attempts cannot exceed 10"));
}
Ok(())
}
fn on_load(&mut self) -> anyhow::Result<()> {
if !self.cache_directory.exists() {
std::fs::create_dir_all(&self.cache_directory)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
pub timeout_seconds: u64,
pub retry_attempts: u32,
pub user_agent: String,
pub proxy_url: Option<String>,
pub max_redirects: u32,
}
impl ConfigModule for NetworkConfig {
fn module_name() -> &'static str {
"network"
}
fn default() -> Self {
Self {
timeout_seconds: 30,
retry_attempts: 3,
user_agent: "bckplayer/0.1.0".to_string(),
proxy_url: None,
max_redirects: 5,
}
}
fn validate(&self) -> anyhow::Result<()> {
if self.timeout_seconds == 0 {
return Err(anyhow::anyhow!("Timeout must be greater than 0"));
}
if self.retry_attempts > 10 {
return Err(anyhow::anyhow!("Retry attempts cannot exceed 10"));
}
if self.max_redirects > 20 {
return Err(anyhow::anyhow!("Max redirects cannot exceed 20"));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppearanceConfig {
pub theme: String,
pub font_size: u16,
pub show_fps: bool,
pub show_logs: bool,
pub log_level: String,
pub progress_bar_style: String,
}
impl ConfigModule for AppearanceConfig {
fn module_name() -> &'static str {
"appearance"
}
fn default() -> Self {
Self {
theme: "dark".to_string(),
font_size: 14,
show_fps: false,
show_logs: true,
log_level: "info".to_string(),
progress_bar_style: "simple".to_string(),
}
}
fn validate(&self) -> anyhow::Result<()> {
let valid_themes = ["dark", "light", "auto"];
if !valid_themes.contains(&self.theme.as_str()) {
return Err(anyhow::anyhow!("Invalid theme. Must be one of: {:?}", valid_themes));
}
if !(8..=72).contains(&self.font_size) {
return Err(anyhow::anyhow!("Font size must be between 8 and 72"));
}
let valid_log_levels = ["trace", "debug", "info", "warn", "error"];
if !valid_log_levels.contains(&self.log_level.as_str()) {
return Err(anyhow::anyhow!("Invalid log level. Must be one of: {:?}", valid_log_levels));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MediaConfig {
pub auto_load_subtitles: bool,
pub subtitle_languages: Vec<String>,
pub audio_languages: Vec<String>,
pub hardware_decoding: bool,
pub buffer_size_seconds: f64,
pub skip_intro_seconds: Option<f64>,
pub skip_outro_seconds: Option<f64>,
}
impl ConfigModule for MediaConfig {
fn module_name() -> &'static str {
"media"
}
fn default() -> Self {
Self {
auto_load_subtitles: true,
subtitle_languages: vec!["en".to_string(), "eng".to_string()],
audio_languages: vec!["en".to_string(), "eng".to_string()],
hardware_decoding: true,
buffer_size_seconds: 10.0,
skip_intro_seconds: None,
skip_outro_seconds: None,
}
}
fn validate(&self) -> anyhow::Result<()> {
if self.buffer_size_seconds < 1.0 || self.buffer_size_seconds > 300.0 {
return Err(anyhow::anyhow!("Buffer size must be between 1 and 300 seconds"));
}
if let Some(skip) = self.skip_intro_seconds {
if skip < 0.0 || skip > 600.0 {
return Err(anyhow::anyhow!("Skip intro must be between 0 and 600 seconds"));
}
}
if let Some(skip) = self.skip_outro_seconds {
if skip < 0.0 || skip > 600.0 {
return Err(anyhow::anyhow!("Skip outro must be between 0 and 600 seconds"));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdvancedConfig {
pub debug_mode: bool,
pub memory_limit_mb: Option<u64>,
pub cpu_cores: Option<u32>,
pub experimental_features: bool,
pub custom_ffmpeg_args: Vec<String>,
pub log_file_path: Option<PathBuf>,
pub performance_monitoring: bool,
}
impl ConfigModule for AdvancedConfig {
fn module_name() -> &'static str {
"advanced"
}
fn default() -> Self {
Self {
debug_mode: false,
memory_limit_mb: None,
cpu_cores: None,
experimental_features: false,
custom_ffmpeg_args: Vec::new(),
log_file_path: None,
performance_monitoring: false,
}
}
fn validate(&self) -> anyhow::Result<()> {
if let Some(memory) = self.memory_limit_mb {
if memory < 128 {
return Err(anyhow::anyhow!("Memory limit must be at least 128MB"));
}
}
if let Some(cores) = self.cpu_cores {
if cores == 0 || cores > 64 {
return Err(anyhow::anyhow!("CPU cores must be between 1 and 64"));
}
}
Ok(())
}
}