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(
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
36pub 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}