mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-25 22:25:44 +01:00
Modify installer to be more stateful
This commit is contained in:
parent
7b184b24ff
commit
dae36a15b9
11
config.toml
11
config.toml
@ -5,7 +5,6 @@ installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff w
|
|||||||
[[packages]]
|
[[packages]]
|
||||||
name = "yuzu Nightly"
|
name = "yuzu Nightly"
|
||||||
description = "The nightly build of yuzu contains already reviewed and tested features."
|
description = "The nightly build of yuzu contains already reviewed and tested features."
|
||||||
default = true
|
|
||||||
[packages.source]
|
[packages.source]
|
||||||
name = "github"
|
name = "github"
|
||||||
match = "^yuzu-#PLATFORM#(-mingw)?-[0-9]*-[0-9a-f]*.zip$"
|
match = "^yuzu-#PLATFORM#(-mingw)?-[0-9]*-[0-9a-f]*.zip$"
|
||||||
@ -20,3 +19,13 @@ description = "The canary build of yuzu has additional features that are still w
|
|||||||
match = "^yuzu-#PLATFORM#(-mingw)?-[0-9]*-[0-9a-f]*.zip$"
|
match = "^yuzu-#PLATFORM#(-mingw)?-[0-9]*-[0-9a-f]*.zip$"
|
||||||
[packages.source.config]
|
[packages.source.config]
|
||||||
repo = "yuzu-emu/yuzu-canary"
|
repo = "yuzu-emu/yuzu-canary"
|
||||||
|
|
||||||
|
[[packages]]
|
||||||
|
name = "Test package"
|
||||||
|
description = "Just a testing package"
|
||||||
|
default = true
|
||||||
|
[packages.source]
|
||||||
|
name = "github"
|
||||||
|
match = "^TestPackage.zip$"
|
||||||
|
[packages.source.config]
|
||||||
|
repo = "j-selby/test-installer"
|
||||||
|
104
src/installer.rs
104
src/installer.rs
@ -19,11 +19,11 @@ use std::env::var;
|
|||||||
use std::env::current_exe;
|
use std::env::current_exe;
|
||||||
use std::env::consts::OS;
|
use std::env::consts::OS;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::io::copy;
|
use std::io::copy;
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@ -47,6 +47,9 @@ pub enum InstallMessage {
|
|||||||
/// etc.
|
/// etc.
|
||||||
pub struct InstallerFramework {
|
pub struct InstallerFramework {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
database: Vec<LocalInstallation>,
|
||||||
|
install_path: Option<String>,
|
||||||
|
preexisting_install: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to track the amount of data that has been downloaded during a HTTP request.
|
/// Used to track the amount of data that has been downloaded during a HTTP request.
|
||||||
@ -83,13 +86,21 @@ impl InstallerFramework {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a request for something to be installed.
|
/// Sends a request for something to be installed.
|
||||||
|
/// items: Array of named packages to be installed
|
||||||
|
/// messages: Channel used to send progress messages
|
||||||
|
/// fresh_install: If the install directory must be empty
|
||||||
pub fn install(
|
pub fn install(
|
||||||
&self,
|
&mut self,
|
||||||
items: Vec<String>,
|
items: Vec<String>,
|
||||||
path: &str,
|
|
||||||
messages: &Sender<InstallMessage>,
|
messages: &Sender<InstallMessage>,
|
||||||
|
fresh_install: bool
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// TODO: Error handling
|
// We have to have a install path for us to be able to do anything
|
||||||
|
let path = match self.install_path.clone() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(format!("No install directory for installer"))
|
||||||
|
};
|
||||||
|
|
||||||
println!("Framework: Installing {:?} to {}", items, path);
|
println!("Framework: Installing {:?} to {}", items, path);
|
||||||
|
|
||||||
// Create our install directory
|
// Create our install directory
|
||||||
@ -106,6 +117,7 @@ impl InstallerFramework {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure it is empty
|
// Make sure it is empty
|
||||||
|
if fresh_install {
|
||||||
let paths = match read_dir(&path) {
|
let paths = match read_dir(&path) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(v) => return Err(format!("Failed to read install destination: {:?}", v)),
|
Err(v) => return Err(format!("Failed to read install destination: {:?}", v)),
|
||||||
@ -114,6 +126,7 @@ impl InstallerFramework {
|
|||||||
if paths.count() != 0 {
|
if paths.count() != 0 {
|
||||||
return Err(format!("Install destination is not empty."));
|
return Err(format!("Install destination is not empty."));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve items in config
|
// Resolve items in config
|
||||||
let mut to_install = Vec::new();
|
let mut to_install = Vec::new();
|
||||||
@ -126,12 +139,12 @@ impl InstallerFramework {
|
|||||||
|
|
||||||
println!("Resolved to {:?}", to_install);
|
println!("Resolved to {:?}", to_install);
|
||||||
|
|
||||||
|
// TODO: Uninstall pre-existing packages
|
||||||
|
|
||||||
// Install packages
|
// Install packages
|
||||||
let mut count = 0.0 as f64;
|
let mut count = 0.0 as f64;
|
||||||
let max = to_install.len() as f64;
|
let max = to_install.len() as f64;
|
||||||
|
|
||||||
let mut installed_packages = Vec::new();
|
|
||||||
|
|
||||||
for package in to_install.iter() {
|
for package in to_install.iter() {
|
||||||
let base_package_percentage = count / max;
|
let base_package_percentage = count / max;
|
||||||
let base_package_range = ((count + 1.0) / max) - base_package_percentage;
|
let base_package_range = ((count + 1.0) / max) - base_package_percentage;
|
||||||
@ -165,6 +178,8 @@ impl InstallerFramework {
|
|||||||
Err(v) => return Err(format!("An error occured while compiling regex: {:?}", v)),
|
Err(v) => return Err(format!("An error occured while compiling regex: {:?}", v)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!("Releases: {:?}", results);
|
||||||
|
|
||||||
// Find the latest release in here
|
// Find the latest release in here
|
||||||
let latest_result = results
|
let latest_result = results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -304,7 +319,8 @@ impl InstallerFramework {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save metadata about this package
|
// Save metadata about this package
|
||||||
installed_packages.push(LocalInstallation {
|
// TODO: Check if this package already exists
|
||||||
|
self.database.push(LocalInstallation {
|
||||||
name: package.name.to_owned(),
|
name: package.name.to_owned(),
|
||||||
version: latest_version,
|
version: latest_version,
|
||||||
files: installed_files,
|
files: installed_files,
|
||||||
@ -313,22 +329,7 @@ impl InstallerFramework {
|
|||||||
count += 1.0;
|
count += 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make copy of metadata
|
self.save_database()?;
|
||||||
// TODO: Combine with already installed database, if needed
|
|
||||||
let metadata_compiled = serde_json::to_string(&installed_packages).unwrap();
|
|
||||||
|
|
||||||
{
|
|
||||||
let metadata_path = path.join("metadata.json");
|
|
||||||
let mut metadata_file = match File::create(metadata_path) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(v) => return Err(format!("Unable to open file handle: {:?}", v)),
|
|
||||||
};
|
|
||||||
|
|
||||||
match metadata_file.write_all(metadata_compiled.as_bytes()) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(v) => return Err(format!("Unable to write to file: {:?}", v)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy installer binary to target directory
|
// Copy installer binary to target directory
|
||||||
messages
|
messages
|
||||||
@ -369,8 +370,63 @@ impl InstallerFramework {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves the applications database.
|
||||||
|
pub fn save_database(&self) -> Result<(), String> {
|
||||||
|
// We have to have a install path for us to be able to do anything
|
||||||
|
let path = match self.install_path.clone() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(format!("No install directory for installer"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let metadata_path = Path::new(&path).join("metadata.json");
|
||||||
|
let metadata_file = match File::create(metadata_path) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(v) => return Err(format!("Unable to open file handle: {:?}", v)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match serde_json::to_writer(metadata_file, &self.database) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(v) => return Err(format!("Unable to write to file: {:?}", v)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures this installer to install to the specified location.
|
||||||
|
/// If there was a currently configured install path, this will be left as-is.
|
||||||
|
pub fn set_install_dir(&mut self, dir : &str) {
|
||||||
|
self.install_path = Some(dir.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new instance of the Installer Framework with a specified Config.
|
/// Creates a new instance of the Installer Framework with a specified Config.
|
||||||
pub fn new(config: Config) -> Self {
|
pub fn new(config: Config) -> Self {
|
||||||
InstallerFramework { config }
|
InstallerFramework {
|
||||||
|
config,
|
||||||
|
database : Vec::new(),
|
||||||
|
install_path : None,
|
||||||
|
preexisting_install : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new instance of the Installer Framework with a specified Config, managing
|
||||||
|
/// a pre-existing installation.
|
||||||
|
pub fn new_with_db(config: Config, install_path : String) -> Result<Self, String> {
|
||||||
|
let metadata_path = Path::new(&install_path).join("metadata.json");
|
||||||
|
let metadata_file = match File::open(metadata_path) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(v) => return Err(format!("Unable to open file handle: {:?}", v)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let database : Vec<LocalInstallation> = match serde_json::from_reader(metadata_file) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(v) => return Err(format!("Unable to read metadata file: {:?}", v)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(InstallerFramework {
|
||||||
|
config,
|
||||||
|
database,
|
||||||
|
install_path : Some(install_path),
|
||||||
|
preexisting_install : true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ use config::Config;
|
|||||||
use installer::InstallerFramework;
|
use installer::InstallerFramework;
|
||||||
|
|
||||||
use rest::WebServer;
|
use rest::WebServer;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
// TODO: Fetch this over a HTTP request?
|
// TODO: Fetch this over a HTTP request?
|
||||||
static RAW_CONFIG: &'static str = include_str!("../config.toml");
|
static RAW_CONFIG: &'static str = include_str!("../config.toml");
|
||||||
@ -47,7 +48,12 @@ fn main() {
|
|||||||
|
|
||||||
let app_name = config.general.name.clone();
|
let app_name = config.general.name.clone();
|
||||||
|
|
||||||
let framework = InstallerFramework::new(config);
|
let metadata_file = Path::new("metadata.json");
|
||||||
|
let framework = if metadata_file.exists() {
|
||||||
|
InstallerFramework::new_with_db(config, format!("./")).unwrap()
|
||||||
|
} else {
|
||||||
|
InstallerFramework::new(config)
|
||||||
|
};
|
||||||
|
|
||||||
let server = WebServer::new(framework).unwrap();
|
let server = WebServer::new(framework).unwrap();
|
||||||
|
|
||||||
|
20
src/rest.rs
20
src/rest.rs
@ -31,6 +31,7 @@ use assets;
|
|||||||
|
|
||||||
use installer::InstallerFramework;
|
use installer::InstallerFramework;
|
||||||
use installer::InstallMessage;
|
use installer::InstallMessage;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct FileSelection {
|
struct FileSelection {
|
||||||
@ -63,7 +64,7 @@ impl WebServer {
|
|||||||
let (sender, receiver) = channel();
|
let (sender, receiver) = channel();
|
||||||
|
|
||||||
let handle = thread::spawn(move || {
|
let handle = thread::spawn(move || {
|
||||||
let shared_framework = Arc::new(framework);
|
let shared_framework = Arc::new(RwLock::new(framework));
|
||||||
|
|
||||||
let server = Http::new()
|
let server = Http::new()
|
||||||
.bind(&addr, move || {
|
.bind(&addr, move || {
|
||||||
@ -89,7 +90,7 @@ impl WebServer {
|
|||||||
|
|
||||||
/// Holds internal state for Hyper
|
/// Holds internal state for Hyper
|
||||||
struct WebService {
|
struct WebService {
|
||||||
framework: Arc<InstallerFramework>,
|
framework: Arc<RwLock<InstallerFramework>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service for WebService {
|
impl Service for WebService {
|
||||||
@ -104,9 +105,11 @@ impl Service for WebService {
|
|||||||
// This endpoint should be usable directly from a <script> tag during loading.
|
// This endpoint should be usable directly from a <script> tag during loading.
|
||||||
// TODO: Handle errors
|
// TODO: Handle errors
|
||||||
(&Get, "/api/config") => {
|
(&Get, "/api/config") => {
|
||||||
|
let framework = self.framework.read().unwrap();
|
||||||
|
|
||||||
let file = enscapsulate_json(
|
let file = enscapsulate_json(
|
||||||
"config",
|
"config",
|
||||||
&self.framework.get_config().to_json_str().unwrap(),
|
&framework.get_config().to_json_str().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Response::<hyper::Body>::new()
|
Response::<hyper::Body>::new()
|
||||||
@ -133,7 +136,8 @@ impl Service for WebService {
|
|||||||
}
|
}
|
||||||
// Returns the default path for a installation
|
// Returns the default path for a installation
|
||||||
(&Get, "/api/default-path") => {
|
(&Get, "/api/default-path") => {
|
||||||
let path = self.framework.get_default_path();
|
let framework = self.framework.read().unwrap();
|
||||||
|
let path = framework.get_default_path();
|
||||||
|
|
||||||
let response = FileSelection { path };
|
let response = FileSelection { path };
|
||||||
|
|
||||||
@ -150,7 +154,7 @@ impl Service for WebService {
|
|||||||
}
|
}
|
||||||
(&Post, "/api/start-install") => {
|
(&Post, "/api/start-install") => {
|
||||||
// We need to bit of pipelining to get this to work
|
// We need to bit of pipelining to get this to work
|
||||||
let cloned_element = self.framework.clone();
|
let framework = self.framework.clone();
|
||||||
|
|
||||||
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())
|
||||||
@ -180,7 +184,11 @@ impl Service for WebService {
|
|||||||
|
|
||||||
// Startup a thread to do this operation for us
|
// Startup a thread to do this operation for us
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
match cloned_element.install(to_install, &path, &sender) {
|
let mut framework = framework.write().unwrap();
|
||||||
|
|
||||||
|
framework.set_install_dir(&path);
|
||||||
|
|
||||||
|
match framework.install(to_install, &sender, true) {
|
||||||
Err(v) => sender.send(InstallMessage::Error(v)).unwrap(),
|
Err(v) => sender.send(InstallMessage::Error(v)).unwrap(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<div class="column" v-if="has_error">
|
<div class="column" v-if="has_error">
|
||||||
<h4 class="subtitle">An error occurred:</h4>
|
<h4 class="subtitle">An error occurred:</h4>
|
||||||
|
|
||||||
<pre><code>{{ error }}</code></pre>
|
<code>{{ error }}</code>
|
||||||
|
|
||||||
|
|
||||||
<a class="button is-primary is-pulled-right" v-on:click="back_to_packages">Back</a>
|
<a class="button is-primary is-pulled-right" v-on:click="back_to_packages">Back</a>
|
||||||
@ -147,7 +147,6 @@
|
|||||||
results["path"] = this.install_location;
|
results["path"] = this.install_location;
|
||||||
|
|
||||||
stream_ajax("/api/start-install", function(line) {
|
stream_ajax("/api/start-install", function(line) {
|
||||||
console.log(line);
|
|
||||||
if (line.hasOwnProperty("Status")) {
|
if (line.hasOwnProperty("Status")) {
|
||||||
app.progress_message = line.Status[0];
|
app.progress_message = line.Status[0];
|
||||||
app.progress = line.Status[1] * 100;
|
app.progress = line.Status[1] * 100;
|
||||||
|
@ -84,6 +84,8 @@ function stream_ajax(path, callback, successCallback, failCallback, data) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Incoming line: " + line);
|
||||||
|
|
||||||
var contents = JSON.parse(line);
|
var contents = JSON.parse(line);
|
||||||
callback(contents);
|
callback(contents);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user