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. /// Serves static files from a asset directory.
extern crate mime_guess; extern crate mime_guess;
use std::borrow::Cow; use std::borrow::Cow;

View File

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

View File

@ -39,7 +39,7 @@ impl InstallerFramework {
} }
/// Sends a request for something to be installed. /// 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 // TODO: Error handling
println!("Framework: Installing {:?}", items); println!("Framework: Installing {:?}", items);
@ -60,17 +60,21 @@ impl InstallerFramework {
let results = package.source.get_current_releases().unwrap(); let results = package.source.get_current_releases().unwrap();
println!("Got releases");
let filtered_regex = package.source.match_regex.replace("#PLATFORM#", OS); let filtered_regex = package.source.match_regex.replace("#PLATFORM#", OS);
println!("Filtered regex: {}" , filtered_regex);
let regex = Regex::new(&filtered_regex).unwrap(); let regex = Regex::new(&filtered_regex).unwrap();
// Find the latest release in here // Find the latest release in here
let latest_result = results.into_iter() let latest_result = results
.filter(|f| f.files.iter().filter(|x| regex.is_match(x)).count() > 0) .into_iter()
.max_by_key(|f| f.version.clone()); .filter(|f| f.files.iter().filter(|x| regex.is_match(&x.name)).count() > 0)
println!("{:?}", latest_result); .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 serde_json;
extern crate toml; extern crate toml;
extern crate semver;
extern crate regex; extern crate regex;
extern crate semver;
mod assets; mod assets;
mod rest; mod rest;

View File

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

View File

@ -18,13 +18,12 @@ use serde_json;
use sources::types::*; use sources::types::*;
pub struct GithubReleases { pub struct GithubReleases {}
}
/// The configuration for this release. /// The configuration for this release.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct GithubConfig { struct GithubConfig {
repo : String repo: String,
} }
impl GithubReleases { impl GithubReleases {
@ -38,87 +37,104 @@ impl ReleaseSource for GithubReleases {
// Reparse our Config as strongly typed // Reparse our Config as strongly typed
let config_string = match toml::to_string(config) { let config_string = match toml::to_string(config) {
Ok(v) => v, 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, 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() { let mut core = match Core::new() {
Ok(v) => v, 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 // Build the HTTP client up
let client = Client::configure() let client = Client::configure()
.connector(match HttpsConnector::new(4, &core.handle()) { .connector(match HttpsConnector::new(4, &core.handle()) {
Ok(v) => v, 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()); .build(&core.handle());
let mut results: Vec<Release> = Vec::new(); let mut results: Vec<Release> = Vec::new();
let target_url : Uri = match format!("https://api.github.com/repos/{}/releases", let target_url: Uri =
config.repo).parse() { match format!("https://api.github.com/repos/{}/releases", config.repo).parse() {
Ok(v) => v, 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); 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 // Build our future
let future = client.request(req).and_then(|res| { let future = client.request(req).and_then(|res| {
res.body().concat2().and_then(move |body| { res.body().concat2().and_then(move |body| {
let raw_json : Result<serde_json::Value, String> let raw_json: Result<serde_json::Value, String> =
= match serde_json::from_slice(&body) { match serde_json::from_slice(&body) {
Ok(v) => Ok(v), 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) Ok(raw_json)
}) })
}); });
// Unwrap the future's results // 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, 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, 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 // Parse JSON from server
for entry in result.into_iter() { for entry in result.into_iter() {
let mut files = Vec::new(); let mut files = Vec::new();
let id : u64 = match entry["id"].as_u64() { let id: u64 = match entry["id"].as_u64() {
Some(v) => v, 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() { let assets = match entry["assets"].as_array() {
Some(v) => v, 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() { for asset in assets.into_iter() {
let string = match asset["name"].as_str() { let string = match asset["name"].as_str() {
Some(v) => v, 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 { results.push(Release {
version: Version::new_number(id), version: Version::new_number(id),
files files,
}); });
} }

View File

@ -9,11 +9,9 @@ pub mod github;
use self::types::ReleaseSource; use self::types::ReleaseSource;
/// Returns a ReleaseSource by a name, if possible /// 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 { match name {
"github" => { "github" => Some(Box::new(github::GithubReleases::new())),
Some(Box::new(github::GithubReleases::new())) _ => None,
}
_ => None
} }
} }

View File

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