Select latest release file in install pipeline

This commit is contained in:
James 2018-01-29 23:37:17 +11:00
parent a61709c3d0
commit 139ff5793c
8 changed files with 93 additions and 73 deletions

View File

@ -1,5 +1,4 @@
/// Serves static files from a asset directory.
extern crate mime_guess;
use std::borrow::Cow;

View File

@ -13,10 +13,9 @@ use sources::get_by_name;
/// Description of the source of a package.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PackageSource {
pub name : String,
#[serde(rename="match")]
pub match_regex : String,
pub config : toml::Value
pub name: String,
#[serde(rename = "match")] pub match_regex: String,
pub config: toml::Value,
}
/// Describes a overview of a individual package.
@ -25,7 +24,7 @@ pub struct PackageDescription {
pub name: String,
pub description: String,
pub default: Option<bool>,
pub source: PackageSource
pub source: PackageSource,
}
/// Describes the application itself.
@ -58,7 +57,7 @@ impl PackageSource {
pub fn get_current_releases(&self) -> Result<Vec<Release>, String> {
let package_handler = match get_by_name(&self.name) {
Some(v) => v,
_ => return Err(format!("Handler {} not found", self.name))
_ => return Err(format!("Handler {} not found", self.name)),
};
package_handler.get_current_releases(&self.config)

View File

@ -39,7 +39,7 @@ impl InstallerFramework {
}
/// Sends a request for something to be installed.
pub fn install(&self, items : Vec<String>) {
pub fn install(&self, items: Vec<String>) {
// TODO: Error handling
println!("Framework: Installing {:?}", items);
@ -60,17 +60,21 @@ impl InstallerFramework {
let results = package.source.get_current_releases().unwrap();
println!("Got releases");
let filtered_regex = package.source.match_regex.replace("#PLATFORM#", OS);
println!("Filtered regex: {}" , filtered_regex);
let regex = Regex::new(&filtered_regex).unwrap();
// Find the latest release in here
let latest_result = results.into_iter()
.filter(|f| f.files.iter().filter(|x| regex.is_match(x)).count() > 0)
.max_by_key(|f| f.version.clone());
println!("{:?}", latest_result);
let latest_result = results
.into_iter()
.filter(|f| f.files.iter().filter(|x| regex.is_match(&x.name)).count() > 0)
.max_by_key(|f| f.version.clone()).unwrap();
// Find the matching file in here
let latest_file = latest_result.files.into_iter()
.filter(|x| regex.is_match(&x.name))
.next().unwrap();
println!("{:?}", latest_file);
}
}

View File

@ -16,8 +16,8 @@ extern crate serde_derive;
extern crate serde_json;
extern crate toml;
extern crate semver;
extern crate regex;
extern crate semver;
mod assets;
mod rest;

View File

@ -2,7 +2,6 @@
///
/// Provides a HTTP/REST server for both frontend<->backend communication, as well
/// as talking to external applications.
extern crate nfd;
extern crate url;
@ -153,7 +152,8 @@ impl Service for WebService {
return Box::new(req.body().concat2().map(move |b| {
let results = form_urlencoded::parse(b.as_ref())
.into_owned().collect::<HashMap<String, String>>();
.into_owned()
.collect::<HashMap<String, String>>();
let mut to_install = Vec::new();
@ -194,7 +194,7 @@ impl Service for WebService {
.with_header(ContentLength(file.len() as u64))
.with_header(content_type)
.with_body(file)
},
}
None => Response::new().with_status(StatusCode::NotFound),
}
}

View File

@ -18,13 +18,12 @@ use serde_json;
use sources::types::*;
pub struct GithubReleases {
}
pub struct GithubReleases {}
/// The configuration for this release.
#[derive(Serialize, Deserialize)]
struct GithubConfig {
repo : String
repo: String,
}
impl GithubReleases {
@ -38,44 +37,45 @@ impl ReleaseSource for GithubReleases {
// Reparse our Config as strongly typed
let config_string = match toml::to_string(config) {
Ok(v) => v,
Err(v) => return Err(format!("Failed to convert config: {:?}", v))
Err(v) => return Err(format!("Failed to convert config: {:?}", v)),
};
let config : GithubConfig = match toml::from_str(&config_string) {
let config: GithubConfig = match toml::from_str(&config_string) {
Ok(v) => v,
Err(v) => return Err(format!("Failed to convert config: {:?}", v))
Err(v) => return Err(format!("Failed to convert config: {:?}", v)),
};
let mut core = match Core::new() {
Ok(v) => v,
Err(v) => return Err(format!("Failed to init Tokio: {:?}", v))
Err(v) => return Err(format!("Failed to init Tokio: {:?}", v)),
};
// Build the HTTP client up
let client = Client::configure()
.connector(match HttpsConnector::new(4, &core.handle()) {
Ok(v) => v,
Err(v) => return Err(format!("Failed to init https: {:?}", v))
Err(v) => return Err(format!("Failed to init https: {:?}", v)),
})
.build(&core.handle());
let mut results: Vec<Release> = Vec::new();
let target_url : Uri = match format!("https://api.github.com/repos/{}/releases",
config.repo).parse() {
let target_url: Uri =
match format!("https://api.github.com/repos/{}/releases", config.repo).parse() {
Ok(v) => v,
Err(v) => return Err(format!("Failed to generate target url: {:?}", v))
Err(v) => return Err(format!("Failed to generate target url: {:?}", v)),
};
let mut req = Request::new(Method::Get, target_url);
req.headers_mut().set(UserAgent::new("installer-rs (j-selby)"));
req.headers_mut()
.set(UserAgent::new("installer-rs (j-selby)"));
// Build our future
let future = client.request(req).and_then(|res| {
res.body().concat2().and_then(move |body| {
let raw_json : Result<serde_json::Value, String>
= match serde_json::from_slice(&body) {
let raw_json: Result<serde_json::Value, String> =
match serde_json::from_slice(&body) {
Ok(v) => Ok(v),
Err(v) => Err(format!("Failed to parse response: {:?}", v))
Err(v) => Err(format!("Failed to parse response: {:?}", v)),
};
Ok(raw_json)
@ -83,42 +83,58 @@ impl ReleaseSource for GithubReleases {
});
// Unwrap the future's results
let result : serde_json::Value = match core.run(future) {
let result: serde_json::Value = match core.run(future) {
Ok(v) => v,
Err(v) => return Err(format!("Failed to fetch info: {:?}", v))
Err(v) => return Err(format!("Failed to fetch info: {:?}", v)),
}?;
let result : &Vec<serde_json::Value> = match result.as_array() {
let result: &Vec<serde_json::Value> = match result.as_array() {
Some(v) => v,
None => return Err(format!("JSON payload not an array"))
None => return Err(format!("JSON payload not an array")),
};
// Parse JSON from server
for entry in result.into_iter() {
let mut files = Vec::new();
let id : u64 = match entry["id"].as_u64() {
let id: u64 = match entry["id"].as_u64() {
Some(v) => v,
None => return Err(format!("JSON payload missing information about ID"))
None => return Err(format!("JSON payload missing information about ID")),
};
let assets = match entry["assets"].as_array() {
Some(v) => v,
None => return Err(format!("JSON payload not an array"))
None => return Err(format!("JSON payload not an array")),
};
for asset in assets.into_iter() {
let string = match asset["name"].as_str() {
Some(v) => v,
None => return Err(format!("JSON payload missing information about ID"))
None => {
return Err(format!(
"JSON payload missing information about release name"
))
}
};
files.push(string.to_owned());
let url = match asset["browser_download_url"].as_str() {
Some(v) => v,
None => {
return Err(format!(
"JSON payload missing information about release URL"
))
}
};
files.push(File {
name: string.to_owned(),
url: url.to_owned(),
});
}
results.push(Release {
version: Version::new_number(id),
files
files,
});
}

View File

@ -9,11 +9,9 @@ pub mod github;
use self::types::ReleaseSource;
/// Returns a ReleaseSource by a name, if possible
pub fn get_by_name(name : &str) -> Option<Box<ReleaseSource>> {
pub fn get_by_name(name: &str) -> Option<Box<ReleaseSource>> {
match name {
"github" => {
Some(Box::new(github::GithubReleases::new()))
}
_ => None
"github" => Some(Box::new(github::GithubReleases::new())),
_ => None,
}
}

View File

@ -12,7 +12,7 @@ pub use toml::value::Value as TomlValue;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Version {
Semver(SemverVersion),
Integer(u64)
Integer(u64),
}
impl Version {
@ -21,18 +21,19 @@ impl Version {
fn coarse_into_semver(&self) -> SemverVersion {
match self {
&Version::Semver(ref version) => version.to_owned(),
&Version::Integer(ref version) => SemverVersion::from((version.to_owned(),
0 as u64, 0 as u64))
&Version::Integer(ref version) => {
SemverVersion::from((version.to_owned(), 0 as u64, 0 as u64))
}
}
}
/// Returns a new Version, backed by semver.
pub fn new_semver(version : SemverVersion) -> Version {
pub fn new_semver(version: SemverVersion) -> Version {
Version::Semver(version)
}
/// Returns a new Version, backed by a integer.
pub fn new_number(version : u64) -> Version {
pub fn new_number(version: u64) -> Version {
Version::Integer(version)
}
}
@ -40,18 +41,14 @@ impl Version {
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
match self {
&Version::Semver(ref version) => {
match other {
&Version::Semver(ref version) => match other {
&Version::Semver(ref other_version) => Some(version.cmp(other_version)),
_ => None
}
_ => None,
},
&Version::Integer(ref num) => {
match other {
&Version::Integer(ref num) => match other {
&Version::Integer(ref other_num) => Some(num.cmp(other_num)),
_ => None
}
}
_ => None,
},
}
}
}
@ -62,16 +59,23 @@ impl Ord for Version {
}
}
/// A individual file in a release.
#[derive(Debug)]
pub struct File {
pub name: String,
pub url: String,
}
/// A individual release of an application.
#[derive(Debug)]
pub struct Release {
pub version : Version,
pub files : Vec<String>
pub version: Version,
pub files: Vec<File>,
}
/// A source of releases.
pub trait ReleaseSource {
/// Gets a list of the available releases from this source. Should cache internally
/// if possible using a mutex.
fn get_current_releases(&self, config : &TomlValue) -> Result<Vec<Release>, String>;
fn get_current_releases(&self, config: &TomlValue) -> Result<Vec<Release>, String>;
}