Rust 实现下载大文件进度条

rust下载大文件,实现进度条的话,必须要用异步的reqwest才能实现,blocking的reqwest无法使用。

引入依赖

[dependencies]
futures = "0.3.4"
tokio = { version = "1", features = ["full"] }

编写下载函数,实现进度条

install.rs


pub async fn download_package(url: &str) -> Result<String, anyhow::Error> {
    let url_last = url.split("/").last().unwrap();
    let base_dir = env::current_dir().expect("not found path");
    let filename = base_dir.join(url_last).to_str().unwrap().to_string();
    let path = Path::new(&filename);
    println!("下载包 {} 到 {:?}", url, filename);


    let client = Client::new();
    let total_size = {
        let resp = client.head(url).send().await?;
        if resp.status().is_success() {
            resp.headers()
                .get(header::CONTENT_LENGTH)
                .and_then(|ct_len| ct_len.to_str().ok())
                .and_then(|ct_len| ct_len.parse().ok())
                .unwrap_or(0)
        } else {
            return Err(anyhow!(
                "Couldn't download URL: {}. Error: {:?}",
                url,
                resp.status(),
            ));
        }
    };
    let client = Client::new();
    let mut request = client.get(url);
    let pb = ProgressBar::new(total_size);
    pb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
        .progress_chars("#>-"));

    if path.exists() {
        let size = path.metadata()?.len().saturating_sub(1);
        request = request.header(header::RANGE, format!("bytes={}-", size));
        pb.inc(size);
    }
    let mut source = request.send().await?;
    let mut dest = fs::OpenOptions::new().create(true).append(true).open(&path)?;
    while let Some(chunk) = source.chunk().await? {
        dest.write_all(&chunk)?;
        pb.inc(chunk.len() as u64);
    }
    println!("下载完成");
    Ok(filename)
}

由于下载函数是async函数了,所以必须await因而main函数也必须改造成async函数


#[tokio::main]
async fn main() {
   // 下载包
            let package = if let Some(p) = sub.value_of("package") {
                if p.starts_with("http") {
                    match install::download_package(p).await {
                        Ok(p) => p,
                        Err(e) => panic!("下载包失败:{}", e),
                    }
                } else {
                    sub.value_of("package").unwrap().to_string()
                }
            } else {
                panic!("package不能为空")
            };
}