mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-25 23:05:37 +01:00
Add communications between frontend<->backend for progress
This commit is contained in:
parent
722bf73316
commit
cfd8c94363
3
src/http.rs
Normal file
3
src/http.rs
Normal file
@ -0,0 +1,3 @@
|
||||
/// http.rs
|
||||
///
|
||||
/// A simple wrapper around
|
@ -10,8 +10,18 @@ use std::env::consts::OS;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use config::Config;
|
||||
|
||||
/// A message thrown during the installation of packages.
|
||||
#[derive(Serialize)]
|
||||
pub enum InstallMessage {
|
||||
Status(String, f64),
|
||||
Error(String),
|
||||
EOF,
|
||||
}
|
||||
|
||||
/// The installer framework contains metadata about packages, what is installable, what isn't,
|
||||
/// etc.
|
||||
pub struct InstallerFramework {
|
||||
@ -39,7 +49,11 @@ impl InstallerFramework {
|
||||
}
|
||||
|
||||
/// Sends a request for something to be installed.
|
||||
pub fn install(&self, items: Vec<String>) {
|
||||
pub fn install(
|
||||
&self,
|
||||
items: Vec<String>,
|
||||
messages: &Sender<InstallMessage>,
|
||||
) -> Result<(), String> {
|
||||
// TODO: Error handling
|
||||
println!("Framework: Installing {:?}", items);
|
||||
|
||||
@ -55,20 +69,50 @@ impl InstallerFramework {
|
||||
println!("Resolved to {:?}", to_install);
|
||||
|
||||
// Install packages
|
||||
let mut count = 0.0 as f64;
|
||||
let max = to_install.len() as f64;
|
||||
|
||||
for package in to_install.iter() {
|
||||
let base_package_percentage = count / max;
|
||||
let base_package_range = ((count + 1.0) / max) - base_package_percentage;
|
||||
|
||||
println!("Installing {}", package.name);
|
||||
|
||||
let results = package.source.get_current_releases().unwrap();
|
||||
messages
|
||||
.send(InstallMessage::Status(
|
||||
format!(
|
||||
"Polling {} for latest version of {}",
|
||||
package.source.name, package.name
|
||||
),
|
||||
base_package_percentage + base_package_range * 0.25,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let results = package.source.get_current_releases()?;
|
||||
|
||||
messages
|
||||
.send(InstallMessage::Status(
|
||||
format!("Resolving dependency for {}", package.name),
|
||||
base_package_percentage + base_package_range * 0.50,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let filtered_regex = package.source.match_regex.replace("#PLATFORM#", OS);
|
||||
let regex = Regex::new(&filtered_regex).unwrap();
|
||||
let regex = match Regex::new(&filtered_regex) {
|
||||
Ok(v) => v,
|
||||
Err(v) => return Err(format!("An error occured while compiling regex: {:?}", v)),
|
||||
};
|
||||
|
||||
// Find the latest release in here
|
||||
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();
|
||||
.max_by_key(|f| f.version.clone());
|
||||
|
||||
let latest_result = match latest_result {
|
||||
Some(v) => v,
|
||||
None => return Err(format!("No release with correct file found")),
|
||||
};
|
||||
|
||||
// Find the matching file in here
|
||||
let latest_file = latest_result
|
||||
@ -79,7 +123,13 @@ impl InstallerFramework {
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", latest_file);
|
||||
|
||||
// TODO: Download found file
|
||||
|
||||
count += 1.0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new instance of the Installer Framework with a specified Config.
|
||||
|
34
src/rest.rs
34
src/rest.rs
@ -12,6 +12,7 @@ use serde_json;
|
||||
use futures::Stream;
|
||||
use futures::Future;
|
||||
use futures::future;
|
||||
use futures::Sink;
|
||||
|
||||
use hyper::{self, Error as HyperError, Get, Post, StatusCode};
|
||||
use hyper::header::{ContentLength, ContentType};
|
||||
@ -29,6 +30,7 @@ use std::collections::HashMap;
|
||||
use assets;
|
||||
|
||||
use installer::InstallerFramework;
|
||||
use installer::InstallMessage;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FileSelection {
|
||||
@ -164,17 +166,39 @@ impl Service for WebService {
|
||||
}
|
||||
}
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
let (tx, rx) = hyper::Body::pair();
|
||||
|
||||
// Startup a thread to do this operation for us
|
||||
thread::spawn(move || {
|
||||
cloned_element.install(to_install);
|
||||
match cloned_element.install(to_install, &sender) {
|
||||
Err(v) => sender.send(InstallMessage::Error(v)).unwrap(),
|
||||
_ => {}
|
||||
}
|
||||
sender.send(InstallMessage::EOF).unwrap();
|
||||
});
|
||||
|
||||
let file = serde_json::to_string(&{}).unwrap();
|
||||
// Spawn a thread for transforming messages to chunk messages
|
||||
thread::spawn(move || {
|
||||
let mut tx = tx;
|
||||
loop {
|
||||
let response = receiver.recv().unwrap();
|
||||
|
||||
match &response {
|
||||
&InstallMessage::EOF => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut response = serde_json::to_string(&response).unwrap();
|
||||
response.push('\n');
|
||||
tx = tx.send(Ok(response.into_bytes().into())).wait().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
Response::<hyper::Body>::new()
|
||||
.with_header(ContentLength(file.len() as u64))
|
||||
.with_header(ContentType::json())
|
||||
.with_body(file)
|
||||
//.with_header(ContentLength(file.len() as u64))
|
||||
.with_header(ContentType::plaintext())
|
||||
.with_body(rx)
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,8 @@ pub struct File {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl File {}
|
||||
|
||||
/// A individual release of an application.
|
||||
#[derive(Debug)]
|
||||
pub struct Release {
|
||||
|
@ -74,6 +74,7 @@
|
||||
<div v-html="config.general.installing_message"></div>
|
||||
<br />
|
||||
|
||||
<div v-html="progress_message"></div>
|
||||
<progress class="progress is-info is-medium" v-bind:value="progress" max="100">
|
||||
{{ progress }}%
|
||||
</progress>
|
||||
@ -106,7 +107,8 @@
|
||||
select_packages : true,
|
||||
is_installing : false,
|
||||
is_finished : false,
|
||||
progress : 0
|
||||
progress : 0,
|
||||
progress_message : ""
|
||||
},
|
||||
methods: {
|
||||
"select_file": function() {
|
||||
@ -132,15 +134,15 @@
|
||||
}
|
||||
console.log(results);
|
||||
|
||||
ajax("/api/start-install", function(e) {
|
||||
// TODO: Remove fake loading
|
||||
setInterval(function() {
|
||||
app.progress += 5;
|
||||
if (app.progress >= 100) {
|
||||
stream_ajax("/api/start-install", function(line) {
|
||||
console.log(line);
|
||||
if (line.hasOwnProperty("Status")) {
|
||||
app.progress_message = line.Status[0];
|
||||
app.progress = line.Status[1] * 100;
|
||||
}
|
||||
}, function(e) {
|
||||
app.is_installing = false;
|
||||
app.is_finished = true;
|
||||
}
|
||||
}, 100);
|
||||
}, undefined, results);
|
||||
},
|
||||
"exit": function() {
|
||||
|
@ -51,6 +51,69 @@ function ajax(path, successCallback, failCallback, data) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a AJAX request, streaming each line as it arrives. Type should be text/plain,
|
||||
* each line will be interpeted as JSON seperately.
|
||||
*
|
||||
* @param path The path to connect to.
|
||||
* @param callback A callback with a JSON payload. Called for every line as it comes.
|
||||
* @param successCallback A callback with a raw text payload.
|
||||
* @param failCallback A fail callback. Optional.
|
||||
* @param data POST data. Optional.
|
||||
*/
|
||||
function stream_ajax(path, callback, successCallback, failCallback, data) {
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
req.addEventListener("load", function() {
|
||||
// The server can sometimes return a string error. Make sure we handle this.
|
||||
if (this.status === 200) {
|
||||
successCallback(this.responseText);
|
||||
} else {
|
||||
failCallback();
|
||||
}
|
||||
});
|
||||
|
||||
req.onreadystatechange = function() {
|
||||
if(req.readyState > 2) {
|
||||
var newData = req.responseText.substr(req.seenBytes);
|
||||
|
||||
var lines = newData.split("\n");
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (line.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var contents = JSON.parse(line);
|
||||
callback(contents);
|
||||
}
|
||||
|
||||
req.seenBytes = req.responseText.length;
|
||||
}
|
||||
};
|
||||
|
||||
req.addEventListener("error", failCallback);
|
||||
|
||||
req.open(data == null ? "GET" : "POST", path + "?nocache=" + request_id++, true);
|
||||
// Rocket only currently supports URL encoded forms.
|
||||
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
if (data != null) {
|
||||
var form = "";
|
||||
|
||||
for (var key in data) {
|
||||
if (form !== "") {
|
||||
form += "&";
|
||||
}
|
||||
form += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]);
|
||||
}
|
||||
|
||||
req.send(form);
|
||||
} else {
|
||||
req.send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default handler if a AJAX request fails. Not to be used directly.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user