use anyhow::{Context, Result};
use futures::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::Client;
use reqwest::header::CONTENT_LENGTH;
use std::fs::File as StdFile;
use std::path::{Path, PathBuf};
use tokio::fs;
use tokio::io::AsyncWriteExt;
pub async fn download_binary(client: &Client, url: &str, dest: &Path) -> Result<()> {
let resp = client
.get(url)
.header(reqwest::header::USER_AGENT, crate::USER_AGENT)
.send()
.await?
.error_for_status()?;
let total = resp
.headers()
.get(CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let mut chunks = resp.bytes_stream();
let tmp_dest = temp_download_path(dest);
let mut file = tokio::fs::File::from_std(StdFile::create(&tmp_dest)?);
let bar = ProgressBar::new(total);
bar.set_style(progress_style()?);
while let Some(chunk) = chunks.next().await.transpose()? {
bar.inc(chunk.len() as u64);
file.write_all(&chunk).await?;
}
file.flush().await?;
drop(file);
fs::rename(&tmp_dest, dest).await?;
bar.finish_and_clear();
Ok(())
}
fn temp_download_path(dest: &Path) -> PathBuf {
let mut file_name = dest
.file_name()
.map(|name| name.to_os_string())
.unwrap_or_else(|| "binary".into());
file_name.push(".download");
dest.with_file_name(file_name)
}
fn progress_style() -> Result<ProgressStyle> {
ProgressStyle::with_template(
"{wide_bar:.cyan/blue} {percent:>3}% {binary_bytes}/{binary_total_bytes} {eta}s",
)
.map(|style| style.progress_chars("=> "))
.context("Failed to configure progress bar")
}