Skip to main content

flodl_cli/util/
http.rs

1//! HTTP file downloads via curl or wget.
2//!
3//! Shells out to curl/wget because TLS in pure std is impractical without
4//! crates, and these tools are ubiquitous (curl ships with Windows 10+).
5
6use std::path::Path;
7use std::process::{Command, Stdio};
8
9use super::system::has_command;
10
11/// Detected download tool.
12enum Downloader {
13    Curl,
14    Wget,
15}
16
17fn detect_downloader() -> Result<Downloader, String> {
18    if has_command("curl") {
19        Ok(Downloader::Curl)
20    } else if has_command("wget") {
21        Ok(Downloader::Wget)
22    } else {
23        Err(
24            "Neither curl nor wget is installed.\n\
25             Install one of them:\n\
26             \n\
27             \x20 Ubuntu/Debian:  sudo apt install curl\n\
28             \x20 Fedora/RHEL:    sudo dnf install curl\n\
29             \x20 macOS:          available by default\n\
30             \x20 Windows 10+:    curl.exe is built-in"
31                .into(),
32        )
33    }
34}
35
36/// Download a file from `url` to `dest`, showing progress on the terminal.
37///
38/// Overwrites `dest` if it exists. Creates parent directories.
39pub fn download_file(url: &str, dest: &Path) -> Result<(), String> {
40    let dl = detect_downloader()?;
41
42    if let Some(parent) = dest.parent() {
43        std::fs::create_dir_all(parent)
44            .map_err(|e| format!("cannot create directory {}: {}", parent.display(), e))?;
45    }
46
47    let dest_str = dest
48        .to_str()
49        .ok_or_else(|| "destination path is not valid UTF-8".to_string())?;
50
51    let status = match dl {
52        Downloader::Curl => Command::new("curl")
53            .args(["-L", "--progress-bar", "-o", dest_str, url])
54            .stdout(Stdio::inherit())
55            .stderr(Stdio::inherit())
56            .status(),
57        Downloader::Wget => Command::new("wget")
58            .args(["-q", "--show-progress", "-O", dest_str, url])
59            .stdout(Stdio::inherit())
60            .stderr(Stdio::inherit())
61            .status(),
62    };
63
64    match status {
65        Ok(s) if s.success() => Ok(()),
66        Ok(s) => Err(format!(
67            "download failed (exit code {})\n  URL: {}",
68            s.code().unwrap_or(-1),
69            url
70        )),
71        Err(e) => Err(format!("failed to run download command: {}", e)),
72    }
73}