mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-25 17:55:46 +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]]
|
||||
name = "yuzu Nightly"
|
||||
description = "The nightly build of yuzu contains already reviewed and tested features."
|
||||
default = true
|
||||
[packages.source]
|
||||
name = "github"
|
||||
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$"
|
||||
[packages.source.config]
|
||||
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"
|
||||
|
116
src/installer.rs
116
src/installer.rs
@ -19,11 +19,11 @@ use std::env::var;
|
||||
use std::env::current_exe;
|
||||
use std::env::consts::OS;
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::io::copy;
|
||||
use std::io::Write;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
@ -47,6 +47,9 @@ pub enum InstallMessage {
|
||||
/// etc.
|
||||
pub struct InstallerFramework {
|
||||
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.
|
||||
@ -83,13 +86,21 @@ impl InstallerFramework {
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
&mut self,
|
||||
items: Vec<String>,
|
||||
path: &str,
|
||||
messages: &Sender<InstallMessage>,
|
||||
fresh_install: bool
|
||||
) -> 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);
|
||||
|
||||
// Create our install directory
|
||||
@ -106,13 +117,15 @@ impl InstallerFramework {
|
||||
}
|
||||
|
||||
// Make sure it is empty
|
||||
let paths = match read_dir(&path) {
|
||||
Ok(v) => v,
|
||||
Err(v) => return Err(format!("Failed to read install destination: {:?}", v)),
|
||||
};
|
||||
if fresh_install {
|
||||
let paths = match read_dir(&path) {
|
||||
Ok(v) => v,
|
||||
Err(v) => return Err(format!("Failed to read install destination: {:?}", v)),
|
||||
};
|
||||
|
||||
if paths.count() != 0 {
|
||||
return Err(format!("Install destination is not empty."));
|
||||
if paths.count() != 0 {
|
||||
return Err(format!("Install destination is not empty."));
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve items in config
|
||||
@ -126,12 +139,12 @@ impl InstallerFramework {
|
||||
|
||||
println!("Resolved to {:?}", to_install);
|
||||
|
||||
// TODO: Uninstall pre-existing packages
|
||||
|
||||
// Install packages
|
||||
let mut count = 0.0 as f64;
|
||||
let max = to_install.len() as f64;
|
||||
|
||||
let mut installed_packages = Vec::new();
|
||||
|
||||
for package in to_install.iter() {
|
||||
let base_package_percentage = count / max;
|
||||
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)),
|
||||
};
|
||||
|
||||
println!("Releases: {:?}", results);
|
||||
|
||||
// Find the latest release in here
|
||||
let latest_result = results
|
||||
.into_iter()
|
||||
@ -304,7 +319,8 @@ impl InstallerFramework {
|
||||
}
|
||||
|
||||
// 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(),
|
||||
version: latest_version,
|
||||
files: installed_files,
|
||||
@ -313,22 +329,7 @@ impl InstallerFramework {
|
||||
count += 1.0;
|
||||
}
|
||||
|
||||
// Make copy of metadata
|
||||
// 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)),
|
||||
};
|
||||
}
|
||||
self.save_database()?;
|
||||
|
||||
// Copy installer binary to target directory
|
||||
messages
|
||||
@ -369,8 +370,63 @@ impl InstallerFramework {
|
||||
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.
|
||||
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 rest::WebServer;
|
||||
use std::path::Path;
|
||||
|
||||
// TODO: Fetch this over a HTTP request?
|
||||
static RAW_CONFIG: &'static str = include_str!("../config.toml");
|
||||
@ -47,7 +48,12 @@ fn main() {
|
||||
|
||||
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();
|
||||
|
||||
|
20
src/rest.rs
20
src/rest.rs
@ -31,6 +31,7 @@ use assets;
|
||||
|
||||
use installer::InstallerFramework;
|
||||
use installer::InstallMessage;
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FileSelection {
|
||||
@ -63,7 +64,7 @@ impl WebServer {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
let shared_framework = Arc::new(framework);
|
||||
let shared_framework = Arc::new(RwLock::new(framework));
|
||||
|
||||
let server = Http::new()
|
||||
.bind(&addr, move || {
|
||||
@ -89,7 +90,7 @@ impl WebServer {
|
||||
|
||||
/// Holds internal state for Hyper
|
||||
struct WebService {
|
||||
framework: Arc<InstallerFramework>,
|
||||
framework: Arc<RwLock<InstallerFramework>>,
|
||||
}
|
||||
|
||||
impl Service for WebService {
|
||||
@ -104,9 +105,11 @@ impl Service for WebService {
|
||||
// This endpoint should be usable directly from a <script> tag during loading.
|
||||
// TODO: Handle errors
|
||||
(&Get, "/api/config") => {
|
||||
let framework = self.framework.read().unwrap();
|
||||
|
||||
let file = enscapsulate_json(
|
||||
"config",
|
||||
&self.framework.get_config().to_json_str().unwrap(),
|
||||
&framework.get_config().to_json_str().unwrap(),
|
||||
);
|
||||
|
||||
Response::<hyper::Body>::new()
|
||||
@ -133,7 +136,8 @@ impl Service for WebService {
|
||||
}
|
||||
// Returns the default path for a installation
|
||||
(&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 };
|
||||
|
||||
@ -150,7 +154,7 @@ impl Service for WebService {
|
||||
}
|
||||
(&Post, "/api/start-install") => {
|
||||
// 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| {
|
||||
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
|
||||
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(),
|
||||
_ => {}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
<div class="column" v-if="has_error">
|
||||
<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>
|
||||
@ -147,7 +147,6 @@
|
||||
results["path"] = this.install_location;
|
||||
|
||||
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;
|
||||
|
@ -84,6 +84,8 @@ function stream_ajax(path, callback, successCallback, failCallback, data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log("Incoming line: " + line);
|
||||
|
||||
var contents = JSON.parse(line);
|
||||
callback(contents);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user