Add ability to wait for a specific process (instead of the current one)

This commit is contained in:
Caelan Sayler
2025-02-02 19:44:38 +00:00
committed by Caelan
parent c8688ebefb
commit c596e0e986
10 changed files with 177 additions and 66 deletions

View File

@@ -179,7 +179,7 @@ private:
try
{
updateManager->WaitExitThenApplyUpdate(updateInfo.value());
updateManager->WaitExitThenApplyUpdates(updateInfo.value());
wxTheApp->ExitMainLoop();
}
catch (...) { /* exception will print in log */ }

View File

@@ -188,7 +188,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
MessageBoxCentered(hWnd, L"Download an update first.", szTitle, MB_OK);
}
else {
manager->WaitExitThenApplyUpdate(updInfo.value());
manager->WaitExitThenApplyUpdates(updInfo.value());
exit(0);
}
}

View File

@@ -294,13 +294,27 @@ bool vpkc_download_updates(vpkc_update_manager_t *p_manager,
* You should then clean up any state and exit your app. The updater will apply updates and then
* optionally restart your app. The updater will only wait for 60 seconds before giving up.
*/
bool vpkc_wait_exit_then_apply_update(vpkc_update_manager_t *p_manager,
bool vpkc_wait_exit_then_apply_updates(vpkc_update_manager_t *p_manager,
struct vpkc_asset_t *p_asset,
bool b_silent,
bool b_restart,
char **p_restart_args,
size_t c_restart_args);
/**
* This will launch the Velopack updater and optionally wait for a program to exit gracefully.
* This method is unsafe because it does not necessarily wait for any / the correct process to exit
* before applying updates. The `vpkc_wait_exit_then_apply_updates` method is recommended for most use cases.
* If dw_wait_pid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended).
*/
bool vpkc_unsafe_apply_updates(vpkc_update_manager_t *p_manager,
struct vpkc_asset_t *p_asset,
bool b_silent,
uint32_t dw_wait_pid,
bool b_restart,
char **p_restart_args,
size_t c_restart_args);
/**
* Frees a vpkc_update_manager_t instance.
*/

View File

@@ -583,10 +583,19 @@ public:
* You should then clean up any state and exit your app. The updater will apply updates and then
* optionally restart your app. The updater will only wait for 60 seconds before giving up.
*/
void WaitExitThenApplyUpdate(const VelopackAsset& asset, bool silent = false, bool restart = true, std::vector<std::string> restartArgs = {}) {
void WaitExitThenApplyUpdates(const UpdateInfo& asset, bool silent = false, bool restart = true, std::vector<std::string> restartArgs = {}) {
this->WaitExitThenApplyUpdates(asset.TargetFullRelease, silent, restart, restartArgs);
};
/**
* This will launch the Velopack updater and tell it to wait for this program to exit gracefully.
* You should then clean up any state and exit your app. The updater will apply updates and then
* optionally restart your app. The updater will only wait for 60 seconds before giving up.
*/
void WaitExitThenApplyUpdates(const VelopackAsset& asset, bool silent = false, bool restart = true, std::vector<std::string> restartArgs = {}) {
char** pRestartArgs = allocate_cstring_array(restartArgs);
vpkc_asset_t vpkc_asset = to_c(asset);
bool result = vpkc_wait_exit_then_apply_update(m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size());
bool result = vpkc_wait_exit_then_apply_updates(m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size());
free_cstring_array(pRestartArgs, restartArgs.size());
if (!result) {
@@ -595,12 +604,20 @@ public:
};
/**
* This will launch the Velopack updater and tell it to wait for this program to exit gracefully.
* You should then clean up any state and exit your app. The updater will apply updates and then
* optionally restart your app. The updater will only wait for 60 seconds before giving up.
* This will launch the Velopack updater and optionally wait for a program to exit gracefully.
* This method is unsafe because it does not necessarily wait for any / the correct process to exit
* before applying updates. The `WaitExitThenApplyUpdates` method is recommended for most use cases.
* If waitPid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended).
*/
void WaitExitThenApplyUpdate(const UpdateInfo& asset, bool silent = false, bool restart = true, std::vector<std::string> restartArgs = {}) {
this->WaitExitThenApplyUpdate(asset.TargetFullRelease, silent, restart, restartArgs);
void UnsafeApplyUpdates(const VelopackAsset& asset, bool silent, uint32_t waitPid, bool restart, std::vector<std::string> restartArgs) {
char** pRestartArgs = allocate_cstring_array(restartArgs);
vpkc_asset_t vpkc_asset = to_c(asset);
bool result = vpkc_unsafe_apply_updates(m_pManager, &vpkc_asset, silent, waitPid, restart, pRestartArgs, restartArgs.size());
free_cstring_array(pRestartArgs, restartArgs.size());
if (!result) {
throw_last_error();
}
};
};

View File

@@ -15,7 +15,7 @@ use anyhow::{anyhow, bail};
use libc::{c_char, c_void, size_t};
use log_derive::{logfn, logfn_inputs};
use std::{ffi::CString, ptr};
use velopack::{sources, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp};
use velopack::{sources, ApplyWaitMode, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp};
/// Create a new FileSource update source for a given file path.
#[no_mangle]
@@ -295,7 +295,7 @@ pub extern "C" fn vpkc_download_updates(
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_wait_exit_then_apply_update(
pub extern "C" fn vpkc_wait_exit_then_apply_updates(
p_manager: *mut vpkc_update_manager_t,
p_asset: *mut vpkc_asset_t,
b_silent: bool,
@@ -316,6 +316,36 @@ pub extern "C" fn vpkc_wait_exit_then_apply_update(
})
}
/// This will launch the Velopack updater and optionally wait for a program to exit gracefully.
/// This method is unsafe because it does not necessarily wait for any / the correct process to exit
/// before applying updates. The `vpkc_wait_exit_then_apply_updates` method is recommended for most use cases.
/// If dw_wait_pid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended).
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_unsafe_apply_updates(
p_manager: *mut vpkc_update_manager_t,
p_asset: *mut vpkc_asset_t,
b_silent: bool,
dw_wait_pid: u32,
b_restart: bool,
p_restart_args: *mut *mut c_char,
c_restart_args: size_t,
) -> bool {
wrap_error(|| {
let manager = match p_manager.to_opaque_ref() {
Some(manager) => manager,
None => bail!("pManager must not be null"),
};
let asset = c_to_velopackasset_opt(p_asset).ok_or(anyhow!("pAsset must not be null"))?;
let restart_args = c_to_string_array_opt(p_restart_args, c_restart_args).unwrap_or_default();
let wait_mode = if dw_wait_pid > 0 { ApplyWaitMode::WaitPid(dw_wait_pid) } else { ApplyWaitMode::NoWait };
manager.unsafe_apply_updates(&asset, b_silent, wait_mode, b_restart, &restart_args)?;
Ok(())
})
}
/// Frees a vpkc_update_manager_t instance.
#[no_mangle]
#[logfn(Trace)]

View File

@@ -58,11 +58,11 @@ namespace Velopack
/// Runs Update.exe in the current working directory with the 'start' command which will simply start the application.
/// Combined with the `waitForExit` parameter, this can be used to gracefully restart the application.
/// </summary>
/// <param name="waitForExit">If true, Update.exe will wait for the current process to exit before re-starting the application.</param>
/// <param name="waitPid">Optionally wait for the specified process to exit before continuing.</param>
/// <param name="locator">The locator to use to find the path to Update.exe and the packages directory.</param>
/// <param name="startArgs">The arguments to pass to the application when it is restarted.</param>
/// <param name="logger">The logger to use for diagnostic messages</param>
public static void Start(IVelopackLocator? locator = null, bool waitForExit = true, string[]? startArgs = null, ILogger? logger = null)
public static void Start(IVelopackLocator? locator = null, uint waitPid = 0, string[]? startArgs = null, ILogger? logger = null)
{
logger ??= NullLogger.Instance;
locator ??= VelopackLocator.GetDefault(logger);
@@ -70,9 +70,9 @@ namespace Velopack
var args = new List<string>();
args.Add("start");
if (waitForExit) {
if (waitPid > 0) {
args.Add("--waitPid");
args.Add(Process.GetCurrentProcess().Id.ToString());
args.Add(waitPid.ToString());
}
if (startArgs != null && startArgs.Length > 0) {
@@ -85,8 +85,8 @@ namespace Velopack
StartUpdateExe(logger, locator, args);
}
private static Process ApplyImpl(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, bool restart, string[]? restartArgs = null,
ILogger? logger = null)
private static Process ApplyImpl(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
string[]? restartArgs = null, ILogger? logger = null)
{
logger ??= NullLogger.Instance;
locator ??= VelopackLocator.GetDefault(logger);
@@ -104,8 +104,10 @@ namespace Velopack
}
}
if (waitPid > 0) {
args.Add("--waitPid");
args.Add(Process.GetCurrentProcess().Id.ToString());
args.Add(waitPid.ToString());
}
if (!restart) args.Add("--norestart"); // restarting is now the default Update.exe behavior
@@ -128,13 +130,14 @@ namespace Velopack
/// <param name="restart">If true, restarts the application after updates are applied (or if they failed)</param>
/// <param name="locator">The locator to use to find the path to Update.exe and the packages directory.</param>
/// <param name="toApply">The update package you wish to apply, can be left null.</param>
/// <param name="waitPid">Optionally wait for the specified process to exit before continuing.</param>
/// <param name="restartArgs">The arguments to pass to the application when it is restarted.</param>
/// <param name="logger">The logger to use for diagnostic messages</param>
/// <exception cref="Exception">Thrown if Update.exe does not initialize properly.</exception>
public static void Apply(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, bool restart, string[]? restartArgs = null,
ILogger? logger = null)
public static void Apply(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
string[]? restartArgs = null, ILogger? logger = null)
{
var process = ApplyImpl(locator, toApply, silent, restart, restartArgs, logger);
var process = ApplyImpl(locator, toApply, silent, waitPid, restart, restartArgs, logger);
Thread.Sleep(500);
if (process.HasExited) {
@@ -143,10 +146,10 @@ namespace Velopack
}
/// <inheritdoc cref="Apply"/>
public static async Task ApplyAsync(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, bool restart, string[]? restartArgs = null,
public static async Task ApplyAsync(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart, string[]? restartArgs = null,
ILogger? logger = null)
{
var process = ApplyImpl(locator, toApply, silent, restart, restartArgs, logger);
var process = ApplyImpl(locator, toApply, silent, waitPid, restart, restartArgs, logger);
await Task.Delay(500).ConfigureAwait(false);
if (process.HasExited) {

View File

@@ -45,13 +45,13 @@ namespace Velopack
/// <param name="restartArgs">The arguments to pass to the application when it is restarted.</param>
public void WaitExitThenApplyUpdates(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null)
{
UpdateExe.Apply(Locator, toApply, silent, restart, restartArgs, Log);
UpdateExe.Apply(Locator, toApply, silent, VelopackRuntimeInfo.ProcessId, restart, restartArgs, Log);
}
/// <inheritdoc cref="WaitExitThenApplyUpdates"/>
public async Task WaitExitThenApplyUpdatesAsync(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null)
{
await UpdateExe.ApplyAsync(Locator, toApply, silent, restart, restartArgs, Log).ConfigureAwait(false);
await UpdateExe.ApplyAsync(Locator, toApply, silent, VelopackRuntimeInfo.ProcessId, restart, restartArgs, Log).ConfigureAwait(false);
}
}
}

View File

@@ -224,7 +224,7 @@ namespace Velopack
log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}");
if (!restarted && _autoApply) {
log.Info("Auto apply is true, so restarting to apply update...");
UpdateExe.Apply(locator, latestLocal, false, true, args, log);
UpdateExe.Apply(locator, latestLocal, false, VelopackRuntimeInfo.ProcessId, true, args, log);
Exit(0);
} else {
log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")");

View File

@@ -91,6 +91,9 @@ namespace Velopack
/// <summary> The path on disk of the entry assembly. </summary>
public static string EntryExePath { get; }
/// <summary> The current executing process ID. </summary>
public static uint ProcessId { get; }
/// <summary> The current machine architecture, ignoring the current process / pe architecture. </summary>
public static RuntimeCpu SystemArch { get; private set; }
@@ -122,7 +125,9 @@ namespace Velopack
static VelopackRuntimeInfo()
{
EntryExePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
EntryExePath = currentProcess.MainModule.FileName;
ProcessId = (uint)currentProcess.Id;
#if DEBUG
InUnitTestRunner = CheckForUnitTestRunner();

View File

@@ -6,24 +6,36 @@ use std::{
sync::mpsc::Sender,
};
use semver::Version;
use serde::{Deserialize, Serialize};
#[cfg(feature = "async")]
use async_std::channel::Sender as AsyncSender;
#[cfg(feature = "async")]
use async_std::task::JoinHandle;
use semver::Version;
use serde::{Deserialize, Serialize};
use crate::{
locator::{self, VelopackLocatorConfig, LocationContext, VelopackLocator},
locator::{self, LocationContext, VelopackLocator, VelopackLocatorConfig},
sources::UpdateSource,
Error,
util,
util, Error,
};
/// Configure how the update process should wait before applying updates.
pub enum ApplyWaitMode {
/// NOT RECOMMENDED: Will not wait for any process before continuing. This could result in the update process being
/// killed, or the update process itself failing.
NoWait,
/// Will wait for the current process to exit before continuing. This is the default and recommended mode.
WaitCurrentProcess,
/// Wait for the specified process ID to exit before continuing. This is useful if you are updating a program
/// different from the one that is currently running.
WaitPid(u32),
}
/// A feed of Velopack assets, usually retrieved from a remote location.
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(default)]
/// A feed of Velopack assets, usually retrieved from a remote location.
pub struct VelopackAssetFeed {
/// The list of assets in the (probably remote) update feed.
pub Assets: Vec<VelopackAsset>,
@@ -36,11 +48,11 @@ impl VelopackAssetFeed {
}
}
/// An individual Velopack asset, could refer to an asset on-disk or in a remote package feed.
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(default)]
/// An individual Velopack asset, could refer to an asset on-disk or in a remote package feed.
pub struct VelopackAsset {
/// The name or Id of the package containing this release.
pub PackageId: String,
@@ -62,11 +74,11 @@ pub struct VelopackAsset {
pub NotesHtml: String,
}
/// Holds information about the current version and pending updates, such as how many there are, and access to release notes.
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(default)]
/// Holds information about the current version and pending updates, such as how many there are, and access to release notes.
pub struct UpdateInfo {
/// The available version that we are updating to.
pub TargetFullRelease: VelopackAsset,
@@ -88,11 +100,11 @@ impl AsRef<VelopackAsset> for VelopackAsset {
}
}
/// Options to customise the behaviour of UpdateManager.
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(default)]
/// Options to customise the behaviour of UpdateManager.
pub struct UpdateOptions {
/// Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
/// This could happen if a release has bugs and was retracted from the release feed, or if you're using
@@ -166,11 +178,7 @@ impl UpdateManager {
} else {
locator::auto_locate_app_manifest(LocationContext::FromCurrentExe)?
};
Ok(UpdateManager {
options: options.unwrap_or_default(),
source,
locator,
})
Ok(UpdateManager { options: options.unwrap_or_default(), source, locator })
}
fn get_practical_channel(&self) -> String {
@@ -235,10 +243,9 @@ impl UpdateManager {
self.source.get_release_feed(&channel, &self.locator.get_manifest())
}
#[cfg(feature = "async")]
/// Get a list of available remote releases from the package source.
pub fn get_release_feed_async(&self) -> JoinHandle<Result<VelopackAssetFeed, Error>>
{
#[cfg(feature = "async")]
pub fn get_release_feed_async(&self) -> JoinHandle<Result<VelopackAssetFeed, Error>> {
let self_clone = self.clone();
async_std::task::spawn_blocking(move || self_clone.get_release_feed())
}
@@ -299,11 +306,10 @@ impl UpdateManager {
}
}
#[cfg(feature = "async")]
/// Checks for updates, returning None if there are none available. If there are updates available, this method will return an
/// UpdateInfo object containing the latest available release, and any delta updates that can be applied if they are available.
pub fn check_for_updates_async(&self) -> JoinHandle<Result<UpdateCheck, Error>>
{
#[cfg(feature = "async")]
pub fn check_for_updates_async(&self) -> JoinHandle<Result<UpdateCheck, Error>> {
let self_clone = self.clone();
async_std::task::spawn_blocking(move || self_clone.check_for_updates())
}
@@ -378,13 +384,13 @@ impl UpdateManager {
Ok(())
}
#[cfg(feature = "async")]
/// Downloads the specified updates to the local app packages directory. Progress is reported back to the caller via an optional Sender.
/// This function will acquire a global update lock so may fail if there is already another update operation in progress.
/// - If the update contains delta packages and the delta feature is enabled
/// this method will attempt to unpack and prepare them.
/// - If there is no delta update available, or there is an error preparing delta
/// packages, this method will fall back to downloading the full version of the update.
#[cfg(feature = "async")]
pub fn download_updates_async(&self, update: &UpdateInfo, progress: Option<AsyncSender<i16>>) -> JoinHandle<Result<(), Error>> {
let mut sync_progress: Option<Sender<i16>> = None;
@@ -446,6 +452,26 @@ impl UpdateManager {
/// Once your app exists, the updater will apply updates and optionally restart your app.
/// The updater will only wait for 60 seconds before giving up.
pub fn wait_exit_then_apply_updates<A, C, S>(&self, to_apply: A, silent: bool, restart: bool, restart_args: C) -> Result<(), Error>
where
A: AsRef<VelopackAsset>,
S: AsRef<str>,
C: IntoIterator<Item = S>,
{
self.unsafe_apply_updates(to_apply, silent, ApplyWaitMode::WaitCurrentProcess, restart, restart_args)?;
Ok(())
}
/// This will launch the Velopack updater and optionally wait for a program to exit gracefully.
/// This method is unsafe because it does not necessarily wait for any / the correct process to exit
/// before applying updates. The `wait_exit_then_apply_updates` method is recommended for most use cases.
pub fn unsafe_apply_updates<A, C, S>(
&self,
to_apply: A,
silent: bool,
wait_mode: ApplyWaitMode,
restart: bool,
restart_args: C,
) -> Result<(), Error>
where
A: AsRef<VelopackAsset>,
S: AsRef<str>,
@@ -457,10 +483,26 @@ impl UpdateManager {
let mut args = Vec::new();
args.push("apply".to_string());
args.push("--package".to_string());
args.push(pkg_path_str.to_string());
if !pkg_path.exists() {
error!("Package does not exist on disk: '{}'", &pkg_path_str);
return Err(Error::FileNotFound(pkg_path_str.to_string()));
}
match wait_mode {
ApplyWaitMode::NoWait => {}
ApplyWaitMode::WaitCurrentProcess => {
args.push("--waitPid".to_string());
args.push(format!("{}", std::process::id()));
args.push("--package".to_string());
args.push(pkg_path_str.into_owned());
}
ApplyWaitMode::WaitPid(pid) => {
args.push("--waitPid".to_string());
args.push(format!("{}", pid));
}
}
if silent {
args.push("--silent".to_string());