use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::constants::workers;
use crate::errors::DownloadResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerConfig {
pub worker_count: usize,
pub max_retries: u32,
pub retry_base_delay: Duration,
pub retry_max_delay: Duration,
pub idle_sleep_duration: Duration,
pub progress_buffer_size: usize,
pub download_timeout: Duration,
pub detailed_progress: bool,
pub speed_calculation_samples: usize,
pub max_backoff_multiplier: u32,
pub backoff_jitter_percentage: f64,
pub max_idle_sleep_ms: u64,
pub error_sleep_duration: Duration,
pub retry_backoff_multiplier: u32,
pub min_progress_update_interval_ms: u64,
pub queue_wait_log_threshold_ms: u64,
pub short_queue_wait_log_threshold_ms: u64,
}
impl Default for WorkerConfig {
fn default() -> Self {
Self {
worker_count: workers::DEFAULT_WORKER_COUNT,
max_retries: workers::MAX_RETRIES,
retry_base_delay: Duration::from_millis(100),
retry_max_delay: Duration::from_secs(30),
idle_sleep_duration: Duration::from_millis(100),
progress_buffer_size: workers::CHANNEL_BUFFER_SIZE,
download_timeout: workers::DEFAULT_DOWNLOAD_TIMEOUT,
detailed_progress: true,
speed_calculation_samples: workers::SPEED_CALCULATION_SAMPLES,
max_backoff_multiplier: workers::MAX_BACKOFF_MULTIPLIER,
backoff_jitter_percentage: workers::BACKOFF_JITTER_PERCENTAGE,
max_idle_sleep_ms: workers::MAX_IDLE_SLEEP_MS,
error_sleep_duration: workers::ERROR_SLEEP_DURATION,
retry_backoff_multiplier: workers::RETRY_BACKOFF_MULTIPLIER,
min_progress_update_interval_ms: workers::MIN_PROGRESS_UPDATE_INTERVAL_MS,
queue_wait_log_threshold_ms: workers::QUEUE_WAIT_LOG_THRESHOLD_MS,
short_queue_wait_log_threshold_ms: workers::SHORT_QUEUE_WAIT_LOG_THRESHOLD_MS,
}
}
}
impl WorkerConfig {
pub fn validate(&self) -> DownloadResult<()> {
if self.worker_count == 0 {
return Err(crate::errors::DownloadError::ConfigurationError(
"Worker count cannot be zero".to_string(),
));
}
if self.worker_count > workers::MAX_WORKER_COUNT {
return Err(crate::errors::DownloadError::ConfigurationError(format!(
"Worker count ({}) exceeds maximum ({})",
self.worker_count,
workers::MAX_WORKER_COUNT
)));
}
if self.retry_base_delay >= self.retry_max_delay {
return Err(crate::errors::DownloadError::ConfigurationError(
"Retry base delay must be less than max delay".to_string(),
));
}
if self.backoff_jitter_percentage < 0.0 || self.backoff_jitter_percentage > 1.0 {
return Err(crate::errors::DownloadError::ConfigurationError(
"Backoff jitter percentage must be between 0.0 and 1.0".to_string(),
));
}
if self.speed_calculation_samples == 0 {
return Err(crate::errors::DownloadError::ConfigurationError(
"Speed calculation samples must be greater than zero".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct WorkerConfigBuilder {
config: WorkerConfig,
}
impl WorkerConfigBuilder {
pub fn new() -> Self {
Self {
config: WorkerConfig::default(),
}
}
pub fn worker_count(mut self, count: usize) -> Self {
self.config.worker_count = count;
self
}
pub fn max_retries(mut self, retries: u32) -> Self {
self.config.max_retries = retries;
self
}
pub fn retry_base_delay(mut self, delay: Duration) -> Self {
self.config.retry_base_delay = delay;
self
}
pub fn retry_max_delay(mut self, delay: Duration) -> Self {
self.config.retry_max_delay = delay;
self
}
pub fn idle_sleep_duration(mut self, duration: Duration) -> Self {
self.config.idle_sleep_duration = duration;
self
}
pub fn progress_buffer_size(mut self, size: usize) -> Self {
self.config.progress_buffer_size = size;
self
}
pub fn download_timeout(mut self, timeout: Duration) -> Self {
self.config.download_timeout = timeout;
self
}
pub fn detailed_progress(mut self, enabled: bool) -> Self {
self.config.detailed_progress = enabled;
self
}
pub fn speed_calculation_samples(mut self, samples: usize) -> Self {
self.config.speed_calculation_samples = samples;
self
}
pub fn backoff_jitter_percentage(mut self, percentage: f64) -> Self {
self.config.backoff_jitter_percentage = percentage;
self
}
pub fn build(self) -> DownloadResult<WorkerConfig> {
self.config.validate()?;
Ok(self.config)
}
pub fn build_unchecked(self) -> WorkerConfig {
self.config
}
}
pub struct ConfigPresets;
impl ConfigPresets {
pub fn production() -> WorkerConfig {
WorkerConfig {
worker_count: 8,
max_retries: 3,
retry_base_delay: Duration::from_millis(1000),
retry_max_delay: Duration::from_secs(60),
idle_sleep_duration: Duration::from_millis(100),
download_timeout: Duration::from_secs(600), detailed_progress: false, ..Default::default()
}
}
pub fn development() -> WorkerConfig {
WorkerConfig {
worker_count: 4,
max_retries: 2,
retry_base_delay: Duration::from_millis(100),
retry_max_delay: Duration::from_secs(10),
idle_sleep_duration: Duration::from_millis(50),
download_timeout: Duration::from_secs(120), detailed_progress: true,
..Default::default()
}
}
pub fn testing() -> WorkerConfig {
WorkerConfig {
worker_count: 2,
max_retries: 1,
retry_base_delay: Duration::from_millis(10),
retry_max_delay: Duration::from_millis(100),
idle_sleep_duration: Duration::from_millis(10),
download_timeout: Duration::from_secs(5),
detailed_progress: true,
progress_buffer_size: 10,
..Default::default()
}
}
pub fn high_throughput() -> WorkerConfig {
WorkerConfig {
worker_count: 16,
max_retries: 5,
retry_base_delay: Duration::from_millis(500),
retry_max_delay: Duration::from_secs(30),
idle_sleep_duration: Duration::from_millis(50),
download_timeout: Duration::from_secs(300), detailed_progress: false, progress_buffer_size: 200,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_worker_config_default() {
let config = WorkerConfig::default();
assert_eq!(config.worker_count, workers::DEFAULT_WORKER_COUNT);
assert_eq!(config.max_retries, workers::MAX_RETRIES);
assert_eq!(config.progress_buffer_size, workers::CHANNEL_BUFFER_SIZE);
assert_eq!(config.download_timeout, workers::DEFAULT_DOWNLOAD_TIMEOUT);
assert!(config.retry_base_delay < config.retry_max_delay);
assert!(config.detailed_progress);
}
#[test]
fn test_config_validation() {
let config = WorkerConfig {
worker_count: 0,
..Default::default()
};
assert!(config.validate().is_err());
let config = WorkerConfig {
worker_count: workers::MAX_WORKER_COUNT + 1,
..Default::default()
};
assert!(config.validate().is_err());
let config = WorkerConfig {
worker_count: 4,
retry_base_delay: Duration::from_secs(60),
retry_max_delay: Duration::from_secs(30),
..Default::default()
};
assert!(config.validate().is_err());
let config = WorkerConfig {
backoff_jitter_percentage: 1.5,
..Default::default()
};
assert!(config.validate().is_err());
let config = WorkerConfig {
speed_calculation_samples: 0,
..Default::default()
};
assert!(config.validate().is_err());
let config = WorkerConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_builder() {
let config = WorkerConfigBuilder::new()
.worker_count(4)
.max_retries(5)
.download_timeout(Duration::from_secs(300))
.detailed_progress(false)
.build()
.unwrap();
assert_eq!(config.worker_count, 4);
assert_eq!(config.max_retries, 5);
assert_eq!(config.download_timeout, Duration::from_secs(300));
assert!(!config.detailed_progress);
}
#[test]
fn test_config_presets() {
let prod = ConfigPresets::production();
let dev = ConfigPresets::development();
let test = ConfigPresets::testing();
let high = ConfigPresets::high_throughput();
assert!(prod.validate().is_ok());
assert!(dev.validate().is_ok());
assert!(test.validate().is_ok());
assert!(high.validate().is_ok());
assert!(prod.worker_count >= dev.worker_count);
assert!(dev.worker_count >= test.worker_count);
assert!(high.worker_count >= prod.worker_count);
assert!(test.download_timeout < dev.download_timeout);
assert!(dev.download_timeout < prod.download_timeout);
assert!(!prod.detailed_progress); assert!(dev.detailed_progress); assert!(test.detailed_progress); }
}