Skip to content

Commit effdfef

Browse files
committed
Move download-related code out of dist and into its own download mod
The idea here is to reduce the API surface of `dist`, while consolidating download-related code into a single place. Was also able to remove a number of pointless pass-arounds of the `notification_handler` attached to DownloadCfg (haven't caught them all yet).
1 parent df030d5 commit effdfef

File tree

7 files changed

+280
-243
lines changed

7 files changed

+280
-243
lines changed

src/rustup-dist/src/dist.rs

Lines changed: 4 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,15 @@ use prefix::InstallPrefix;
77
use manifest::Component;
88
use manifest::Manifest as ManifestV2;
99
use manifestation::{Manifestation, UpdateStatus, Changes};
10+
use download::{DownloadCfg};
1011

11-
use std::path::{Path, PathBuf};
12-
use std::ops;
13-
use url::Url;
12+
use std::path::Path;
1413
use std::fmt;
1514
use std::env;
16-
use std::fs;
1715

1816
use regex::Regex;
19-
use sha2::{Sha256, Digest};
2017

2118
pub const DEFAULT_DIST_SERVER: &'static str = "https://static.rust-lang.org";
22-
pub const UPDATE_HASH_LEN: usize = 20;
2319

2420
// Deprecated
2521
pub const DEFAULT_DIST_ROOT: &'static str = "https://static.rust-lang.org/dist";
@@ -440,168 +436,6 @@ impl fmt::Display for ToolchainDesc {
440436
}
441437
}
442438

