use anyhow::{Context, Result};
use reqwest::Client;
use std::path::Path;
use tokio::io::AsyncWriteExt;
pub struct Downloader {
client: Client,
}
impl Downloader {
pub fn new() -> Self {
Self {
client: Client::builder()
.timeout(std::time::Duration::from_secs(300))
.build()
.expect("failed to build HTTP client"),
}
}
pub async fn download(&self, url: &str, dest: &Path) -> Result<()> {
self.download_with_headers(url, dest, &reqwest::header::HeaderMap::new())
.await
}
pub async fn download_with_headers(
&self,
url: &str,
dest: &Path,
headers: &reqwest::header::HeaderMap,
) -> Result<()> {
let mut request = self.client.get(url);
if !headers.is_empty() {
request = request.headers(headers.clone());
}
let response = request
.send()
.await
.context("Failed to send HTTP request")?;
if !response.status().is_success() {
anyhow::bail!(
"Download failed: HTTP {} for URL: {}",
response.status(),
url
);
}
let bytes = response
.bytes()
.await
.context("Failed to read response bytes")?;
let mut file = tokio::fs::File::create(dest)
.await
.context(format!("Failed to create file: {}", dest.display()))?;
file.write_all(&bytes)
.await
.context("Failed to write to file")?;
file.sync_all().await.context("Failed to sync file")?;
Ok(())
}
}
impl Default for Downloader {
fn default() -> Self {
Self::new()
}
}