whiley/
package.rs

1use std::error::Error;
2use std::fmt;
3use std::fs;
4use std::path::{Path,PathBuf};
5use log::{error,info};
6use reqwest;
7use reqwest::Url;
8
9// ================================================================
10// Dependency
11// ================================================================
12
13#[derive(Clone)]
14pub struct Dependency {
15    name: String,
16    version: String
17}
18
19impl Dependency {
20    pub fn new(name: String, version: String) -> Self {
21        Dependency{name,version}
22    }
23    pub fn to_zipname(&self) -> String {
24        format!("{}-v{}.zip",self.name,self.version)
25    }
26
27    pub fn to_url(&self, base: &Url) -> Url {
28        let n = format!("{}/{}/{}",self.name,self.version,self.to_zipname());
29	base.join(&n).unwrap()
30    }
31}
32
33impl fmt::Display for Dependency {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        write!(f,"{}-{}",self.name,self.version)
36    }
37}
38
39// ================================================================
40// Package Resolver
41// ================================================================
42
43#[derive(Clone,Debug,PartialEq)]
44pub struct PackageResolver<T: AsRef<Path>> {
45    /// Path to cache root on local filesyste
46    dir: T,
47    /// Base URL for downloading packages
48    url: Url
49}
50
51impl<T: AsRef<Path>> PackageResolver<T> {
52    /// Construct a package resolver which stores cached files in a
53    /// given filesystem directory, and downloads them from a given
54    /// base URL.
55    pub fn new(dir: T, url: Url) -> Self {
56	// Ensure cache directory exists
57	fs::create_dir_all(dir.as_ref()).unwrap();
58	// Done
59	PackageResolver{dir,url}
60    }
61
62    /// Resolve a given set of dependencies.  This is a non-trivial
63    /// process as we identify all transitive dependencies, and
64    /// determine a coherent set which matches all versioning
65    /// constraints (if one exists).
66    pub fn resolve(&self, deps : &[Dependency]) -> Result<(),Box<dyn Error>> {
67        for dep in deps {
68            self.get(&dep)?;
69        }
70        // Done
71        Ok(())
72    }
73
74    pub fn get<'b>(&self, dep: &Dependency) -> Result<PathBuf,Box<dyn Error>> {
75	// Determine dependency location
76	let mut zip = PathBuf::new();
77	zip.push(self.dir.as_ref());
78	zip.push(dep.to_zipname());
79	//
80	if !zip.as_path().exists() {
81	    // Cache miss, try to download
82	    let url = dep.to_url(&self.url);
83	    let response = reqwest::blocking::get(url.clone())?;
84            // Check status code
85            if response.status().is_success() {
86                info!("Downloaded {}",url.as_str());
87	        fs::write(zip.as_path(),response.bytes()?)?;
88            } else {
89                error!("Downloading {} ({:?})",url.as_str(),response.status());
90                return Err(Box::new(ResolutionError{dep:(*dep).clone()}));
91            }
92	}
93	//
94	Ok(zip)
95    }
96}
97
98// ================================================================
99// Resolution Error
100// ================================================================
101
102#[derive(Clone)]
103struct ResolutionError {
104    dep: Dependency
105}
106
107impl fmt::Display for ResolutionError {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        write!(f, "failed resolving package {}",self.dep)
110    }
111}
112
113impl fmt::Debug for ResolutionError {
114    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115        write!(f, "failed resolving package {}",self.dep)
116    }
117}
118
119impl Error for ResolutionError {}