443-
pub fn download_and_check<'a>(url_str: &str,
444-
update_hash: Option<&Path>,
445-
ext: &str,
446-
cfg: DownloadCfg<'a>)
447-
-> Result<Option<(temp::File<'a>, String)>> {
448-
let hash = try!(download_hash(url_str, cfg));
449-
let partial_hash: String = hash.chars().take(UPDATE_HASH_LEN).collect();
450-
451-
if let Some(hash_file) = update_hash {
452-
if utils::is_file(hash_file) {
453-
if let Ok(contents) = utils::read_file("update hash", hash_file) {
454-
if contents == partial_hash {
455-
// Skip download, update hash matches
456-
return Ok(None);
457-
}
458-
} else {
459-
(cfg.notify_handler)(Notification::CantReadUpdateHash(hash_file));
460-
}
461-
} else {
462-
(cfg.notify_handler)(Notification::NoUpdateHash(hash_file));
463-
}
464-
}
465-
466-
let url = try!(utils::parse_url(url_str));
467-
let file = try!(cfg.temp_cfg.new_file_with_ext("", ext));
468-
469-
let mut hasher = Sha256::new();
470-
try!(utils::download_file(&url,
471-
&file,
472-
Some(&mut hasher),
473-
&|n| (cfg.notify_handler)(n.into())));
474-
let actual_hash = hasher.result_str();
475-
476-
if hash != actual_hash {
477-
// Incorrect hash
478-
return Err(ErrorKind::ChecksumFailed {
479-
url: url_str.to_owned(),
480-
expected: hash,
481-
calculated: actual_hash,
482-
}
483-
.into());
484-
} else {
485-
(cfg.notify_handler)(Notification::ChecksumValid(url_str));
486-
}
487-
488-
// TODO: Check the signature of the file
489-
490-
Ok(Some((file, partial_hash)))
491-
}
492-
493-
#[derive(Copy, Clone)]
494-
pub struct DownloadCfg<'a> {
495-
pub dist_root: &'a str,
496-
pub temp_cfg: &'a temp::Cfg,
497-
pub download_dir: &'a PathBuf,
498-
pub notify_handler: &'a Fn(Notification),
499-
}
500-
501-
502-
pub struct File {
503-
path: PathBuf,
504-
}
505-
506-
impl ops::Deref for File {
507-
type Target = Path;
508-
509-
fn deref(&self) -> &Path {
510-
ops::Deref::deref(&self.path)
511-
}
512-
}
513-
514-
fn file_hash(path: &Path) -> Result<String> {
515-
let mut hasher = Sha256::new();
516-
use std::io::Read;
517-
let mut downloaded = try!(fs::File::open(&path).chain_err(|| "opening already downloaded file"));
518-
let mut buf = vec![0; 32768];
519-
loop {
520-
if let Ok(n) = downloaded.read(&mut buf) {
521-
if n == 0 { break; }
522-
hasher.input(&buf[..n]);
523-
} else {
524-
break;
525-
}
526-
}
527-
528-
Ok(hasher.result_str())
529-
}
530-
531-
impl<'a> DownloadCfg<'a> {
532-
533-
pub fn download(&self, url: &Url, hash: &str, notify_handler: &'a Fn(Notification)) -> Result<File> {
534-
535-
try!(utils::ensure_dir_exists("Download Directory", &self.download_dir, &|n| notify_handler(n.into())));
536-
let target_file = self.download_dir.join(Path::new(hash));
537-
538-
if target_file.exists() {
539-
let cached_result = try!(file_hash(&target_file));
540-
if hash == cached_result {
541-
notify_handler(Notification::FileAlreadyDownloaded);
542-
notify_handler(Notification::ChecksumValid(&url.to_string()));
543-
return Ok(File { path: target_file, });
544-
} else {
545-
notify_handler(Notification::CachedFileChecksumFailed);
546-
try!(fs::remove_file(&target_file).chain_err(|| "cleaning up previous download"));
547-
}
548-
}
549-
550-
551-
let partial_file_path =
552-
target_file.with_file_name(
553-
target_file.file_name().map(|s| {
554-
s.to_str().unwrap_or("_")})
555-
.unwrap_or("_")
556-
.to_owned()
557-
+ ".partial");
558-
559-
let mut hasher = Sha256::new();
560-
561-
try!(utils::download_file_with_resume(&url,
562-
&partial_file_path,
563-
Some(&mut hasher),
564-
true,
565-
&|n| notify_handler(n.into())));
566-
567-
let actual_hash = hasher.result_str();
568-
569-
if hash != actual_hash {
570-
// Incorrect hash
571-
return Err(ErrorKind::ChecksumFailed {
572-
url: url.to_string(),
573-
expected: hash.to_string(),
574-
calculated: actual_hash,
575-
}.into());
576-
} else {
577-
notify_handler(Notification::ChecksumValid(&url.to_string()));
578-
try!(fs::rename(&partial_file_path, &target_file));
579-
return Ok(File { path: target_file });
580-
}
581-
}
582-
583-
pub fn clean(&self, hashes: &Vec<String>) -> Result<()> {
584-
for hash in hashes.iter() {
585-
let used_file = self.download_dir.join(hash);
586-
if self.download_dir.join(&used_file).exists() {
587-
try!(fs::remove_file(used_file).chain_err(|| "cleaning up cached downloads"));
588-
}
589-
}
590-
Ok(())
591-
}
592-
}
593-
594-
pub fn download_hash(url: &str, cfg: DownloadCfg) -> Result<String> {
595-
let hash_url = try!(utils::parse_url(&(url.to_owned() + ".sha256")));
596-
let hash_file = try!(cfg.temp_cfg.new_file());
597-
598-
try!(utils::download_file(&hash_url,
599-
&hash_file,
600-
None,
601-
&|n| (cfg.notify_handler)(n.into())));
602-
603-
Ok(try!(utils::read_file("hash", &hash_file).map(|s| s[0..64].to_owned())))
604-
}
605439

