use super::algorithms::parse_content_disposition;
use super::enums::{AuthMethod, FileChecksum};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DownloadProgress {
pub bytes_downloaded: u64,
pub total_bytes: u64,
pub rate: f64,
pub remaining_time: std::time::Duration,
pub progress_percentage: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DownloadOptions {
pub save_path: String,
pub create_dirs: bool,
pub path_policy: PathPolicy,
#[serde(default)]
pub headers: Vec<(String, String)>,
pub user_agent: Option<String>,
pub timeout: u64,
pub max_redirects: u32,
pub tls_verify: bool,
pub concurrency: u32,
pub chunk_size: Option<u64>,
pub enable_range: bool,
pub rate_limit: Option<u64>,
pub per_connection_rate_limit: Option<u64>,
pub max_retries: u32,
pub proxy: Option<String>,
pub resume_download: bool,
pub buffer_size: usize,
pub progress_interval: u64,
}
impl Default for DownloadOptions {
fn default() -> Self {
Self {
save_path: "downloads".to_string(),
create_dirs: true,
path_policy: PathPolicy::default(),
headers: Vec::new(),
user_agent: Some("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36".into()),
timeout: 30,
max_redirects: 5,
tls_verify: true,
concurrency: 4,
chunk_size: None,
enable_range: true,
rate_limit: None,
per_connection_rate_limit: None,
max_retries: 3,
proxy: None,
resume_download: false,
buffer_size: 8192, progress_interval: 500,
}
}
}
impl DownloadOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_path_policy(mut self, policy: PathPolicy) -> Self {
self.path_policy = policy;
self
}
pub fn with_save_path(mut self, path: impl Into<String>) -> Self {
self.save_path = path.into();
self
}
pub fn with_concurrency(mut self, n: u32) -> Self {
self.concurrency = n;
self
}
pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((key.into(), value.into()));
self
}
pub fn with_proxy(mut self, proxy: impl Into<String>) -> Self {
self.proxy = Some(proxy.into());
self
}
pub fn with_rate_limit(mut self, limit: u64) -> Self {
self.rate_limit = Some(limit);
self
}
pub fn with_per_connection_rate_limit(mut self, limit: u64) -> Self {
self.per_connection_rate_limit = Some(limit);
self
}
pub fn with_resume_download(mut self, resume: bool) -> Self {
self.resume_download = resume;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DownloadMeta {
pub content_type: Option<String>,
pub etag: Option<String>,
pub last_modified: Option<String>,
pub expected_size: Option<u64>,
pub suggested_filename: Option<String>,
pub download_start: Option<DateTime<Utc>>,
pub checksum: Option<FileChecksum>,
}
impl DownloadMeta {
pub fn from_headers(headers: &reqwest::header::HeaderMap) -> Self {
let content_type = headers
.get("Content-Type")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
let etag = headers
.get("ETag")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
let last_modified = headers
.get("Last-Modified")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
let content_length = headers
.get("Content-Length")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse().ok());
let suggested_filename = headers
.get("Content-Disposition")
.and_then(|v| v.to_str().ok())
.and_then(parse_content_disposition);
Self {
content_type,
etag,
last_modified,
expected_size: content_length,
suggested_filename,
download_start: Some(Utc::now()),
checksum: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedResource {
pub id: u32,
pub url: String,
pub headers: Vec<(String, String)>,
pub auth: Option<AuthMethod>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathPolicy {
pub naming: String,
pub template: Option<String>,
pub organization: String,
pub dir_template: Option<String>,
pub conflict: String,
pub sanitize: bool,
pub max_length: Option<usize>,
}
impl Default for PathPolicy {
fn default() -> Self {
Self {
naming: "auto".to_string(),
template: None,
organization: "flat".to_string(),
dir_template: None,
conflict: "overwrite".to_string(),
sanitize: true,
max_length: None,
}
}
}
impl PathPolicy {
pub fn new() -> Self {
Self::default()
}
pub fn with_naming(mut self, naming: impl Into<String>) -> Self {
self.naming = naming.into();
self
}
pub fn with_template(mut self, template: impl Into<String>) -> Self {
self.template = Some(template.into());
self
}
pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
self.organization = organization.into();
self
}
pub fn with_dir_template(mut self, dir_template: impl Into<String>) -> Self {
self.dir_template = Some(dir_template.into());
self
}
pub fn with_conflict(mut self, conflict: impl Into<String>) -> Self {
self.conflict = conflict.into();
self
}
pub fn with_sanitize(mut self, sanitize: bool) -> Self {
self.sanitize = sanitize;
self
}
}