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 try
{ {
updateManager->WaitExitThenApplyUpdate(updateInfo.value()); updateManager->WaitExitThenApplyUpdates(updateInfo.value());
wxTheApp->ExitMainLoop(); wxTheApp->ExitMainLoop();
} }
catch (...) { /* exception will print in log */ } 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); MessageBoxCentered(hWnd, L"Download an update first.", szTitle, MB_OK);
} }
else { else {
manager->WaitExitThenApplyUpdate(updInfo.value()); manager->WaitExitThenApplyUpdates(updInfo.value());
exit(0); exit(0);
} }
} }

View File

@@ -294,12 +294,26 @@ 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 * 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. * 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, struct vpkc_asset_t *p_asset,
bool b_silent, bool b_silent,
bool b_restart, bool b_restart,
char **p_restart_args, char **p_restart_args,
size_t c_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. * 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 * 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. * 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); char** pRestartArgs = allocate_cstring_array(restartArgs);
vpkc_asset_t vpkc_asset = to_c(asset); 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()); free_cstring_array(pRestartArgs, restartArgs.size());
if (!result) { if (!result) {
@@ -595,12 +604,20 @@ public:
}; };
/** /**
* This will launch the Velopack updater and tell it to wait for this program to exit gracefully. * This will launch the Velopack updater and optionally wait for a program to exit gracefully.
* You should then clean up any state and exit your app. The updater will apply updates and then * This method is unsafe because it does not necessarily wait for any / the correct process to exit
* optionally restart your app. The updater will only wait for 60 seconds before giving up. * 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 = {}) { void UnsafeApplyUpdates(const VelopackAsset& asset, bool silent, uint32_t waitPid, bool restart, std::vector<std::string> restartArgs) {
this->WaitExitThenApplyUpdate(asset.TargetFullRelease, silent, restart, 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 libc::{c_char, c_void, size_t};
use log_derive::{logfn, logfn_inputs}; use log_derive::{logfn, logfn_inputs};
use std::{ffi::CString, ptr}; 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. /// Create a new FileSource update source for a given file path.
#[no_mangle] #[no_mangle]
@@ -295,7 +295,7 @@ pub extern "C" fn vpkc_download_updates(
#[no_mangle] #[no_mangle]
#[logfn(Trace)] #[logfn(Trace)]
#[logfn_inputs(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_manager: *mut vpkc_update_manager_t,
p_asset: *mut vpkc_asset_t, p_asset: *mut vpkc_asset_t,
b_silent: bool, 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. /// Frees a vpkc_update_manager_t instance.
#[no_mangle] #[no_mangle]
#[logfn(Trace)] #[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. /// 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. /// Combined with the `waitForExit` parameter, this can be used to gracefully restart the application.
/// </summary> /// </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="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="startArgs">The arguments to pass to the application when it is restarted.</param>
/// <param name="logger">The logger to use for diagnostic messages</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; logger ??= NullLogger.Instance;
locator ??= VelopackLocator.GetDefault(logger); locator ??= VelopackLocator.GetDefault(logger);
@@ -70,9 +70,9 @@ namespace Velopack
var args = new List<string>(); var args = new List<string>();
args.Add("start"); args.Add("start");
if (waitForExit) { if (waitPid > 0) {
args.Add("--waitPid"); args.Add("--waitPid");
args.Add(Process.GetCurrentProcess().Id.ToString()); args.Add(waitPid.ToString());
} }
if (startArgs != null && startArgs.Length > 0) { if (startArgs != null && startArgs.Length > 0) {
@@ -85,8 +85,8 @@ namespace Velopack
StartUpdateExe(logger, locator, args); StartUpdateExe(logger, locator, args);
} }
private static Process ApplyImpl(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, bool restart, string[]? restartArgs = null, private static Process ApplyImpl(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
ILogger? logger = null) string[]? restartArgs = null, ILogger? logger = null)
{ {
logger ??= NullLogger.Instance; logger ??= NullLogger.Instance;
locator ??= VelopackLocator.GetDefault(logger); locator ??= VelopackLocator.GetDefault(logger);
@@ -104,8 +104,10 @@ namespace Velopack
} }
} }
args.Add("--waitPid"); if (waitPid > 0) {
args.Add(Process.GetCurrentProcess().Id.ToString()); args.Add("--waitPid");
args.Add(waitPid.ToString());
}
if (!restart) args.Add("--norestart"); // restarting is now the default Update.exe behavior 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="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="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="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="restartArgs">The arguments to pass to the application when it is restarted.</param>
/// <param name="logger">The logger to use for diagnostic messages</param> /// <param name="logger">The logger to use for diagnostic messages</param>
/// <exception cref="Exception">Thrown if Update.exe does not initialize properly.</exception> /// <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, public static void Apply(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
ILogger? logger = null) 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); Thread.Sleep(500);
if (process.HasExited) { if (process.HasExited) {
@@ -143,10 +146,10 @@ namespace Velopack
} }
/// <inheritdoc cref="Apply"/> /// <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) 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); await Task.Delay(500).ConfigureAwait(false);
if (process.HasExited) { 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> /// <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) 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"/> /// <inheritdoc cref="WaitExitThenApplyUpdates"/>
public async Task WaitExitThenApplyUpdatesAsync(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null) 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}"); log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}");
if (!restarted && _autoApply) { if (!restarted && _autoApply) {
log.Info("Auto apply is true, so restarting to apply update..."); 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); Exit(0);
} else { } else {
log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")"); log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")");

View File

@@ -90,6 +90,9 @@ namespace Velopack
/// <summary> The path on disk of the entry assembly. </summary> /// <summary> The path on disk of the entry assembly. </summary>
public static string EntryExePath { get; } 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> /// <summary> The current machine architecture, ignoring the current process / pe architecture. </summary>
public static RuntimeCpu SystemArch { get; private set; } public static RuntimeCpu SystemArch { get; private set; }
@@ -122,7 +125,9 @@ namespace Velopack
static VelopackRuntimeInfo() 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 #if DEBUG
InUnitTestRunner = CheckForUnitTestRunner(); InUnitTestRunner = CheckForUnitTestRunner();

View File

@@ -6,24 +6,36 @@ use std::{
sync::mpsc::Sender, sync::mpsc::Sender,
}; };
use semver::Version;
use serde::{Deserialize, Serialize};
#[cfg(feature = "async")] #[cfg(feature = "async")]
use async_std::channel::Sender as AsyncSender; use async_std::channel::Sender as AsyncSender;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use async_std::task::JoinHandle; use async_std::task::JoinHandle;
use semver::Version;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
locator::{self, VelopackLocatorConfig, LocationContext, VelopackLocator}, locator::{self, LocationContext, VelopackLocator, VelopackLocatorConfig},
sources::UpdateSource, sources::UpdateSource,
Error, util, Error,
util,
}; };
/// 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)] #[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(default)] #[serde(default)]
/// A feed of Velopack assets, usually retrieved from a remote location.
pub struct VelopackAssetFeed { pub struct VelopackAssetFeed {
/// The list of assets in the (probably remote) update feed. /// The list of assets in the (probably remote) update feed.
pub Assets: Vec<VelopackAsset>, 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)] #[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(default)] #[serde(default)]
/// An individual Velopack asset, could refer to an asset on-disk or in a remote package feed.
pub struct VelopackAsset { pub struct VelopackAsset {
/// The name or Id of the package containing this release. /// The name or Id of the package containing this release.
pub PackageId: String, pub PackageId: String,
@@ -62,11 +74,11 @@ pub struct VelopackAsset {
pub NotesHtml: String, 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)] #[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(default)] #[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 { pub struct UpdateInfo {
/// The available version that we are updating to. /// The available version that we are updating to.
pub TargetFullRelease: VelopackAsset, pub TargetFullRelease: VelopackAsset,
@@ -88,11 +100,11 @@ impl AsRef<VelopackAsset> for VelopackAsset {
} }
} }
/// Options to customise the behaviour of UpdateManager.
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(default)] #[serde(default)]
/// Options to customise the behaviour of UpdateManager.
pub struct UpdateOptions { pub struct UpdateOptions {
/// Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading). /// 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 /// 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 { } else {
locator::auto_locate_app_manifest(LocationContext::FromCurrentExe)? locator::auto_locate_app_manifest(LocationContext::FromCurrentExe)?
}; };
Ok(UpdateManager { Ok(UpdateManager { options: options.unwrap_or_default(), source, locator })
options: options.unwrap_or_default(),
source,
locator,
})
} }
fn get_practical_channel(&self) -> String { fn get_practical_channel(&self) -> String {
@@ -189,7 +197,7 @@ impl UpdateManager {
pub fn get_current_version_as_string(&self) -> String { pub fn get_current_version_as_string(&self) -> String {
self.locator.get_manifest_version_full_string() self.locator.get_manifest_version_full_string()
} }
/// The currently installed app version as a semver Version. /// The currently installed app version as a semver Version.
pub fn get_current_version(&self) -> Version { pub fn get_current_version(&self) -> Version {
self.locator.get_manifest_version() self.locator.get_manifest_version()
@@ -206,7 +214,7 @@ impl UpdateManager {
self.locator.get_is_portable() self.locator.get_is_portable()
} }
/// Returns None if there is no local package waiting to be applied. Returns a VelopackAsset /// Returns None if there is no local package waiting to be applied. Returns a VelopackAsset
/// if there is an update downloaded which has not yet been applied. In that case, the /// if there is an update downloaded which has not yet been applied. In that case, the
/// VelopackAsset can be applied by calling apply_updates_and_restart or wait_exit_then_apply_updates. /// VelopackAsset can be applied by calling apply_updates_and_restart or wait_exit_then_apply_updates.
pub fn get_update_pending_restart(&self) -> Option<VelopackAsset> { pub fn get_update_pending_restart(&self) -> Option<VelopackAsset> {
@@ -235,10 +243,9 @@ impl UpdateManager {
self.source.get_release_feed(&channel, &self.locator.get_manifest()) self.source.get_release_feed(&channel, &self.locator.get_manifest())
} }
#[cfg(feature = "async")]
/// Get a list of available remote releases from the package source. /// 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(); let self_clone = self.clone();
async_std::task::spawn_blocking(move || self_clone.get_release_feed()) 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 /// 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. /// 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(); let self_clone = self.clone();
async_std::task::spawn_blocking(move || self_clone.check_for_updates()) async_std::task::spawn_blocking(move || self_clone.check_for_updates())
} }
@@ -331,7 +337,7 @@ impl UpdateManager {
let old_nupkg_pattern = format!("{}/*.nupkg", packages_dir.to_string_lossy()); let old_nupkg_pattern = format!("{}/*.nupkg", packages_dir.to_string_lossy());
let old_partial_pattern = format!("{}/*.partial", packages_dir.to_string_lossy()); let old_partial_pattern = format!("{}/*.partial", packages_dir.to_string_lossy());
let mut to_delete = Vec::new(); let mut to_delete = Vec::new();
fn find_files_to_delete(pattern: &str, to_delete: &mut Vec<String>) { fn find_files_to_delete(pattern: &str, to_delete: &mut Vec<String>) {
info!("Searching for packages to clean: '{}'", pattern); info!("Searching for packages to clean: '{}'", pattern);
match glob::glob(pattern) { match glob::glob(pattern) {
@@ -345,13 +351,13 @@ impl UpdateManager {
} }
} }
} }
find_files_to_delete(&old_nupkg_pattern, &mut to_delete); find_files_to_delete(&old_nupkg_pattern, &mut to_delete);
find_files_to_delete(&old_partial_pattern, &mut to_delete); find_files_to_delete(&old_partial_pattern, &mut to_delete);
self.source.download_release_entry(&update.TargetFullRelease, &partial_file.to_string_lossy(), progress)?; self.source.download_release_entry(&update.TargetFullRelease, &partial_file.to_string_lossy(), progress)?;
info!("Successfully placed file: '{}'", partial_file.to_string_lossy()); info!("Successfully placed file: '{}'", partial_file.to_string_lossy());
info!("Renaming partial file to final target: '{}'", final_target_file.to_string_lossy()); info!("Renaming partial file to final target: '{}'", final_target_file.to_string_lossy());
fs::rename(&partial_file, &final_target_file)?; fs::rename(&partial_file, &final_target_file)?;
@@ -378,13 +384,13 @@ impl UpdateManager {
Ok(()) 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. /// 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. /// 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 /// - If the update contains delta packages and the delta feature is enabled
/// this method will attempt to unpack and prepare them. /// this method will attempt to unpack and prepare them.
/// - If there is no delta update available, or there is an error preparing delta /// - 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. /// 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>> { pub fn download_updates_async(&self, update: &UpdateInfo, progress: Option<AsyncSender<i16>>) -> JoinHandle<Result<(), Error>> {
let mut sync_progress: Option<Sender<i16>> = None; let mut sync_progress: Option<Sender<i16>> = None;
@@ -424,7 +430,7 @@ impl UpdateManager {
where where
A: AsRef<VelopackAsset>, A: AsRef<VelopackAsset>,
S: AsRef<str>, S: AsRef<str>,
C: IntoIterator<Item=S>, C: IntoIterator<Item = S>,
{ {
self.wait_exit_then_apply_updates(to_apply, false, true, restart_args)?; self.wait_exit_then_apply_updates(to_apply, false, true, restart_args)?;
exit(0); exit(0);
@@ -449,7 +455,27 @@ impl UpdateManager {
where where
A: AsRef<VelopackAsset>, A: AsRef<VelopackAsset>,
S: AsRef<str>, S: AsRef<str>,
C: IntoIterator<Item=S>, 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>,
C: IntoIterator<Item = S>,
{ {
let to_apply = to_apply.as_ref(); let to_apply = to_apply.as_ref();
let pkg_path = self.locator.get_packages_dir().join(&to_apply.FileName); let pkg_path = self.locator.get_packages_dir().join(&to_apply.FileName);
@@ -457,10 +483,26 @@ impl UpdateManager {
let mut args = Vec::new(); let mut args = Vec::new();
args.push("apply".to_string()); args.push("apply".to_string());
args.push("--waitPid".to_string());
args.push(format!("{}", std::process::id()));
args.push("--package".to_string()); args.push("--package".to_string());
args.push(pkg_path_str.into_owned()); 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()));
}
ApplyWaitMode::WaitPid(pid) => {
args.push("--waitPid".to_string());
args.push(format!("{}", pid));
}
}
if silent { if silent {
args.push("--silent".to_string()); args.push("--silent".to_string());
@@ -495,4 +537,4 @@ impl UpdateManager {
p.spawn()?; p.spawn()?;
Ok(()) Ok(())
} }
} }