use crate::http::{
Error, HttpDownloadMode, Status, builder_utils, config::HttpDownloadConfig,
request_utils::RequestBuilderExt,
};
use super::{HttpDownloader, HttpDownloaderSetupErrors, info::HttpDownloadInfo};
use reqwest::{
Client,
header::{ACCEPT_RANGES, CONTENT_DISPOSITION, CONTENT_LENGTH},
};
use std::{
marker::PhantomData,
sync::{Arc, Mutex},
time::Duration,
};
use tokio_util::sync::CancellationToken;
pub struct ClientRequired;
pub struct UrlRequired;
pub struct SetupBuilder;
pub struct HttpDownloaderSetupBuilder<State = SetupBuilder> {
client: Option<Client>,
raw_url: Option<String>,
tasks_count: Option<u8>,
throttle_speed: Option<u64>,
state: PhantomData<State>,
timeout: Option<Duration>,
}
impl HttpDownloaderSetupBuilder<ClientRequired> {
pub fn client(mut self, client: Client) -> HttpDownloaderSetupBuilder<UrlRequired> {
self.client = Some(client);
HttpDownloaderSetupBuilder {
client: self.client,
raw_url: self.raw_url,
tasks_count: self.tasks_count,
state: PhantomData::<UrlRequired>,
throttle_speed: self.throttle_speed,
timeout: self.timeout,
}
}
}
impl HttpDownloaderSetupBuilder<UrlRequired> {
pub fn url(mut self, raw_url: &str) -> HttpDownloaderSetupBuilder<SetupBuilder> {
self.raw_url = Some(raw_url.to_string());
HttpDownloaderSetupBuilder {
client: self.client,
raw_url: self.raw_url,
tasks_count: self.tasks_count,
state: PhantomData::<SetupBuilder>,
throttle_speed: self.throttle_speed,
timeout: self.timeout,
}
}
}
impl HttpDownloaderSetupBuilder {
pub(super) fn default() -> HttpDownloaderSetupBuilder<ClientRequired> {
HttpDownloaderSetupBuilder::<ClientRequired> {
client: None,
raw_url: None,
tasks_count: None,
state: PhantomData::<ClientRequired>,
throttle_speed: None,
timeout: None,
}
}
pub fn tasks_count(mut self, count: u8) -> Self {
self.tasks_count = Some(count);
self
}
pub fn speed_limit(mut self, kilobytes_per_second: u64) -> Self {
self.throttle_speed = Some(1024 * kilobytes_per_second);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
fn generate_config(&self) -> Result<HttpDownloadConfig, HttpDownloaderSetupErrors> {
Ok(HttpDownloadConfig::default()
.try_set_tasks_count(self.tasks_count)?
.set_throttle_speed(self.throttle_speed)
.set_timeout(self.timeout))
}
pub fn build(self) -> Result<HttpDownloaderSetup, HttpDownloaderSetupErrors> {
let config = self.generate_config()?;
Ok(HttpDownloaderSetup {
client: self.client.unwrap(),
raw_url: self.raw_url.unwrap(),
config,
})
}
}
pub struct HttpDownloaderSetup {
client: Client,
raw_url: String,
config: HttpDownloadConfig,
}
impl HttpDownloaderSetup {
async fn get_headers(&self) -> Result<reqwest::Response, Error> {
self.client
.head(&self.raw_url)
.send_with_timeout(self.config.timeout)
.await
}
fn generate_info(&self, headers_response: reqwest::Response) -> HttpDownloadInfo {
let content_disposition = &headers_response.headers().get(CONTENT_DISPOSITION);
let content_length = &headers_response.headers().get(CONTENT_LENGTH);
let accept_ranges = &headers_response.headers().get(ACCEPT_RANGES);
HttpDownloadInfo::default()
.extract_and_set_filename(&self.raw_url, content_disposition)
.extract_and_set_content_length(content_length)
.extract_and_set_is_resumable(accept_ranges)
}
fn generate_byte_ranges(
config: &HttpDownloadConfig,
mode: &HttpDownloadMode,
) -> Vec<(u64, u64)> {
match mode {
HttpDownloadMode::NonResumable => vec![],
HttpDownloadMode::ResumableStream => vec![(0, 0)],
HttpDownloadMode::ResumableMultithread => {
let mut byte_ranges = vec![];
let split_content = config.split_result.unwrap();
for index in 0..config.tasks_count as u64 {
byte_ranges.push(builder_utils::calculate_part_range(split_content, index));
}
byte_ranges
}
}
}
pub async fn init(self) -> Result<HttpDownloader, Error> {
let headers_response = self.get_headers().await?;
let info = self.generate_info(headers_response);
let mode = builder_utils::determine_mode(self.config.tasks_count, &info);
let mut config = self.config;
(mode == HttpDownloadMode::NonResumable).then(|| config.tasks_count = 0);
config.split_result =
builder_utils::try_split_content(&mode, &info.content_length(), config.tasks_count);
Ok(HttpDownloader {
client: Arc::new(self.client),
raw_url: Arc::new(self.raw_url),
info,
byte_ranges: HttpDownloaderSetup::generate_byte_ranges(&config, &mode),
mode,
config,
status: Arc::new(Mutex::new(Status::Pending)),
token: CancellationToken::new(),
})
}
}