use crate::{Error, RESUME_FLAG, UpdateState};
use human_errors::ResultExt;
use std::{path::Path, process::Command};
use tracing::debug;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(test)]
use mockall::automock;
#[cfg(windows)]
mod windows {
pub const DETACHED_PROCESS: u32 = 0x0000_0008;
pub const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
}
pub(crate) fn default() -> Box<dyn Launcher + Send + Sync> {
Box::new(DefaultLauncher {})
}
#[cfg_attr(test, automock)]
pub trait Launcher {
fn launch(&self, app_path: &Path, state: &UpdateState) -> Result<(), Error> {
let state_json = serde_json::to_string(state).wrap_system_err(
"Failed to serialize the update state into a JSON payload.",
&["Please report this issue to the application's maintainers."],
)?;
debug!(
"Launching '{}' to perform the '{}' phase of the update.",
app_path.display(),
state.phase
);
let mut cmd = Command::new(app_path);
cmd.arg(RESUME_FLAG).arg(&state_json);
#[cfg(windows)]
cmd.creation_flags(windows::DETACHED_PROCESS | windows::CREATE_NEW_PROCESS_GROUP);
self.spawn(cmd).wrap_system_err(
format!(
"Could not launch the new application version to continue the update process (-> {} phase).",
state.phase
),
&["Please report this issue to the application's maintainers, or try updating manually by downloading the latest release yourself."],
)
}
fn spawn(&self, cmd: Command) -> Result<(), Error>;
}
struct DefaultLauncher {}
impl Launcher for DefaultLauncher {
fn spawn(&self, mut cmd: Command) -> Result<(), Error> {
cmd.spawn().wrap_user_err(
"Failed to launch the application to complete the update process.",
&[
"Try re-running the update.",
"Download the latest release and install it manually if the problem continues.",
],
)?;
Ok(())
}
}