1use crate::{
2 loader::{Bar, Spinner},
3 sysinfo::{platform_arch, platform_name},
4 types::{DownloadDir, ReleaseDir},
5};
6use console::style;
7use indicatif::HumanBytes;
8use std::{io::Read, path::PathBuf};
9use url::Url;
10
11use super::{version::DistVersion, SnmRes};
12
13#[derive(Debug)]
14struct Dist(String);
15
16impl Dist {
17 fn new(mirror: &Url, version: &DistVersion) -> Self {
18 #[cfg(unix)]
19 let extension = "tar.xz";
20
21 #[cfg(windows)]
22 let extension = "zip";
23
24 Dist(format!(
25 "{}/v{version}/node-v{version}-{}-{}.{}",
26 mirror,
27 platform_name(),
28 platform_arch(),
29 extension,
30 version = version
31 ))
32 }
33}
34
35impl AsRef<str> for Dist {
36 fn as_ref(&self) -> &str {
37 &self.0
38 }
39}
40
41pub struct Downloader<'a> {
42 version: &'a DistVersion,
43 dist: Dist,
44}
45
46impl<'a> Downloader<'a> {
47 pub fn new(mirror: &'a Url, version: &'a DistVersion) -> Self {
48 let dist = Dist::new(mirror, version);
49
50 Self { version, dist }
51 }
52
53 #[cfg(unix)]
54 fn extract_to<S: Read + Send>(
55 &self,
56 source: &mut S,
57 r_dir: &ReleaseDir,
58 d_dir: &DownloadDir,
59 ) -> SnmRes<()> {
60 let tmp_dir = tempfile::Builder::new().tempdir_in(d_dir.as_ref())?;
61
62 let xz_stream = xz2::read::XzDecoder::new(source);
63 let mut tar_stream = tar::Archive::new(xz_stream);
64 let entries = tar_stream.entries()?;
65
66 for entry in entries {
67 let mut entry = entry?;
68
69 let entry_path: PathBuf = entry.path()?.iter().skip(1).collect();
71
72 let tmp_dir = tmp_dir.as_ref().join(entry_path);
73
74 entry.unpack(tmp_dir)?;
75 }
76
77 let install_dir = r_dir.as_ref().join(self.version.to_string());
78
79 std::fs::rename(&tmp_dir, &install_dir)?;
80
81 Ok(())
82 }
83
84 #[cfg(windows)]
85 fn extract_to<S: Read + Send>(
86 &self,
87 source: &mut S,
88 r_dir: &ReleaseDir,
89 d_dir: &DownloadDir,
90 ) -> SnmRes<()> {
91 use std::{fs, io};
92
93 let mut tmp_file = tempfile::Builder::new().tempfile_in(d_dir.as_ref())?;
94
95 io::copy(source, &mut tmp_file)?;
96
97 let mut archive = zip::read::ZipArchive::new(&tmp_file)?;
98
99 let install_dir = r_dir.as_ref().join(self.version.to_string());
100
101 for i in 0..archive.len() {
102 let mut file = archive.by_index(i)?;
103
104 let outpath = {
105 let f = match file.enclosed_name() {
106 Some(path) => path.iter().skip(1).collect::<PathBuf>(),
108 None => continue,
109 };
110
111 install_dir.join(f)
112 };
113
114 {
115 let comment = file.comment();
116 if !comment.is_empty() {
117 println!("File {} comment: {}", i, comment);
118 }
119 }
120
121 if file.name().ends_with('/') {
122 fs::create_dir_all(&outpath)?;
123 } else {
124 if let Some(p) = outpath.parent() {
125 if !p.exists() {
126 fs::create_dir_all(&p)?;
127 }
128 }
129
130 let mut outfile = fs::File::create(&outpath)?;
131
132 io::copy(&mut file, &mut outfile)?;
133 }
134
135 #[cfg(unix)]
137 {
138 use std::os::unix::fs::PermissionsExt;
139
140 if let Some(mode) = file.unix_mode() {
141 fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
142 }
143 }
144 }
145
146 Ok(())
147 }
148
149 pub fn download(&self, r_dir: &ReleaseDir, d_dir: &DownloadDir) -> SnmRes<ReleaseDir> {
150 let version: String = self.version.to_string();
151
152 let dest = r_dir.join(&version);
153
154 if dest.as_ref().exists() {
155 anyhow::bail!("Version {} already exists locally", style(version).bold());
156 }
157
158 let resp = ureq::get(&self.dist.0).call()?;
159
160 let len = resp
161 .header("Content-Length")
162 .and_then(|x| x.parse::<u64>().ok());
163
164 let size = match len {
165 Some(l) => HumanBytes(l).to_string(),
166 None => "unknown".into(),
167 };
168
169 println!("Version : {}", style(version).bold());
170 println!("Release : {}", style(self.dist.as_ref()).bold());
171 println!("Size : {}", style(size).bold());
172 println!();
173
174 if let Some(l) = len {
175 let bar = Bar::new(l);
176
177 self.extract_to(&mut bar.take_reader(resp.into_reader()), r_dir, d_dir)?;
178
179 bar.finish();
180 } else {
181 let spinner = Spinner::new("Installing...");
182
183 self.extract_to(&mut resp.into_reader(), r_dir, d_dir)?;
184
185 spinner.finish()
186 }
187
188 println!("Installed : {}", style(dest.as_ref().display()).bold());
189
190 Ok(dest)
191 }
192}