use std::cmp::Ordering;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use typed_builder::TypedBuilder;
use crate::client::proxy::ProxyConfig;
use crate::download::config::speed_profile::SpeedProfile;
pub type ProgressCallback = Arc<dyn Fn(u64, u64) + Send + Sync>;
pub(crate) type ProgressCounters = Arc<std::sync::Mutex<HashMap<u64, (Arc<AtomicU64>, Arc<AtomicU64>)>>>;
pub(super) const DEFAULT_RETRY_ATTEMPTS: usize = 3;
pub(super) const DEFAULT_CLEANUP_THRESHOLD: usize = 1000;
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DownloadPriority {
Low = 0,
#[default]
Normal = 1,
High = 2,
Critical = 3,
}
impl DownloadPriority {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Self::Low,
1 => Self::Normal,
2 => Self::High,
3 => Self::Critical,
_ => Self::Normal,
}
}
}
impl std::fmt::Display for DownloadPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Low => f.write_str("Low"),
Self::Normal => f.write_str("Normal"),
Self::High => f.write_str("High"),
Self::Critical => f.write_str("Critical"),
}
}
}
pub(crate) struct DownloadTask {
pub(crate) url: String,
pub(crate) destination: PathBuf,
pub(crate) priority: DownloadPriority,
pub(crate) id: u64,
pub(crate) progress_callback: Option<ProgressCallback>,
pub(crate) http_headers: Option<crate::model::format::HttpHeaders>,
pub(crate) range_constraint: Option<(u64, u64)>,
}
impl std::fmt::Debug for DownloadTask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DownloadTask")
.field("url", &self.url)
.field("destination", &self.destination)
.field("priority", &self.priority)
.field("id", &self.id)
.field("range_constraint", &self.range_constraint)
.field(
"progress_callback",
&format_args!(
"{}",
if self.progress_callback.is_some() {
"Some(Fn)"
} else {
"None"
}
),
)
.finish()
}
}
impl PartialEq for DownloadTask {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for DownloadTask {}
impl PartialOrd for DownloadTask {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DownloadTask {
fn cmp(&self, other: &Self) -> Ordering {
let priority_cmp = (other.priority as i32).cmp(&(self.priority as i32));
if priority_cmp != Ordering::Equal {
return priority_cmp;
}
other.id.cmp(&self.id)
}
}
#[derive(Debug, Clone, TypedBuilder)]
pub struct ManagerConfig {
#[builder(default = SpeedProfile::default().max_concurrent_downloads())]
pub max_concurrent_downloads: usize,
#[builder(default = SpeedProfile::default().segment_size())]
pub segment_size: usize,
#[builder(default = SpeedProfile::default().parallel_segments())]
pub parallel_segments: usize,
#[builder(default = DEFAULT_RETRY_ATTEMPTS)]
pub retry_attempts: usize,
#[builder(default = SpeedProfile::default().max_buffer_size())]
pub max_buffer_size: usize,
#[builder(default)]
pub proxy: Option<ProxyConfig>,
#[builder(default)]
pub speed_profile: SpeedProfile,
#[builder(default = DEFAULT_CLEANUP_THRESHOLD)]
pub cleanup_threshold: usize,
#[builder(default)]
pub user_agent: Option<String>,
}
impl ManagerConfig {
pub fn from_speed_profile(profile: SpeedProfile) -> Self {
Self {
max_concurrent_downloads: profile.max_concurrent_downloads(),
segment_size: profile.segment_size(),
parallel_segments: profile.parallel_segments(),
retry_attempts: DEFAULT_RETRY_ATTEMPTS,
max_buffer_size: profile.max_buffer_size(),
proxy: None,
speed_profile: profile,
cleanup_threshold: DEFAULT_CLEANUP_THRESHOLD,
user_agent: None,
}
}
pub fn with_speed_profile(mut self, profile: SpeedProfile) -> Self {
self.max_concurrent_downloads = profile.max_concurrent_downloads();
self.segment_size = profile.segment_size();
self.parallel_segments = profile.parallel_segments();
self.max_buffer_size = profile.max_buffer_size();
self.speed_profile = profile;
self
}
}
impl Default for ManagerConfig {
fn default() -> Self {
let profile = SpeedProfile::default();
Self::from_speed_profile(profile)
}
}
impl std::fmt::Display for ManagerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ManagerConfig(profile={}, concurrent={}, segments={}x{}, retries={})",
self.speed_profile,
self.max_concurrent_downloads,
self.parallel_segments,
self.segment_size,
self.retry_attempts
)
}
}
#[derive(Clone)]
pub struct ProgressUpdate {
pub download_id: u64,
pub downloaded_bytes: u64,
pub total_bytes: u64,
}
impl std::fmt::Display for ProgressUpdate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ProgressUpdate(id={}, downloaded={}, total={})",
self.download_id, self.downloaded_bytes, self.total_bytes
)
}
}
#[derive(Debug, Clone)]
pub enum DownloadStatus {
Queued,
Downloading {
downloaded_bytes: u64,
total_bytes: u64,
},
Completed,
Failed {
reason: String,
},
Canceled,
}
impl std::fmt::Display for DownloadStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Queued => f.write_str("Queued"),
Self::Downloading {
downloaded_bytes,
total_bytes,
} => write!(f, "Downloading({}/{})", downloaded_bytes, total_bytes),
Self::Completed => f.write_str("Completed"),
Self::Failed { reason } => write!(f, "Failed(reason={})", reason),
Self::Canceled => f.write_str("Canceled"),
}
}
}