1use eyre::{Context, ContextCompat};
2#[cfg(unix)]
3use std::os::unix::prelude::PermissionsExt;
4use std::path::{Path, PathBuf};
5use tokio::io::AsyncWriteExt;
6
7fn get_filename(response: &reqwest::Response) -> Option<String> {
8 if let Some(content_disposition) = response.headers().get("content-disposition") {
9 if let Ok(filename) = content_disposition.to_str() {
10 if let Some(name) = filename.split("filename=").nth(1) {
11 return Some(name.trim_matches('"').to_string());
12 }
13 }
14 }
15
16 let path = Path::new(response.url().as_str());
18 if let Some(name) = path.file_name() {
19 if let Some(filename) = name.to_str() {
20 return Some(filename.to_string());
21 }
22 }
23
24 None
25}
26
27pub async fn download_file<T>(url: T, target_dir: &Path) -> Result<PathBuf, eyre::ErrReport>
28where
29 T: reqwest::IntoUrl + std::fmt::Display + Copy,
30{
31 tokio::fs::create_dir_all(&target_dir)
32 .await
33 .wrap_err("failed to create parent folder")?;
34
35 let response = reqwest::get(url)
36 .await
37 .wrap_err_with(|| format!("failed to request operator from `{url}`"))?;
38
39 let filename = get_filename(&response).context("Could not find a filename")?;
40 let bytes = response
41 .bytes()
42 .await
43 .wrap_err("failed to read operator from `{uri}`")?;
44 let path = target_dir.join(filename);
45 let mut file = tokio::fs::File::create(&path)
46 .await
47 .wrap_err("failed to create target file")?;
48 file.write_all(&bytes)
49 .await
50 .wrap_err("failed to write downloaded operator to file")?;
51 file.sync_all().await.wrap_err("failed to `sync_all`")?;
52
53 #[cfg(unix)]
54 file.set_permissions(std::fs::Permissions::from_mode(0o764))
55 .await
56 .wrap_err("failed to make downloaded file executable")?;
57
58 Ok(path.to_path_buf())
59}