mirror of
https://github.com/yuzu-emu/liftinstall.git
synced 2024-11-26 03:05:42 +01:00
Overhaul all unwraps/expects to use new logging interface
This commit is contained in:
parent
66fc287770
commit
518565f422
2
build.rs
2
build.rs
@ -5,7 +5,7 @@ extern crate winres;
|
||||
fn main() {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("static/favicon.ico");
|
||||
res.compile().unwrap();
|
||||
res.compile().expect("Failed to generate metadata");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
|
@ -22,6 +22,8 @@ use tasks::install::InstallTask;
|
||||
use tasks::uninstall::UninstallTask;
|
||||
use tasks::DependencyTree;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
/// A message thrown during the installation of packages.
|
||||
#[derive(Serialize)]
|
||||
pub enum InstallMessage {
|
||||
@ -91,7 +93,7 @@ impl InstallerFramework {
|
||||
items,
|
||||
self.install_path
|
||||
.clone()
|
||||
.expect("Install directory not initialised")
|
||||
.log_expect("Install directory not initialised")
|
||||
);
|
||||
|
||||
// Calculate packages to *uninstall*
|
||||
|
@ -1,9 +1,10 @@
|
||||
//! Contains functions to help with logging.
|
||||
|
||||
use fern;
|
||||
use chrono;
|
||||
use fern;
|
||||
use log;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
|
||||
pub fn setup_logger() -> Result<(), fern::InitError> {
|
||||
@ -23,3 +24,42 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
|
||||
.apply()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An additional wrapper usable on Result/Option types to add logging to the regular
|
||||
/// panic route.
|
||||
pub trait LoggingErrors<T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Unwraps this object. See `unwrap()`.
|
||||
fn log_unwrap(self) -> T {
|
||||
self.log_expect("Failed to unwrap")
|
||||
}
|
||||
|
||||
/// Unwraps this object, with a specified error message on failure. See `expect()`.
|
||||
fn log_expect(self, msg: &str) -> T;
|
||||
}
|
||||
|
||||
impl<T, E: Debug> LoggingErrors<T> for Result<T, E> {
|
||||
fn log_expect(self, msg: &str) -> T {
|
||||
match self {
|
||||
Ok(v) => v,
|
||||
Err(v) => {
|
||||
error!("Fatal error: {}: {:?}", msg, v);
|
||||
panic!("Expectation failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> LoggingErrors<T> for Option<T> {
|
||||
fn log_expect(self, msg: &str) -> T {
|
||||
match self {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
error!("Fatal error: {}", msg);
|
||||
panic!("Expectation failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
47
src/main.rs
47
src/main.rs
@ -38,10 +38,10 @@ mod assets;
|
||||
mod config;
|
||||
mod http;
|
||||
mod installer;
|
||||
mod logging;
|
||||
mod rest;
|
||||
mod sources;
|
||||
mod tasks;
|
||||
mod logging;
|
||||
|
||||
use web_view::*;
|
||||
|
||||
@ -60,6 +60,8 @@ use std::net::TcpListener;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
// TODO: Fetch this over a HTTP request?
|
||||
static RAW_CONFIG: &'static str = include_str!("../config.toml");
|
||||
|
||||
@ -71,18 +73,20 @@ enum CallbackType {
|
||||
fn main() {
|
||||
logging::setup_logger().expect("Unable to setup logging!");
|
||||
|
||||
let config = Config::from_toml_str(RAW_CONFIG).unwrap();
|
||||
let config = Config::from_toml_str(RAW_CONFIG).log_expect("Config file could not be read");
|
||||
|
||||
let app_name = config.general.name.clone();
|
||||
|
||||
info!("{} installer", app_name);
|
||||
|
||||
let current_exe = std::env::current_exe().unwrap();
|
||||
let current_path = current_exe.parent().unwrap();
|
||||
let current_exe = std::env::current_exe().log_expect("Current executable could not be found");
|
||||
let current_path = current_exe
|
||||
.parent()
|
||||
.log_expect("Parent directory of executable could not be found");
|
||||
let metadata_file = current_path.join("metadata.json");
|
||||
let framework = if metadata_file.exists() {
|
||||
info!("Using pre-existing metadata file: {:?}", metadata_file);
|
||||
InstallerFramework::new_with_db(config, current_path).unwrap()
|
||||
InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata")
|
||||
} else {
|
||||
info!("Starting fresh install");
|
||||
InstallerFramework::new(config)
|
||||
@ -90,18 +94,18 @@ fn main() {
|
||||
|
||||
// Firstly, allocate us an epidermal port
|
||||
let target_port = {
|
||||
let listener =
|
||||
TcpListener::bind("127.0.0.1:0").expect("At least one local address should be free");
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.log_expect("At least one local address should be free");
|
||||
listener
|
||||
.local_addr()
|
||||
.expect("Should be able to pull address from listener")
|
||||
.log_expect("Should be able to pull address from listener")
|
||||
.port()
|
||||
};
|
||||
|
||||
// Now, iterate over all ports
|
||||
let addresses = "localhost:0"
|
||||
.to_socket_addrs()
|
||||
.expect("No localhost address found");
|
||||
.log_expect("No localhost address found");
|
||||
|
||||
let mut servers = Vec::new();
|
||||
let mut http_address = None;
|
||||
@ -112,12 +116,12 @@ fn main() {
|
||||
for mut address in addresses {
|
||||
address.set_port(target_port);
|
||||
|
||||
let server = WebServer::with_addr(framework.clone(), address).unwrap();
|
||||
let server = WebServer::with_addr(framework.clone(), address.clone())
|
||||
.log_expect("Failed to bind to address");
|
||||
|
||||
let addr = server.get_addr();
|
||||
debug!("Server: {:?}", addr);
|
||||
debug!("Server: {:?}", address);
|
||||
|
||||
http_address = Some(addr);
|
||||
http_address = Some(address);
|
||||
|
||||
servers.push(server);
|
||||
}
|
||||
@ -143,26 +147,27 @@ fn main() {
|
||||
|_| {},
|
||||
|wv, msg, _| {
|
||||
let command: CallbackType =
|
||||
serde_json::from_str(msg).expect(&format!("Unable to parse string: {:?}", msg));
|
||||
serde_json::from_str(msg).log_expect(&format!("Unable to parse string: {:?}", msg));
|
||||
|
||||
debug!("Incoming payload: {:?}", command);
|
||||
|
||||
match command {
|
||||
CallbackType::SelectInstallDir { callback_name } => {
|
||||
#[cfg(windows)]
|
||||
let result =
|
||||
match nfd::open_pick_folder(None).expect("Unable to open folder dialog") {
|
||||
Response::Okay(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
let result = match nfd::open_pick_folder(None)
|
||||
.log_expect("Unable to open folder dialog")
|
||||
{
|
||||
Response::Okay(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let result =
|
||||
wv.dialog(Dialog::ChooseDirectory, "Select a install directory...", "");
|
||||
|
||||
if result.len() > 0 {
|
||||
let result =
|
||||
serde_json::to_string(&result).expect("Unable to serialize response");
|
||||
let result = serde_json::to_string(&result)
|
||||
.log_expect("Unable to serialize response");
|
||||
let command = format!("{}({});", callback_name, result);
|
||||
debug!("Injecting response: {}", command);
|
||||
wv.eval(&command);
|
||||
|
137
src/rest.rs
137
src/rest.rs
@ -29,6 +29,8 @@ use assets;
|
||||
use installer::InstallMessage;
|
||||
use installer::InstallerFramework;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FileSelection {
|
||||
path: Option<String>,
|
||||
@ -38,22 +40,14 @@ struct FileSelection {
|
||||
/// application.
|
||||
pub struct WebServer {
|
||||
_handle: JoinHandle<()>,
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl WebServer {
|
||||
/// Returns the bound address that the server is running from.
|
||||
pub fn get_addr(&self) -> SocketAddr {
|
||||
self.addr.clone()
|
||||
}
|
||||
|
||||
/// Creates a new web server with the specified address.
|
||||
pub fn with_addr(
|
||||
framework: Arc<RwLock<InstallerFramework>>,
|
||||
addr: SocketAddr,
|
||||
) -> Result<Self, HyperError> {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
let server = Http::new()
|
||||
.bind(&addr, move || {
|
||||
@ -61,19 +55,12 @@ impl WebServer {
|
||||
framework: framework.clone(),
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
.log_expect("Failed to bind to port");
|
||||
|
||||
sender.send(server.local_addr().unwrap()).unwrap();
|
||||
|
||||
server.run().unwrap();
|
||||
server.run().log_expect("Failed to run HTTP server");
|
||||
});
|
||||
|
||||
let addr = receiver.recv().unwrap();
|
||||
|
||||
Ok(WebServer {
|
||||
_handle: handle,
|
||||
addr,
|
||||
})
|
||||
Ok(WebServer { _handle: handle })
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,10 +81,18 @@ 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 framework = self
|
||||
.framework
|
||||
.read()
|
||||
.log_expect("InstallerFramework has been dirtied");
|
||||
|
||||
let file =
|
||||
enscapsulate_json("config", &framework.get_config().to_json_str().unwrap());
|
||||
let file = enscapsulate_json(
|
||||
"config",
|
||||
&framework
|
||||
.get_config()
|
||||
.to_json_str()
|
||||
.log_expect("Failed to render JSON representation of config"),
|
||||
);
|
||||
|
||||
Response::<hyper::Body>::new()
|
||||
.with_header(ContentLength(file.len() as u64))
|
||||
@ -107,11 +102,15 @@ impl Service for WebService {
|
||||
// This endpoint should be usable directly from a <script> tag during loading.
|
||||
// TODO: Handle errors
|
||||
(&Get, "/api/packages") => {
|
||||
let framework = self.framework.read().unwrap();
|
||||
let framework = self
|
||||
.framework
|
||||
.read()
|
||||
.log_expect("InstallerFramework has been dirtied");
|
||||
|
||||
let file = enscapsulate_json(
|
||||
"packages",
|
||||
&serde_json::to_string(&framework.database).unwrap(),
|
||||
&serde_json::to_string(&framework.database)
|
||||
.log_expect("Failed to render JSON representation of database"),
|
||||
);
|
||||
|
||||
Response::<hyper::Body>::new()
|
||||
@ -121,12 +120,16 @@ impl Service for WebService {
|
||||
}
|
||||
// Returns the default path for a installation
|
||||
(&Get, "/api/default-path") => {
|
||||
let framework = self.framework.read().unwrap();
|
||||
let framework = self
|
||||
.framework
|
||||
.read()
|
||||
.log_expect("InstallerFramework has been dirtied");
|
||||
let path = framework.get_default_path();
|
||||
|
||||
let response = FileSelection { path };
|
||||
|
||||
let file = serde_json::to_string(&response).unwrap();
|
||||
let file = serde_json::to_string(&response)
|
||||
.log_expect("Failed to render JSON payload of default path object");
|
||||
|
||||
Response::<hyper::Body>::new()
|
||||
.with_header(ContentLength(file.len() as u64))
|
||||
@ -139,11 +142,15 @@ impl Service for WebService {
|
||||
}
|
||||
// Gets properties such as if the application is in maintenance mode
|
||||
(&Get, "/api/installation-status") => {
|
||||
let framework = self.framework.read().unwrap();
|
||||
let framework = self
|
||||
.framework
|
||||
.read()
|
||||
.log_expect("InstallerFramework has been dirtied");
|
||||
|
||||
let response = framework.get_installation_status();
|
||||
|
||||
let file = serde_json::to_string(&response).unwrap();
|
||||
let file = serde_json::to_string(&response)
|
||||
.log_expect("Failed to render JSON payload of installation status object");
|
||||
|
||||
Response::<hyper::Body>::new()
|
||||
.with_header(ContentLength(file.len() as u64))
|
||||
@ -161,32 +168,51 @@ impl Service for WebService {
|
||||
|
||||
// Startup a thread to do this operation for us
|
||||
thread::spawn(move || {
|
||||
let mut framework = framework.write().unwrap();
|
||||
let mut framework = framework
|
||||
.write()
|
||||
.log_expect("InstallerFramework has been dirtied");
|
||||
|
||||
match framework.uninstall(&sender) {
|
||||
Err(v) => {
|
||||
error!("Uninstall error occurred: {:?}", v);
|
||||
sender.send(InstallMessage::Error(v)).unwrap();
|
||||
},
|
||||
match sender.send(InstallMessage::Error(v)) {
|
||||
Err(v) => {
|
||||
error!("Failed to send uninstall error: {:?}", v);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
sender.send(InstallMessage::EOF).unwrap();
|
||||
|
||||
match sender.send(InstallMessage::EOF) {
|
||||
Err(v) => {
|
||||
error!("Failed to send EOF to client: {:?}", v);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
});
|
||||
|
||||
// Spawn a thread for transforming messages to chunk messages
|
||||
thread::spawn(move || {
|
||||
let mut tx = tx;
|
||||
loop {
|
||||
let response = receiver.recv().unwrap();
|
||||
let response = receiver
|
||||
.recv()
|
||||
.log_expect("Failed to recieve message from runner thread");
|
||||
|
||||
match &response {
|
||||
&InstallMessage::EOF => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut response = serde_json::to_string(&response).unwrap();
|
||||
let mut response = serde_json::to_string(&response)
|
||||
.log_expect("Failed to render JSON logging response payload");
|
||||
response.push('\n');
|
||||
tx = tx.send(Ok(response.into_bytes().into())).wait().unwrap();
|
||||
tx = tx
|
||||
.send(Ok(response.into_bytes().into()))
|
||||
.wait()
|
||||
.log_expect("Failed to write JSON response payload to client");
|
||||
}
|
||||
});
|
||||
|
||||
@ -222,14 +248,18 @@ impl Service for WebService {
|
||||
}
|
||||
|
||||
// The frontend always provides this
|
||||
let path = path.unwrap();
|
||||
let path = path.log_expect(
|
||||
"No path specified by frontend when one should have already existed",
|
||||
);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
let (tx, rx) = hyper::Body::pair();
|
||||
|
||||
// Startup a thread to do this operation for us
|
||||
thread::spawn(move || {
|
||||
let mut framework = framework.write().unwrap();
|
||||
let mut framework = framework
|
||||
.write()
|
||||
.log_expect("InstallerFramework has been dirtied");
|
||||
|
||||
let new_install = !framework.preexisting_install;
|
||||
if new_install {
|
||||
@ -238,28 +268,45 @@ impl Service for WebService {
|
||||
|
||||
match framework.install(to_install, &sender, new_install) {
|
||||
Err(v) => {
|
||||
error!("Install error occurred: {:?}", v);
|
||||
sender.send(InstallMessage::Error(v)).unwrap();
|
||||
},
|
||||
error!("Uninstall error occurred: {:?}", v);
|
||||
match sender.send(InstallMessage::Error(v)) {
|
||||
Err(v) => {
|
||||
error!("Failed to send uninstall error: {:?}", v);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
sender.send(InstallMessage::EOF).unwrap();
|
||||
|
||||
match sender.send(InstallMessage::EOF) {
|
||||
Err(v) => {
|
||||
error!("Failed to send EOF to client: {:?}", v);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
});
|
||||
|
||||
// Spawn a thread for transforming messages to chunk messages
|
||||
thread::spawn(move || {
|
||||
let mut tx = tx;
|
||||
loop {
|
||||
let response = receiver.recv().unwrap();
|
||||
let response = receiver
|
||||
.recv()
|
||||
.log_expect("Failed to recieve message from runner thread");
|
||||
|
||||
match &response {
|
||||
&InstallMessage::EOF => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut response = serde_json::to_string(&response).unwrap();
|
||||
let mut response = serde_json::to_string(&response)
|
||||
.log_expect("Failed to render JSON logging response payload");
|
||||
response.push('\n');
|
||||
tx = tx.send(Ok(response.into_bytes().into())).wait().unwrap();
|
||||
tx = tx
|
||||
.send(Ok(response.into_bytes().into()))
|
||||
.wait()
|
||||
.log_expect("Failed to write JSON response payload to client");
|
||||
}
|
||||
});
|
||||
|
||||
@ -281,7 +328,9 @@ impl Service for WebService {
|
||||
|
||||
match assets::file_from_string(&path) {
|
||||
Some((content_type, file)) => {
|
||||
let content_type = ContentType(content_type.parse().unwrap());
|
||||
let content_type = ContentType(content_type.parse().log_expect(
|
||||
"Failed to parse content type into correct representation",
|
||||
));
|
||||
Response::<hyper::Body>::new()
|
||||
.with_header(ContentLength(file.len() as u64))
|
||||
.with_header(content_type)
|
||||
|
@ -11,6 +11,8 @@ use http::stream_file;
|
||||
|
||||
use number_prefix::{decimal_prefix, Prefixed, Standalone};
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
pub struct DownloadPackageTask {
|
||||
pub name: String,
|
||||
}
|
||||
@ -24,7 +26,7 @@ impl Task for DownloadPackageTask {
|
||||
) -> Result<TaskParamType, String> {
|
||||
assert_eq!(input.len(), 1);
|
||||
|
||||
let file = input.pop().expect("Should have input from resolver!");
|
||||
let file = input.pop().log_expect("Should have input from resolver!");
|
||||
let (version, file) = match file {
|
||||
TaskParamType::File(v, f) => (v, f),
|
||||
_ => return Err(format!("Unexpected param type to download package")),
|
||||
|
@ -8,6 +8,8 @@ use tasks::TaskParamType;
|
||||
use std::fs::create_dir_all;
|
||||
use std::fs::read_dir;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
pub struct VerifyInstallDirTask {
|
||||
pub clean_install: bool,
|
||||
}
|
||||
@ -25,7 +27,7 @@ impl Task for VerifyInstallDirTask {
|
||||
let path = context
|
||||
.install_path
|
||||
.as_ref()
|
||||
.expect("No install path specified");
|
||||
.log_expect("No install path specified");
|
||||
|
||||
if !path.exists() {
|
||||
create_dir_all(&path)
|
||||
|
@ -18,6 +18,8 @@ use tasks::uninstall_pkg::UninstallPackageTask;
|
||||
|
||||
use zip::ZipArchive;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
pub struct InstallPackageTask {
|
||||
pub name: String,
|
||||
}
|
||||
@ -34,7 +36,7 @@ impl Task for InstallPackageTask {
|
||||
let path = context
|
||||
.install_path
|
||||
.as_ref()
|
||||
.expect("No install path specified");
|
||||
.log_expect("No install path specified");
|
||||
|
||||
let mut installed_files = Vec::new();
|
||||
|
||||
@ -52,13 +54,16 @@ impl Task for InstallPackageTask {
|
||||
};
|
||||
|
||||
// Check to see if no archive was available.
|
||||
match input.pop().expect("Should have input from uninstaller!") {
|
||||
match input
|
||||
.pop()
|
||||
.log_expect("Should have input from uninstaller!")
|
||||
{
|
||||
// No file to install, but all is good.
|
||||
TaskParamType::Break => return Ok(TaskParamType::None),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let data = input.pop().expect("Should have input from resolver!");
|
||||
let data = input.pop().log_expect("Should have input from resolver!");
|
||||
let (file, data) = match data {
|
||||
TaskParamType::FileContents(file, data) => (file, data),
|
||||
_ => return Err(format!("Unexpected param type to install package")),
|
||||
@ -74,7 +79,7 @@ impl Task for InstallPackageTask {
|
||||
let zip_size = zip.len();
|
||||
|
||||
for i in 0..zip_size {
|
||||
let mut file = zip.by_index(i).unwrap();
|
||||
let mut file = zip.by_index(i).log_expect("Failed to iterate on .zip file");
|
||||
|
||||
messenger(
|
||||
&format!("Extracting {} ({} of {})", file.name(), i + 1, zip_size),
|
||||
|
@ -11,6 +11,8 @@ use config::PackageDescription;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
pub struct ResolvePackageTask {
|
||||
pub name: String,
|
||||
}
|
||||
@ -76,7 +78,7 @@ impl Task for ResolvePackageTask {
|
||||
.into_iter()
|
||||
.filter(|x| regex.is_match(&x.name))
|
||||
.next()
|
||||
.unwrap();
|
||||
.log_expect("Searched file should have existed, but didn't");
|
||||
|
||||
info!("Selected file: {:?}", latest_file);
|
||||
|
||||
|
@ -12,6 +12,8 @@ use std::io::copy;
|
||||
|
||||
use std::env::current_exe;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
pub struct SaveExecutableTask {}
|
||||
|
||||
impl Task for SaveExecutableTask {
|
||||
@ -27,7 +29,7 @@ impl Task for SaveExecutableTask {
|
||||
let path = context
|
||||
.install_path
|
||||
.as_ref()
|
||||
.expect("No install path specified");
|
||||
.log_expect("No install path specified");
|
||||
|
||||
let current_app = match current_exe() {
|
||||
Ok(v) => v,
|
||||
|
@ -10,6 +10,8 @@ use installer::LocalInstallation;
|
||||
use std::fs::remove_dir;
|
||||
use std::fs::remove_file;
|
||||
|
||||
use logging::LoggingErrors;
|
||||
|
||||
pub struct UninstallPackageTask {
|
||||
pub name: String,
|
||||
pub optional: bool,
|
||||
@ -27,7 +29,7 @@ impl Task for UninstallPackageTask {
|
||||
let path = context
|
||||
.install_path
|
||||
.as_ref()
|
||||
.expect("No install path specified");
|
||||
.log_expect("No install path specified");
|
||||
|
||||
let mut metadata: Option<LocalInstallation> = None;
|
||||
for i in 0..context.database.len() {
|
||||
|
Loading…
Reference in New Issue
Block a user