use crate::client::retry_client;
use anyhow::Result;
use futures_util::StreamExt;
use reqwest::{self, header, StatusCode};
use std::path::PathBuf;
use std::str::FromStr;
use tokio::fs::{self, OpenOptions};
use tokio::io::AsyncWriteExt;
use tokio::time::{sleep, timeout, Duration};
fn get_etag(response: &reqwest::Response) -> String {
response
.headers()
.get(header::ETAG)
.map(|tag| tag.to_str().unwrap_or(""))
.unwrap_or("")
.to_string()
}
fn get_content_length(response: &reqwest::Response) -> u64 {
response
.headers()
.get(header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok())
.and_then(|value_str| u64::from_str(value_str).ok())
.unwrap_or(1)
}
async fn download_resume(url: &str, file_name: &PathBuf, etag: &str) -> Result<String> {
let client = retry_client();
let file_size = if file_name.exists() {
fs::metadata(file_name).await?.len()
} else {
0
};
let head_resp: reqwest::Response = client.head(url).send().await?;
let latest_etag = get_etag(&head_resp);
let content_length = get_content_length(&head_resp);
if file_size == content_length && latest_etag != "" && latest_etag == etag {
return Ok(etag.into());
}
let mut headers = header::HeaderMap::new();
headers.insert(header::RANGE, format!("bytes={}-", file_size).parse()?);
let response = client.get(url).headers(headers).send().await?;
let status = response.status();
if status == StatusCode::PARTIAL_CONTENT {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&file_name)
.await?;
let mut stream = response.bytes_stream();
while let Some(chunk) = timeout(Duration::from_secs(30), stream.next()).await? {
let chunk = chunk?;
file.write_all(&chunk).await?;
}
}
Ok(latest_etag)
}
pub async fn retry_download(
url: &str,
file_name: &PathBuf,
etag: &str,
retry: i32,
) -> Result<String> {
let mut count = retry;
let mut err = anyhow::anyhow!("download failed");
while count > 0 {
let result = download_resume(url, file_name, etag).await;
match result {
Ok(retrieved_etag) => return Ok(retrieved_etag),
Err(e) => err = e,
}
count -= 1;
sleep(Duration::from_secs(3)).await;
}
Err(err)
}