multhreadown/
config.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3use std::str::FromStr;
4use thiserror::Error;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RetryConfig {
9    pub max_retries: u32,
10    pub initial_delay: u64,
11    pub max_delay: u64,
12    pub backoff_factor: f64,
13}
14
15impl Default for RetryConfig {
16    fn default() -> Self {
17        Self {
18            max_retries: 3,
19            initial_delay: 1,
20            max_delay: 30,
21            backoff_factor: 2.0,
22        }
23    }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Config {
28    pub download_dir: PathBuf,
29    pub workers: usize,
30    pub random_order: bool,
31    pub urls: Vec<String>,
32    pub rate_limit_kb: Option<u64>,
33    pub retry: RetryConfig,
34    pub concurrent_downloads: usize,
35    pub connection_timeout: u64,
36}
37
38impl Default for Config {
39    fn default() -> Self {
40        Self {
41            download_dir: PathBuf::from("downloads"),
42            workers: 4,
43            random_order: false,
44            urls: Vec::new(),
45            rate_limit_kb: None,
46            retry: RetryConfig::default(),
47            concurrent_downloads: 4,
48            connection_timeout: 30,
49        }
50    }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct IntegrityCheck {
55    pub enabled: bool,
56    pub algorithm: ChecksumAlgorithm,
57    pub checksums: HashMap<String, String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum ChecksumAlgorithm {
62    MD5,
63    SHA256,
64    SHA512,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DownloadFilter {
69    pub include_patterns: Vec<String>,
70    pub exclude_patterns: Vec<String>,
71    pub min_size: Option<u64>,
72    pub max_size: Option<u64>,
73}
74
75#[derive(Debug, Error)]
76pub enum ConfigError {
77    #[error("Invalid download directory: {0}")]
78    InvalidDownloadDir(String),
79    #[error("Invalid number of workers: {0}")]
80    InvalidWorkers(usize),
81    #[error("No download URLs provided")]
82    NoUrls,
83    #[error("Invalid URL format: {0}")]
84    InvalidUrl(String),
85}
86
87impl Config {
88    pub fn validate(&self) -> Result<(), ConfigError> {
89        // Validate download directory
90        if !self.download_dir.to_str().map_or(false, |s| !s.is_empty()) {
91            return Err(ConfigError::InvalidDownloadDir(
92                self.download_dir.to_string_lossy().to_string(),
93            ));
94        }
95        
96        // Check if download directory is writable
97        if let Err(e) = std::fs::create_dir_all(&self.download_dir) {
98            return Err(ConfigError::InvalidDownloadDir(format!(
99                "Cannot create directory: {}",
100                e
101            )));
102        }
103
104        // Validate workers count
105        if self.workers == 0 || self.workers > 100 {
106            return Err(ConfigError::InvalidWorkers(self.workers));
107        }
108
109        // Validate URLs
110        if self.urls.is_empty() {
111            return Err(ConfigError::NoUrls);
112        }
113        
114        // Validate URL count matches expected file indices
115        if self.urls.len() > 163 {
116            return Err(ConfigError::InvalidUrl(format!(
117                "Too many URLs (max 163), got {}",
118                self.urls.len()
119            )));
120        }
121
122        for (index, url) in self.urls.iter().enumerate() {
123            if !url.starts_with("http://") && !url.starts_with("https://") {
124                return Err(ConfigError::InvalidUrl(format!(
125                    "Invalid URL format at index {}: {}",
126                    index, url
127                )));
128            }
129        }
130
131        Ok(())
132    }
133
134    pub fn from_file(path: &PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
135        let content = std::fs::read_to_string(path)?;
136        let config: Config = toml::from_str(&content)?;
137        Ok(config)
138    }
139}
140
141impl FromStr for Config {
142    type Err = toml::de::Error;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        toml::from_str(s)
146    }
147}