upstream-rs 1.5.3

Fetch package updates directly from the source.
Documentation
use anyhow::{Context, Result, bail};
use reqwest::Client;
use std::path::Path;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;

pub async fn download_file<F>(
    client: &Client,
    url: &str,
    destination: &Path,
    progress: &mut Option<F>,
) -> Result<()>
where
    F: FnMut(u64, u64),
{
    let response = client
        .get(url)
        .send()
        .await
        .context(format!("Failed to download from {}", url))?;

    response
        .error_for_status_ref()
        .context("Download request failed")?;

    let total_bytes = response.content_length().unwrap_or(0);

    let mut file = File::create(destination)
        .await
        .context(format!("Failed to create file at {:?}", destination))?;

    let mut stream = response.bytes_stream();
    let mut total_read: u64 = 0;

    use futures_util::StreamExt;
    while let Some(chunk) = stream.next().await {
        let chunk = chunk.context("Failed to read download chunk")?;

        file.write_all(&chunk)
            .await
            .context("Failed to write to file")?;

        total_read += chunk.len() as u64;

        if let Some(cb) = progress.as_mut() {
            cb(total_read, total_bytes);
        }
    }

    file.flush().await.context("Failed to flush file")?;

    if total_bytes > 0 && total_read != total_bytes {
        bail!(
            "Download size mismatch: expected {} bytes, got {} bytes",
            total_bytes,
            total_read
        );
    }

    Ok(())
}