stackify_common/
download.rs1use std::{
2 fmt::Debug,
3 fs::{self, File, Permissions},
4 io::{copy, BufReader, Write},
5 os::unix::fs::PermissionsExt,
6 path::{Path, PathBuf},
7 time::Duration,
8};
9
10use color_eyre::eyre::{eyre, Result};
11use flate2::bufread::GzDecoder;
12use reqwest::{header, Client, Url};
13use tar::Archive;
14
15pub async fn download_file<P: AsRef<Path> + Debug>(
16 url_str: &str,
17 tmp_dir: &P,
18 dl_start: impl FnOnce(u64),
19 mut dl_chunk: impl FnMut(u64, u64),
20) -> Result<PathBuf> {
21 let url = Url::parse(&url_str)?;
22 let filename = url
23 .path_segments()
24 .unwrap()
25 .last()
26 .expect("Could not determine filename.");
27
28 let tmp_file_path = tmp_dir.as_ref().join(filename);
29
30 let download_size = get_download_size(url).await?;
31 dl_start(download_size);
32
33 let mut tmp_file = File::create(&tmp_file_path)?;
34
35 let client = Client::builder()
36 .timeout(Duration::from_secs(500))
37 .build()?;
38
39 let request = client.get(url_str);
40 let mut download = request.send().await?;
41 while let Some(chunk) = download.chunk().await? {
42 dl_chunk(chunk.len() as u64, download_size);
43 tmp_file.write(&chunk)?;
44 }
45
46 tmp_file.flush()?;
47
48 Ok(tmp_file_path)
49}
50
51pub async fn download_bitcoin_core_binaries<P: AsRef<Path> + Debug>(
52 version: &str,
53 tmp_dir: &P,
54 dest_dir: &P,
55 dl_start: impl FnOnce(u64),
56 mut dl_chunk: impl FnMut(u64, u64),
57 dl_finished: impl FnOnce(),
58) -> Result<()> {
59 let filename = format!("bitcoin-{version}-x86_64-linux-gnu.tar.gz");
60 let url_str = format!("https://bitcoincore.org/bin/bitcoin-core-{version}/{filename}");
61
62 let url = Url::parse(&url_str)?;
63
64 let tmp_file_path = tmp_dir.as_ref().join(filename);
65
66 let download_size = get_download_size(url).await?;
67 dl_start(download_size);
68
69 let mut tmp_file = File::create(&tmp_file_path)?;
70
71 let client = Client::builder()
72 .timeout(Duration::from_secs(500))
73 .build()?;
74
75 let request = client.get(&url_str);
76 let mut download = request.send().await?;
77 while let Some(chunk) = download.chunk().await? {
78 dl_chunk(chunk.len() as u64, download_size);
79 tmp_file.write(&chunk)?;
80 }
81
82 tmp_file.flush()?;
83 dl_finished();
84
85 let tmp_file = File::open(&tmp_file_path)?;
86 let gz = GzDecoder::new(BufReader::new(tmp_file));
87
88 Archive::new(gz).unpack(&tmp_dir)?;
90
91 let bin_dir = tmp_dir
92 .as_ref()
93 .join(format!("bitcoin-{version}"))
94 .join("bin");
95
96 let bitcoin_cli_src = bin_dir.join("bitcoin-cli");
97 let bitcoin_cli_dest = dest_dir.as_ref().join("bitcoin-cli");
98 inner_copy(&bitcoin_cli_src, &bitcoin_cli_dest)?;
99 let bitcoind_src = bin_dir.join("bitcoind");
100 let bitcoind_dest = dest_dir.as_ref().join("bitcoind");
101 inner_copy(&bitcoind_src, &bitcoind_dest)?;
102
103 fs::remove_dir_all(&tmp_dir)?;
104
105 set_executable(&bitcoin_cli_dest)?;
106 set_executable(&bitcoind_dest)?;
107
108 Ok(())
109}
110
111pub fn download_dasel_binary<P: AsRef<Path>>(version: &str, dest_dir: P) -> Result<()> {
112 let url = format!(
113 "https://github.com/TomWright/dasel/releases/download/v{version}/dasel_linux_amd64"
114 );
115 let dest = dest_dir.as_ref().join("dasel");
116 let mut dest_file = std::fs::File::create(&dest)?;
117 let response = reqwest::blocking::get(&url)?;
118 copy(&mut response.text()?.as_bytes(), &mut dest_file)?;
119
120 set_executable(&dest)?;
121
122 Ok(())
123}
124
125pub fn inner_copy<P: AsRef<Path>>(src: &P, dest: &P) -> Result<()> {
126 let mut src_file = std::fs::File::open(src)?;
127 let mut dest_file = std::fs::File::create(dest)?;
128
129 copy(&mut src_file, &mut dest_file)?;
130
131 Ok(())
132}
133
134pub fn set_executable<P: AsRef<Path>>(path: &P) -> Result<()> {
135 let perm = Permissions::from_mode(0o744);
136 let file = File::open(path)?;
137 file.set_permissions(perm)?;
138 Ok(())
139}
140
141async fn get_download_size(url: Url) -> Result<u64> {
143 let client = reqwest::Client::new();
144 let download_size = {
147 let resp = client.head(url.as_str()).send().await?;
148 if resp.status().is_success() {
149 resp.headers() .get(header::CONTENT_LENGTH) .and_then(|ct_len| ct_len.to_str().ok()) .and_then(|ct_len| ct_len.parse().ok()) .unwrap_or(0) } else {
155 return Err(
157 eyre!("Couldn't download URL: {}. Error: {:?}", url, resp.status(),).into(),
158 );
159 }
160 };
161
162 Ok(download_size as u64)
163}