From 139ff5793c28f98308ed75bcfbf338c8745f733e Mon Sep 17 00:00:00 2001
From: James <jselby@jselby.net>
Date: Mon, 29 Jan 2018 23:37:17 +1100
Subject: [PATCH] Select latest release file in install pipeline

---
 src/assets.rs             |  1 -
 src/config.rs             | 13 ++++---
 src/installer.rs          | 20 ++++++-----
 src/main.rs               |  2 +-
 src/rest.rs               |  6 ++--
 src/sources/github/mod.rs | 74 ++++++++++++++++++++++++---------------
 src/sources/mod.rs        |  8 ++---
 src/sources/types.rs      | 42 ++++++++++++----------
 8 files changed, 93 insertions(+), 73 deletions(-)

diff --git a/src/assets.rs b/src/assets.rs
index 7d9611e..6a36ca5 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -1,5 +1,4 @@
 /// Serves static files from a asset directory.
-
 extern crate mime_guess;
 
 use std::borrow::Cow;
diff --git a/src/config.rs b/src/config.rs
index e0f855d..737d764 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -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,9 +57,9 @@ 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)
     }
-}
\ No newline at end of file
+}
diff --git a/src/installer.rs b/src/installer.rs
index 2c99a16..caa8242 100644
--- a/src/installer.rs
+++ b/src/installer.rs
@@ -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);
         }
     }
 
diff --git a/src/main.rs b/src/main.rs
index 83b5d8b..415b2eb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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;
diff --git a/src/rest.rs b/src/rest.rs
index 8c09578..2ca42a2 100644
--- a/src/rest.rs
+++ b/src/rest.rs
@@ -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),
                 }
             }
diff --git a/src/sources/github/mod.rs b/src/sources/github/mod.rs
index 817b7e9..7ce2c6d 100644
--- a/src/sources/github/mod.rs
+++ b/src/sources/github/mod.rs
@@ -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,87 +37,104 @@ 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() {
-            Ok(v) => v,
-            Err(v) => return Err(format!("Failed to generate target url: {:?}", v))
-        };
+        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)),
+            };
 
         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) {
-                    Ok(v) => Ok(v),
-                    Err(v) => Err(format!("Failed to parse response: {:?}", v))
-                };
+                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)),
+                    };
 
                 Ok(raw_json)
             })
         });
 
         // 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,
             });
         }
 
diff --git a/src/sources/mod.rs b/src/sources/mod.rs
index 1c41433..d3a0533 100644
--- a/src/sources/mod.rs
+++ b/src/sources/mod.rs
@@ -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,
     }
 }
diff --git a/src/sources/types.rs b/src/sources/types.rs
index aac5cbb..b8fcbc6 100644
--- a/src/sources/types.rs
+++ b/src/sources/types.rs
@@ -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 other_version) => Some(version.cmp(other_version)),
-                    _ => None
-                }
+            &Version::Semver(ref version) => match other {
+                &Version::Semver(ref other_version) => Some(version.cmp(other_version)),
+                _ => 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.
 #[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>;
 }