From 3109d48dce44fae2660c7a112ecea19cded98ac0 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 23 Jun 2019 21:46:04 +1000 Subject: [PATCH] Migrate self updating functions to own module --- src/main.rs | 104 ++++-------------------------------------- src/self_update.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 96 deletions(-) create mode 100644 src/self_update.rs diff --git a/src/main.rs b/src/main.rs index b8a996d..84ac684 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,20 +56,12 @@ mod http; mod installer; mod logging; mod native; +mod self_update; mod sources; mod tasks; use installer::InstallerFramework; -use std::path::PathBuf; - -use std::process::exit; -use std::process::Command; -use std::{thread, time}; - -use std::fs::remove_file; -use std::fs::File; - use logging::LoggingErrors; use clap::App; @@ -85,6 +77,7 @@ fn main() { logging::setup_logger(format!("{}_installer.log", config.name)) .expect("Unable to setup logging!"); + // Parse CLI arguments let app_name = config.name.clone(); let app_about = format!("An interactive installer for {}", app_name); @@ -111,100 +104,19 @@ fn main() { info!("{} installer", app_name); + // Handle self-updating if needed 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"); - // Check to see if we are currently in a self-update - if let Some(to_path) = matches.value_of("swap") { - let to_path = PathBuf::from(to_path); - - // Sleep a little bit to allow Windows to close the previous file handle - thread::sleep(time::Duration::from_millis(3000)); - - info!( - "Swapping installer from {} to {}", - current_exe.display(), - to_path.display() - ); - - // Attempt it a few times because Windows can hold a lock - for i in 1..=5 { - let swap_result = if cfg!(windows) { - use std::fs::copy; - - copy(¤t_exe, &to_path).map(|_x| ()) - } else { - use std::fs::rename; - - rename(¤t_exe, &to_path) - }; - - match swap_result { - Ok(_) => break, - Err(e) => { - if i < 5 { - info!("Copy attempt failed: {:?}, retrying in 3 seconds.", e); - thread::sleep(time::Duration::from_millis(3000)); - } else { - Err::<(), _>(e).log_expect("Copying new binary failed"); - } - } - } - } - - Command::new(to_path) - .spawn() - .log_expect("Unable to start child process"); - - exit(0); + self_update::perform_swap(¤t_exe, matches.value_of("swap")); + if let Some(new_matches) = self_update::check_args(reinterpret_app, current_path) { + matches = new_matches; } + self_update::cleanup(current_path); - // If we just finished a update, we need to inject our previous command line arguments - let args_file = current_path.join("args.json"); - - if args_file.exists() { - let database: Vec = { - let metadata_file = - File::open(&args_file).log_expect("Unable to open args file handle"); - - serde_json::from_reader(metadata_file).log_expect("Unable to read metadata file") - }; - - matches = reinterpret_app.get_matches_from(database); - - info!("Parsed command line arguments from original instance"); - remove_file(args_file).log_expect("Unable to clean up args file"); - } - - // Cleanup any remaining new maintenance tool instances if they exist - if cfg!(windows) { - let updater_executable = current_path.join("maintenancetool_new.exe"); - - if updater_executable.exists() { - // Sleep a little bit to allow Windows to close the previous file handle - thread::sleep(time::Duration::from_millis(3000)); - - // Attempt it a few times because Windows can hold a lock - for i in 1..=5 { - let swap_result = remove_file(&updater_executable); - match swap_result { - Ok(_) => break, - Err(e) => { - if i < 5 { - info!("Cleanup attempt failed: {:?}, retrying in 3 seconds.", e); - thread::sleep(time::Duration::from_millis(3000)); - } else { - warn!("Deleting temp binary failed after 5 attempts: {:?}", e); - } - } - } - } - } - } - - // Load in metadata as to learn about the environment + // Load in metadata + setup the installer framework let metadata_file = current_path.join("metadata.json"); let mut framework = if metadata_file.exists() { info!("Using pre-existing metadata file: {:?}", metadata_file); diff --git a/src/self_update.rs b/src/self_update.rs new file mode 100644 index 0000000..463f2b0 --- /dev/null +++ b/src/self_update.rs @@ -0,0 +1,111 @@ +//! self_update.rs +//! +//! Handles different components of self-updating. + +use std::fs::{remove_file, File}; +use std::path::{Path, PathBuf}; +use std::process::{exit, Command}; +use std::{thread, time}; + +use clap::{App, ArgMatches}; + +use logging::LoggingErrors; + +/// Swaps around the main executable if needed. +pub fn perform_swap(current_exe: &PathBuf, to_path: Option<&str>) { + // Check to see if we are currently in a self-update + if let Some(to_path) = to_path { + let to_path = PathBuf::from(to_path); + + // Sleep a little bit to allow Windows to close the previous file handle + thread::sleep(time::Duration::from_millis(3000)); + + info!( + "Swapping installer from {} to {}", + current_exe.display(), + to_path.display() + ); + + // Attempt it a few times because Windows can hold a lock + for i in 1..=5 { + let swap_result = if cfg!(windows) { + use std::fs::copy; + + copy(¤t_exe, &to_path).map(|_x| ()) + } else { + use std::fs::rename; + + rename(¤t_exe, &to_path) + }; + + match swap_result { + Ok(_) => break, + Err(e) => { + if i < 5 { + info!("Copy attempt failed: {:?}, retrying in 3 seconds.", e); + thread::sleep(time::Duration::from_millis(3000)); + } else { + Err::<(), _>(e).log_expect("Copying new binary failed"); + } + } + } + } + + Command::new(to_path) + .spawn() + .log_expect("Unable to start child process"); + + exit(0); + } +} + +pub fn check_args<'a>(app: App<'a, '_>, current_path: &Path) -> Option> { + // If we just finished a update, we need to inject our previous command line arguments + let args_file = current_path.join("args.json"); + + if args_file.exists() { + let database: Vec = { + let metadata_file = + File::open(&args_file).log_expect("Unable to open args file handle"); + + serde_json::from_reader(metadata_file).log_expect("Unable to read metadata file") + }; + + let matches = app.get_matches_from(database); + + info!("Parsed command line arguments from original instance"); + remove_file(args_file).log_expect("Unable to clean up args file"); + + Some(matches) + } else { + None + } +} + +pub fn cleanup(current_path: &Path) { + // Cleanup any remaining new maintenance tool instances if they exist + if cfg!(windows) { + let updater_executable = current_path.join("maintenancetool_new.exe"); + + if updater_executable.exists() { + // Sleep a little bit to allow Windows to close the previous file handle + thread::sleep(time::Duration::from_millis(3000)); + + // Attempt it a few times because Windows can hold a lock + for i in 1..=5 { + let swap_result = remove_file(&updater_executable); + match swap_result { + Ok(_) => break, + Err(e) => { + if i < 5 { + info!("Cleanup attempt failed: {:?}, retrying in 3 seconds.", e); + thread::sleep(time::Duration::from_millis(3000)); + } else { + warn!("Deleting temp binary failed after 5 attempts: {:?}", e); + } + } + } + } + } + } +}