liftinstall/src/main.rs

218 lines
5.9 KiB
Rust
Raw Normal View History

2018-08-03 11:52:31 +00:00
//! main.rs
//!
//! The main entrypoint for the application. Orchestrates the building of the installation
//! framework, and opens necessary HTTP servers/frontends.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
2018-08-04 07:15:27 +00:00
#![deny(unsafe_code)]
2018-08-04 08:35:00 +00:00
#![deny(missing_docs)]
2018-01-26 12:29:28 +00:00
#[cfg(windows)]
extern crate nfd;
2018-01-26 12:29:28 +00:00
extern crate web_view;
2018-07-28 05:33:06 +00:00
extern crate futures;
extern crate hyper;
2018-08-03 11:52:31 +00:00
extern crate url;
2018-01-29 10:27:54 +00:00
2018-01-30 04:53:28 +00:00
extern crate number_prefix;
2018-01-30 04:54:44 +00:00
extern crate reqwest;
2018-01-30 04:53:28 +00:00
2018-01-27 03:27:41 +00:00
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate toml;
2018-01-29 12:28:14 +00:00
extern crate regex;
extern crate semver;
2018-08-04 08:35:00 +00:00
extern crate dirs;
2018-01-30 07:29:34 +00:00
extern crate zip;
2018-08-04 06:28:13 +00:00
extern crate fern;
#[macro_use]
extern crate log;
extern crate chrono;
2018-08-04 13:35:56 +00:00
extern crate clap;
2018-01-26 12:29:28 +00:00
mod assets;
2018-01-27 03:27:41 +00:00
mod config;
mod http;
2018-01-27 03:27:41 +00:00
mod installer;
mod logging;
mod rest;
mod sources;
mod tasks;
2018-01-26 12:29:28 +00:00
use web_view::*;
2018-01-27 03:27:41 +00:00
use installer::InstallerFramework;
2018-01-26 12:29:28 +00:00
2018-08-03 12:04:58 +00:00
#[cfg(windows)]
use nfd::Response;
2018-08-03 12:04:58 +00:00
2018-01-27 03:27:41 +00:00
use rest::WebServer;
2018-08-03 14:54:03 +00:00
use std::net::ToSocketAddrs;
2018-08-03 15:01:20 +00:00
use std::net::TcpListener;
2018-08-03 14:54:03 +00:00
use std::sync::Arc;
use std::sync::RwLock;
use logging::LoggingErrors;
2018-08-04 13:35:56 +00:00
use clap::App;
use clap::Arg;
use log::Level;
2018-08-07 05:34:57 +00:00
use config::BaseAttributes;
2018-01-27 11:58:56 +00:00
static RAW_CONFIG: &'static str = include_str!("../config.toml");
2018-01-26 12:29:28 +00:00
#[derive(Deserialize, Debug)]
enum CallbackType {
SelectInstallDir { callback_name: String },
2018-08-04 13:35:56 +00:00
Log { msg: String, kind: String },
}
2018-01-26 12:29:28 +00:00
fn main() {
2018-08-04 06:28:13 +00:00
logging::setup_logger().expect("Unable to setup logging!");
2018-08-07 05:34:57 +00:00
let config =
BaseAttributes::from_toml_str(RAW_CONFIG).log_expect("Config file could not be read");
2018-01-27 03:27:41 +00:00
2018-08-07 05:34:57 +00:00
let app_name = config.name.clone();
2018-08-04 13:35:56 +00:00
let matches = App::new(format!("{} installer", app_name))
.version(env!("CARGO_PKG_VERSION"))
.about(format!("An interactive installer for {}", app_name).as_ref())
.arg(
Arg::with_name("launcher")
.long("launcher")
.value_name("TARGET")
.help("Launches the specified executable after checking for updates")
.takes_value(true),
2018-08-07 10:17:01 +00:00
).get_matches();
2018-08-04 13:35:56 +00:00
2018-08-04 06:28:13 +00:00
info!("{} installer", app_name);
2018-08-03 10:59:43 +00:00
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");
2018-08-04 13:35:56 +00:00
let mut framework = if metadata_file.exists() {
2018-08-04 06:28:13 +00:00
info!("Using pre-existing metadata file: {:?}", metadata_file);
InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata")
2018-05-03 03:30:58 +00:00
} else {
2018-08-04 06:28:13 +00:00
info!("Starting fresh install");
2018-05-03 03:30:58 +00:00
InstallerFramework::new(config)
};
2018-01-27 03:27:41 +00:00
2018-08-04 13:35:56 +00:00
let is_launcher = if let Some(string) = matches.value_of("launcher") {
framework.is_launcher = true;
framework.launcher_path = Some(string.to_string());
true
} else {
false
};
2018-08-03 15:01:20 +00:00
// Firstly, allocate us an epidermal port
let target_port = {
let listener = TcpListener::bind("127.0.0.1:0")
.log_expect("At least one local address should be free");
2018-08-03 15:01:20 +00:00
listener
.local_addr()
.log_expect("Should be able to pull address from listener")
2018-08-03 15:01:20 +00:00
.port()
};
// Now, iterate over all ports
2018-08-03 14:54:03 +00:00
let addresses = "localhost:0"
.to_socket_addrs()
.log_expect("No localhost address found");
2018-08-03 14:54:03 +00:00
let mut servers = Vec::new();
let mut http_address = None;
let framework = Arc::new(RwLock::new(framework));
2018-01-27 03:27:41 +00:00
2018-01-26 12:29:28 +00:00
// Startup HTTP server for handling the web view
2018-08-03 15:01:20 +00:00
for mut address in addresses {
address.set_port(target_port);
2018-08-04 08:35:00 +00:00
let server = WebServer::with_addr(framework.clone(), address)
.log_expect("Failed to bind to address");
2018-08-03 14:54:03 +00:00
2018-08-07 10:23:28 +00:00
info!("Server: {:?}", address);
2018-08-03 14:54:03 +00:00
http_address = Some(address);
2018-08-03 14:54:03 +00:00
servers.push(server);
}
let http_address = http_address.log_expect("No HTTP address found");
2018-08-03 14:54:03 +00:00
let http_address = format!("http://localhost:{}", http_address.port());
2018-01-27 03:27:41 +00:00
2018-01-26 12:29:28 +00:00
// Init the web view
2018-08-04 13:35:56 +00:00
let size = if is_launcher { (600, 300) } else { (1024, 500) };
2018-01-26 12:29:28 +00:00
let resizable = false;
let debug = true;
run(
&format!("{} Installer", app_name),
2018-05-03 13:08:26 +00:00
Content::Url(http_address),
2018-01-26 12:29:28 +00:00
Some(size),
resizable,
debug,
|_| {},
2018-07-28 05:33:06 +00:00
|wv, msg, _| {
let command: CallbackType =
serde_json::from_str(msg).log_expect(&format!("Unable to parse string: {:?}", msg));
2018-08-04 06:28:13 +00:00
debug!("Incoming payload: {:?}", command);
match command {
CallbackType::SelectInstallDir { callback_name } => {
#[cfg(windows)]
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...", "");
2018-08-04 08:35:00 +00:00
if !result.is_empty() {
let result = serde_json::to_string(&result)
.log_expect("Unable to serialize response");
let command = format!("{}({});", callback_name, result);
2018-08-04 06:28:13 +00:00
debug!("Injecting response: {}", command);
wv.eval(&command);
}
}
2018-08-04 13:35:56 +00:00
CallbackType::Log { msg, kind } => {
let kind = match kind.as_ref() {
"info" | "log" => Level::Info,
"warn" => Level::Warn,
"error" => Level::Error,
_ => Level::Error,
};
log!(target: "liftinstall::frontend-js", kind, "{}", msg);
}
2018-07-28 05:33:06 +00:00
}
},
(),
2018-01-26 12:29:28 +00:00
);
}