606440
// Installs or updates a toolchain from a dist server. If an initial
607441
// install then it will be installed with the default components. If
@@ -711,7 +545,7 @@ fn dl_v2_manifest<'a>(download: DownloadCfg<'a>,
711545
toolchain: &ToolchainDesc)
712546
-> Result<Option<(ManifestV2, String)>> {
713547
let manifest_url = toolchain.manifest_v2_url(download.dist_root);
714-
let manifest_dl_res = download_and_check(&manifest_url, update_hash, ".toml", download);
548+
let manifest_dl_res = download.download_and_check(&manifest_url, update_hash, ".toml");
715549

716550
if let Ok(manifest_dl) = manifest_dl_res {
717551
// Downloaded ok!
@@ -751,7 +585,7 @@ fn dl_v1_manifest<'a>(download: DownloadCfg<'a>, toolchain: &ToolchainDesc) -> R
751585
}
752586

753587
let manifest_url = toolchain.manifest_v1_url(download.dist_root);
754-
let manifest_dl = try!(download_and_check(&manifest_url, None, "", download));
588+
let manifest_dl = try!(download.download_and_check(&manifest_url, None, ""));
755589
let (manifest_file, _) = manifest_dl.unwrap();
756590
let manifest_str = try!(utils::read_file("manifest", &manifest_file));
757591
let urls = manifest_str.lines().map(|s| format!("{}/{}", root_url, s)).collect();

