Fix up legacy migrations

This commit is contained in:
Caelan
2024-10-02 12:22:58 -06:00
parent 0a46b6a52e
commit 80a0b458e7
6 changed files with 178 additions and 210 deletions

View File

@@ -1,7 +1,5 @@
use crate::{
shared::{self, OperationWait},
};
use velopack::{bundle::load_bundle_from_file, bundle::Manifest, locator::VelopackLocator, constants};
use crate::shared::{self, OperationWait};
use velopack::{locator::VelopackLocator, constants};
use anyhow::{bail, Result};
use std::path::PathBuf;
@@ -19,13 +17,14 @@ pub fn apply<'a>(
package: Option<&PathBuf>,
exe_args: Option<Vec<&str>>,
run_hooks: bool,
) -> Result<()> {
) -> Result<VelopackLocator> {
shared::operation_wait(wait);
let package = package.cloned().map_or_else(|| auto_locate_package(&locator), Ok);
let packages_dir = locator.get_packages_dir();
let package = package.cloned().or_else(|| shared::find_latest_full_package(&packages_dir).map(|x| x.0));
match package {
Ok(package) => {
Some(package) => {
info!("Getting ready to apply package to {} ver {}: {}",
locator.get_manifest_id(),
locator.get_manifest_version_full_string(),
@@ -37,15 +36,15 @@ pub fn apply<'a>(
if restart {
shared::start_package(&applied_locator, exe_args, Some(constants::HOOK_ENV_RESTART))?;
}
return Ok(());
return Ok(applied_locator);
}
Err(e) => {
error!("Error applying package: {}", e);
}
}
}
Err(e) => {
error!("Failed to locate package ({}).", e);
None => {
error!("Failed to locate full package to apply. Please provide with the --package {{path}} argument");
}
}
@@ -56,33 +55,3 @@ pub fn apply<'a>(
bail!("Apply failed, see logs for details.");
}
fn auto_locate_package(locator: &VelopackLocator) -> Result<PathBuf> {
let packages_dir = locator.get_packages_dir_as_string();
info!("Attempting to auto-detect package in: {}", packages_dir);
let mut package_path: Option<PathBuf> = None;
let mut package_manifest: Option<Manifest> = None;
if let Ok(paths) = glob::glob(format!("{}/*.nupkg", packages_dir).as_str()) {
for path in paths {
if let Ok(path) = path {
trace!("Checking package: '{}'", path.to_string_lossy());
if let Ok(mut bun) = load_bundle_from_file(&path) {
if let Ok(mani) = bun.read_manifest() {
if package_manifest.is_none() || mani.version > package_manifest.clone().unwrap().version {
info!("Found {}: '{}'", mani.version, path.to_string_lossy());
package_manifest = Some(mani);
package_path = Some(path);
}
}
}
}
}
}
if let Some(p) = package_path {
Ok(p)
} else {
bail!("Unable to find/load suitable package. Provide via the --package argument.");
}
}

View File

@@ -1,10 +1,8 @@
use crate::shared::{self, OperationWait};
use anyhow::Result;
use velopack::locator::VelopackLocator;
#[allow(unused_variables, unused_imports)]
pub fn start(
locator: &VelopackLocator,
wait: OperationWait,
exe_name: Option<&String>,
exe_args: Option<Vec<&str>>,
@@ -20,10 +18,14 @@ pub fn start(
shared::operation_wait(wait);
#[cfg(target_os = "windows")]
super::start_windows_impl::start_impl(&locator, exe_name, exe_args, legacy_args)?;
super::start_windows_impl::start_impl(exe_name, exe_args, legacy_args)?;
#[cfg(not(target_os = "windows"))]
shared::start_package(&locator, exe_args, None)?;
{
use velopack::locator::{auto_locate_app_manifest, LocationContext};
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
shared::start_package(&locator, exe_args, None)?;
}
Ok(())
}

View File

@@ -8,63 +8,122 @@ use std::os::windows::process::CommandExt;
use std::{
fs,
path::Path,
path::PathBuf,
process::Command as Process,
};
use velopack::{bundle, constants};
use velopack::locator::VelopackLocator;
use velopack::{bundle::Manifest, constants};
use velopack::locator::{auto_locate_app_manifest, create_config_from_root_dir, LocationContext, VelopackLocator};
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
pub fn start_impl(
locator: &VelopackLocator,
exe_name: Option<&String>,
exe_args: Option<Vec<&str>>,
legacy_args: Option<&String>,
) -> Result<()> {
let root_dir = locator.get_root_dir();
let app_title = locator.get_manifest_title();
match shared::has_app_prefixed_folder(&root_dir) {
Ok(has_prefix) => {
if has_prefix {
info!("This is a legacy app. Will try and upgrade it now.");
enum LocatorResult
{
Normal(VelopackLocator),
Legacy(PathBuf, Manifest),
}
// if started by legacy Squirrel, the working dir of Update.exe may be inside the app-* folder,
// meaning we can not clean up properly.
std::env::set_current_dir(&root_dir)?;
return match try_legacy_migration(&locator) {
Ok(new_locator) => {
shared::start_package(&new_locator, exe_args, Some(constants::HOOK_ENV_RESTART))?;
Ok(())
}
Err(e) => {
warn!("Failed to migrate legacy app ({}).", e);
dialogs::show_error(
&app_title,
Some("Unable to start app"),
"This app installation has been corrupted and cannot be started. Please reinstall the app.",
);
Err(e)
impl LocatorResult {
pub fn get_root_dir(&self) -> PathBuf {
match self {
LocatorResult::Normal(locator) => locator.get_root_dir(),
LocatorResult::Legacy(path, _) => path.clone(),
}
}
pub fn get_manifest(&self) -> Manifest {
match self {
LocatorResult::Normal(locator) => locator.get_manifest(),
LocatorResult::Legacy(_, manifest) => manifest.clone(),
}
}
pub fn get_current_dir(&self) -> PathBuf {
match self {
LocatorResult::Normal(locator) => locator.get_current_bin_dir(),
LocatorResult::Legacy(path, _) => path.join("current"),
}
}
pub fn get_exe_to_start(&self, name: Option<&String>) -> Result<PathBuf> {
let current_dir = self.get_current_dir();
if let Some(name) = name {
Ok(Path::new(&current_dir).join(name))
} else {
match self {
LocatorResult::Normal(locator) => Ok(locator.get_main_exe_path()),
LocatorResult::Legacy(_, manifest) => {
if manifest.main_exe.is_empty() {
bail!("No main exe specified in manifest and exe name argument was not provided.");
} else {
Ok(Path::new(&current_dir).join(&manifest.main_exe))
}
}
}
}
Err(e) => warn!("Failed legacy check ({}).", e),
}
}
fn legacy_locator() -> Result<LocatorResult> {
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe);
match locator {
Ok(locator) => Ok(LocatorResult::Normal(locator)),
Err(e) => {
warn!("Failed to locate app manifest ({}), trying legacy package resolution...", e);
let my_exe = std::env::current_exe()?;
let parent_dir = my_exe.parent().expect("Unable to determine parent directory");
let packages_dir = parent_dir.join("packages");
if let Some((path, manifest)) = shared::find_latest_full_package(&packages_dir) {
info!("Found full package to read: {}", path.to_string_lossy());
Ok(LocatorResult::Legacy(path, manifest))
} else {
bail!("Unable to locate app manifest or full package in {}.", packages_dir.to_string_lossy());
}
}
}
}
pub fn start_impl(
exe_name: Option<&String>,
exe_args: Option<Vec<&str>>,
legacy_args: Option<&String>,
) -> Result<()> {
let locator = legacy_locator()?;
let root_dir = locator.get_root_dir();
let manifest = locator.get_manifest();
if shared::has_app_prefixed_folder(&root_dir) {
match try_legacy_migration(&root_dir, &manifest) {
Ok(new_locator) => {
shared::start_package(&new_locator, exe_args, Some(constants::HOOK_ENV_RESTART))?;
Ok(())
}
Err(e) => {
warn!("Failed to migrate legacy app ({}).", e);
dialogs::show_error(
&manifest.title,
Some("Unable to start app"),
"This app installation has been corrupted and cannot be started. Please re-install the app.",
);
Err(e)
}
}
} else {
start_regular(locator, exe_name, exe_args, legacy_args)?;
Ok(())
}
}
fn start_regular(
locator: LocatorResult,
exe_name: Option<&String>,
exe_args: Option<Vec<&str>>,
legacy_args: Option<&String>,
) -> Result<()> {
// we can't just run the normal start_package command, because legacy squirrel might provide
// an "exe name" to restart which no longer exists in the package
let current = locator.get_current_bin_dir();
let exe_to_execute = if let Some(exe) = exe_name {
Path::new(&current).join(exe)
} else {
locator.get_main_exe_path()
};
let exe_to_execute = locator.get_exe_to_start(exe_name)?;
if !exe_to_execute.exists() {
bail!("Unable to find executable to start: '{:?}'", exe_to_execute);
}
let current = locator.get_current_dir();
info!("About to launch: '{:?}' in dir '{:?}'", exe_to_execute, current);
let mut cmd = Process::new(&exe_to_execute);
@@ -81,15 +140,22 @@ pub fn start_impl(
Ok(())
}
fn try_legacy_migration(locator: &VelopackLocator) -> Result<VelopackLocator> {
let root_dir = locator.get_root_dir();
let current_dir = locator.get_current_bin_dir();
let package = shared::find_latest_full_package(&locator).ok_or_else(|| anyhow!("Unable to find latest full package."))?;
let mut bundle = bundle::load_bundle_from_file(&package.file_path)?;
let bundle_manifest = bundle.read_manifest()?; // this verifies it's a bundle we support
fn try_legacy_migration(root_dir: &PathBuf, manifest: &Manifest) -> Result<VelopackLocator> {
info!("This is a legacy app. Will try and upgrade it now.");
// if started by legacy Squirrel, the working dir of Update.exe may be inside the app-* folder,
// meaning we can not clean up properly.
std::env::set_current_dir(&root_dir)?;
let _mutex = shared::retry_io(|| crate::windows::create_global_mutex(&manifest.id))?;
let path_config = create_config_from_root_dir(root_dir);
let package = shared::find_latest_full_package(&path_config.PackagesDir).ok_or_else(|| anyhow!("Unable to find latest full package."))?;
warn!("This application is installed in a folder prefixed with 'app-'. Attempting to migrate...");
let _ = shared::force_stop_package(&root_dir);
let current_dir = &path_config.CurrentBinaryDir;
if !Path::new(&current_dir).exists() {
info!("Renaming latest app-* folder to current.");
if let Some((latest_app_dir, _latest_ver)) = shared::get_latest_app_version_folder(&root_dir)? {
@@ -102,16 +168,16 @@ fn try_legacy_migration(locator: &VelopackLocator) -> Result<VelopackLocator> {
// reset current manifest shortcuts, so when the new manifest is being read
// new shortcuts will be force-created
let modified_app = locator.clone_self_with_blank_shortcuts();
let mut modified_manifest = manifest.clone();
modified_manifest.shortcut_locations = String::new();
info!("Applying latest full package...");
let buf = Path::new(&package.file_path).to_path_buf();
super::apply(&modified_app, false, OperationWait::NoWait, Some(&buf), None, false)?;
let buf = Path::new(&package.0).to_path_buf();
let locator = VelopackLocator::new(path_config, modified_manifest);
let new_locator = super::apply(&locator, false, OperationWait::NoWait, Some(&buf), None, false)?;
info!("Removing old app-* folders...");
shared::delete_app_prefixed_folders(&root_dir)?;
let _ = remove_dir_all::remove_dir_all(root_dir.join("staging"));
let new_locator = locator.clone_self_with_new_manifest(&bundle_manifest);
Ok(new_locator)
}

View File

@@ -2,6 +2,8 @@ use anyhow::{anyhow, Result};
use rand::distributions::{Alphanumeric, DistString};
use regex::Regex;
use std::{path::Path, thread, time::Duration};
use std::path::PathBuf;
use velopack::bundle::{load_bundle_from_file, Manifest};
#[derive(Debug, Clone, Copy)]
pub enum OperationWait {
@@ -24,6 +26,30 @@ pub fn operation_wait(wait: OperationWait) {
}
}
pub fn find_latest_full_package(packages_dir: &PathBuf) -> Option<(PathBuf, Manifest)> {
let packages_dir = packages_dir.to_string_lossy();
info!("Attempting to auto-detect package in: {}", packages_dir);
let mut package: Option<(PathBuf, Manifest)> = None;
if let Ok(paths) = glob::glob(format!("{}/*.nupkg", packages_dir).as_str()) {
for path in paths {
if let Ok(path) = path {
trace!("Checking package: '{}'", path.to_string_lossy());
if let Ok(mut bun) = load_bundle_from_file(&path) {
if let Ok(mani) = bun.read_manifest() {
if package.is_none() || mani.version > package.clone().unwrap().1.version {
info!("Found {}: '{}'", mani.version, path.to_string_lossy());
package = Some((path, mani));
}
}
}
}
}
}
package
}
pub fn retry_io<F, T, E>(op: F) -> Result<T, E>
where
F: Fn() -> Result<T, E>,

View File

@@ -13,7 +13,6 @@ use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInf
use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
use winsafe::{self as w, co, prelude::*};
use velopack::bundle::{self, EntryNameInfo};
use velopack::locator::VelopackLocator;
pub fn wait_for_pid_to_exit(pid: u32, ms_to_wait: u32) -> Result<()> {
@@ -246,8 +245,14 @@ pub fn get_latest_app_version_folder<P: AsRef<Path>>(parent_path: P) -> Result<O
Ok(latest_folder.zip(latest_version))
}
pub fn has_app_prefixed_folder<P: AsRef<Path>>(parent_path: P) -> Result<bool> {
Ok(!get_app_prefixed_folders(parent_path)?.is_empty())
pub fn has_app_prefixed_folder<P: AsRef<Path>>(parent_path: P) -> bool {
match get_app_prefixed_folders(parent_path) {
Ok(folders) => !folders.is_empty(),
Err(e) => {
warn!("Failed to check for app-prefixed folders: {}", e);
false
},
}
}
pub fn delete_app_prefixed_folders<P: AsRef<Path>>(parent_path: P) -> Result<()> {
@@ -262,71 +267,6 @@ fn parse_version_from_folder_name(folder_name: &str) -> Option<Version> {
folder_name.strip_prefix("app-").and_then(|v| Version::parse(v).ok())
}
// fn find_manifest_from_root_dir(root_path: &PathBuf) -> Result<Manifest> {
// // default to checking current/sq.version
// let cm = find_current_manifest(root_path);
// if cm.is_ok() {
// return cm;
// }
//
// // if that fails, check for latest full package
// warn!("Unable to find current manifest, checking for latest full package. (LEGACY MODE)");
// let latest = find_latest_full_package(root_path);
// if let Some(latest) = latest {
// let mani = latest.load_manifest()?;
// return Ok(mani);
// }
//
// bail!("Unable to locate manifest or package.");
// }
//
// fn find_current_manifest(root_path: &PathBuf) -> Result<Manifest> {
// let m = Manifest::default();
// let nuspec_path = m.get_nuspec_path(root_path);
// if Path::new(&nuspec_path).exists() {
// if let Ok(nuspec) = super::retry_io(|| std::fs::read_to_string(&nuspec_path)) {
// return Ok(bundle::read_manifest_from_string(&nuspec)?);
// }
// }
// bail!("Unable to read nuspec file in current directory.")
// }
pub fn find_latest_full_package(locator: &VelopackLocator) -> Option<EntryNameInfo> {
let packages = get_all_packages(locator);
let mut latest: Option<EntryNameInfo> = None;
for pkg in packages {
if pkg.is_delta {
continue;
}
if latest.is_none() {
latest = Some(pkg);
} else {
let latest_ver = latest.clone().unwrap().version;
if pkg.version > latest_ver {
latest = Some(pkg);
}
}
}
latest
}
fn get_all_packages(locator: &VelopackLocator) -> Vec<EntryNameInfo> {
let packages = locator.get_packages_dir();
let mut vec = Vec::new();
debug!("Scanning for packages in {:?}", packages);
if let Ok(entries) = fs::read_dir(packages) {
for entry in entries {
if let Ok(entry) = entry {
if let Some(pkg) = bundle::parse_package_file_path(entry.path()) {
debug!("Found package: {}", entry.path().to_string_lossy());
vec.push(pkg);
}
}
}
}
vec
}
#[test]
fn test_get_running_processes_finds_cargo() {
let profile = crate::windows::known_path::get_user_profile().unwrap();

View File

@@ -63,7 +63,7 @@ fn root_command() -> Command {
}
#[cfg(target_os = "windows")]
fn parse_command_line_matches(input_args: Vec<String>) -> ArgMatches {
fn try_parse_command_line_matches(input_args: Vec<String>) -> Result<ArgMatches> {
// Split the arguments manually to handle the legacy `--flag=value` syntax
// Also, replace `--processStartAndWait` with `--processStart --wait`
let mut args = Vec::new();
@@ -93,7 +93,7 @@ fn parse_command_line_matches(input_args: Vec<String>) -> ArgMatches {
args.push(arg);
}
}
root_command().get_matches_from(&args)
Ok(root_command().try_get_matches_from(&args)?)
}
fn get_flag_or_false(matches: &ArgMatches, id: &str) -> bool {
@@ -118,9 +118,9 @@ fn main() -> Result<()> {
windows::mitigate::pre_main_sideload_mitigation();
#[cfg(windows)]
let matches = parse_command_line_matches(env::args().collect());
let matches = try_parse_command_line_matches(env::args().collect())?;
#[cfg(unix)]
let matches = root_command().get_matches();
let matches = root_command().try_get_matches()?;
let (subcommand, subcommand_matches) = matches.subcommand().ok_or_else(|| anyhow!("No subcommand was used. Try `--help` for more information."))?;
@@ -131,7 +131,7 @@ fn main() -> Result<()> {
dialogs::set_silent(silent);
let desired_log_file = log_file.cloned().unwrap_or(locator::default_log_location(LocationContext::IAmUpdateExe));
logging::setup_logging("update", Some(&desired_log_file), true, verbose)?;
// change working directory to the parent directory of the exe
let mut containing_dir = env::current_exe()?;
containing_dir.pop();
@@ -191,7 +191,8 @@ fn apply(matches: &ArgMatches) -> Result<()> {
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
#[cfg(target_os = "windows")]
let _mutex = shared::retry_io(|| windows::create_global_mutex(&locator.get_manifest_id()))?;
commands::apply(&locator, restart, wait, package, exe_args, true)
let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?;
Ok(())
}
fn start(matches: &ArgMatches) -> Result<()> {
@@ -208,11 +209,8 @@ fn start(matches: &ArgMatches) -> Result<()> {
info!(" Legacy Args: {:?}", legacy_args);
warn!("Legacy args format is deprecated and will be removed in a future release. Please update your application to use the new format.");
}
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
#[cfg(target_os = "windows")]
let _mutex = shared::retry_io(|| windows::create_global_mutex(&locator.get_manifest_id()))?;
commands::start(&locator, wait, exe_name, exe_args, legacy_args)
commands::start(wait, exe_name, exe_args, legacy_args)
}
#[cfg(target_os = "windows")]
@@ -230,40 +228,7 @@ fn test_start_command_supports_legacy_commands() {
let wait_for_parent = get_flag_or_false(&matches, "wait");
let exe_name = matches.get_one::<String>("EXE_NAME");
let exe_args: Option<Vec<&String>> = matches.get_many::<String>("EXE_ARGS").map(|v| v.collect());
return (wait_for_parent, exe_name, legacy_args, exe_args);
}
fn try_parse_command_line_matches(input_args: Vec<String>) -> Result<ArgMatches> {
// Split the arguments manually to handle the legacy `--flag=value` syntax
// Also, replace `--processStartAndWait` with `--processStart --wait`
let mut args = Vec::new();
let mut preserve = false;
for arg in input_args {
if preserve {
args.push(arg);
} else if arg == "--" {
args.push("--".to_string());
preserve = true;
} else if arg.eq_ignore_ascii_case("--processStartAndWait") {
args.push("--processStart".to_string());
args.push("--wait".to_string());
} else if arg.starts_with("--processStartAndWait=") {
let mut split_arg = arg.splitn(2, '=');
split_arg.next(); // Skip the `--processStartAndWait` part
args.push("--processStart".to_string());
args.push("--wait".to_string());
if let Some(rest) = split_arg.next() {
args.push(rest.to_string());
}
} else if arg.contains('=') {
let mut split_arg = arg.splitn(2, '=');
args.push(split_arg.next().unwrap().to_string());
args.push(split_arg.next().unwrap().to_string());
} else {
args.push(arg);
}
}
root_command().try_get_matches_from(&args).map_err(|e| anyhow!("{}", e))
(wait_for_parent, exe_name, legacy_args, exe_args)
}
let command = vec!["Update.exe", "--processStart=hello.exe"];