tonhowtf/omniget · error · anyhow::Error

Failed to download attachment

Error message

Failed to download attachment: {}

What it means

download_attachment performs an HTTP GET for a course attachment via reqwest. If the request fails at the transport level (connection error, DNS failure, TLS error, timeout), the reqwest::Error is wrapped as "Failed to download attachment: {}". The download never got an HTTP response at all.

Solutions

  1. Check network connectivity to the attachment host (curl -v the URL to reproduce).
  2. Retry with backoff — transport errors are frequently transient.
  3. Verify proxy/VPN settings and that reqwest client config (timeouts, TLS features) matches the environment.
  4. Inspect the wrapped reqwest::Error message for the specific cause (DNS vs connect vs timeout) and fix that layer.

Example fix

// before
let resp = client.get(url).send().await
    .map_err(|e| anyhow!("Failed to download attachment: {}", e))?;

// after
let resp = client.get(url).send().await
    .map_err(|e| anyhow!("Failed to download attachment: {}", e))
    .or_else(|e| async move {
        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
        client.get(url).send().await
            .map_err(|e2| anyhow!("Failed to download attachment after retry: {}", e2))
    })
    .await?;
Defensive patterns

Strategy: retry

Validate before calling

// Cheap reachability probe before the download
let host_ok = reqwest::Client::new().head(url).send().await.is_ok();
if !host_ok { eprintln!("Host unreachable; check network before retrying"); }

Type guard

fn is_transport_error(e: &anyhow::Error) -> bool {
    e.to_string().contains("Failed to download attachment")
}

Try / catch

let mut attempt = 0;
loop {
    match download_attachment(&client, &url, &dest, &token).await {
        Err(e) if e.to_string().contains("Failed to download attachment") && attempt < 3 => {
            attempt += 1;
            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
        }
        other => break other,
    }
}

Prevention

When it happens

Trigger: client.get(url).send().await returns Err — network unreachable, DNS resolution failure, TLS handshake failure, connection reset, or request timeout — while downloading a course attachment.

Common situations: Offline or flaky network; VPN/proxy interference; firewall blocking the course host; misconfigured DNS; server temporarily down; TLS certificate problems on the endpoint.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/429f7d59426fa8cb. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/course_utils.rs:81

    let path = format!("{}/{}", dir, filename);

    if Path::new(&path).exists() {
        let meta = std::fs::metadata(&path);
        if meta.map(|m| m.len() > 0).unwrap_or(false) {
            return Ok(0);
        }
    }

    if cancel_token.is_cancelled() {
        return Err(anyhow!("Download cancelled"));
    }

    let resp = client
        .get(url)
        .send()
        .await
        .map_err(|e| anyhow!("Failed to download attachment: {}", e))?;

    if !resp.status().is_success() {
        return Err(anyhow!(
            "Attachment download failed: HTTP {}",
            resp.status()
        ));
    }

    let bytes = resp.bytes().await?;
    let size = bytes.len() as u64;

    if size == 0 {
        return Ok(0);
    }

    let part_path = format!("{}.part", path);
    std::fs::write(&part_path, &bytes)?;
    std::fs::rename(&part_path, &path)?;

View on GitHub (pinned to 8600b91f42)