src/rustup-dist/src/download.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use rustup_utils::{utils};
2+
use errors::*;
3+
use temp;
4+
use notifications::*;
5+
use sha2::{Sha256, Digest};
6+
use url::Url;
7+
8+
9+
use std::path::{Path, PathBuf};
10+
use std::fs;
11+
use std::ops;
12+
13+
const UPDATE_HASH_LEN: usize = 20;
14+
15+
#[derive(Copy, Clone)]
16+
pub struct DownloadCfg<'a> {
17+
pub dist_root: &'a str,
18+
pub temp_cfg: &'a temp::Cfg,
19+
pub download_dir: &'a PathBuf,
20+
pub notify_handler: &'a Fn(Notification),
21+
}
22+
23+
24+
pub struct File {
25+
path: PathBuf,
26+
}
27+
28+
impl ops::Deref for File {
29+
type Target = Path;
30+
31+
fn deref(&self) -> &Path {
32+
ops::Deref::deref(&self.path)
33+
}
34+
}
35+
36+
37+
impl<'a> DownloadCfg<'a> {
38+
39+
/// Downloads a file, validating its hash, and resuming interrupted downloads
40+
/// Partial downloads are stored in `self.download_dir`, keyed by hash. If the
41+
/// target file already exists, then the hash is checked and it is returned
42+
/// immediately without re-downloading.
43+
pub fn download(&self, url: &Url, hash: &str) -> Result<File> {
44+
45+
try!(utils::ensure_dir_exists("Download Directory", &self.download_dir, &|n| (self.notify_handler)(n.into())));
46+
let target_file = self.download_dir.join(Path::new(hash));
47+
48+
if target_file.exists() {
49+
let cached_result = try!(file_hash(&target_file));
50+
if hash == cached_result {
51+
(self.notify_handler)(Notification::FileAlreadyDownloaded);
52+
(self.notify_handler)(Notification::ChecksumValid(&url.to_string()));
53+
return Ok(File { path: target_file, });
54+
} else {
55+
(self.notify_handler)(Notification::CachedFileChecksumFailed);
56+
try!(fs::remove_file(&target_file).chain_err(|| "cleaning up previous download"));
57+
}
58+
}
59+
60+
61+
let partial_file_path =
62+
target_file.with_file_name(
63+
target_file.file_name().map(|s| {
64+
s.to_str().unwrap_or("_")})
65+
.unwrap_or("_")
66+
.to_owned()
67+
+ ".partial");
68+
69+
let mut hasher = Sha256::new();
70+
71+
try!(utils::download_file_with_resume(&url,
72+
&partial_file_path,
73+
Some(&mut hasher),
74+
true,
75+
&|n| (self.notify_handler)(n.into())));
76+
77+
let actual_hash = hasher.result_str();
78+
79+
if hash != actual_hash {
80+
// Incorrect hash
81+
return Err(ErrorKind::ChecksumFailed {
82+
url: url.to_string(),
83+
expected: hash.to_string(),
84+
calculated: actual_hash,
85+
}.into());
86+
} else {
87+
(self.notify_handler)(Notification::ChecksumValid(&url.to_string()));
88+
try!(fs::rename(&partial_file_path, &target_file));
89+
return Ok(File { path: target_file });
90+
}
91+
}
92+
93+
pub fn clean(&self, hashes: &Vec<String>) -> Result<()> {
94+
for hash in hashes.iter() {
95+
let used_file = self.download_dir.join(hash);
96+
if self.download_dir.join(&used_file).exists() {
97+
try!(fs::remove_file(used_file).chain_err(|| "cleaning up cached downloads"));
98+
}
99+
}
100+
Ok(())
101+
}
102+
103+
fn download_hash(&self, url: &str) -> Result<String> {
104+
let hash_url = try!(utils::parse_url(&(url.to_owned() + ".sha256")));
105+
let hash_file = try!(self.temp_cfg.new_file());
106+
107+
try!(utils::download_file(&hash_url,
108+
&hash_file,
109+
None,
110+
&|n| (self.notify_handler)(n.into())));
111+
112+
Ok(try!(utils::read_file("hash", &hash_file).map(|s| s[0..64].to_owned())))
113+
}
114+
115+
/// Downloads a file, sourcing its hash from the same url with a `.sha256` suffix.
116+
/// If `update_hash` is present, then that will be compared to the downloaded hash,
117+
/// and if they match, the download is skipped.
118+
pub fn download_and_check(&self,
119+
url_str: &str,
120+
update_hash: Option<&Path>,
121+
ext: &str)
122+
-> Result<Option<(temp::File<'a>, String)>> {
123+
let hash = try!(self.download_hash(url_str));
124+
let partial_hash: String = hash.chars().take(UPDATE_HASH_LEN).collect();
125+
126+
if let Some(hash_file) = update_hash {
127+
128+
if utils::is_file(hash_file) {
129+
if let Ok(contents) = utils::read_file("update hash", hash_file) {
130+
if contents == partial_hash {
131+
// Skip download, update hash matches
132+
return Ok(None);
133+
}
134+
} else {
135+
(self.notify_handler)(Notification::CantReadUpdateHash(hash_file));
136+
}
137+
} else {
138+
(self.notify_handler)(Notification::NoUpdateHash(hash_file));
139+
}
140+
}
141+
142+
let url = try!(utils::parse_url(url_str));
143+
let file = try!(self.temp_cfg.new_file_with_ext("", ext));
144+
145+
let mut hasher = Sha256::new();
146+
try!(utils::download_file(&url,
147+
&file,
148+
Some(&mut hasher),
149+
&|n| (self.notify_handler)(n.into())));
150+
let actual_hash = hasher.result_str();
151+
152+
if hash != actual_hash {
153+
// Incorrect hash
154+
return Err(ErrorKind::ChecksumFailed {
155+
url: url_str.to_owned(),
156+
expected: hash,
157+
calculated: actual_hash,
158+
}
159+
.into());
160+
} else {
161+
(self.notify_handler)(Notification::ChecksumValid(url_str));
162+
}
163+
164+
// TODO: Check the signature of the file
165+
166+
Ok(Some((file, partial_hash)))
167+
}
168+
}
169+
170+
171+
fn file_hash(path: &Path) -> Result<String> {
172+
let mut hasher = Sha256::new();
173+
use std::io::Read;
174+
let mut downloaded = try!(fs::File::open(&path).chain_err(|| "opening already downloaded file"));
175+
let mut buf = vec![0; 32768];
176+
loop {
177+
if let Ok(n) = downloaded.read(&mut buf) {
178+
if n == 0 { break; }
179+
hasher.input(&buf[..n]);
180+
} else {
181+
break;
182+
}
183+
}
184+
185+
Ok(hasher.result_str())
186+
}

0 commit comments

Comments
 (0)