tonhowtf/omniget · error

Failed to read .torrent file

Error message

Failed to read .torrent file: {}

What it means

Wraps a `tokio::fs::read` failure in `download` when the URL is actually a local path to a `.torrent` file. Before the read, the code checks `path.exists()` and the `.torrent` extension; the read itself then failed (I/O error), so the raw metainfo bytes could not be loaded for `AddTorrent::from_bytes`.

Solutions

  1. Read the wrapped `{}` IO error to distinguish permission-denied vs not-found vs is-a-directory
  2. Re-check that the file still exists and is a regular readable file right before download; re-pick the .torrent file if it moved
  3. Fix filesystem permissions (chmod/ACL) or run the app with access to the file's location
  4. Pass a magnet URI instead of a file path if local file access is problematic

Example fix

// before
let bytes = tokio::fs::read(path).await
    .map_err(|e| anyhow::anyhow!("Failed to read .torrent file: {}", e))?;
// after
let bytes = match tokio::fs::read(path).await {
    Ok(b) => b,
    Err(e) => {
        tracing::warn!("cannot read torrent file {}: {e}; falling back to magnet URL", path.display());
        return Err(anyhow!("Torrent file unreadable: {e}. Re-select the .torrent file."));
    }
};
Defensive patterns

Strategy: validation

Validate before calling

fn validate_torrent_file(path: &str) -> Result<(), String> {
    let p = std::path::Path::new(path);
    if !p.is_file() { return Err(format!("not a regular file: {path}")); }
    if p.extension().map(|e| e != "torrent").unwrap_or(true) { return Err("missing .torrent extension".into()); }
    std::fs::metadata(p).and_then(|m| m.permissions()).map_err(|e| e.to_string())?;
    Ok(())
}

Type guard

fn is_readable_torrent_path(url: &str) -> bool {
    let p = std::path::Path::new(url);
    p.is_file() && p.extension().map(|e| e == "torrent").unwrap_or(false)
}

Try / catch

match downloader.download(&info, &opts, tx).await {
    Err(e) if e.to_string().contains("Failed to read .torrent file") => {
        Err(anyhow!("The .torrent file could not be read ({e}). Re-select or re-download the file."))
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `download` with a local `.torrent` file path that passed the `exists()` check but failed to read: permission denied on the file, it's a directory named `x.torrent`, the file vanished between the exists-check and the read (TOCTOU), or a broken symlink.

Common situations: Torrent file moved/deleted after being picked in the UI; file on a disconnected network drive or unmounted volume; OS permission restrictions (e.g. macOS sandbox or Windows ACLs) blocking read; a folder misleadingly named `foo.torrent`.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at src-tauri/src/platforms/magnet/mod.rs:178

                *dir_guard = Some(output_dir.clone());
                s
            } else {
                tracing::info!("[magnet] reusing existing session");
                guard.as_ref().unwrap().clone()
            }
        };

        let add_torrent = if url.starts_with("magnet:")
            || url.starts_with("http://")
            || url.starts_with("https://")
        {
            AddTorrent::from_url(url)
        } else {
            let path = std::path::Path::new(url);
            if path.exists() && path.extension().map(|e| e == "torrent").unwrap_or(false) {
                let bytes = tokio::fs::read(path)
                    .await
                    .map_err(|e| anyhow::anyhow!("Failed to read .torrent file: {}", e))?;
                AddTorrent::from_bytes(bytes)
            } else {
                AddTorrent::from_url(url)
            }
        };
        let only_files = opts
            .torrent_files
            .as_ref()
            .filter(|v| !v.is_empty())
            .cloned();
        if let Some(sel) = &only_files {
            tracing::info!("[magnet] selective download: {} file(s)", sel.len());
        }
        let trackers = if opts.torrent_auto_trackers {
            let list = crate::core::trackers::extra_trackers();
            tracing::info!("[magnet] injecting {} public trackers", list.len());
            Some(list)
        } else {

View on GitHub (pinned to 8600b91f42)