1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use anyhow::{bail, Context};
use bytes::Bytes;
use flate2::read::GzDecoder;
use reqwest::{header, StatusCode};
use sha2::{Digest, Sha256};
use std::{fs, os::unix::fs::PermissionsExt, path::PathBuf};
use tar::Archive;
pub trait Installer {
/// 1. Download package: e.g. https://nodejs.org/dist/v16.18.1/node-v16.18.1-darwin-arm64.tar.gz
/// 2. Verify checkum
/// 3. Unpack to path: e.g. /User/Application Support/dip/bundle/installs/nodejs/16.18.1/
fn install(
&self,
download_url: &String,
install_path: &PathBuf,
file_name: &String,
checksum: Option<&String>,
) -> anyhow::Result<()> {
let res = reqwest::blocking::get(download_url)
.context("Failed to download. Check internet connection.")?;
match res.status() {
StatusCode::OK => {
match res.headers()[header::CONTENT_TYPE].to_str()? {
"application/gzip" => {
let bytes = res.bytes()?;
if let Some(checksum) = checksum {
self.verify_checksum(&bytes, checksum)?;
}
let mut cloned_path = install_path.clone();
cloned_path.pop();
let tar = GzDecoder::new(&bytes[..]);
let mut archive = Archive::new(tar);
archive.unpack(&cloned_path)?;
fs::rename(
// e.g. /User/Application Support/dip/bundle/installs/nodejs/node-v16.18.1-darwin-arm64
cloned_path.join(&file_name),
// e.g. /User/Application Support/dip/bundle/installs/nodejs/16.18.1/
&install_path,
)?;
Ok(())
}
"application/octet-stream" => {
let file_path = &install_path.join(file_name);
fs::create_dir_all(&install_path)?;
fs::write(&file_path, &res.bytes()?)?;
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o755))?;
Ok(())
}
unsupported_content_type => {
bail!(
"Content-Type is not supported: {}",
unsupported_content_type
);
}
}
}
StatusCode::NOT_FOUND => {
bail!("Download URL not found: {download_url}");
}
_ => {
bail!("Fail to download binary")
}
}
}
fn verify_checksum(&self, bytes: &Bytes, checksum: &String) -> anyhow::Result<()> {
let hash = Sha256::digest(&bytes);
if hash[..] == hex::decode(checksum)? {
Ok(())
} else {
bail!("Checksum doesn't match")
}
}
}