mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-22 21:35:42 +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::path::PathBuf;
|
||||||
|
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use config::Config;
|
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,
|
/// The installer framework contains metadata about packages, what is installable, what isn't,
|
||||||
/// etc.
|
/// etc.
|
||||||
pub struct InstallerFramework {
|
pub struct InstallerFramework {
|
||||||
@ -39,7 +49,11 @@ 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>,
|
||||||
|
messages: &Sender<InstallMessage>,
|
||||||
|
) -> Result<(), String> {
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
println!("Framework: Installing {:?}", items);
|
println!("Framework: Installing {:?}", items);
|
||||||
|
|
||||||
@ -55,20 +69,50 @@ impl InstallerFramework {
|
|||||||
println!("Resolved to {:?}", to_install);
|
println!("Resolved to {:?}", to_install);
|
||||||
|
|
||||||
// Install packages
|
// Install packages
|
||||||
|
let mut count = 0.0 as f64;
|
||||||
|
let max = to_install.len() as f64;
|
||||||
|
|
||||||
for package in to_install.iter() {
|
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);
|
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 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
|
// Find the latest release in here
|
||||||
let latest_result = results
|
let latest_result = results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|f| f.files.iter().filter(|x| regex.is_match(&x.name)).count() > 0)
|
.filter(|f| f.files.iter().filter(|x| regex.is_match(&x.name)).count() > 0)
|
||||||
.max_by_key(|f| f.version.clone())
|
.max_by_key(|f| f.version.clone());
|
||||||
.unwrap();
|
|
||||||
|
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
|
// Find the matching file in here
|
||||||
let latest_file = latest_result
|
let latest_file = latest_result
|
||||||
@ -79,7 +123,13 @@ impl InstallerFramework {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
println!("{:?}", latest_file);
|
println!("{:?}", latest_file);
|
||||||
|
|
||||||
|
// TODO: Download found file
|
||||||
|
|
||||||
|
count += 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new instance of the Installer Framework with a specified Config.
|
/// 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::Stream;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
|
use futures::Sink;
|
||||||
|
|
||||||
use hyper::{self, Error as HyperError, Get, Post, StatusCode};
|
use hyper::{self, Error as HyperError, Get, Post, StatusCode};
|
||||||
use hyper::header::{ContentLength, ContentType};
|
use hyper::header::{ContentLength, ContentType};
|
||||||
@ -29,6 +30,7 @@ use std::collections::HashMap;
|
|||||||
use assets;
|
use assets;
|
||||||
|
|
||||||
use installer::InstallerFramework;
|
use installer::InstallerFramework;
|
||||||
|
use installer::InstallMessage;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct FileSelection {
|
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
|
// Startup a thread to do this operation for us
|
||||||
thread::spawn(move || {
|
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()
|
Response::<hyper::Body>::new()
|
||||||
.with_header(ContentLength(file.len() as u64))
|
//.with_header(ContentLength(file.len() as u64))
|
||||||
.with_header(ContentType::json())
|
.with_header(ContentType::plaintext())
|
||||||
.with_body(file)
|
.with_body(rx)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,8 @@ pub struct File {
|
|||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl File {}
|
||||||
|
|
||||||
/// A individual release of an application.
|
/// A individual release of an application.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Release {
|
pub struct Release {
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
<div v-html="config.general.installing_message"></div>
|
<div v-html="config.general.installing_message"></div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
<div v-html="progress_message"></div>
|
||||||
<progress class="progress is-info is-medium" v-bind:value="progress" max="100">
|
<progress class="progress is-info is-medium" v-bind:value="progress" max="100">
|
||||||
{{ progress }}%
|
{{ progress }}%
|
||||||
</progress>
|
</progress>
|
||||||
@ -106,7 +107,8 @@
|
|||||||
select_packages : true,
|
select_packages : true,
|
||||||
is_installing : false,
|
is_installing : false,
|
||||||
is_finished : false,
|
is_finished : false,
|
||||||
progress : 0
|
progress : 0,
|
||||||
|
progress_message : ""
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
"select_file": function() {
|
"select_file": function() {
|
||||||
@ -132,15 +134,15 @@
|
|||||||
}
|
}
|
||||||
console.log(results);
|
console.log(results);
|
||||||
|
|
||||||
ajax("/api/start-install", function(e) {
|
stream_ajax("/api/start-install", function(line) {
|
||||||
// TODO: Remove fake loading
|
console.log(line);
|
||||||
setInterval(function() {
|
if (line.hasOwnProperty("Status")) {
|
||||||
app.progress += 5;
|
app.progress_message = line.Status[0];
|
||||||
if (app.progress >= 100) {
|
app.progress = line.Status[1] * 100;
|
||||||
|
}
|
||||||
|
}, function(e) {
|
||||||
app.is_installing = false;
|
app.is_installing = false;
|
||||||
app.is_finished = true;
|
app.is_finished = true;
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}, undefined, results);
|
}, undefined, results);
|
||||||
},
|
},
|
||||||
"exit": function() {
|
"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.
|
* The default handler if a AJAX request fails. Not to be used directly.
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user