|
|
|
@ -17,6 +17,7 @@ use http::{ |
|
|
|
Uri,
|
|
|
|
};
|
|
|
|
use http_body_util::BodyDataStream;
|
|
|
|
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
|
|
|
use log::{debug, error};
|
|
|
|
use reqwest::{redirect::Policy, Body};
|
|
|
|
use tokio::{fs::File, io::AsyncWriteExt as _, time::timeout};
|
|
|
|
@ -54,6 +55,7 @@ pub(super) struct Client { |
|
|
|
client: HttpClient,
|
|
|
|
default_headers: HeaderMap,
|
|
|
|
body_timeout: Option<Duration>,
|
|
|
|
progress: Option<MultiProgress>,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -93,6 +95,7 @@ impl Client { |
|
|
|
header::USER_AGENT,
|
|
|
|
HeaderValue::from_str(&( crate_name!().to_string() + "/"
|
|
|
|
+ crate_version!() )).unwrap() );
|
|
|
|
let progress = None;
|
|
|
|
|
|
|
|
Ok(Self { buffer
|
|
|
|
, rate_limit
|
|
|
|
@ -100,7 +103,8 @@ impl Client { |
|
|
|
, timeout
|
|
|
|
, client
|
|
|
|
, default_headers
|
|
|
|
, body_timeout })
|
|
|
|
, body_timeout
|
|
|
|
, progress })
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_http_client( buffer: usize |
|
|
|
@ -133,6 +137,11 @@ impl Client { |
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn init_progress(mut self) -> Self {
|
|
|
|
self.progress = Some(MultiProgress::new());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn set_body_timeout(mut self, timeout: Option<Duration>) -> Self {
|
|
|
|
self.body_timeout = timeout;
|
|
|
|
self
|
|
|
|
@ -185,13 +194,15 @@ impl Client { |
|
|
|
, header::CONTENT_TYPE )
|
|
|
|
. or(Some("unknown".into()));
|
|
|
|
|
|
|
|
if let Some(content_length) = content_length {
|
|
|
|
let content_length = if let Some(content_length) = content_length {
|
|
|
|
if from != 0 && content_length - 1 <= from {
|
|
|
|
return Ok(DownloadState::Done { content_type, size: from as usize });
|
|
|
|
}
|
|
|
|
content_length
|
|
|
|
} else {
|
|
|
|
from = 0;
|
|
|
|
}
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
// - do the neccessry request.
|
|
|
|
let headers = &mut headers.clone();
|
|
|
|
@ -203,8 +214,11 @@ impl Client { |
|
|
|
// - open or create file
|
|
|
|
let file = util::open_or_create(&response.status(), &filename).await;
|
|
|
|
// - download Data
|
|
|
|
self.clone().store_body( file
|
|
|
|
let filename = filename.as_ref().as_os_str().to_string_lossy().to_string();
|
|
|
|
self.clone().store_body( &filename
|
|
|
|
, file
|
|
|
|
, from as usize
|
|
|
|
, content_length
|
|
|
|
, content_type
|
|
|
|
, response.body_mut() ).await
|
|
|
|
}
|
|
|
|
@ -263,8 +277,10 @@ impl Client { |
|
|
|
}
|
|
|
|
|
|
|
|
async fn store_body( self
|
|
|
|
, filename: &str |
|
|
|
, mut file: File
|
|
|
|
, mut size: usize |
|
|
|
, content_length: u64 |
|
|
|
, content_type: Option<String>
|
|
|
|
, body: &mut ClientBody ) -> DownloadResult {
|
|
|
|
let mut body = BodyDataStream::new(body);
|
|
|
|
@ -272,12 +288,31 @@ impl Client { |
|
|
|
content_type: content_type.clone(),
|
|
|
|
size
|
|
|
|
};
|
|
|
|
let bar_style = format!( "{} {{bar:40.cyan/blue}} {{decimal_bytes:>7}}/{{decimal_total_bytes:7}}"
|
|
|
|
, filename);
|
|
|
|
let bar_style = ProgressStyle::with_template(&bar_style)
|
|
|
|
. ok()
|
|
|
|
. map(|s| s.progress_chars("#=-"));
|
|
|
|
let bar = if let Some(bar_style) = bar_style {
|
|
|
|
self.progress.as_ref().map(|progress| {
|
|
|
|
progress.add( ProgressBar::new(content_length)
|
|
|
|
. with_style(bar_style)
|
|
|
|
. with_position(size as u64) )
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let data_future = body.next();
|
|
|
|
let data = if let Some(io_timeout) = self.body_timeout {
|
|
|
|
// give timeout somehow... probably from client.
|
|
|
|
timeout(io_timeout, data_future).await.map_err(|e| {
|
|
|
|
if let Some(bar) = &bar {
|
|
|
|
if let Some(progress) = &self.progress {
|
|
|
|
progress.remove(bar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
error::DownloadError::from(e).set_state(&state)
|
|
|
|
})?
|
|
|
|
} else {
|
|
|
|
@ -286,10 +321,19 @@ impl Client { |
|
|
|
|
|
|
|
match data {
|
|
|
|
None => break,
|
|
|
|
Some(Err(e)) =>
|
|
|
|
Err(error::DownloadError::from(e).set_state(&state))?,
|
|
|
|
Some(Err(e)) => {
|
|
|
|
if let Some(bar) = &bar {
|
|
|
|
if let Some(progress) = &self.progress {
|
|
|
|
progress.remove(bar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(error::DownloadError::from(e).set_state(&state))?
|
|
|
|
},
|
|
|
|
Some(Ok(data)) => {
|
|
|
|
size += data.len();
|
|
|
|
if let Some(bar) = &bar {
|
|
|
|
bar.set_position(size as u64);
|
|
|
|
}
|
|
|
|
state = DownloadState::Partial {
|
|
|
|
content_type: content_type.clone(),
|
|
|
|
size
|
|
|
|
|