1use std::path::Path;
7use std::process::{Command, Stdio};
8
9use super::system::has_command;
10
11enum 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("Neither curl nor wget is installed.\n\
24 Install one of them:\n\
25 \n\
26 \x20 Ubuntu/Debian: sudo apt install curl\n\
27 \x20 Fedora/RHEL: sudo dnf install curl\n\
28 \x20 macOS: available by default\n\
29 \x20 Windows 10+: curl.exe is built-in"
30 .into())
31 }
32}
33
34pub fn download_file(url: &str, dest: &Path) -> Result<(), String> {
38 let dl = detect_downloader()?;
39
40 if let Some(parent) = dest.parent() {
41 std::fs::create_dir_all(parent)
42 .map_err(|e| format!("cannot create directory {}: {}", parent.display(), e))?;
43 }
44
45 let dest_str = dest
46 .to_str()
47 .ok_or_else(|| "destination path is not valid UTF-8".to_string())?;
48
49 let status = match dl {
50 Downloader::Curl => Command::new("curl")
51 .args(["-L", "--progress-bar", "-o", dest_str, url])
52 .stdout(Stdio::inherit())
53 .stderr(Stdio::inherit())
54 .status(),
55 Downloader::Wget => Command::new("wget")
56 .args(["-q", "--show-progress", "-O", dest_str, url])
57 .stdout(Stdio::inherit())
58 .stderr(Stdio::inherit())
59 .status(),
60 };
61
62 match status {
63 Ok(s) if s.success() => Ok(()),
64 Ok(s) => Err(format!(
65 "download failed (exit code {})\n URL: {}",
66 s.code().unwrap_or(-1),
67 url
68 )),
69 Err(e) => Err(format!("failed to run download command: {}", e)),
70 }
71}