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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use super::CrateTrait;
use crate::Workspace;
use failure::Error;
use flate2::read::GzDecoder;
use log::info;
use remove_dir_all::remove_dir_all;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read};
use std::path::{Path, PathBuf};
use tar::Archive;
static CRATES_ROOT: &str = "https://static.crates.io/crates";
impl CratesIOCrate {
pub(super) fn new(name: &str, version: &str) -> Self {
CratesIOCrate {
name: name.into(),
version: version.into(),
}
}
fn cache_path(&self, workspace: &Workspace) -> PathBuf {
workspace
.cache_dir()
.join("cratesio-sources")
.join(&self.name)
.join(format!("{}-{}.crate", self.name, self.version))
}
}
pub(super) struct CratesIOCrate {
name: String,
version: String,
}
impl CrateTrait for CratesIOCrate {
fn fetch(&self, workspace: &Workspace) -> Result<(), Error> {
let local = self.cache_path(workspace);
if local.exists() {
info!("crate {} {} is already in cache", self.name, self.version);
return Ok(());
}
info!("fetching crate {} {}...", self.name, self.version);
if let Some(parent) = local.parent() {
std::fs::create_dir_all(parent)?;
}
let remote = format!(
"{0}/{1}/{1}-{2}.crate",
CRATES_ROOT, self.name, self.version
);
let mut resp = workspace
.http_client()
.get(&remote)
.send()?
.error_for_status()?;
resp.copy_to(&mut BufWriter::new(File::create(&local)?))?;
Ok(())
}
fn purge_from_cache(&self, workspace: &Workspace) -> Result<(), Error> {
let path = self.cache_path(workspace);
if path.exists() {
std::fs::remove_file(&path)?;
}
Ok(())
}
fn copy_source_to(&self, workspace: &Workspace, dest: &Path) -> Result<(), Error> {
let cached = self.cache_path(workspace);
let mut file = File::open(cached)?;
let mut tar = Archive::new(GzDecoder::new(BufReader::new(&mut file)));
info!(
"extracting crate {} {} into {}",
self.name,
self.version,
dest.display()
);
if let Err(err) = unpack_without_first_dir(&mut tar, dest) {
let _ = remove_dir_all(dest);
Err(err
.context(format!(
"unable to download {} version {}",
self.name, self.version
))
.into())
} else {
Ok(())
}
}
}
impl std::fmt::Display for CratesIOCrate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "crates.io crate {} {}", self.name, self.version)
}
}
fn unpack_without_first_dir<R: Read>(archive: &mut Archive<R>, path: &Path) -> Result<(), Error> {
let entries = archive.entries()?;
for entry in entries {
let mut entry = entry?;
let relpath = {
let path = entry.path();
let path = path?;
path.into_owned()
};
let mut components = relpath.components();
components.next();
let full_path = path.join(&components.as_path());
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
}
entry.unpack(&full_path)?;
}
Ok(())
}