assemble_std/dependencies/
web.rs

1//! Simple web-based dependencies
2
3use assemble_core::cryptography::hash_sha256;
4use assemble_core::dependencies::{
5    AcquisitionError, Dependency, DependencyType, Registry, ResolvedDependency,
6    ResolvedDependencyBuilder,
7};
8use assemble_core::project::buildable::{BuildableObject, GetBuildable};
9
10use std::ffi::{OsStr, OsString};
11use std::fmt::Debug;
12use std::fs::File;
13use std::io::Read;
14use std::path::{Path, PathBuf};
15use std::{fs, io};
16use url::Url;
17
18/// A web registry
19pub struct WebRegistry {
20    base_url: Url,
21    name: String,
22}
23
24impl WebRegistry {
25    /// Create a new web registry from a url
26    pub fn new(name: &str, url: &str) -> Result<WebRegistry, url::ParseError> {
27        Url::parse(url).map(|url| Self {
28            base_url: url,
29            name: name.to_string(),
30        })
31    }
32}
33
34impl Registry for WebRegistry {
35    fn url(&self) -> Url {
36        self.base_url.clone()
37    }
38
39    fn supported(&self) -> Vec<DependencyType> {
40        vec![remote_file_system_type(&self.name)]
41    }
42}
43
44/// Create a remote file system dependency type for a given name
45pub fn remote_file_system_type(name: &str) -> DependencyType {
46    DependencyType::new(name, name, ["*"])
47}
48
49/// A dependency that can be found on the web. This is an absolute path from a host
50#[derive(Debug)]
51pub struct WebDependency {
52    file_path: PathBuf,
53    from_registry: String,
54    file_name: Option<OsString>,
55}
56
57impl WebDependency {
58    /// Create a new web dependency
59    pub fn new<P: AsRef<Path>, S: AsRef<str>>(file_path: P, from_registry: S) -> Self {
60        Self {
61            file_path: file_path.as_ref().to_path_buf(),
62            from_registry: from_registry.as_ref().to_string(),
63            file_name: None,
64        }
65    }
66
67    /// Set an alternate file name to use.
68    pub fn with_file_name(mut self, file: impl AsRef<OsStr>) -> Self {
69        self.file_name = Some(file.as_ref().to_os_string());
70        self
71    }
72
73    /// Gets the file name.
74    pub fn file_name(&self) -> OsString {
75        self.file_name
76            .as_deref()
77            .or(self.file_path.file_name())
78            .unwrap_or(OsStr::new("tmp.bin"))
79            .to_os_string()
80    }
81}
82
83impl GetBuildable for WebDependency {
84    fn as_buildable(&self) -> BuildableObject {
85        BuildableObject::None
86    }
87}
88
89impl Dependency for WebDependency {
90    fn id(&self) -> String {
91        self.file_name().to_string_lossy().to_string()
92    }
93
94    fn dep_type(&self) -> DependencyType {
95        remote_file_system_type(&self.from_registry)
96    }
97
98    fn try_resolve(
99        &self,
100        registry: &dyn Registry,
101        cache_path: &Path,
102    ) -> Result<ResolvedDependency, AcquisitionError> {
103        let registry_url = registry.url();
104        let joined = registry_url
105            .join(&self.file_path.to_string_lossy())
106            .map_err(AcquisitionError::custom)?;
107
108        let file_name = self.file_name();
109
110        let file_name_sha = hash_sha256(&format!("{}", joined));
111        let download_location = cache_path
112            .join("downloads")
113            .join(file_name_sha.to_string())
114            .join(file_name);
115
116        fs::create_dir_all(download_location.parent().unwrap())
117            .map_err(AcquisitionError::custom)?;
118
119        let response = reqwest::blocking::get(joined).map_err(AcquisitionError::custom)?;
120
121        let body = response.bytes().map_err(AcquisitionError::custom)?;
122
123        let mut file = File::options()
124            .write(true)
125            .create(true)
126            .open(&download_location)
127            .map_err(AcquisitionError::custom)?;
128        io::copy(&mut body.as_ref(), &mut file).map_err(AcquisitionError::custom)?;
129
130        Ok(ResolvedDependencyBuilder::new(download_location).finish())
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use assemble_core::file_collection::FileCollection;
138    use std::env;
139
140    use tempfile::tempdir_in;
141
142    #[test]
143    fn download_rustup_init_script() {
144        let registry = WebRegistry::new("rust-site", "https://sh.rustup.rs/").unwrap();
145        let web_dependency =
146            WebDependency::new("", "rust-site").with_file_name("rustup-startup.sh");
147
148        let current_dir = env::current_dir().unwrap();
149        let temp_dir = tempdir_in(current_dir).expect("couldn't create temporary directory");
150
151        let dependency = web_dependency
152            .try_resolve(&registry, temp_dir.path())
153            .unwrap();
154
155        println!("dependency = {:#?}", dependency);
156        assert_eq!(dependency.artifact_files().files().len(), 1)
157    }
158}