Refactor string handling to use OsString where possible

This commit is contained in:
Caelan Sayler
2025-05-25 01:06:25 +01:00
parent 3c7d82aa22
commit 661cc022f8
33 changed files with 642 additions and 650 deletions

View File

@@ -1,7 +1,7 @@
use crate::shared::{self, OperationWait}; use crate::shared::{self, OperationWait};
use velopack::{locator, locator::VelopackLocator, constants};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::path::PathBuf; use std::{ffi::OsString, path::PathBuf};
use velopack::{constants, locator, locator::VelopackLocator};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use super::apply_linux_impl::apply_package_impl; use super::apply_linux_impl::apply_package_impl;
@@ -15,7 +15,7 @@ pub fn apply<'a>(
restart: bool, restart: bool,
wait: OperationWait, wait: OperationWait,
package: Option<&PathBuf>, package: Option<&PathBuf>,
exe_args: Option<Vec<&str>>, exe_args: Option<Vec<OsString>>,
run_hooks: bool, run_hooks: bool,
) -> Result<VelopackLocator> { ) -> Result<VelopackLocator> {
shared::operation_wait(wait); shared::operation_wait(wait);
@@ -25,10 +25,12 @@ pub fn apply<'a>(
match package { match package {
Some(package) => { Some(package) => {
info!("Getting ready to apply package to {} ver {}: {}", info!(
locator.get_manifest_id(), "Getting ready to apply package to {} ver {}: {:?}",
locator.get_manifest_version_full_string(), locator.get_manifest_id(),
package.to_string_lossy()); locator.get_manifest_version_full_string(),
package
);
match apply_package_impl(&locator, &package, run_hooks) { match apply_package_impl(&locator, &package, run_hooks) {
Ok(applied_locator) => { Ok(applied_locator) => {
info!("Package version {} applied successfully.", applied_locator.get_manifest_version_full_string()); info!("Package version {} applied successfully.", applied_locator.get_manifest_version_full_string());

View File

@@ -6,7 +6,7 @@ use velopack::{bundle, locator::VelopackLocator};
pub fn apply_package_impl<'a>(locator: &VelopackLocator, pkg: &PathBuf, _runhooks: bool) -> Result<VelopackLocator> { pub fn apply_package_impl<'a>(locator: &VelopackLocator, pkg: &PathBuf, _runhooks: bool) -> Result<VelopackLocator> {
// on linux, the current "dir" is actually an AppImage file which we need to replace. // on linux, the current "dir" is actually an AppImage file which we need to replace.
info!("Loading bundle from {}", pkg.to_string_lossy()); info!("Loading bundle from {:?}", pkg);
let mut bundle = bundle::load_bundle_from_file(pkg)?; let mut bundle = bundle::load_bundle_from_file(pkg)?;
let manifest = bundle.read_manifest()?; let manifest = bundle.read_manifest()?;
let temp_path = locator.get_temp_dir_rand16().to_string_lossy().to_string(); let temp_path = locator.get_temp_dir_rand16().to_string_lossy().to_string();

View File

@@ -4,7 +4,7 @@ use crate::{
windows::{self, splash}, windows::{self, splash},
}; };
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use std::{fs, path::PathBuf}; use std::{ffi::OsString, fs, path::PathBuf};
use std::{sync::mpsc, time::Duration}; use std::{sync::mpsc, time::Duration};
use velopack::{bundle::load_bundle_from_file, constants, locator::VelopackLocator, process}; use velopack::{bundle::load_bundle_from_file, constants, locator::VelopackLocator, process};
@@ -51,8 +51,7 @@ pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_
} else { } else {
info!("Re-launching as administrator to update in {:?}", root_path); info!("Re-launching as administrator to update in {:?}", root_path);
let package_string = package.to_string_lossy().to_string(); let args: Vec<OsString> = vec!["--norestart".into(), "--package".into(), package.into()];
let args = vec!["--norestart".to_string(), "--package".to_string(), package_string];
let exe_path = std::env::current_exe()?; let exe_path = std::env::current_exe()?;
let work_dir: Option<String> = None; // same as this process let work_dir: Option<String> = None; // same as this process
let process_handle = process::run_process_as_admin(&exe_path, args, work_dir, false)?; let process_handle = process::run_process_as_admin(&exe_path, args, work_dir, false)?;
@@ -108,7 +107,7 @@ pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_
// } // }
// fourth, we make as backup of the current dir to temp_path_old // fourth, we make as backup of the current dir to temp_path_old
info!("Backing up current dir to {}", &temp_path_old.to_string_lossy()); info!("Backing up current dir to {:?}", &temp_path_old);
shared::retry_io_ex(|| fs::rename(&current_dir, &temp_path_old), 1000, 10) shared::retry_io_ex(|| fs::rename(&current_dir, &temp_path_old), 1000, 10)
.context("Unable to start the update, because one or more running processes prevented it. Try again later, or if the issue persists, restart your computer.")?; .context("Unable to start the update, because one or more running processes prevented it. Try again later, or if the issue persists, restart your computer.")?;
@@ -121,7 +120,7 @@ pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_
// fifth, we try to replace the current dir with temp_path_new // fifth, we try to replace the current dir with temp_path_new
// if this fails we will yolo a rollback... // if this fails we will yolo a rollback...
info!("Replacing current dir with {}", &temp_path_new.to_string_lossy()); info!("Replacing current dir with {:?}", &temp_path_new);
shared::retry_io_ex(|| fs::rename(&temp_path_new, &current_dir), 1000, 30) shared::retry_io_ex(|| fs::rename(&temp_path_new, &current_dir), 1000, 30)
.context("Unable to complete the update, and the app was left in a broken state. You may need to re-install or repair this application manually.")?; .context("Unable to complete the update, and the app was left in a broken state. You may need to re-install or repair this application manually.")?;

View File

@@ -3,25 +3,24 @@ use crate::{
shared::{self}, shared::{self},
windows, windows,
}; };
use velopack::bundle::BundleZip;
use velopack::locator::*;
use velopack::constants; use velopack::constants;
use velopack::locator::*;
use velopack::{bundle::BundleZip, wide_strings::string_to_wide};
use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use pretty_bytes_rust::pretty_bytes; use pretty_bytes_rust::pretty_bytes;
use std::{ use std::{
ffi::OsString,
fs::{self}, fs::{self},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use ::windows::core::PCWSTR;
use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Option<Vec<&str>>) -> Result<()> { pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Option<Vec<OsString>>) -> Result<()> {
// find and parse nuspec // find and parse nuspec
info!("Reading package manifest..."); info!("Reading package manifest...");
let app = pkg.read_manifest()?; let app = pkg.read_manifest()?;
info!("Package manifest loaded successfully."); info!("Package manifest loaded successfully.");
info!(" Package ID: {}", &app.id); info!(" Package ID: {}", &app.id);
info!(" Package Version: {}", &app.version); info!(" Package Version: {}", &app.version);
@@ -49,17 +48,15 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
shared::retry_io(|| fs::create_dir_all(&root_path))?; shared::retry_io(|| fs::create_dir_all(&root_path))?;
} }
let root_path_str = root_path.to_str().unwrap(); info!("Installation Directory: {:?}", root_path);
info!("Installation Directory: {:?}", root_path_str);
// do we have enough disk space? // do we have enough disk space?
let (compressed_size, extracted_size) = pkg.calculate_size(); let (compressed_size, extracted_size) = pkg.calculate_size();
let required_space = compressed_size + extracted_size + (50 * 1000 * 1000); // archive + velopack overhead let required_space = compressed_size + extracted_size + (50 * 1000 * 1000); // archive + velopack overhead
let mut free_space: u64 = 0; let mut free_space: u64 = 0;
let root_pcwstr = windows::strings::string_to_u16(root_path_str); let root_pcwstr = string_to_wide(&root_path);
let root_pcwstr: PCWSTR = PCWSTR(root_pcwstr.as_ptr()); if let Ok(()) = unsafe { GetDiskFreeSpaceExW(root_pcwstr.as_pcwstr(), None, None, Some(&mut free_space)) } {
if let Ok(()) = unsafe { GetDiskFreeSpaceExW(root_pcwstr, None, None, Some(&mut free_space)) } {
if free_space < required_space { if free_space < required_space {
bail!( bail!(
"{} requires at least {} disk space to be installed. There is only {} available.", "{} requires at least {} disk space to be installed. There is only {} available.",
@@ -85,7 +82,7 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
bail!("This application ({}) does not support your CPU architecture.", &app.machine_architecture); bail!("This application ({}) does not support your CPU architecture.", &app.machine_architecture);
} }
let mut root_path_renamed = String::new(); let mut root_path_renamed: Option<PathBuf> = None;
// does the target directory exist and have files? (eg. already installed) // does the target directory exist and have files? (eg. already installed)
if !shared::is_dir_empty(&root_path) { if !shared::is_dir_empty(&root_path) {
// the target directory is not empty, and not dead // the target directory is not empty, and not dead
@@ -100,20 +97,22 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z) anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z)
})?; })?;
root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(16)); let renamed = root_path.with_extension(shared::random_string(16));
info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed); info!("Renaming existing directory to '{:?}' to allow rollback...", renamed);
shared::retry_io(|| fs::rename(&root_path, &root_path_renamed)).map_err(|_| { shared::retry_io(|| fs::rename(&root_path, &renamed)).map_err(|_| {
anyhow!( anyhow!(
"Failed to remove existing application directory, please close the application and try running the installer again. \ "Failed to remove existing application directory, please close the application and try running the installer again. \
If the issue persists, try uninstalling first via Programs & Features, or restarting your computer." If the issue persists, try uninstalling first via Programs & Features, or restarting your computer."
) )
})?; })?;
root_path_renamed = Some(renamed);
} }
info!("Preparing and cleaning installation directory..."); info!("Preparing and cleaning installation directory...");
remove_dir_all::ensure_empty_dir(&root_path)?; remove_dir_all::ensure_empty_dir(&root_path)?;
info!("Acquiring lock..."); info!("Acquiring lock...");
let paths = create_config_from_root_dir(&root_path); let paths = create_config_from_root_dir(&root_path);
let locator = VelopackLocator::new_with_manifest(paths, app); let locator = VelopackLocator::new_with_manifest(paths, app);
@@ -134,17 +133,17 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
if install_result.is_ok() { if install_result.is_ok() {
info!("Installation completed successfully!"); info!("Installation completed successfully!");
if !root_path_renamed.is_empty() { if let Some(renamed) = root_path_renamed {
info!("Removing rollback directory..."); info!("Removing rollback directory...");
let _ = shared::retry_io(|| fs::remove_dir_all(&root_path_renamed)); let _ = shared::retry_io(|| remove_dir_all::remove_dir_all(&renamed));
} }
} else { } else {
error!("Installation failed!"); error!("Installation failed!");
if !root_path_renamed.is_empty() { if let Some(renamed) = root_path_renamed {
info!("Rolling back installation..."); info!("Rolling back installation...");
let _ = shared::force_stop_package(&root_path); let _ = shared::force_stop_package(&root_path);
let _ = shared::retry_io(|| fs::remove_dir_all(&root_path)); let _ = shared::retry_io(|| remove_dir_all::remove_dir_all(&root_path));
let _ = shared::retry_io(|| fs::rename(&root_path_renamed, &root_path)); let _ = shared::retry_io(|| fs::rename(&renamed, &root_path));
} }
install_result?; install_result?;
} }
@@ -152,7 +151,12 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
Ok(()) Ok(())
} }
fn install_impl(pkg: &mut BundleZip, locator: &VelopackLocator, tx: &std::sync::mpsc::Sender<i16>, start_args: Option<Vec<&str>>) -> Result<()> { fn install_impl(
pkg: &mut BundleZip,
locator: &VelopackLocator,
tx: &std::sync::mpsc::Sender<i16>,
start_args: Option<Vec<OsString>>,
) -> Result<()> {
info!("Starting installation!"); info!("Starting installation!");
// all application paths // all application paths

View File

@@ -12,11 +12,11 @@ pub fn zstd_patch_single<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(old_
let output_file = output_file.as_ref(); let output_file = output_file.as_ref();
if !old_file.exists() { if !old_file.exists() {
bail!("Old file does not exist: {}", old_file.to_string_lossy()); bail!("Old file does not exist: {:?}", old_file);
} }
if !patch_file.exists() { if !patch_file.exists() {
bail!("Patch file does not exist: {}", patch_file.to_string_lossy()); bail!("Patch file does not exist: {:?}", patch_file);
} }
let dict = fs::read(old_file)?; let dict = fs::read(old_file)?;
@@ -62,7 +62,7 @@ pub fn delta<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(
let output_file = output_file.as_ref().to_path_buf(); let output_file = output_file.as_ref().to_path_buf();
if !old_file.exists() { if !old_file.exists() {
bail!("Old file does not exist: {}", old_file.to_string_lossy()); bail!("Old file does not exist: {:?}", old_file);
} }
if delta_files.is_empty() { if delta_files.is_empty() {
@@ -71,13 +71,13 @@ pub fn delta<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(
for delta_file in &delta_files { for delta_file in &delta_files {
if !delta_file.exists() { if !delta_file.exists() {
bail!("Delta file does not exist: {}", delta_file.to_string_lossy()); bail!("Delta file does not exist: {:?}", delta_file);
} }
} }
let time = simple_stopwatch::Stopwatch::start_new(); let time = simple_stopwatch::Stopwatch::start_new();
info!("Extracting base package for delta patching: {}", temp_dir.to_string_lossy()); info!("Extracting base package for delta patching: {:?}", temp_dir);
let work_dir = temp_dir.join("_work"); let work_dir = temp_dir.join("_work");
fs::create_dir_all(&work_dir)?; fs::create_dir_all(&work_dir)?;
fastzip::extract_to_directory(&old_file, &work_dir, None)?; fastzip::extract_to_directory(&old_file, &work_dir, None)?;
@@ -85,7 +85,7 @@ pub fn delta<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(
info!("Base package extracted. {} delta packages to apply.", delta_files.len()); info!("Base package extracted. {} delta packages to apply.", delta_files.len());
for (i, delta_file) in delta_files.iter().enumerate() { for (i, delta_file) in delta_files.iter().enumerate() {
info!("{}: extracting apply delta patch: {}", i, delta_file.to_string_lossy()); info!("{}: extracting apply delta patch: {:?}", i, delta_file);
let delta_dir = temp_dir.join(format!("delta_{}", i)); let delta_dir = temp_dir.join(format!("delta_{}", i));
fs::create_dir_all(&delta_dir)?; fs::create_dir_all(&delta_dir)?;
fastzip::extract_to_directory(&delta_file, &delta_dir, None)?; fastzip::extract_to_directory(&delta_file, &delta_dir, None)?;
@@ -152,7 +152,7 @@ pub fn delta<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(
} }
} }
info!("All delta patches applied. Asembling output package at: {}", output_file.to_string_lossy()); info!("All delta patches applied. Asembling output package at: {:?}", output_file);
fastzip::compress_directory(&work_dir, &output_file, fastzip::CompressionLevel::fast())?; fastzip::compress_directory(&work_dir, &output_file, fastzip::CompressionLevel::fast())?;

View File

@@ -1,22 +1,16 @@
use crate::shared::{self, OperationWait}; use crate::shared::{self, OperationWait};
use anyhow::Result; use anyhow::Result;
use std::ffi::OsString;
use velopack::locator::LocationContext; use velopack::locator::LocationContext;
#[allow(unused_variables, unused_imports)] #[allow(unused_variables)]
pub fn start( pub fn start(
wait: OperationWait, wait: OperationWait,
context: LocationContext, context: LocationContext,
exe_name: Option<&String>, exe_name: Option<&OsString>,
exe_args: Option<Vec<&str>>, exe_args: Option<Vec<OsString>>,
legacy_args: Option<&String>, legacy_args: Option<&OsString>,
) -> Result<()> { ) -> Result<()> {
use anyhow::bail;
#[cfg(target_os = "windows")]
if legacy_args.is_some() && exe_args.is_some() {
bail!("Cannot use both legacy args and new args format.");
}
shared::operation_wait(wait); shared::operation_wait(wait);
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]

View File

@@ -4,19 +4,16 @@ use crate::{
windows as win, windows as win,
}; };
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use std::os::windows::process::CommandExt;
use std::{ use std::{
fs, ffi::{OsStr, OsString},
path::Path, os::windows::process::CommandExt,
path::PathBuf,
process::Command as Process,
}; };
use velopack::{bundle::Manifest, constants}; use std::{fs, path::Path, path::PathBuf, process::Command as Process};
use velopack::locator::{self, LocationContext, VelopackLocator}; use velopack::locator::{self, LocationContext, VelopackLocator};
use velopack::{bundle::Manifest, constants};
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
enum LocatorResult enum LocatorResult {
{
Normal(VelopackLocator), Normal(VelopackLocator),
Legacy(PathBuf, Manifest), Legacy(PathBuf, Manifest),
} }
@@ -34,18 +31,18 @@ impl LocatorResult {
LocatorResult::Legacy(_, manifest) => manifest.clone(), LocatorResult::Legacy(_, manifest) => manifest.clone(),
} }
} }
pub fn get_current_dir(&self) -> PathBuf { pub fn get_current_dir(&self) -> PathBuf {
match self { match self {
LocatorResult::Normal(locator) => locator.get_current_bin_dir(), LocatorResult::Normal(locator) => locator.get_current_bin_dir(),
LocatorResult::Legacy(path, _) => path.join("current"), LocatorResult::Legacy(path, _) => path.join("current"),
} }
} }
pub fn get_exe_to_start(&self, name: Option<&String>) -> Result<PathBuf> { pub fn get_exe_to_start<P: AsRef<OsStr>>(&self, name: Option<P>) -> Result<PathBuf> {
let current_dir = self.get_current_dir(); let current_dir = self.get_current_dir();
if let Some(name) = name { if let Some(name) = name {
Ok(Path::new(&current_dir).join(name)) Ok(Path::new(&current_dir).join(name.as_ref()))
} else { } else {
match self { match self {
LocatorResult::Normal(locator) => Ok(locator.get_main_exe_path()), LocatorResult::Normal(locator) => Ok(locator.get_main_exe_path()),
@@ -71,10 +68,10 @@ fn legacy_locator(context: LocationContext) -> Result<LocatorResult> {
let parent_dir = my_exe.parent().expect("Unable to determine parent directory"); let parent_dir = my_exe.parent().expect("Unable to determine parent directory");
let packages_dir = parent_dir.join("packages"); let packages_dir = parent_dir.join("packages");
if let Some((path, manifest)) = locator::find_latest_full_package(&packages_dir) { if let Some((path, manifest)) = locator::find_latest_full_package(&packages_dir) {
info!("Found full package to read: {}", path.to_string_lossy()); info!("Found full package to read: {:?}", path);
Ok(LocatorResult::Legacy(parent_dir.to_path_buf(), manifest)) Ok(LocatorResult::Legacy(parent_dir.to_path_buf(), manifest))
} else { } else {
bail!("Unable to locate app manifest or full package in {}.", packages_dir.to_string_lossy()); bail!("Unable to locate app manifest or full package in {:?}.", packages_dir);
} }
} }
} }
@@ -82,10 +79,14 @@ fn legacy_locator(context: LocationContext) -> Result<LocatorResult> {
pub fn start_impl( pub fn start_impl(
context: LocationContext, context: LocationContext,
exe_name: Option<&String>, exe_name: Option<&OsString>,
exe_args: Option<Vec<&str>>, exe_args: Option<Vec<OsString>>,
legacy_args: Option<&String>, legacy_args: Option<&OsString>,
) -> Result<()> { ) -> Result<()> {
if legacy_args.is_some() && exe_args.is_some() {
anyhow::bail!("Cannot use both legacy args and new args format at the same time.");
}
let locator = legacy_locator(context)?; let locator = legacy_locator(context)?;
let root_dir = locator.get_root_dir(); let root_dir = locator.get_root_dir();
let manifest = locator.get_manifest(); let manifest = locator.get_manifest();
@@ -113,17 +114,17 @@ pub fn start_impl(
fn start_regular( fn start_regular(
locator: LocatorResult, locator: LocatorResult,
exe_name: Option<&String>, exe_name: Option<&OsString>,
exe_args: Option<Vec<&str>>, exe_args: Option<Vec<OsString>>,
legacy_args: Option<&String>, legacy_args: Option<&OsString>,
) -> Result<()> { ) -> Result<()> {
// we can't just run the normal start_package command, because legacy squirrel might provide // 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 // an "exe name" to restart which no longer exists in the package
let exe_to_execute = locator.get_exe_to_start(exe_name)?; let exe_to_execute = locator.get_exe_to_start(exe_name)?;
if !exe_to_execute.exists() { if !exe_to_execute.exists() {
bail!("Unable to find executable to start: '{:?}'", exe_to_execute); bail!("Unable to find executable to start: '{:?}'", exe_to_execute);
} }
let current = locator.get_current_dir(); let current = locator.get_current_dir();
info!("About to launch: '{:?}' in dir '{:?}'", exe_to_execute, current); info!("About to launch: '{:?}' in dir '{:?}'", exe_to_execute, current);
@@ -143,13 +144,14 @@ fn start_regular(
fn try_legacy_migration(root_dir: &PathBuf, manifest: &Manifest) -> Result<VelopackLocator> { fn try_legacy_migration(root_dir: &PathBuf, manifest: &Manifest) -> Result<VelopackLocator> {
info!("This is a legacy app. Will try and upgrade it now."); 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, // if started by legacy Squirrel, the working dir of Update.exe may be inside the app-* folder,
// meaning we can not clean up properly. // meaning we can not clean up properly.
std::env::set_current_dir(&root_dir)?; std::env::set_current_dir(&root_dir)?;
let path_config = locator::create_config_from_root_dir(root_dir); let path_config = locator::create_config_from_root_dir(root_dir);
let package = locator::find_latest_full_package(&path_config.PackagesDir).ok_or_else(|| anyhow!("Unable to find latest full package."))?; let package =
locator::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..."); warn!("This application is installed in a folder prefixed with 'app-'. Attempting to migrate...");
let _ = shared::force_stop_package(&root_dir); let _ = shared::force_stop_package(&root_dir);

View File

@@ -19,7 +19,7 @@ pub fn uninstall(locator: &VelopackLocator, delete_self: bool) -> Result<()> {
// remove all shortcuts pointing to the app // remove all shortcuts pointing to the app
windows::remove_all_shortcuts_for_root_dir(&root_path); windows::remove_all_shortcuts_for_root_dir(&root_path);
info!("Removing directory '{}'", root_path.to_string_lossy()); info!("Removing directory '{:?}'", root_path);
let _ = remove_dir_all::remove_dir_contents(&root_path); let _ = remove_dir_all::remove_dir_contents(&root_path);
if let Err(e) = windows::registry::remove_uninstall_entry(&locator) { if let Err(e) = windows::registry::remove_uninstall_entry(&locator) {

View File

@@ -7,6 +7,7 @@ extern crate log;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use clap::{arg, value_parser, Command}; use clap::{arg, value_parser, Command};
use memmap2::Mmap; use memmap2::Mmap;
use std::ffi::OsString;
use std::fs::File; use std::fs::File;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use velopack_bins::*; use velopack_bins::*;
@@ -83,7 +84,7 @@ fn main_inner() -> Result<()> {
let debug = matches.get_one::<PathBuf>("debug"); let debug = matches.get_one::<PathBuf>("debug");
let install_to = matches.get_one::<PathBuf>("installto"); let install_to = matches.get_one::<PathBuf>("installto");
let exe_args: Option<Vec<&str>> = matches.get_many::<String>("EXE_ARGS").map(|v| v.map(|f| f.as_str()).collect()); let exe_args = matches.get_many::<OsString>("EXE_ARGS").map(|v| v.map(|f| f.to_os_string()).collect());
info!("Starting Velopack Setup ({})", env!("NGBV_VERSION")); info!("Starting Velopack Setup ({})", env!("NGBV_VERSION"));
info!(" Location: {:?}", env::current_exe()?); info!(" Location: {:?}", env::current_exe()?);

View File

@@ -1,10 +1,10 @@
use super::{dialogs_common::*, dialogs_const::*}; use super::{dialogs_common::*, dialogs_const::*};
use crate::windows::strings::string_to_wide;
use anyhow::Result; use anyhow::Result;
use std::path::PathBuf; use std::path::PathBuf;
use velopack::{ use velopack::{
bundle::Manifest, bundle::Manifest,
locator::{auto_locate_app_manifest, LocationContext}, locator::{auto_locate_app_manifest, LocationContext},
wide_strings::{string_to_wide, string_to_wide_opt},
}; };
use windows::{ use windows::{
core::HRESULT, core::HRESULT,
@@ -74,7 +74,7 @@ pub fn show_uninstall_complete_with_errors_dialog(app_title: &str, log_path: Opt
let setup_name = string_to_wide(format!("{} Uninstall", app_title)); let setup_name = string_to_wide(format!("{} Uninstall", app_title));
let instruction = string_to_wide(format!("{} uninstall has completed with errors.", app_title)); let instruction = string_to_wide(format!("{} uninstall has completed with errors.", app_title));
let content = string_to_wide( let content = string_to_wide(
"There may be left-over files or directories on your system. You can attempt to remove these manually or re-install the application and try again.", "There may be left-over files or directories on your system. You can attempt to remove these manually or re-install the application and try again."
); );
let mut config = TASKDIALOGCONFIG::default(); let mut config = TASKDIALOGCONFIG::default();
@@ -224,7 +224,7 @@ pub fn generate_confirm(
ico: DialogIcon, ico: DialogIcon,
) -> Result<DialogResult> { ) -> Result<DialogResult> {
let hparent = unsafe { GetDesktopWindow() }; let hparent = unsafe { GetDesktopWindow() };
let mut ok_text_buf = ok_text.map(string_to_wide); let mut ok_text_buf = string_to_wide_opt(ok_text);
let mut custom_btns = if let Some(ok_text_buf) = ok_text_buf.as_mut() { let mut custom_btns = if let Some(ok_text_buf) = ok_text_buf.as_mut() {
let td_btn = TASKDIALOG_BUTTON { nButtonID: IDOK.0, pszButtonText: ok_text_buf.as_pcwstr() }; let td_btn = TASKDIALOG_BUTTON { nButtonID: IDOK.0, pszButtonText: ok_text_buf.as_pcwstr() };
vec![td_btn] vec![td_btn]
@@ -246,7 +246,7 @@ pub fn generate_confirm(
let title_buf = string_to_wide(title); let title_buf = string_to_wide(title);
tdc.pszWindowTitle = title_buf.as_pcwstr(); tdc.pszWindowTitle = title_buf.as_pcwstr();
let mut header_buf = header.map(string_to_wide); let mut header_buf = string_to_wide_opt(header);
if let Some(header_buf) = header_buf.as_mut() { if let Some(header_buf) = header_buf.as_mut() {
tdc.pszMainInstruction = header_buf.as_pcwstr(); tdc.pszMainInstruction = header_buf.as_pcwstr();
} }

View File

@@ -24,7 +24,7 @@ pub fn wait_for_parent_to_exit(ms_to_wait: u32) -> Result<()> {
} }
pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> { pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
let root_dir = root_dir.as_ref().to_string_lossy().to_string(); let root_dir = root_dir.as_ref();
let command = format!("quit app \"{}\"", root_dir); let command = format!("quit app \"{}\"", root_dir);
Process::new("/usr/bin/osascript").arg("-e").arg(command).spawn().map_err(|z| anyhow!("Failed to stop application ({}).", z))?; Process::new("/usr/bin/osascript").arg("-e").arg(command).spawn().map_err(|z| anyhow!("Failed to stop application ({}).", z))?;
Ok(()) Ok(())

View File

@@ -1,4 +1,3 @@
use crate::windows::strings;
use ::windows::{ use ::windows::{
core::PWSTR, core::PWSTR,
Win32::{ Win32::{
@@ -11,10 +10,11 @@ use regex::Regex;
use semver::Version; use semver::Version;
use std::{ use std::{
collections::HashMap, collections::HashMap,
ffi::OsString,
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use velopack::{locator::VelopackLocator, process}; use velopack::{locator::VelopackLocator, process, wide_strings::wide_to_os_string};
// https://github.com/nushell/nushell/blob/4458aae3d41517d74ce1507ad3e8cd94021feb16/crates/nu-system/src/windows.rs#L593 // https://github.com/nushell/nushell/blob/4458aae3d41517d74ce1507ad3e8cd94021feb16/crates/nu-system/src/windows.rs#L593
fn get_pids() -> Result<Vec<u32>> { fn get_pids() -> Result<Vec<u32>> {
@@ -55,12 +55,9 @@ unsafe fn get_processes_running_in_directory<P: AsRef<Path>>(dir: P) -> Result<V
continue; continue;
} }
let full_path = strings::u16_to_string(&full_path_vec); let slice = &full_path_vec[..full_path_len as usize];
if let Err(_) = full_path { let full_path = wide_to_os_string(slice);
continue; let full_path = PathBuf::from(full_path);
}
let full_path = PathBuf::from(full_path.unwrap());
if let Ok(is_subpath) = crate::windows::is_sub_path(&full_path, dir) { if let Ok(is_subpath) = crate::windows::is_sub_path(&full_path, dir) {
if is_subpath { if is_subpath {
oup.push((pid, full_path, process)); oup.push((pid, full_path, process));
@@ -93,21 +90,20 @@ fn _force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
Ok(()) Ok(())
} }
pub fn start_package(locator: &VelopackLocator, exe_args: Option<Vec<&str>>, set_env: Option<&str>) -> Result<()> { pub fn start_package(locator: &VelopackLocator, exe_args: Option<Vec<OsString>>, set_env: Option<&str>) -> Result<()> {
let current = locator.get_current_bin_dir(); let current = locator.get_current_bin_dir();
let exe_to_execute = locator.get_main_exe_path(); let exe_to_execute = locator.get_main_exe_path();
if !exe_to_execute.exists() { if !exe_to_execute.exists() {
bail!("Unable to find executable to start: '{}'", exe_to_execute.to_string_lossy()); bail!("Unable to find executable to start: '{:?}'", exe_to_execute);
} }
let args: Vec<String> = exe_args.unwrap_or_default().iter().map(|s| s.to_string()).collect();
let mut environment = HashMap::new(); let mut environment = HashMap::new();
if let Some(env_var) = set_env { if let Some(env_var) = set_env {
debug!("Setting environment variable: {}={}", env_var, "true"); debug!("Setting environment variable: {}={}", env_var, "true");
environment.insert(env_var.to_string(), "true".to_string()); environment.insert(env_var.to_string(), "true".to_string());
} }
process::run_process(exe_to_execute, args, Some(current), true, Some(environment))?; process::run_process(exe_to_execute, exe_args.unwrap_or_default(), Some(current), true, Some(environment))?;
Ok(()) Ok(())
} }

View File

@@ -37,7 +37,7 @@ fn main() -> ExitCode {
args.insert(0, "start".to_owned()); args.insert(0, "start".to_owned());
args.insert(1, "--".to_owned()); args.insert(1, "--".to_owned());
info!("Stub about to start Update.exe ({}) with args: {:?}", update_exe.to_string_lossy(), args); info!("Stub about to start Update.exe ({:?}) with args: {:?}", update_exe, args);
const CREATE_NO_WINDOW: u32 = 0x08000000; const CREATE_NO_WINDOW: u32 = 0x08000000;
match Process::new(update_exe).args(args).creation_flags(CREATE_NO_WINDOW).spawn() { match Process::new(update_exe).args(args).creation_flags(CREATE_NO_WINDOW).spawn() {

View File

@@ -6,6 +6,7 @@ extern crate log;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use clap::{arg, value_parser, ArgAction, ArgMatches, Command}; use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
use std::ffi::OsString;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use velopack::locator::{auto_locate_app_manifest, LocationContext}; use velopack::locator::{auto_locate_app_manifest, LocationContext};
use velopack::logging::*; use velopack::logging::*;
@@ -22,15 +23,15 @@ fn root_command() -> Command {
.arg(arg!(-w --wait "Wait for the parent process to terminate before applying the update").hide(true)) .arg(arg!(-w --wait "Wait for the parent process to terminate before applying the update").hide(true))
.arg(arg!(--waitPid <PID> "Wait for the specified process to terminate before applying the update").value_parser(value_parser!(u32))) .arg(arg!(--waitPid <PID> "Wait for the specified process to terminate before applying the update").value_parser(value_parser!(u32)))
.arg(arg!(-p --package <FILE> "Update package to apply").value_parser(value_parser!(PathBuf))) .arg(arg!(-p --package <FILE> "Update package to apply").value_parser(value_parser!(PathBuf)))
.arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..)) .arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..).value_parser(value_parser!(OsString)))
) )
.subcommand(Command::new("start") .subcommand(Command::new("start")
.about("Starts the currently installed version of the application") .about("Starts the currently installed version of the application")
.arg(arg!(-a --args <ARGS> "Legacy args format").aliases(vec!["processStartArgs", "process-start-args"]).hide(true).allow_hyphen_values(true).num_args(1)) .arg(arg!(-a --args <ARGS> "Legacy args format").aliases(vec!["processStartArgs", "process-start-args"]).hide(true).allow_hyphen_values(true).num_args(1).value_parser(value_parser!(OsString)))
.arg(arg!(-w --wait "Wait for the parent process to terminate before starting the application").hide(true)) .arg(arg!(-w --wait "Wait for the parent process to terminate before starting the application").hide(true))
.arg(arg!(--waitPid <PID> "Wait for the specified process to terminate before applying the update").value_parser(value_parser!(u32))) .arg(arg!(--waitPid <PID> "Wait for the specified process to terminate before applying the update").value_parser(value_parser!(u32)))
.arg(arg!([EXE_NAME] "The optional name of the binary to execute")) .arg(arg!([EXE_NAME] "The optional name of the binary to execute").value_parser(value_parser!(OsString)))
.arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..)) .arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..).value_parser(value_parser!(OsString)))
.long_flag_aliases(vec!["processStart", "processStartAndWait"]) .long_flag_aliases(vec!["processStart", "processStartAndWait"])
) )
.subcommand(Command::new("patch") .subcommand(Command::new("patch")
@@ -158,8 +159,8 @@ fn main() -> Result<()> {
info!("--"); info!("--");
info!("Starting Velopack Updater ({})", env!("NGBV_VERSION")); info!("Starting Velopack Updater ({})", env!("NGBV_VERSION"));
info!(" Location: {}", env::current_exe()?.to_string_lossy()); info!(" Location: {:?}", env::current_exe()?);
info!(" CWD: {}", env::current_dir()?.to_string_lossy()); info!(" CWD: {:?}", env::current_dir()?);
info!(" Verbose: {}", verbose); info!(" Verbose: {}", verbose);
info!(" Silent: {}", silent); info!(" Silent: {}", silent);
info!(" Log File: {:?}", log_file); info!(" Log File: {:?}", log_file);
@@ -220,11 +221,11 @@ fn patch(_context: LocationContext, matches: &ArgMatches) -> Result<()> {
Ok(()) Ok(())
} }
fn get_exe_args(matches: &ArgMatches) -> Option<Vec<&str>> { fn get_exe_args(matches: &ArgMatches) -> Option<Vec<OsString>> {
matches.get_many::<String>("EXE_ARGS").map(|v| v.map(|f| f.as_str()).collect()) matches.get_many::<OsString>("EXE_ARGS").map(|v| v.map(|f| f.to_os_string()).collect())
} }
fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf>, Option<Vec<&str>>) { fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf>, Option<Vec<OsString>>) {
let restart = !get_flag_or_false(&matches, "norestart"); let restart = !get_flag_or_false(&matches, "norestart");
let package = matches.get_one::<PathBuf>("package"); let package = matches.get_one::<PathBuf>("package");
let exe_args = get_exe_args(matches); let exe_args = get_exe_args(matches);
@@ -246,9 +247,9 @@ fn apply(context: LocationContext, matches: &ArgMatches) -> Result<()> {
Ok(()) Ok(())
} }
fn get_start_args(matches: &ArgMatches) -> (OperationWait, Option<&String>, Option<&String>, Option<Vec<&str>>) { fn get_start_args(matches: &ArgMatches) -> (OperationWait, Option<&OsString>, Option<&OsString>, Option<Vec<OsString>>) {
let legacy_args = matches.get_one::<String>("args"); let legacy_args = matches.get_one::<OsString>("args");
let exe_name = matches.get_one::<String>("EXE_NAME"); let exe_name = matches.get_one::<OsString>("EXE_NAME");
let exe_args = get_exe_args(matches); let exe_args = get_exe_args(matches);
let wait = get_op_wait(&matches); let wait = get_op_wait(&matches);
(wait, exe_name, legacy_args, exe_args) (wait, exe_name, legacy_args, exe_args)
@@ -346,7 +347,7 @@ fn test_start_command_supports_legacy_commands() {
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::NoWait); assert_eq!(wait_for_parent, OperationWait::NoWait);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, None); assert_eq!(legacy_args, None);
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
@@ -354,7 +355,7 @@ fn test_start_command_supports_legacy_commands() {
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::NoWait); assert_eq!(wait_for_parent, OperationWait::NoWait);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, None); assert_eq!(legacy_args, None);
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
@@ -362,7 +363,7 @@ fn test_start_command_supports_legacy_commands() {
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, None); assert_eq!(legacy_args, None);
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
@@ -370,40 +371,40 @@ fn test_start_command_supports_legacy_commands() {
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, None); assert_eq!(legacy_args, None);
assert_eq!(exe_args, Some(vec!["Foo=Bar"])); assert_eq!(exe_args, Some(vec!["Foo=Bar".into()]));
let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "-a", "myarg"]; let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "-a", "myarg"];
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, Some(&"myarg".to_string())); assert_eq!(legacy_args, Some(&"myarg".into()));
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "-a", "myarg"]; let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "-a", "myarg"];
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, Some(&"myarg".to_string())); assert_eq!(legacy_args, Some(&"myarg".into()));
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "--processStartArgs", "myarg"]; let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "--processStartArgs", "myarg"];
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, Some(&"myarg".to_string())); assert_eq!(legacy_args, Some(&"myarg".into()));
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "--process-start-args", "myarg"]; let command = vec!["Update.exe", "--processStartAndWait", "hello.exe", "--process-start-args", "myarg"];
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, Some(&"hello.exe".to_string())); assert_eq!(exe_name, Some(&"hello.exe".into()));
assert_eq!(legacy_args, Some(&"myarg".to_string())); assert_eq!(legacy_args, Some(&"myarg".into()));
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
let command = vec!["Update.exe", "--processStartAndWait", "-a", "myarg"]; let command = vec!["Update.exe", "--processStartAndWait", "-a", "myarg"];
@@ -411,7 +412,7 @@ fn test_start_command_supports_legacy_commands() {
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, None); assert_eq!(exe_name, None);
assert_eq!(legacy_args, Some(&"myarg".to_string())); assert_eq!(legacy_args, Some(&"myarg".into()));
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
let command = vec!["Update.exe", "--processStartAndWait", "-a", "-- -c \" asda --aasd"]; let command = vec!["Update.exe", "--processStartAndWait", "-a", "-- -c \" asda --aasd"];
@@ -419,6 +420,6 @@ fn test_start_command_supports_legacy_commands() {
let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap()); let (wait_for_parent, exe_name, legacy_args, exe_args) = get_start_args(matches.subcommand_matches("start").unwrap());
assert_eq!(wait_for_parent, OperationWait::WaitParent); assert_eq!(wait_for_parent, OperationWait::WaitParent);
assert_eq!(exe_name, None); assert_eq!(exe_name, None);
assert_eq!(legacy_args, Some(&"-- -c \" asda --aasd".to_string())); assert_eq!(legacy_args, Some(&"-- -c \" asda --aasd".into()));
assert_eq!(exe_args, None); assert_eq!(exe_args, None);
} }

View File

@@ -1,5 +1,6 @@
use anyhow::Result; use anyhow::{bail, Result};
use std::path::Path; use std::path::PathBuf;
use velopack::wide_strings::wide_to_os_string;
use windows::{ use windows::{
core::GUID, core::GUID,
Win32::UI::Shell::{ Win32::UI::Shell::{
@@ -8,54 +9,60 @@ use windows::{
}, },
}; };
fn get_known_folder(rfid: *const GUID) -> Result<String> { fn get_known_folder(rfid: *const GUID) -> Result<PathBuf> {
unsafe { unsafe {
let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0); let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0);
let result = SHGetKnownFolderPath(rfid, flag, None)?; let result = SHGetKnownFolderPath(rfid, flag, None)?;
super::strings::u16_to_string(result) if result.is_null() {
bail!("Failed to get known folder path (SHGetKnownFolderPath returned null)");
}
let str = wide_to_os_string(result);
let path = PathBuf::from(str);
Ok(path)
} }
} }
pub fn get_local_app_data() -> Result<String> { pub fn get_local_app_data() -> Result<PathBuf> {
get_known_folder(&FOLDERID_LocalAppData) get_known_folder(&FOLDERID_LocalAppData)
} }
pub fn get_roaming_app_data() -> Result<String> { pub fn get_roaming_app_data() -> Result<PathBuf> {
get_known_folder(&FOLDERID_RoamingAppData) get_known_folder(&FOLDERID_RoamingAppData)
} }
pub fn get_user_desktop() -> Result<String> { pub fn get_user_desktop() -> Result<PathBuf> {
get_known_folder(&FOLDERID_Desktop) get_known_folder(&FOLDERID_Desktop)
} }
pub fn get_user_profile() -> Result<String> { pub fn get_user_profile() -> Result<PathBuf> {
get_known_folder(&FOLDERID_Profile) get_known_folder(&FOLDERID_Profile)
} }
pub fn get_start_menu() -> Result<String> { pub fn get_start_menu() -> Result<PathBuf> {
let start_menu = get_known_folder(&FOLDERID_StartMenu)?; let start_menu = get_known_folder(&FOLDERID_StartMenu)?;
let programs_path = Path::new(&start_menu).join("Programs"); let programs_path = start_menu.join("Programs");
Ok(programs_path.to_string_lossy().to_string()) Ok(programs_path)
} }
pub fn get_startup() -> Result<String> { pub fn get_startup() -> Result<PathBuf> {
get_known_folder(&FOLDERID_Startup) get_known_folder(&FOLDERID_Startup)
} }
pub fn get_downloads() -> Result<String> { pub fn get_downloads() -> Result<PathBuf> {
get_known_folder(&FOLDERID_Downloads) get_known_folder(&FOLDERID_Downloads)
} }
pub fn get_program_files_x64() -> Result<String> { pub fn get_program_files_x64() -> Result<PathBuf> {
get_known_folder(&FOLDERID_ProgramFilesX64) get_known_folder(&FOLDERID_ProgramFilesX64)
} }
pub fn get_program_files_x86() -> Result<String> { pub fn get_program_files_x86() -> Result<PathBuf> {
get_known_folder(&FOLDERID_ProgramFilesX86) get_known_folder(&FOLDERID_ProgramFilesX86)
} }
pub fn get_user_pinned() -> Result<String> { pub fn get_user_pinned() -> Result<PathBuf> {
let pinned_str = get_roaming_app_data()?; let pinned_str = get_roaming_app_data()?;
let pinned_path = Path::new(&pinned_str).join("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned"); let pinned_path = pinned_str.join("Microsoft").join("Internet Explorer").join("Quick Launch").join("User Pinned");
Ok(pinned_path.to_string_lossy().to_string()) Ok(pinned_path)
} }

View File

@@ -5,7 +5,6 @@ pub mod runtimes;
pub mod splash; pub mod splash;
pub mod known_path; pub mod known_path;
pub mod strings;
pub mod registry; pub mod registry;
pub mod webview2; pub mod webview2;

View File

@@ -1,9 +1,7 @@
use super::{runtimes, splash}; use super::{runtimes, splash};
use crate::shared::dialogs; use crate::shared::dialogs;
use velopack::{bundle, download};
use anyhow::Result; use anyhow::Result;
use std::path::Path; use velopack::{bundle, download};
pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result<bool> { pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result<bool> {
info!("Checking application pre-requisites..."); info!("Checking application pre-requisites...");
@@ -39,7 +37,6 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
} }
let downloads = super::known_path::get_downloads()?; let downloads = super::known_path::get_downloads()?;
let downloads = Path::new(downloads.as_str());
info!("Downloading {} missing pre-requisites...", missing.len()); info!("Downloading {} missing pre-requisites...", missing.len());
let quiet = dialogs::get_silent(); let quiet = dialogs::get_silent();
@@ -47,15 +44,19 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
for i in 0..missing.len() { for i in 0..missing.len() {
let dep = &missing[i]; let dep = &missing[i];
let url = dep.get_download_url()?; let url = dep.get_download_url()?;
let exe_name = downloads.join(dep.get_exe_name()); let exe_path = downloads.join(dep.get_exe_name());
if !exe_name.exists() { if !exe_path.exists() {
let window_title = if updating_from.is_some() { format!("{} Update", dep.display_name()) } else { format!("{} Setup", dep.display_name()) }; let window_title = if updating_from.is_some() {
format!("{} Update", dep.display_name())
} else {
format!("{} Setup", dep.display_name())
};
let content = format!("Downloading {}...", dep.display_name()); let content = format!("Downloading {}...", dep.display_name());
info!(" {}", content); info!(" {}", content);
let tx = splash::show_progress_dialog(window_title, content); let tx = splash::show_progress_dialog(window_title, content);
let result = download::download_url_to_file(&url, &exe_name.to_str().unwrap(), |p| { let result = download::download_url_to_file(&url, &exe_path, |p| {
let _ = tx.send(p); let _ = tx.send(p);
}); });
@@ -64,7 +65,7 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
} }
info!(" Installing {}...", dep.display_name()); info!(" Installing {}...", dep.display_name());
let result = dep.install(exe_name.to_str().unwrap(), quiet)?; let result = dep.install(&exe_path, quiet)?;
if result == runtimes::RuntimeInstallResult::RestartRequired { if result == runtimes::RuntimeInstallResult::RestartRequired {
warn!("A restart is required to complete the installation of {}.", dep.display_name()); warn!("A restart is required to complete the installation of {}.", dep.display_name());
dialogs::show_restart_required(&app); dialogs::show_restart_required(&app);

View File

@@ -1,5 +1,6 @@
use anyhow::Result; use anyhow::Result;
use chrono::{Datelike, Local as DateTime}; use chrono::{Datelike, Local as DateTime};
use std::ffi::OsString;
use velopack::locator::VelopackLocator; use velopack::locator::VelopackLocator;
use winreg::{enums::*, RegKey}; use winreg::{enums::*, RegKey};
@@ -12,9 +13,9 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
let app_title = locator.get_manifest_title(); let app_title = locator.get_manifest_title();
let app_authors = locator.get_manifest_authors(); let app_authors = locator.get_manifest_authors();
let root_path_str = locator.get_root_dir_as_string(); let root_path = locator.get_root_dir();
let main_exe_path = locator.get_main_exe_path_as_string(); let main_exe_path = locator.get_main_exe_path();
let updater_path = locator.get_update_path_as_string(); let updater_path = locator.get_update_path();
let folder_size = fs_extra::dir::get_size(locator.get_current_bin_dir()).unwrap_or(0); let folder_size = fs_extra::dir::get_size(locator.get_current_bin_dir()).unwrap_or(0);
let folder_size_kb = folder_size / 1024; let folder_size_kb = folder_size / 1024;
@@ -23,8 +24,13 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
let now = DateTime::now(); let now = DateTime::now();
let formatted_date = format!("{}{:02}{:02}", now.year(), now.month(), now.day()); let formatted_date = format!("{}{:02}{:02}", now.year(), now.month(), now.day());
let uninstall_cmd = format!("\"{}\" --uninstall", updater_path); let mut uninstall_cmd = OsString::from("\"");
let uninstall_quiet: String = format!("\"{}\" --uninstall --silent", updater_path); uninstall_cmd.push(&updater_path);
uninstall_cmd.push("\" --uninstall");
let mut uninstall_quiet = OsString::from("\"");
uninstall_quiet.push(&updater_path);
uninstall_quiet.push("\" --uninstall --silent");
let hkcu = RegKey::predef(HKEY_CURRENT_USER); let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let (reg_uninstall, _reg_uninstall_disp) = hkcu.create_subkey(UNINSTALL_REGISTRY_KEY)?; let (reg_uninstall, _reg_uninstall_disp) = hkcu.create_subkey(UNINSTALL_REGISTRY_KEY)?;
@@ -33,11 +39,11 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
let u32true = 1u32; let u32true = 1u32;
let language = 0x0409u32; let language = 0x0409u32;
reg_app.set_value("DisplayIcon", &main_exe_path)?; reg_app.set_value("DisplayIcon", &main_exe_path.as_os_str())?;
reg_app.set_value("DisplayName", &app_title)?; reg_app.set_value("DisplayName", &app_title)?;
reg_app.set_value("DisplayVersion", &short_version)?; reg_app.set_value("DisplayVersion", &short_version)?;
reg_app.set_value("InstallDate", &formatted_date)?; reg_app.set_value("InstallDate", &formatted_date)?;
reg_app.set_value("InstallLocation", &root_path_str)?; reg_app.set_value("InstallLocation", &root_path.as_os_str())?;
reg_app.set_value("Publisher", &app_authors)?; reg_app.set_value("Publisher", &app_authors)?;
reg_app.set_value("QuietUninstallString", &uninstall_quiet)?; reg_app.set_value("QuietUninstallString", &uninstall_quiet)?;
reg_app.set_value("UninstallString", &uninstall_cmd)?; reg_app.set_value("UninstallString", &uninstall_cmd)?;

View File

@@ -67,7 +67,7 @@ pub enum RuntimeInstallResult {
RestartRequired, RestartRequired,
} }
fn def_installer_routine(installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult> { fn def_installer_routine(installer_path: &Path, quiet: bool) -> Result<RuntimeInstallResult> {
let mut args = Vec::new(); let mut args = Vec::new();
args.push("/norestart"); args.push("/norestart");
if quiet { if quiet {
@@ -77,7 +77,7 @@ fn def_installer_routine(installer_path: &str, quiet: bool) -> Result<RuntimeIns
args.push("/showrmui"); args.push("/showrmui");
} }
info!("Running installer: '{}', args={:?}", installer_path, args); info!("Running installer: '{:?}', args={:?}", installer_path, args);
let mut cmd = Process::new(installer_path).args(&args).spawn()?; let mut cmd = Process::new(installer_path).args(&args).spawn()?;
let result: i32 = cmd.wait()?.code().ok_or_else(|| anyhow!("Unable to get installer exit code."))?; let result: i32 = cmd.wait()?.code().ok_or_else(|| anyhow!("Unable to get installer exit code."))?;
@@ -99,7 +99,7 @@ pub trait RuntimeInfo {
fn display_name(&self) -> &str; fn display_name(&self) -> &str;
fn is_installed(&self) -> bool; fn is_installed(&self) -> bool;
fn get_download_url(&self) -> Result<String>; fn get_download_url(&self) -> Result<String>;
fn install(&self, installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult>; fn install(&self, installer_path: &Path, quiet: bool) -> Result<RuntimeInstallResult>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -143,7 +143,7 @@ impl RuntimeInfo for FullFrameworkInfo {
} }
} }
fn install(&self, installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult> { fn install(&self, installer_path: &Path, quiet: bool) -> Result<RuntimeInstallResult> {
def_installer_routine(installer_path, quiet) def_installer_routine(installer_path, quiet)
} }
} }
@@ -204,7 +204,7 @@ impl RuntimeInfo for VCRedistInfo {
false false
} }
fn install(&self, installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult> { fn install(&self, installer_path: &Path, quiet: bool) -> Result<RuntimeInstallResult> {
def_installer_routine(installer_path, quiet) def_installer_routine(installer_path, quiet)
} }
} }
@@ -460,7 +460,7 @@ impl RuntimeInfo for DotnetInfo {
false false
} }
fn install(&self, installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult> { fn install(&self, installer_path: &Path, quiet: bool) -> Result<RuntimeInstallResult> {
def_installer_routine(installer_path, quiet) def_installer_routine(installer_path, quiet)
} }
} }
@@ -549,14 +549,14 @@ impl RuntimeInfo for WebView2Info {
} }
} }
fn install(&self, installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult> { fn install(&self, installer_path: &Path, quiet: bool) -> Result<RuntimeInstallResult> {
let args = if quiet { let args = if quiet {
vec!["/silent", "/install"] vec!["/silent", "/install"]
} else { } else {
vec!["/install"] vec!["/install"]
}; };
info!("Running installer: '{}', args={:?}", installer_path, args); info!("Running installer: '{:?}', args={:?}", installer_path, args);
let mut cmd = Process::new(installer_path).args(&args).spawn()?; let mut cmd = Process::new(installer_path).args(&args).spawn()?;
let result: i32 = cmd.wait()?.code().ok_or_else(|| anyhow!("Unable to get installer exit code."))?; let result: i32 = cmd.wait()?.code().ok_or_else(|| anyhow!("Unable to get installer exit code."))?;

View File

@@ -5,8 +5,8 @@ use std::time::Duration;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use glob::glob; use glob::glob;
use same_file::is_same_file; use same_file::is_same_file;
use velopack::{locator::ShortcutLocationFlags, locator::VelopackLocator}; use velopack::{locator::ShortcutLocationFlags, locator::VelopackLocator, wide_strings::*};
use windows::core::{Interface, GUID, PCWSTR}; use windows::core::{Interface, GUID};
use windows::Win32::Storage::EnhancedStorage::PKEY_AppUserModel_ID; use windows::Win32::Storage::EnhancedStorage::PKEY_AppUserModel_ID;
use windows::Win32::System::Com::{ use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, StructuredStorage::InitPropVariantFromStringVector, CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, StructuredStorage::InitPropVariantFromStringVector,
@@ -17,7 +17,7 @@ use windows::Win32::UI::Shell::{
}; };
use crate::shared as util; use crate::shared as util;
use crate::windows::{known_path as known, strings::*}; use crate::windows::known_path as known;
// https://github.com/vaginessa/PWAsForFirefox/blob/fba68dbcc7ca27b970dc5a278ebdad32e0ab3c83/native/src/integrations/implementation/windows.rs#L28 // https://github.com/vaginessa/PWAsForFirefox/blob/fba68dbcc7ca27b970dc5a278ebdad32e0ab3c83/native/src/integrations/implementation/windows.rs#L28
@@ -52,7 +52,11 @@ unsafe fn create_instance<T: Interface>(clsid: &GUID) -> Result<T> {
} }
fn get_shortcut_filename(app_id: &str, app_title: &str) -> String { fn get_shortcut_filename(app_id: &str, app_title: &str) -> String {
let name = if app_title.is_empty() { app_id.to_owned() } else { app_title.to_owned() }; let name = if app_title.is_empty() {
app_id.to_owned()
} else {
app_title.to_owned()
};
let shortcut_file_name = name + ".lnk"; let shortcut_file_name = name + ".lnk";
shortcut_file_name shortcut_file_name
} }
@@ -96,8 +100,8 @@ unsafe fn unsafe_update_app_manifest_lnks(next_app: &VelopackLocator, previous_a
let app_title = next_app.get_manifest_title(); let app_title = next_app.get_manifest_title();
let app_authors = next_app.get_manifest_authors(); let app_authors = next_app.get_manifest_authors();
let app_model_id: Option<String> = next_app.get_manifest_shortcut_amuid(); let app_model_id: Option<String> = next_app.get_manifest_shortcut_amuid();
let app_main_exe = next_app.get_main_exe_path_as_string(); let app_main_exe = next_app.get_main_exe_path();
let app_work_dir = next_app.get_current_bin_dir_as_string(); let app_work_dir = next_app.get_current_bin_dir();
info!("App Model ID: {:?}", app_model_id); info!("App Model ID: {:?}", app_model_id);
let mut current_shortcuts = unsafe_get_shortcuts_for_root_dir(root_path); let mut current_shortcuts = unsafe_get_shortcuts_for_root_dir(root_path);
@@ -113,7 +117,7 @@ unsafe fn unsafe_update_app_manifest_lnks(next_app: &VelopackLocator, previous_a
// set the target path to the main exe if it is missing or incorrect // set the target path to the main exe if it is missing or incorrect
if target_option.is_none() || !PathBuf::from(target_option.unwrap()).exists() { if target_option.is_none() || !PathBuf::from(target_option.unwrap()).exists() {
warn!("Shortcut {} target does not exist, updating to mainExe and setting workdir to current.", lnk.get_link_path()); warn!("Shortcut {:?} target does not exist, updating to mainExe and setting workdir to current.", lnk.get_link_path());
if let Err(e) = lnk.set_target_path(&app_main_exe) { if let Err(e) = lnk.set_target_path(&app_main_exe) {
warn!("Failed to update shortcut target: {}", e); warn!("Failed to update shortcut target: {}", e);
} }
@@ -213,7 +217,7 @@ unsafe fn unsafe_update_app_manifest_lnks(next_app: &VelopackLocator, previous_a
break; break;
} }
if let Err(e) = lnk.save_as(&path.to_string_lossy()) { if let Err(e) = lnk.save_as(&path) {
warn!("Failed to save shortcut: {}", e); warn!("Failed to save shortcut: {}", e);
break; break;
} }
@@ -280,33 +284,34 @@ unsafe fn unsafe_find_best_rename_candidates<P: AsRef<Path>>(
unsafe fn unsafe_get_shortcuts_for_root_dir<P: AsRef<Path>>(root_dir: P) -> Vec<(ShortcutLocationFlags, Lnk)> { unsafe fn unsafe_get_shortcuts_for_root_dir<P: AsRef<Path>>(root_dir: P) -> Vec<(ShortcutLocationFlags, Lnk)> {
let root_dir = root_dir.as_ref(); let root_dir = root_dir.as_ref();
info!("Searching for shortcuts containing root: '{}'", root_dir.to_string_lossy()); info!("Searching for shortcuts containing root: '{:?}'", root_dir);
let mut search_paths = Vec::new(); let mut search_paths = Vec::new();
match known::get_user_desktop() { match known::get_user_desktop() {
Ok(user_desktop) => search_paths.push((ShortcutLocationFlags::DESKTOP, format!("{}/*.lnk", user_desktop))), Ok(user_desktop) => search_paths.push((ShortcutLocationFlags::DESKTOP, user_desktop, "/*.lnk")),
Err(e) => error!("Failed to get user desktop directory, it will not be searched: {}", e), Err(e) => error!("Failed to get user desktop directory, it will not be searched: {}", e),
} }
match known::get_startup() { match known::get_startup() {
Ok(startup) => search_paths.push((ShortcutLocationFlags::STARTUP, format!("{}/*.lnk", startup))), Ok(startup) => search_paths.push((ShortcutLocationFlags::STARTUP, startup, "/*.lnk")),
Err(e) => error!("Failed to get startup directory, it will not be searched: {}", e), Err(e) => error!("Failed to get startup directory, it will not be searched: {}", e),
} }
match known::get_start_menu() { match known::get_start_menu() {
// this handles START_MENU and START_MENU_ROOT // this handles START_MENU and START_MENU_ROOT
Ok(start_menu) => search_paths.push((ShortcutLocationFlags::START_MENU, format!("{}/**/*.lnk", start_menu))), Ok(start_menu) => search_paths.push((ShortcutLocationFlags::START_MENU, start_menu, "/**/*.lnk")),
Err(e) => error!("Failed to get start menu directory, it will not be searched: {}", e), Err(e) => error!("Failed to get start menu directory, it will not be searched: {}", e),
} }
match known::get_user_pinned() { match known::get_user_pinned() {
Ok(user_pinned) => search_paths.push((ShortcutLocationFlags::USER_PINNED, format!("{}/**/*.lnk", user_pinned))), Ok(user_pinned) => search_paths.push((ShortcutLocationFlags::USER_PINNED, user_pinned, "/**/*.lnk")),
Err(e) => error!("Failed to get user pinned directory, it will not be searched: {}", e), Err(e) => error!("Failed to get user pinned directory, it will not be searched: {}", e),
} }
let mut paths: Vec<(ShortcutLocationFlags, Lnk)> = Vec::new(); let mut paths: Vec<(ShortcutLocationFlags, Lnk)> = Vec::new();
for (flag, search_glob) in search_paths { for (flag, folder_root, search_pattern) in search_paths {
let search_glob = folder_root.to_string_lossy().to_string() + search_pattern;
info!("Searching for shortcuts in: {:?} ({})", flag, search_glob); info!("Searching for shortcuts in: {:?} ({})", flag, search_glob);
if let Ok(glob_paths) = glob(&search_glob) { if let Ok(glob_paths) = glob(&search_glob) {
for path in glob_paths.filter_map(Result::ok) { for path in glob_paths.filter_map(Result::ok) {
@@ -315,16 +320,16 @@ unsafe fn unsafe_get_shortcuts_for_root_dir<P: AsRef<Path>>(root_dir: P) -> Vec<
Ok(properties) => { Ok(properties) => {
if let Ok(target) = properties.get_target_path() { if let Ok(target) = properties.get_target_path() {
if super::is_sub_path(&target, root_dir).unwrap_or(false) { if super::is_sub_path(&target, root_dir).unwrap_or(false) {
info!("Selected shortcut for update '{}' because target '{}' matched.", path.to_string_lossy(), target); info!("Selected shortcut for update '{:?}' because target '{:?}' matched.", path, target);
paths.push((flag, properties)); paths.push((flag, properties));
} }
} else if let Ok(work_dir) = properties.get_working_directory() { } else if let Ok(work_dir) = properties.get_working_directory() {
if super::is_sub_path(&work_dir, root_dir).unwrap_or(false) { if super::is_sub_path(&work_dir, root_dir).unwrap_or(false) {
info!("Selected shortcut for update '{}' because work_dir '{}' matched.", path.to_string_lossy(), work_dir); info!("Selected shortcut for update '{:?}' because work_dir '{:?}' matched.", path, work_dir);
paths.push((flag, properties)); paths.push((flag, properties));
} }
} else { } else {
warn!("Could not resolve target or work_dir for shortcut '{}'.", path.to_string_lossy()); warn!("Could not resolve target or work_dir for shortcut '{:?}'.", path);
} }
} }
Err(e) => { Err(e) => {
@@ -341,7 +346,7 @@ unsafe fn unsafe_remove_all_shortcuts_for_root_dir<P: AsRef<Path>>(root_dir: P)
let shortcuts = unsafe_get_shortcuts_for_root_dir(root_dir); let shortcuts = unsafe_get_shortcuts_for_root_dir(root_dir);
for (flag, properties) in shortcuts { for (flag, properties) in shortcuts {
let path = properties.get_link_path(); let path = properties.get_link_path();
info!("Removing shortcut '{}' ({:?}).", path, flag); info!("Removing shortcut '{:?}' ({:?}).", path, flag);
let remove_parent_if_empty = flag == ShortcutLocationFlags::START_MENU; let remove_parent_if_empty = flag == ShortcutLocationFlags::START_MENU;
if let Err(e) = unsafe_delete_lnk_file(&path, remove_parent_if_empty) { if let Err(e) = unsafe_delete_lnk_file(&path, remove_parent_if_empty) {
warn!("Failed to remove shortcut: {}", e); warn!("Failed to remove shortcut: {}", e);
@@ -383,8 +388,8 @@ unsafe fn unsafe_unpin_lnk_from_start<P: AsRef<Path>>(path: P) -> Result<()> {
return Ok(()); return Ok(());
} }
let path = string_to_u16(path.to_string_lossy()); let path = string_to_wide(path);
let item_result: IShellItem = SHCreateItemFromParsingName(PCWSTR(path.as_ptr()), None)?; let item_result: IShellItem = SHCreateItemFromParsingName(path.as_pcwstr(), None)?;
let pinned_list: IStartMenuPinnedList = create_instance(&StartMenuPin)?; let pinned_list: IStartMenuPinnedList = create_instance(&StartMenuPin)?;
pinned_list.RemoveFromList(&item_result)?; pinned_list.RemoveFromList(&item_result)?;
Ok(()) Ok(())
@@ -422,89 +427,86 @@ where
struct Lnk { struct Lnk {
me: IShellLinkW, me: IShellLinkW,
pf: IPersistFile, pf: IPersistFile,
my_path: String, my_path: PathBuf,
} }
#[allow(dead_code)] #[allow(dead_code)]
impl Lnk { impl Lnk {
pub unsafe fn get_link_path(&self) -> String { pub unsafe fn get_link_path(&self) -> PathBuf {
self.my_path.clone() self.my_path.clone()
} }
pub unsafe fn get_arguments(&self) -> Result<String> { pub unsafe fn get_arguments(&self) -> Result<String> {
let mut pszargs = [0u16; 1024]; let mut pszargs = [0u16; 1024];
self.me.GetArguments(&mut pszargs)?; self.me.GetArguments(&mut pszargs)?;
let args = u16_to_string(pszargs)?; let args = wide_to_string(pszargs)?;
Ok(args) Ok(args)
} }
pub unsafe fn get_description(&self) -> Result<String> { pub unsafe fn get_description(&self) -> Result<String> {
let mut pszdesc = [0u16; 1024]; let mut pszdesc = [0u16; 1024];
self.me.GetDescription(&mut pszdesc)?; self.me.GetDescription(&mut pszdesc)?;
let desc = u16_to_string(pszdesc)?; let desc = wide_to_string(pszdesc)?;
Ok(desc) Ok(desc)
} }
pub unsafe fn get_icon_location(&self) -> Result<(String, i32)> { pub unsafe fn get_icon_location(&self) -> Result<(PathBuf, i32)> {
let mut pszfile = [0u16; 1024]; let mut pszfile = [0u16; 1024];
let mut pindex = 0; let mut pindex = 0;
self.me.GetIconLocation(&mut pszfile, &mut pindex)?; self.me.GetIconLocation(&mut pszfile, &mut pindex)?;
let icon = u16_to_string(pszfile)?; let icon = wide_to_os_string(pszfile);
let icon = PathBuf::from(icon);
Ok((icon, pindex)) Ok((icon, pindex))
} }
pub unsafe fn get_target_path(&self) -> Result<String> { pub unsafe fn get_target_path(&self) -> Result<PathBuf> {
let mut pszfile = [0u16; 1024]; let mut pszfile = [0u16; 1024];
self.me.GetPath(&mut pszfile, std::ptr::null_mut(), 0)?; self.me.GetPath(&mut pszfile, std::ptr::null_mut(), 0)?;
let target = u16_to_string(pszfile)?; let target = wide_to_os_string(pszfile);
let target = PathBuf::from(target);
Ok(target) Ok(target)
} }
pub unsafe fn get_working_directory(&self) -> Result<String> { pub unsafe fn get_working_directory(&self) -> Result<PathBuf> {
let mut pszdir = [0u16; 1024]; let mut pszdir = [0u16; 1024];
self.me.GetWorkingDirectory(&mut pszdir)?; self.me.GetWorkingDirectory(&mut pszdir)?;
let work_dir = u16_to_string(pszdir)?; let work_dir = wide_to_os_string(pszdir);
let work_dir = PathBuf::from(work_dir);
Ok(work_dir) Ok(work_dir)
} }
pub unsafe fn set_arguments(&mut self, path: &str) -> Result<()> { pub unsafe fn set_arguments<P: AsRef<str>>(&mut self, path: P) -> Result<()> {
let args = string_to_u16(path); let args = string_to_wide(path.as_ref());
let args = PCWSTR(args.as_ptr()); Ok(self.me.SetArguments(args.as_pcwstr())?)
Ok(self.me.SetArguments(args)?)
} }
pub unsafe fn set_description(&mut self, path: &str) -> Result<()> { pub unsafe fn set_description<P: AsRef<str>>(&mut self, path: P) -> Result<()> {
let desc = string_to_u16(path); let desc = string_to_wide(path.as_ref());
let desc = PCWSTR(desc.as_ptr()); Ok(self.me.SetDescription(desc.as_pcwstr())?)
Ok(self.me.SetDescription(desc)?)
} }
pub unsafe fn set_icon_location(&mut self, path: &str, index: i32) -> Result<()> { pub unsafe fn set_icon_location<P: AsRef<Path>>(&mut self, path: P, index: i32) -> Result<()> {
let icon = string_to_u16(path); let icon = string_to_wide(path.as_ref());
let icon = PCWSTR(icon.as_ptr()); Ok(self.me.SetIconLocation(icon.as_pcwstr(), index)?)
Ok(self.me.SetIconLocation(icon, index)?)
} }
pub unsafe fn set_target_path(&mut self, path: &str) -> Result<()> { pub unsafe fn set_target_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let target = string_to_u16(path); let target = string_to_wide(path.as_ref());
let target = PCWSTR(target.as_ptr()); Ok(self.me.SetPath(target.as_pcwstr())?)
Ok(self.me.SetPath(target)?)
} }
pub unsafe fn set_working_directory(&mut self, path: &str) -> Result<()> { pub unsafe fn set_working_directory<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let work_dir = string_to_u16(path); let work_dir = string_to_wide(path.as_ref());
let work_dir = PCWSTR(work_dir.as_ptr()); Ok(self.me.SetWorkingDirectory(work_dir.as_pcwstr())?)
Ok(self.me.SetWorkingDirectory(work_dir)?)
} }
pub unsafe fn set_aumid(&mut self, app_model_id: Option<&str>) -> Result<()> { pub unsafe fn set_aumid<P: AsRef<str>>(&mut self, app_model_id: Option<P>) -> Result<()> {
// Set app user model ID property // Set app user model ID property
// Docs: https://docs.microsoft.com/windows/win32/properties/props-system-appusermodel-id // Docs: https://docs.microsoft.com/windows/win32/properties/props-system-appusermodel-id
let store: IPropertyStore = self.me.cast()?; let store: IPropertyStore = self.me.cast()?;
if let Some(app_model_id) = app_model_id { if let Some(app_model_id) = app_model_id {
let id = string_to_u16(app_model_id); let id = string_to_wide(app_model_id.as_ref());
let id = PCWSTR(id.as_ptr()); let variant = InitPropVariantFromStringVector(Some(&[id.as_pcwstr()]))?;
let variant = InitPropVariantFromStringVector(Some(&[id]))?;
store.SetValue(&PKEY_AppUserModel_ID, &variant)?; store.SetValue(&PKEY_AppUserModel_ID, &variant)?;
} else { } else {
let prop_variant = PROPVARIANT::default(); // defaults to VT_EMPTY let prop_variant = PROPVARIANT::default(); // defaults to VT_EMPTY
@@ -515,26 +517,26 @@ impl Lnk {
} }
pub unsafe fn save(&mut self) -> Result<()> { pub unsafe fn save(&mut self) -> Result<()> {
if self.my_path.as_os_str().is_empty() {
return Err(anyhow!("Cannot save shortcut without a path."));
}
Ok(self.pf.Save(None, true)?) Ok(self.pf.Save(None, true)?)
} }
pub unsafe fn save_as(&mut self, path: &str) -> Result<()> { pub unsafe fn save_as<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let output = string_to_u16(path); let output = string_to_wide(path.as_ref());
let output = PCWSTR(output.as_ptr()); self.my_path = path.as_ref().to_path_buf();
self.my_path = path.to_string(); Ok(self.pf.Save(output.as_pcwstr(), true)?)
Ok(self.pf.Save(output, true)?)
} }
pub unsafe fn open_write<P: AsRef<Path>>(link_path: P) -> Result<Lnk> { pub unsafe fn open_write<P: AsRef<Path>>(link_path: P) -> Result<Lnk> {
let link_path = link_path.as_ref().to_string_lossy().to_string(); let link_path = link_path.as_ref();
let link: IShellLinkW = create_instance(&ShellLink)?; let link: IShellLinkW = create_instance(&ShellLink)?;
let persist: IPersistFile = link.cast()?; let persist: IPersistFile = link.cast()?;
debug!("Loading link: {}", link_path); debug!("Loading link: {:?}", link_path);
let link_pcwstr = string_to_u16(&link_path); let link_pcwstr = string_to_wide(link_path);
let link_pcwstr = PCWSTR(link_pcwstr.as_ptr()); persist.Load(link_pcwstr.as_pcwstr(), STGM_READWRITE)?;
persist.Load(link_pcwstr, STGM_READWRITE)?;
// we don't really want to "resolve" the shortcut in the middle of an update operation // we don't really want to "resolve" the shortcut in the middle of an update operation
// this can cause Windows to move the target path of a shortcut to one of our temp dirs etc // this can cause Windows to move the target path of a shortcut to one of our temp dirs etc
@@ -548,19 +550,19 @@ impl Lnk {
// warn!("Failed to resolve link {} ({:?})", link_path, e); // warn!("Failed to resolve link {} ({:?})", link_path, e);
// } // }
Ok(Lnk { me: link, pf: persist, my_path: link_path }) Ok(Lnk { me: link, pf: persist, my_path: link_path.to_path_buf() })
} }
pub unsafe fn create_new() -> Result<Lnk> { pub unsafe fn create_new() -> Result<Lnk> {
let link: IShellLinkW = create_instance(&ShellLink)?; let link: IShellLinkW = create_instance(&ShellLink)?;
let persist: IPersistFile = link.cast()?; let persist: IPersistFile = link.cast()?;
Ok(Lnk { me: link, pf: persist, my_path: String::new() }) Ok(Lnk { me: link, pf: persist, my_path: PathBuf::new() })
} }
} }
impl std::fmt::Debug for Lnk { impl std::fmt::Debug for Lnk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Lnk({})", self.my_path) write!(f, "Lnk({:?})", self.my_path)
} }
} }
@@ -626,7 +628,7 @@ fn test_can_resolve_existing_shortcut() {
unsafe { unsafe {
unsafe_run_delegate_in_com_context(move || { unsafe_run_delegate_in_com_context(move || {
let l = Lnk::open_write(link_path).unwrap(); let l = Lnk::open_write(link_path).unwrap();
let target = l.get_target_path().unwrap(); let target = l.get_target_path().unwrap().to_string_lossy().to_string();
assert_eq!(target, "C:\\Users\\Caelan\\AppData\\Local\\Discord\\Update.exe"); assert_eq!(target, "C:\\Users\\Caelan\\AppData\\Local\\Discord\\Update.exe");
Ok(()) Ok(())
}) })
@@ -696,8 +698,8 @@ fn test_shortcut_full_integration() {
assert_eq!(shortcuts[2].0, ShortcutLocationFlags::START_MENU); assert_eq!(shortcuts[2].0, ShortcutLocationFlags::START_MENU);
assert_eq!(PathBuf::from(&shortcuts[2].1.my_path), link2); assert_eq!(PathBuf::from(&shortcuts[2].1.my_path), link2);
assert_eq!(shortcuts[0].1.get_target_path().unwrap(), target); assert_eq!(shortcuts[0].1.get_target_path().unwrap().to_string_lossy().to_string(), target);
assert_eq!(shortcuts[0].1.get_working_directory().unwrap(), work); assert_eq!(shortcuts[0].1.get_working_directory().unwrap().to_string_lossy().to_string(), work);
unsafe_remove_all_shortcuts_for_root_dir(root); unsafe_remove_all_shortcuts_for_root_dir(root);
assert!(!link1.exists()); assert!(!link1.exists());

View File

@@ -1,8 +1,8 @@
use super::strings::string_to_wide;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, ImageFormat, ImageReader}; use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, ImageFormat, ImageReader};
use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::mpsc::{self, Receiver, Sender};
use std::{io::Cursor, thread}; use std::{io::Cursor, thread};
use velopack::wide_strings::string_to_wide;
use windows::{ use windows::{
core::HRESULT, core::HRESULT,
Win32::{ Win32::{

View File

@@ -1,149 +0,0 @@
use anyhow::Result;
use windows::core::{PCWSTR, PWSTR};
pub struct WideString {
inner: Vec<u16>,
}
impl WideString {
pub fn as_ptr(&self) -> *const u16 {
self.inner.as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut u16 {
self.inner.as_mut_ptr()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn as_pcwstr(&self) -> PCWSTR {
PCWSTR(self.as_ptr())
}
pub fn as_pwstr(&mut self) -> PWSTR {
PWSTR(self.as_mut_ptr())
}
}
impl Into<Vec<u16>> for WideString {
fn into(self) -> Vec<u16> {
self.inner
}
}
impl Into<PCWSTR> for WideString {
fn into(self) -> PCWSTR {
self.as_pcwstr()
}
}
impl AsRef<[u16]> for WideString {
fn as_ref(&self) -> &[u16] {
&self.inner
}
}
impl AsMut<[u16]> for WideString {
fn as_mut(&mut self) -> &mut [u16] {
&mut self.inner
}
}
pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
let input = input.as_ref();
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
}
pub fn string_to_wide<P: AsRef<str>>(input: P) -> WideString {
WideString { inner: string_to_u16(input) }
}
pub trait WideStringRef {
fn to_wide_slice(&self) -> &[u16];
}
impl WideStringRef for PWSTR {
fn to_wide_slice(&self) -> &[u16] {
unsafe { self.as_wide() }
}
}
impl WideStringRef for PCWSTR {
fn to_wide_slice(&self) -> &[u16] {
unsafe { self.as_wide() }
}
}
impl WideStringRef for Vec<u16> {
fn to_wide_slice(&self) -> &[u16] {
self.as_ref()
}
}
impl WideStringRef for &Vec<u16> {
fn to_wide_slice(&self) -> &[u16] {
self.as_ref()
}
}
// impl WideString for [u16] {
// fn to_wide_slice(&self) -> &[u16] {
// self.as_ref()
// }
// }
impl<const N: usize> WideStringRef for [u16; N] {
fn to_wide_slice(&self) -> &[u16] {
self.as_ref()
}
}
pub fn u16_to_string_lossy<T: WideStringRef>(input: T) -> String {
let slice = input.to_wide_slice();
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
let trimmed_slice = &slice[..null_pos];
String::from_utf16_lossy(trimmed_slice)
}
pub fn u16_to_string<T: WideStringRef>(input: T) -> Result<String> {
let slice = input.to_wide_slice();
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
let trimmed_slice = &slice[..null_pos];
Ok(String::from_utf16(trimmed_slice)?)
}
// pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
// unsafe {
// let hstring = input.to_hstring();
// let string = hstring.to_string_lossy();
// Ok(string.trim_end_matches('\0').to_string())
// }
// }
// pub fn pcwstr_to_string(input: PCWSTR) -> Result<String> {
// unsafe {
// let hstring = input.to_hstring();
// let string = hstring.to_string_lossy();
// Ok(string.trim_end_matches('\0').to_string())
// }
// }
// pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
// let input = input.as_ref();
// let hstring = HSTRING::from_wide(input);
// let string = hstring.to_string_lossy();
// Ok(string.trim_end_matches('\0').to_string())
// }
// pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
// let input = input.as_ref();
// // Find position of first null byte (0)
// let null_pos = input.iter().position(|&x| x == 0).unwrap_or(input.len());
// // Take only up to the first null byte
// let trimmed_input = &input[..null_pos];
// let hstring = HSTRING::from_wide(trimmed_input);
// Ok(hstring.to_string_lossy())
// }

View File

@@ -1,16 +1,17 @@
use crate::{ use crate::shared::{self, runtime_arch::RuntimeArch};
shared::{self, runtime_arch::RuntimeArch},
windows::strings::{string_to_u16, u16_to_string},
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use normpath::PathExt; use normpath::PathExt;
use std::{ use std::{
ffi::{OsStr, OsString},
path::{Path, PathBuf}, path::{Path, PathBuf},
time::Duration, time::Duration,
}; };
use velopack::{locator::VelopackLocator, process, process::WaitResult}; use velopack::{
locator::VelopackLocator,
process::{self, WaitResult},
wide_strings::{string_to_wide, wide_to_os_string},
};
use windows::{ use windows::{
core::PCWSTR,
Win32::Storage::FileSystem::GetLongPathNameW, Win32::Storage::FileSystem::GetLongPathNameW,
Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS}, Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS},
}; };
@@ -21,17 +22,11 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
let current_path = locator.get_current_bin_dir(); let current_path = locator.get_current_bin_dir();
let main_exe_path = locator.get_main_exe_path(); let main_exe_path = locator.get_main_exe_path();
let ver_string = locator.get_manifest_version_full_string(); let ver_string = locator.get_manifest_version_full_string();
let args = vec![hook_name, &ver_string]; let args: Vec<OsString> = vec![hook_name.into(), ver_string.into()];
let mut success = false; let mut success = false;
info!("Running {} hook...", hook_name); info!("Running {} hook...", hook_name);
let cmd = process::run_process( let cmd = process::run_process(main_exe_path, args, Some(current_path), false, None);
main_exe_path,
args.iter().map(|f| f.to_string()).collect(),
Some(current_path.to_string_lossy().to_string()),
false,
None,
);
if let Err(e) = cmd { if let Err(e) = cmd {
warn!("Failed to start hook {}: {}", hook_name, e); warn!("Failed to start hook {}: {}", hook_name, e);
@@ -66,22 +61,22 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
success success
} }
pub fn expand_environment_strings<P: AsRef<str>>(input: P) -> Result<String> { pub fn expand_environment_strings<P: AsRef<OsStr>>(input: P) -> Result<OsString> {
use windows::Win32::System::Environment::ExpandEnvironmentStringsW; use windows::Win32::System::Environment::ExpandEnvironmentStringsW;
let encoded_u16 = super::strings::string_to_u16(input); let input = input.as_ref();
let encoded = PCWSTR(encoded_u16.as_ptr()); let encoded = string_to_wide(input);
let mut buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, None) }; let mut buffer_size = unsafe { ExpandEnvironmentStringsW(encoded.as_pcwstr(), None) };
if buffer_size == 0 { if buffer_size == 0 {
return Err(anyhow!(windows::core::Error::from_win32())); return Err(anyhow!(windows::core::Error::from_win32()));
} }
let mut buffer: Vec<u16> = vec![0; buffer_size as usize]; let mut buffer: Vec<u16> = vec![0; buffer_size as usize];
buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, Some(&mut buffer)) }; buffer_size = unsafe { ExpandEnvironmentStringsW(encoded.as_pcwstr(), Some(&mut buffer)) };
if buffer_size == 0 { if buffer_size == 0 {
return Err(anyhow!(windows::core::Error::from_win32())); return Err(anyhow!(windows::core::Error::from_win32()));
} }
super::strings::u16_to_string(buffer) Ok(wide_to_os_string(buffer))
} }
#[test] #[test]
@@ -91,24 +86,23 @@ fn test_expand_environment_strings() {
assert!(expand_environment_strings("%windir%\\system32\\").unwrap().eq_ignore_ascii_case("C:\\Windows\\system32\\")); assert!(expand_environment_strings("%windir%\\system32\\").unwrap().eq_ignore_ascii_case("C:\\Windows\\system32\\"));
} }
pub fn get_long_path<P: AsRef<str>>(str: P) -> Result<String> { pub fn get_long_path<P: AsRef<OsStr>>(str: P) -> Result<OsString> {
let str = str.as_ref().to_string(); let str = str.as_ref();
let str = string_to_u16(str); let str = string_to_wide(str);
let str = PCWSTR(str.as_ptr());
// SAFETY: str is a valid wide string, this call will return required size of buffer // SAFETY: str is a valid wide string, this call will return required size of buffer
let len = unsafe { GetLongPathNameW(str, None) }; let len = unsafe { GetLongPathNameW(str.as_pcwstr(), None) };
if len == 0 { if len == 0 {
return Err(anyhow!(windows::core::Error::from_win32())); return Err(anyhow!(windows::core::Error::from_win32()));
} }
let mut vec = vec![0u16; len as usize]; let mut vec = vec![0u16; len as usize];
let len = unsafe { GetLongPathNameW(str, Some(vec.as_mut_slice())) }; let len = unsafe { GetLongPathNameW(str.as_pcwstr(), Some(vec.as_mut_slice())) };
if len == 0 { if len == 0 {
return Err(anyhow!(windows::core::Error::from_win32())); return Err(anyhow!(windows::core::Error::from_win32()));
} }
let result = u16_to_string(vec)?; Ok(wide_to_os_string(vec))
Ok(result)
} }
pub fn is_directory_writable<P1: AsRef<Path>>(path: P1) -> bool { pub fn is_directory_writable<P1: AsRef<Path>>(path: P1) -> bool {
@@ -165,21 +159,12 @@ pub fn is_sub_path<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, parent: P2) -> Re
let path = path.normalize().or_else(|_| path.normalize_virtually())?; let path = path.normalize().or_else(|_| path.normalize_virtually())?;
let parent = parent.normalize().or_else(|_| parent.normalize_virtually())?; let parent = parent.normalize().or_else(|_| parent.normalize_virtually())?;
let mut path = path.as_path().to_string_lossy().to_string();
let mut parent = parent.as_path().to_string_lossy().to_string();
// calls GetLongPathNameW // calls GetLongPathNameW
match get_long_path(&path) { let mut path = get_long_path(&path).unwrap_or(path.into());
Ok(p) => path = p, let mut parent = get_long_path(&parent).unwrap_or(parent.into());
Err(e) => warn!("Failed to get long path for '{}': {}", path, e),
}
match get_long_path(&parent) {
Ok(p) => parent = p,
Err(e) => warn!("Failed to get long path for '{}': {}", parent, e),
}
path = path.to_lowercase(); path = path.to_ascii_lowercase();
parent = parent.to_lowercase(); parent = parent.to_ascii_lowercase();
let path = PathBuf::from(path); let path = PathBuf::from(path);
let parent = PathBuf::from(parent); let parent = PathBuf::from(parent);
@@ -309,7 +294,11 @@ pub fn is_os_version_or_greater(version: &str) -> Result<bool> {
} }
if major == 8 { if major == 8 {
return Ok(if minor >= 1 { is_windows_8_or_greater() } else { is_windows_8_1_or_greater() }); return Ok(if minor >= 1 {
is_windows_8_or_greater()
} else {
is_windows_8_1_or_greater()
});
} }
// https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions // https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions

View File

@@ -4,6 +4,7 @@ use libc::{c_void, size_t};
use std::{ use std::{
collections::HashMap, collections::HashMap,
ffi::CString, ffi::CString,
path::Path,
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
mpsc::Sender, mpsc::Sender,
@@ -43,7 +44,7 @@ impl UpdateSource for CCallbackUpdateSource {
if let Some(cb_get_release_feed) = self.cb_get_release_feed { if let Some(cb_get_release_feed) = self.cb_get_release_feed {
let json_cstr_ptr = (cb_get_release_feed)(self.p_user_data, releases_name_cstr.as_ptr()); let json_cstr_ptr = (cb_get_release_feed)(self.p_user_data, releases_name_cstr.as_ptr());
let json = c_to_String(json_cstr_ptr).map_err(|_| { let json = c_to_String(json_cstr_ptr).map_err(|_| {
Error::Generic("User vpkc_release_feed_delegate_t returned a null pointer instead of an asset feed".to_string()) Error::Other("User vpkc_release_feed_delegate_t returned a null pointer instead of an asset feed".to_string())
})?; })?;
if let Some(cb_free_release_feed) = self.cb_free_release_feed { if let Some(cb_free_release_feed) = self.cb_free_release_feed {
(cb_free_release_feed)(self.p_user_data, json_cstr_ptr); // Free the C string returned by the callback (cb_free_release_feed)(self.p_user_data, json_cstr_ptr); // Free the C string returned by the callback
@@ -53,12 +54,13 @@ impl UpdateSource for CCallbackUpdateSource {
let feed: VelopackAssetFeed = serde_json::from_str(&json)?; let feed: VelopackAssetFeed = serde_json::from_str(&json)?;
Ok(feed) Ok(feed)
} else { } else {
Err(Error::Generic("User vpkc_release_feed_delegate_t is null".to_string())) Err(Error::Other("User vpkc_release_feed_delegate_t is null".to_string()))
} }
} }
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> { fn download_release_entry(&self, asset: &VelopackAsset, local_file: &Path, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
if let Some(cb_download_release_entry) = self.cb_download_release_entry { if let Some(cb_download_release_entry) = self.cb_download_release_entry {
let local_file = local_file.to_string_lossy().to_string();
let local_file_cstr = CString::new(local_file).unwrap(); let local_file_cstr = CString::new(local_file).unwrap();
let asset_ptr = unsafe { allocate_VelopackAsset(asset) }; let asset_ptr = unsafe { allocate_VelopackAsset(asset) };
@@ -76,12 +78,12 @@ impl UpdateSource for CCallbackUpdateSource {
} }
if !success { if !success {
return Err(Error::Generic("User vpkc_download_asset_delegate_t returned false to indicate download failed".to_owned())); return Err(Error::Other("User vpkc_download_asset_delegate_t returned false to indicate download failed".to_owned()));
} }
Ok(()) Ok(())
} else { } else {
Err(Error::Generic("User vpkc_download_asset_delegate_t is null".to_string())) Err(Error::Other("User vpkc_download_asset_delegate_t is null".to_string()))
} }
} }

View File

@@ -1,8 +1,8 @@
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use libc::{c_char, c_void, size_t}; use libc::{c_char, c_void, size_t};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::path::PathBuf;
use std::mem::size_of; use std::mem::size_of;
use std::path::PathBuf;
use velopack::{locator::VelopackLocatorConfig, UpdateInfo, UpdateOptions, VelopackAsset}; use velopack::{locator::VelopackLocatorConfig, UpdateInfo, UpdateOptions, VelopackAsset};
/// The result of a call to check for updates. This can indicate that an update is available, or that an error occurred. /// The result of a call to check for updates. This can indicate that an update is available, or that an error occurred.

View File

@@ -1,5 +1,6 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use std::io::Cursor;
use std::{ use std::{
cell::RefCell, cell::RefCell,
fs::{self, File}, fs::{self, File},
@@ -7,15 +8,14 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
}; };
use std::io::Cursor;
use regex::Regex; use regex::Regex;
use semver::Version; use semver::Version;
use xml::EventReader;
use xml::reader::XmlEvent; use xml::reader::XmlEvent;
use xml::EventReader;
use zip::ZipArchive; use zip::ZipArchive;
use crate::{Error, misc}; use crate::{misc, Error};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@@ -38,14 +38,14 @@ pub struct BundleZip<'a> {
pub fn load_bundle_from_file<'a, P: AsRef<Path>>(file_name: P) -> Result<BundleZip<'a>, Error> { pub fn load_bundle_from_file<'a, P: AsRef<Path>>(file_name: P) -> Result<BundleZip<'a>, Error> {
let file_name = file_name.as_ref(); let file_name = file_name.as_ref();
debug!("Loading bundle from file '{}'...", file_name.to_string_lossy()); debug!("Loading bundle from file '{:?}'...", file_name);
let file = misc::retry_io(|| File::open(&file_name))?; let file = misc::retry_io(|| File::open(&file_name))?;
let cursor: Box<dyn ReadSeek> = Box::new(file); let cursor: Box<dyn ReadSeek> = Box::new(file);
let zip = ZipArchive::new(cursor)?; let zip = ZipArchive::new(cursor)?;
Ok(BundleZip { Ok(BundleZip {
zip: Rc::new(RefCell::new(zip)), zip: Rc::new(RefCell::new(zip)),
zip_from_file: true, zip_from_file: true,
file_path: Some(file_name.to_owned()), file_path: Some(file_name.to_owned()),
zip_range: None, zip_range: None,
manifest: None, manifest: None,
}) })
@@ -55,13 +55,7 @@ pub fn load_bundle_from_memory(zip_range: &[u8]) -> Result<BundleZip, Error> {
info!("Loading bundle from embedded zip..."); info!("Loading bundle from embedded zip...");
let cursor: Box<dyn ReadSeek> = Box::new(Cursor::new(zip_range)); let cursor: Box<dyn ReadSeek> = Box::new(Cursor::new(zip_range));
let zip = ZipArchive::new(cursor)?; let zip = ZipArchive::new(cursor)?;
Ok(BundleZip { Ok(BundleZip { zip: Rc::new(RefCell::new(zip)), zip_from_file: false, zip_range: Some(zip_range), file_path: None, manifest: None })
zip: Rc::new(RefCell::new(zip)),
zip_from_file: false,
zip_range: Some(zip_range),
file_path: None,
manifest: None,
})
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -75,7 +69,7 @@ impl BundleZip<'_> {
} }
Ok(()) Ok(())
} }
pub fn calculate_size(&self) -> (u64, u64) { pub fn calculate_size(&self) -> (u64, u64) {
let mut total_uncompressed_size = 0u64; let mut total_uncompressed_size = 0u64;
let mut total_compressed_size = 0u64; let mut total_compressed_size = 0u64;
@@ -140,7 +134,7 @@ impl BundleZip<'_> {
pub fn extract_zip_idx_to_path<T: AsRef<Path>>(&self, index: usize, path: T) -> Result<(), Error> { pub fn extract_zip_idx_to_path<T: AsRef<Path>>(&self, index: usize, path: T) -> Result<(), Error> {
let path = path.as_ref(); let path = path.as_ref();
debug!("Extracting zip file to path: {}", path.to_string_lossy()); debug!("Extracting zip file to path: {:?}", path);
let p = PathBuf::from(path); let p = PathBuf::from(path);
let parent = p.parent().unwrap(); let parent = p.parent().unwrap();
@@ -172,7 +166,7 @@ impl BundleZip<'_> {
{ {
let idx = self.find_zip_file(predicate); let idx = self.find_zip_file(predicate);
if idx.is_none() { if idx.is_none() {
return Err(Error::FileNotFound("(zip bundle predicate)".to_owned())); return Err(Error::InvalidPackage("(zip bundle predicate not found)".to_owned()));
} }
let idx = idx.unwrap(); let idx = idx.unwrap();
self.extract_zip_idx_to_path(idx, path)?; self.extract_zip_idx_to_path(idx, path)?;
@@ -183,15 +177,15 @@ impl BundleZip<'_> {
if let Some(manifest) = &self.manifest { if let Some(manifest) = &self.manifest {
return Ok(manifest.clone()); return Ok(manifest.clone());
} }
let nuspec_idx = self.find_zip_file(|name| name.ends_with(".nuspec")) let nuspec_idx =
.ok_or(Error::MissingNuspec)?; self.find_zip_file(|name| name.ends_with(".nuspec")).ok_or(Error::InvalidPackage("No .nuspec manifest found".into()))?;
let mut contents = String::new(); let mut contents = String::new();
let mut archive = self.zip.borrow_mut(); let mut archive = self.zip.borrow_mut();
archive.by_index(nuspec_idx)?.read_to_string(&mut contents)?; archive.by_index(nuspec_idx)?.read_to_string(&mut contents)?;
let app = read_manifest_from_string(&contents)?; let app = read_manifest_from_string(&contents)?;
self.manifest = Some(app.clone()); self.manifest = Some(app.clone());
Ok(app) Ok(app)
} }
@@ -218,19 +212,23 @@ impl BundleZip<'_> {
{ {
let absolute_path = link_path.parent().unwrap().join(&target_path); let absolute_path = link_path.parent().unwrap().join(&target_path);
trace!( trace!(
"Creating symlink '{}' -> '{}', target isfile={}, isdir={}, relative={}", "Creating symlink '{:?}' -> '{:?}', target isfile={}, isdir={}, relative={:?}",
link_path.to_string_lossy(), link_path,
absolute_path.to_string_lossy(), absolute_path,
absolute_path.is_file(), absolute_path.is_file(),
absolute_path.is_dir(), absolute_path.is_dir(),
target_path.to_string_lossy() target_path
); );
if absolute_path.is_file() { if absolute_path.is_file() {
std::os::windows::fs::symlink_file(target_path, link_path)?; std::os::windows::fs::symlink_file(target_path, link_path)?;
} else if absolute_path.is_dir() { } else if absolute_path.is_dir() {
std::os::windows::fs::symlink_dir(target_path, link_path)?; std::os::windows::fs::symlink_dir(target_path, link_path)?;
} else { } else {
return Err(Error::Generic("Could not create symlink: target is not a file or directory.".to_owned())); return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Target path '{:?}' does not exist or is of an invalid type", absolute_path),
)
.into());
} }
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
@@ -246,7 +244,7 @@ impl BundleZip<'_> {
let files = self.get_file_names()?; let files = self.get_file_names()?;
let num_files = files.len(); let num_files = files.len();
info!("Extracting {} app files to '{}'...", num_files, current_path.to_string_lossy()); info!("Extracting {} app files to '{:?}'...", num_files, current_path);
let re = Regex::new(r"lib[\\\/][^\\\/]*[\\\/]").unwrap(); let re = Regex::new(r"lib[\\\/][^\\\/]*[\\\/]").unwrap();
let stub_regex = Regex::new("_ExecutionStub.exe$").unwrap(); let stub_regex = Regex::new("_ExecutionStub.exe$").unwrap();
let symlink_regex = Regex::new(".__symlink$").unwrap(); let symlink_regex = Regex::new(".__symlink$").unwrap();
@@ -259,7 +257,7 @@ impl BundleZip<'_> {
let nuspec_path = current_path.join("sq.version"); let nuspec_path = current_path.join("sq.version");
let _ = self let _ = self
.extract_zip_predicate_to_path(|name| name.ends_with(".nuspec"), nuspec_path) .extract_zip_predicate_to_path(|name| name.ends_with(".nuspec"), nuspec_path)
.map_err(|_| Error::MissingNuspec)?; .map_err(|_| Error::InvalidPackage("No .nuspec manifest found".into()))?;
} }
// we extract the symlinks after, because the target must exist. // we extract the symlinks after, because the target must exist.
@@ -294,16 +292,16 @@ impl BundleZip<'_> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let file_path_on_disk = file_path_on_disk.as_path(); let file_path_on_disk = file_path_on_disk.as_path();
debug!(" {} Extracting '{}' to '{}'", i, key, file_path_on_disk.to_string_lossy()); debug!(" {} Extracting '{}' to '{:?}'", i, key, file_path_on_disk);
self.extract_zip_idx_to_path(i, &file_path_on_disk)?; self.extract_zip_idx_to_path(i, &file_path_on_disk)?;
// on macos, we need to chmod +x the executable files // on macos, we need to chmod +x the executable files
// for now, we just chmod 755 every file we extract. this is not great, ideally we // for now, we just chmod 755 every file we extract. this is not great, ideally we
// will preserve the mode as it was when packaging. This will come in a future release. // will preserve the mode as it was when packaging. This will come in a future release.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if let Err(e) = std::fs::set_permissions(&file_path_on_disk, std::fs::Permissions::from_mode(0o755)) { if let Err(e) = std::fs::set_permissions(&file_path_on_disk, std::fs::Permissions::from_mode(0o755)) {
warn!("Failed to set mode 755 on '{}': {}", file_path_on_disk.to_string_lossy(), e); warn!("Failed to set mode 755 on '{}': {:?}", file_path_on_disk, e);
} }
} }
@@ -316,7 +314,7 @@ impl BundleZip<'_> {
let mut file = archive.by_index(i)?; let mut file = archive.by_index(i)?;
let mut contents = String::new(); let mut contents = String::new();
file.read_to_string(&mut contents)?; file.read_to_string(&mut contents)?;
info!(" {} Creating symlink '{}' -> '{}'", i, link_path.to_string_lossy(), contents); info!(" {} Creating symlink '{:?}' -> '{}'", i, link_path, contents);
let contents = contents.trim_end_matches('/'); let contents = contents.trim_end_matches('/');
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@@ -418,16 +416,16 @@ pub fn read_manifest_from_string(xml: &str) -> Result<Manifest, Error> {
} }
if obj.id.is_empty() { if obj.id.is_empty() {
return Err(Error::MissingNuspecProperty("id".to_owned())); return Err(Error::InvalidPackage("Missing required manifest property: id".into()));
} }
if obj.version == Version::new(0, 0, 0) { if obj.version == Version::new(0, 0, 0) {
return Err(Error::MissingNuspecProperty("version".to_owned())); return Err(Error::InvalidPackage("Missing required manifest property: version".into()));
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if obj.main_exe.is_empty() { if obj.main_exe.is_empty() {
return Err(Error::MissingNuspecProperty("mainExe".to_owned())); return Err(Error::InvalidPackage("Missing required manifest property: mainExe".into()));
} }
if obj.title.is_empty() { if obj.title.is_empty() {
@@ -485,7 +483,11 @@ fn parse_package_file_name<T: AsRef<str>>(name: T) -> Option<EntryNameInfo> {
let mut entry = EntryNameInfo::default(); let mut entry = EntryNameInfo::default();
entry.is_delta = delta; entry.is_delta = delta;
let name_and_ver = if full { ENTRY_SUFFIX_FULL.replace(name, "") } else { ENTRY_SUFFIX_DELTA.replace(name, "") }; let name_and_ver = if full {
ENTRY_SUFFIX_FULL.replace(name, "")
} else {
ENTRY_SUFFIX_DELTA.replace(name, "")
};
let ver_idx = ENTRY_VERSION_START.find(&name_and_ver); let ver_idx = ENTRY_VERSION_START.find(&name_and_ver);
if ver_idx.is_none() { if ver_idx.is_none() {
return None; return None;
@@ -528,4 +530,4 @@ fn test_parse_package_file_name() {
assert!(parse_package_file_name("MyCoolApp-1.2.3-beta1-win7-x64-full.zip").is_none()); assert!(parse_package_file_name("MyCoolApp-1.2.3-beta1-win7-x64-full.zip").is_none());
assert!(parse_package_file_name("MyCoolApp-1.2.3.nupkg").is_none()); assert!(parse_package_file_name("MyCoolApp-1.2.3.nupkg").is_none());
assert!(parse_package_file_name("MyCoolApp-1.2-full.nupkg").is_none()); assert!(parse_package_file_name("MyCoolApp-1.2-full.nupkg").is_none());
} }

View File

@@ -1,10 +1,11 @@
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::Path;
use crate::{misc, Error}; use crate::{misc, Error};
/// Downloads a file from a URL and writes it to a file while reporting progress from 0-100. /// Downloads a file from a URL and writes it to a file while reporting progress from 0-100.
pub fn download_url_to_file<A>(url: &str, file_path: &str, mut progress: A) -> Result<(), Error> pub fn download_url_to_file<A>(url: &str, file_path: &Path, mut progress: A) -> Result<(), Error>
where where
A: FnMut(i16), A: FnMut(i16),
{ {
@@ -74,7 +75,7 @@ fn test_download_file_reports_progress() {
let tmpfile = tempfile::NamedTempFile::new().unwrap(); let tmpfile = tempfile::NamedTempFile::new().unwrap();
let tmppath = tmpfile.path(); let tmppath = tmpfile.path();
download_url_to_file(test_file, tmppath.to_str().unwrap(), |p| { download_url_to_file(test_file, Path::new(tmppath), |p| {
assert!(p >= last_prog); assert!(p >= last_prog);
prog_count += 1; prog_count += 1;
last_prog = p; last_prog = p;
@@ -117,11 +118,7 @@ fn test_interrupted_download() {
}); });
let tmpfile = tempfile::NamedTempFile::new().unwrap(); let tmpfile = tempfile::NamedTempFile::new().unwrap();
let result = download_url_to_file( let result = download_url_to_file(&format!("http://{}", addr), tmpfile.path(), |_| {});
&format!("http://{}", addr),
tmpfile.path().to_str().unwrap(),
|_| {}
);
assert!(result.is_err(), "Download should fail due to connection interruption"); assert!(result.is_err(), "Download should fail due to connection interruption");
} }
@@ -152,13 +149,9 @@ fn test_successful_download() {
}); });
let tmpfile = tempfile::NamedTempFile::new().unwrap(); let tmpfile = tempfile::NamedTempFile::new().unwrap();
let _ = download_url_to_file( let _ = download_url_to_file(&format!("http://{}", addr), tmpfile.path(), |_| {}).unwrap();
&format!("http://{}", addr),
tmpfile.path().to_str().unwrap(),
|_| {},
).unwrap();
// Verify that the downloaded file has the expected size // Verify that the downloaded file has the expected size
let metadata = tmpfile.path().metadata().unwrap(); let metadata = tmpfile.path().metadata().unwrap();
assert_eq!(metadata.len(), 10240, "Downloaded file size should match the expected content size"); assert_eq!(metadata.len(), 10240, "Downloaded file size should match the expected content size");
} }

View File

@@ -116,6 +116,8 @@ macro_rules! maybe_pub_os {
}; };
} }
use std::path::PathBuf;
mod app; mod app;
pub use app::*; pub use app::*;
@@ -128,6 +130,7 @@ pub mod locator;
/// Sources are abstractions for custom update sources (eg. url, local file, github releases, etc). /// Sources are abstractions for custom update sources (eg. url, local file, github releases, etc).
pub mod sources; pub mod sources;
maybe_pub!(wide_strings);
maybe_pub!(download, bundle, constants, lockfile, logging, misc); maybe_pub!(download, bundle, constants, lockfile, logging, misc);
maybe_pub_os!(process, "process_win.rs", "process_unix.rs"); maybe_pub_os!(process, "process_win.rs", "process_unix.rs");
@@ -147,13 +150,13 @@ pub enum NetworkError {
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum Error { pub enum Error {
#[error("File does not exist: {0}")] #[error("File does not exist: {0}")]
FileNotFound(String), FileNotFound(PathBuf),
#[error("IO error: {0}")] #[error("IO error: {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("Checksum did not match for {0} (expected {1}, actual {2})")] #[error("Checksum did not match for {0} (expected {1}, actual {2})")]
ChecksumInvalid(String, String, String), ChecksumInvalid(PathBuf, String, String),
#[error("Size did not match for {0} (expected {1}, actual {2})")] #[error("Size did not match for {0} (expected {1}, actual {2})")]
SizeInvalid(String, u64, u64), SizeInvalid(PathBuf, u64, u64),
#[error("Zip error: {0}")] #[error("Zip error: {0}")]
Zip(#[from] zip::result::ZipError), Zip(#[from] zip::result::ZipError),
#[error("Network error: {0}")] #[error("Network error: {0}")]
@@ -162,16 +165,14 @@ pub enum Error {
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),
#[error("Semver parse error: {0}")] #[error("Semver parse error: {0}")]
Semver(#[from] semver::Error), Semver(#[from] semver::Error),
#[error("This application is missing a package manifest (.nuspec) or it could not be parsed.")] #[error("This update package is invalid: {0}.")]
MissingNuspec, InvalidPackage(String),
#[error("This application is missing a required property in its package manifest: {0}")]
MissingNuspecProperty(String),
#[error("This application is missing an Update.exe/UpdateNix/UpdateMac binary.")]
MissingUpdateExe,
#[error("This application is not properly installed: {0}")] #[error("This application is not properly installed: {0}")]
NotInstalled(String), NotInstalled(String),
#[error("Generic error: {0}")] #[error("This is not supported: {0}")]
Generic(String), NotSupported(String),
#[error("{0}")]
Other(String),
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[error("Win32 error: {0}")] #[error("Win32 error: {0}")]
Win32(#[from] windows::core::Error), Win32(#[from] windows::core::Error),

View File

@@ -110,10 +110,10 @@ impl VelopackLocator {
/// Creates a new VelopackLocator from the given paths, trying to auto-detect the manifest. /// Creates a new VelopackLocator from the given paths, trying to auto-detect the manifest.
pub fn new(config: &VelopackLocatorConfig) -> Result<VelopackLocator, Error> { pub fn new(config: &VelopackLocatorConfig) -> Result<VelopackLocator, Error> {
if !config.UpdateExePath.exists() { if !config.UpdateExePath.exists() {
return Err(Error::MissingUpdateExe); return Err(Error::NotInstalled("Update.exe does not exist in the expected path".to_owned()));
} }
if !config.ManifestPath.exists() { if !config.ManifestPath.exists() {
return Err(Error::MissingNuspec); return Err(Error::NotInstalled("Manifest file does not exist in the expected path".to_owned()));
} }
let manifest = read_current_manifest(&config.ManifestPath)?; let manifest = read_current_manifest(&config.ManifestPath)?;
@@ -152,11 +152,6 @@ impl VelopackLocator {
self.paths.PackagesDir.clone() self.paths.PackagesDir.clone()
} }
/// Returns the path to the current app's packages directory as a string.
pub fn get_packages_dir_as_string(&self) -> String {
Self::path_as_string(&self.paths.PackagesDir)
}
/// Returns the path to the ideal local nupkg path. /// Returns the path to the ideal local nupkg path.
pub fn get_ideal_local_nupkg_path(&self, id: Option<&str>, version: Option<Version>) -> PathBuf { pub fn get_ideal_local_nupkg_path(&self, id: Option<&str>, version: Option<Version>) -> PathBuf {
let id = id.unwrap_or(&self.manifest.id); let id = id.unwrap_or(&self.manifest.id);
@@ -164,11 +159,6 @@ impl VelopackLocator {
self.paths.RootAppDir.join("packages").join(format!("{}-{}-full.nupkg", id, version)) self.paths.RootAppDir.join("packages").join(format!("{}-{}-full.nupkg", id, version))
} }
/// Returns the path to the ideal local nupkg path as a string.
pub fn get_ideal_local_nupkg_path_as_string(&self, id: Option<&str>, version: Option<Version>) -> String {
Self::path_as_string(&self.get_ideal_local_nupkg_path(id, version))
}
/// Returns the path to the current app temporary directory. /// Returns the path to the current app temporary directory.
pub fn get_temp_dir_root(&self) -> PathBuf { pub fn get_temp_dir_root(&self) -> PathBuf {
self.paths.PackagesDir.join("VelopackTemp") self.paths.PackagesDir.join("VelopackTemp")
@@ -179,51 +169,26 @@ impl VelopackLocator {
self.get_temp_dir_root().join("tmp_".to_string() + &misc::random_string(16)) self.get_temp_dir_root().join("tmp_".to_string() + &misc::random_string(16))
} }
/// Returns the path to the current app temporary directory as a string.
pub fn get_temp_dir_as_string(&self) -> String {
Self::path_as_string(&self.get_temp_dir_root())
}
/// Returns the root directory of the current app. /// Returns the root directory of the current app.
pub fn get_root_dir(&self) -> PathBuf { pub fn get_root_dir(&self) -> PathBuf {
self.paths.RootAppDir.clone() self.paths.RootAppDir.clone()
} }
/// Returns the root directory of the current app as a string.
pub fn get_root_dir_as_string(&self) -> String {
Self::path_as_string(&self.paths.RootAppDir)
}
/// Returns the path to the current app's Update.exe binary. /// Returns the path to the current app's Update.exe binary.
pub fn get_update_path(&self) -> PathBuf { pub fn get_update_path(&self) -> PathBuf {
self.paths.UpdateExePath.clone() self.paths.UpdateExePath.clone()
} }
/// Returns the path to the current app's Update.exe binary as a string.
pub fn get_update_path_as_string(&self) -> String {
Self::path_as_string(&self.paths.UpdateExePath)
}
/// Returns the path to the current app's main executable. /// Returns the path to the current app's main executable.
pub fn get_main_exe_path(&self) -> PathBuf { pub fn get_main_exe_path(&self) -> PathBuf {
self.paths.CurrentBinaryDir.join(&self.manifest.main_exe) self.paths.CurrentBinaryDir.join(&self.manifest.main_exe)
} }
/// Returns the path to the current app's main executable as a string.
pub fn get_main_exe_path_as_string(&self) -> String {
Self::path_as_string(&self.get_main_exe_path())
}
/// Returns the path to the current app's user binary directory. /// Returns the path to the current app's user binary directory.
pub fn get_current_bin_dir(&self) -> PathBuf { pub fn get_current_bin_dir(&self) -> PathBuf {
self.paths.CurrentBinaryDir.clone() self.paths.CurrentBinaryDir.clone()
} }
/// Returns the path to the current app's user binary directory as a string.
pub fn get_current_bin_dir_as_string(&self) -> String {
Self::path_as_string(&self.paths.CurrentBinaryDir)
}
/// Returns a clone of the current app's manifest. /// Returns a clone of the current app's manifest.
pub fn get_manifest(&self) -> Manifest { pub fn get_manifest(&self) -> Manifest {
self.manifest.clone() self.manifest.clone()
@@ -311,10 +276,6 @@ impl VelopackLocator {
Ok(lock_file) Ok(lock_file)
} }
fn path_as_string(path: &PathBuf) -> String {
path.to_string_lossy().to_string()
}
fn get_or_create_staged_user_id(&self) -> String { fn get_or_create_staged_user_id(&self) -> String {
let packages_dir = self.get_packages_dir(); let packages_dir = self.get_packages_dir();
let beta_id_path = packages_dir.join(".betaId"); let beta_id_path = packages_dir.join(".betaId");
@@ -535,24 +496,24 @@ fn read_current_manifest(nuspec_path: &PathBuf) -> Result<Manifest, Error> {
return bundle::read_manifest_from_string(&nuspec); return bundle::read_manifest_from_string(&nuspec);
} }
} }
Err(Error::MissingNuspec) Err(Error::NotInstalled(format!("Manifest file does not exist or is not readable: {:?}", nuspec_path)))
} }
/// Returns the path and manifest of the latest full package in the given directory. /// Returns the path and manifest of the latest full package in the given directory.
pub fn find_latest_full_package(packages_dir: &PathBuf) -> Option<(PathBuf, Manifest)> { pub fn find_latest_full_package(packages_dir: &PathBuf) -> Option<(PathBuf, Manifest)> {
let packages_dir = packages_dir.to_string_lossy(); let packages_dir = packages_dir.to_string_lossy();
info!("Attempting to auto-detect package in: {}", packages_dir); info!("Attempting to auto-detect package in: {:?}", packages_dir);
let mut package: Option<(PathBuf, Manifest)> = None; let mut package: Option<(PathBuf, Manifest)> = None;
let search_glob = format!("{}/*-full.nupkg", packages_dir); let search_glob = format!("{}/*-full.nupkg", packages_dir);
if let Ok(paths) = glob::glob(search_glob.as_str()) { if let Ok(paths) = glob::glob(search_glob.as_str()) {
for path in paths.into_iter().flatten() { for path in paths.into_iter().flatten() {
trace!("Checking package: '{}'", path.to_string_lossy()); trace!("Checking package: '{:?}'", path);
if let Ok(mut bun) = bundle::load_bundle_from_file(&path) { if let Ok(mut bun) = bundle::load_bundle_from_file(&path) {
if let Ok(mani) = bun.read_manifest() { if let Ok(mani) = bun.read_manifest() {
if package.is_none() || mani.version > package.clone()?.1.version { if package.is_none() || mani.version > package.clone()?.1.version {
info!("Found {}: '{}'", mani.version, path.to_string_lossy()); info!("Found {}: '{:?}'", mani.version, path);
package = Some((path, mani)); package = Some((path, mani));
} }
} }

View File

@@ -1,6 +1,7 @@
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::{fs, process::exit, sync::mpsc::Sender}; use std::{fs, process::exit, sync::mpsc::Sender};
#[cfg(feature = "async")] #[cfg(feature = "async")]
@@ -396,7 +397,7 @@ impl UpdateManager {
let partial_file = final_target_file.with_extension("partial"); let partial_file = final_target_file.with_extension("partial");
if final_target_file.exists() { if final_target_file.exists() {
info!("Package already exists on disk, skipping download: '{}'", final_target_file.to_string_lossy()); info!("Package already exists on disk, skipping download: '{:?}'", final_target_file);
return Ok(()); return Ok(());
} }
@@ -405,11 +406,11 @@ impl UpdateManager {
let delta_pattern = format!("{}/*-delta.nupkg", packages_dir.to_string_lossy()); let delta_pattern = format!("{}/*-delta.nupkg", 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<PathBuf>) {
match glob::glob(pattern) { match glob::glob(pattern) {
Ok(paths) => { Ok(paths) => {
for path in paths.into_iter().flatten() { for path in paths.into_iter().flatten() {
to_delete.push(path.to_string_lossy().to_string()); to_delete.push(path);
} }
} }
Err(e) => { Err(e) => {
@@ -423,20 +424,19 @@ impl UpdateManager {
if update.BaseRelease.is_some() && !update.DeltasToTarget.is_empty() { if update.BaseRelease.is_some() && !update.DeltasToTarget.is_empty() {
info!("Beginning delta update process."); info!("Beginning delta update process.");
if let Err(e) = self.download_and_apply_delta_updates(update, &partial_file, progress.clone()) { if self.download_and_apply_delta_updates(update, &partial_file, progress.clone()).is_err() {
error!("Error downloading delta updates: {}", e);
info!("Falling back to full update..."); info!("Falling back to full update...");
self.source.download_release_entry(&update.TargetFullRelease, &partial_file.to_string_lossy(), progress)?; self.source.download_release_entry(&update.TargetFullRelease, &partial_file, progress)?;
self.verify_package_checksum(&partial_file, &update.TargetFullRelease)?; self.verify_package_checksum(&partial_file, &update.TargetFullRelease)?;
info!("Successfully downloaded file: '{}'", partial_file.to_string_lossy()); info!("Successfully downloaded file: '{:?}'", partial_file);
} }
} else { } else {
self.source.download_release_entry(&update.TargetFullRelease, &partial_file.to_string_lossy(), progress)?; self.source.download_release_entry(&update.TargetFullRelease, &partial_file, progress)?;
self.verify_package_checksum(&partial_file, &update.TargetFullRelease)?; self.verify_package_checksum(&partial_file, &update.TargetFullRelease)?;
info!("Successfully downloaded file: '{}'", partial_file.to_string_lossy()); info!("Successfully downloaded file: '{:?}'", partial_file);
} }
info!("Renaming partial file to final target: '{}'", final_target_file.to_string_lossy()); info!("Renaming partial file to final target: '{:?}'", final_target_file);
fs::rename(&partial_file, &final_target_file)?; fs::rename(&partial_file, &final_target_file)?;
find_files_to_delete(&delta_pattern, &mut to_delete); find_files_to_delete(&delta_pattern, &mut to_delete);
@@ -457,7 +457,7 @@ impl UpdateManager {
} }
for path in to_delete { for path in to_delete {
info!("Deleting up old package: '{}'", path); info!("Deleting up old package: '{:?}'", path);
let _ = fs::remove_file(&path); let _ = fs::remove_file(&path);
} }
@@ -467,23 +467,21 @@ impl UpdateManager {
fn download_and_apply_delta_updates( fn download_and_apply_delta_updates(
&self, &self,
update: &UpdateInfo, update: &UpdateInfo,
target_file: &PathBuf, output_file: &PathBuf,
progress: Option<Sender<i16>>, progress: Option<Sender<i16>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let packages_dir = self.locator.get_packages_dir(); let packages_dir = self.locator.get_packages_dir();
let base_release_path = packages_dir.join(&update.BaseRelease.as_ref().unwrap().FileName); let base_release_path = packages_dir.join(&update.BaseRelease.as_ref().unwrap().FileName);
let base_release_path = base_release_path.to_string_lossy().to_string();
let output_path = target_file.to_string_lossy().to_string();
let mut args: Vec<String> = let mut args: Vec<OsString> =
["patch", "--old", &base_release_path, "--output", &output_path].iter().map(|s| s.to_string()).collect(); vec!["patch".into(), "--old".into(), base_release_path.clone().into(), "--output".into(), output_file.clone().into()];
for (i, delta) in update.DeltasToTarget.iter().enumerate() { for (i, delta) in update.DeltasToTarget.iter().enumerate() {
let delta_file = packages_dir.join(&delta.FileName); let delta_file = packages_dir.join(&delta.FileName);
let partial_file = delta_file.with_extension("partial"); let partial_file = delta_file.with_extension("partial");
info!("Downloading delta package: '{}'", &delta.FileName); info!("Downloading delta package: '{}'", &delta.FileName);
self.source.download_release_entry(&delta, &partial_file.to_string_lossy(), None)?; self.source.download_release_entry(&delta, &partial_file, None)?;
self.verify_package_checksum(&partial_file, delta)?; self.verify_package_checksum(&partial_file, delta)?;
fs::rename(&partial_file, &delta_file)?; fs::rename(&partial_file, &delta_file)?;
@@ -492,11 +490,11 @@ impl UpdateManager {
let _ = progress.send(((i as f64 / update.DeltasToTarget.len() as f64) * 70.0) as i16); let _ = progress.send(((i as f64 / update.DeltasToTarget.len() as f64) * 70.0) as i16);
} }
args.push("--delta".to_string()); args.push("--delta".into());
args.push(delta_file.to_string_lossy().to_string()); args.push(delta_file.into());
} }
info!("Applying {} patches to {}.", update.DeltasToTarget.len(), output_path); info!("Applying {} patches to {:?}.", update.DeltasToTarget.len(), output_file);
if let Some(progress) = &progress { if let Some(progress) = &progress {
let _ = progress.send(70); let _ = progress.send(70);
@@ -508,7 +506,7 @@ impl UpdateManager {
} else { } else {
let error_message = String::from_utf8_lossy(&output.stderr); let error_message = String::from_utf8_lossy(&output.stderr);
error!("Error applying delta updates: {}", error_message); error!("Error applying delta updates: {}", error_message);
return Err(Error::Generic(error_message.to_string())); return Err(Error::Io(std::io::Error::new(std::io::ErrorKind::Other, "Process exited with non-zero status")));
} }
if let Some(progress) = &progress { if let Some(progress) = &progress {
@@ -517,17 +515,17 @@ impl UpdateManager {
Ok(()) Ok(())
} }
fn verify_package_checksum(&self, file: &PathBuf, asset: &VelopackAsset) -> Result<(), Error> { fn verify_package_checksum(&self, file: &Path, asset: &VelopackAsset) -> Result<(), Error> {
let file_size = file.metadata()?.len(); let file_size = file.metadata()?.len();
if file_size != asset.Size { if file_size != asset.Size {
error!("File size mismatch for file '{}': expected {}, got {}", file.to_string_lossy(), asset.Size, file_size); error!("File size mismatch for file '{:?}': expected {}, got {}", file, asset.Size, file_size);
return Err(Error::SizeInvalid(file.to_string_lossy().to_string(), asset.Size, file_size)); return Err(Error::SizeInvalid(file.to_path_buf(), asset.Size, file_size));
} }
let (sha1, _) = misc::calculate_sha1_sha256(file)?; let (sha1, _) = misc::calculate_sha1_sha256(file)?;
if !sha1.eq_ignore_ascii_case(&asset.SHA1) { if !sha1.eq_ignore_ascii_case(&asset.SHA1) {
error!("SHA1 checksum mismatch for file '{}': expected '{}', got '{}'", file.to_string_lossy(), asset.SHA1, sha1); error!("SHA1 checksum mismatch for file '{:?}': expected '{}', got '{}'", file, asset.SHA1, sha1);
return Err(Error::ChecksumInvalid(file.to_string_lossy().to_string(), asset.SHA1.clone(), sha1)); return Err(Error::ChecksumInvalid(file.to_path_buf(), asset.SHA1.clone(), sha1));
} }
Ok(()) Ok(())
} }
@@ -577,7 +575,7 @@ impl UpdateManager {
pub fn apply_updates_and_restart_with_args<A, C, S>(&self, to_apply: A, restart_args: C) -> Result<(), Error> pub fn apply_updates_and_restart_with_args<A, C, S>(&self, to_apply: A, restart_args: C) -> Result<(), Error>
where where
A: AsRef<VelopackAsset>, A: AsRef<VelopackAsset>,
S: AsRef<str>, S: AsRef<OsStr>,
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)?;
@@ -602,7 +600,7 @@ impl UpdateManager {
pub fn wait_exit_then_apply_updates<A, C, S>(&self, to_apply: A, silent: bool, restart: bool, restart_args: C) -> Result<(), Error> pub fn wait_exit_then_apply_updates<A, C, S>(&self, to_apply: A, silent: bool, restart: bool, restart_args: C) -> Result<(), Error>
where where
A: AsRef<VelopackAsset>, A: AsRef<VelopackAsset>,
S: AsRef<str>, S: AsRef<OsStr>,
C: IntoIterator<Item = S>, C: IntoIterator<Item = S>,
{ {
self.unsafe_apply_updates(to_apply, silent, ApplyWaitMode::WaitCurrentProcess, restart, restart_args)?; self.unsafe_apply_updates(to_apply, silent, ApplyWaitMode::WaitCurrentProcess, restart, restart_args)?;
@@ -622,50 +620,49 @@ impl UpdateManager {
) -> Result<(), Error> ) -> Result<(), Error>
where where
A: AsRef<VelopackAsset>, A: AsRef<VelopackAsset>,
S: AsRef<str>, S: AsRef<OsStr>,
C: IntoIterator<Item = S>, 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);
let pkg_path_str = pkg_path.to_string_lossy();
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() { if !pkg_path.exists() {
error!("Package does not exist on disk: '{}'", &pkg_path_str); error!("Package does not exist on disk: '{:?}'", &pkg_path);
return Err(Error::FileNotFound(pkg_path_str.to_string())); return Err(Error::FileNotFound(pkg_path));
} }
let mut args: Vec<OsString> = Vec::new();
args.push("apply".into());
args.push("--package".into());
args.push(pkg_path.into());
match wait_mode { match wait_mode {
ApplyWaitMode::NoWait => {} ApplyWaitMode::NoWait => {}
ApplyWaitMode::WaitCurrentProcess => { ApplyWaitMode::WaitCurrentProcess => {
args.push("--waitPid".to_string()); args.push("--waitPid".into());
args.push(format!("{}", std::process::id())); args.push(format!("{}", std::process::id()).into());
} }
ApplyWaitMode::WaitPid(pid) => { ApplyWaitMode::WaitPid(pid) => {
args.push("--waitPid".to_string()); args.push("--waitPid".into());
args.push(format!("{}", pid)); args.push(format!("{}", pid).into());
} }
} }
if silent { if silent {
args.push("--silent".to_string()); args.push("--silent".into());
} }
if !restart { if !restart {
args.push("--norestart".to_string()); args.push("--norestart".into());
} }
args.push("--root".to_string()); args.push("--root".into());
args.push(self.locator.get_root_dir_as_string()); args.push(self.locator.get_root_dir().into());
let restart_args: Vec<String> = restart_args.into_iter().map(|item| item.as_ref().to_string()).collect(); let restart_args: Vec<OsString> = restart_args.into_iter().map(|item| item.as_ref().to_os_string()).collect();
if !restart_args.is_empty() { if !restart_args.is_empty() {
args.push("--".to_string()); args.push("--".into());
for arg in restart_args { for arg in restart_args {
args.push(arg); args.push(arg);
} }

View File

@@ -1,8 +1,9 @@
use crate::wide_strings::*;
use std::{ use std::{
collections::HashMap, collections::HashMap,
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult}, io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult},
os::{raw::c_void, windows::ffi::OsStrExt}, os::windows::ffi::OsStrExt,
path::Path, path::Path,
time::Duration, time::Duration,
}; };
@@ -101,7 +102,7 @@ fn append_arg(cmd: &mut Vec<u16>, arg: &Arg, force_quotes: bool) -> IoResult<()>
Ok(()) Ok(())
} }
fn make_command_line(argv0: Option<&OsStr>, args: &[Arg], force_quotes: bool) -> IoResult<Vec<u16>> { fn make_command_line(argv0: Option<&OsStr>, args: &[Arg], force_quotes: bool) -> IoResult<WideString> {
// Encode the command and arguments in a command line string such // Encode the command and arguments in a command line string such
// that the spawned process may recover them using CommandLineToArgvW. // that the spawned process may recover them using CommandLineToArgvW.
let mut cmd: Vec<u16> = Vec::new(); let mut cmd: Vec<u16> = Vec::new();
@@ -123,10 +124,10 @@ fn make_command_line(argv0: Option<&OsStr>, args: &[Arg], force_quotes: bool) ->
} }
cmd.push(0); cmd.push(0);
Ok(cmd) Ok(cmd.into())
} }
fn make_envp(maybe_env: Option<HashMap<String, String>>) -> IoResult<(Option<*const c_void>, Vec<u16>)> { fn make_envp(maybe_env: Option<HashMap<String, String>>) -> IoResult<Option<WideString>> {
// On Windows we pass an "environment block" which is not a char**, but // On Windows we pass an "environment block" which is not a char**, but
// rather a concatenation of null-terminated k=v\0 sequences, with a final // rather a concatenation of null-terminated k=v\0 sequences, with a final
// \0 to terminate. // \0 to terminate.
@@ -148,9 +149,9 @@ fn make_envp(maybe_env: Option<HashMap<String, String>>) -> IoResult<(Option<*co
blk.push(0); blk.push(0);
} }
blk.push(0); blk.push(0);
Ok((Some(blk.as_ptr() as *mut c_void), blk)) Ok(Some(blk.into()))
} else { } else {
Ok((None, Vec::new())) Ok(None)
} }
} }
@@ -232,36 +233,18 @@ impl AsRef<HANDLE> for SafeProcessHandle {
// } // }
// } // }
fn os_to_pcwstr<P: AsRef<OsStr>>(d: P) -> IoResult<(PCWSTR, Vec<u16>)> {
let d = d.as_ref();
let d = OsString::from(d);
let mut d_str: Vec<u16> = ensure_no_nuls(d)?.encode_wide().collect();
d_str.push(0);
Ok((PCWSTR(d_str.as_ptr()), d_str))
}
fn pathopt_to_pcwstr<P: AsRef<Path>>(d: Option<P>) -> IoResult<(PCWSTR, Vec<u16>)> {
match d {
Some(dir) => {
let dir = dir.as_ref();
os_to_pcwstr(dir)
}
None => Ok((PCWSTR::null(), Vec::new())),
}
}
pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>( pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>(
exe_path: P1, exe_path: P1,
args: Vec<String>, args: Vec<OsString>,
work_dir: Option<P2>, work_dir: Option<P2>,
show_window: bool, show_window: bool,
) -> IoResult<SafeProcessHandle> { ) -> IoResult<SafeProcessHandle> {
let verb = os_to_pcwstr("runas")?; let verb = string_to_wide("runas");
let exe = os_to_pcwstr(exe_path.as_ref())?; let exe = string_to_wide(exe_path.as_ref());
let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect(); let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect();
let params = make_command_line(None, &wrapped_args, false)?; let params = make_command_line(None, &wrapped_args, false)?;
let params = PCWSTR(params.as_ptr()); let params = PCWSTR(params.as_ptr());
let work_dir = pathopt_to_pcwstr(work_dir.as_ref())?; let work_dir = string_to_wide_opt(work_dir.map(|w| w.as_ref().to_path_buf()));
let n_show = if show_window { let n_show = if show_window {
windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0 windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0
@@ -272,10 +255,10 @@ pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>(
let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW { let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32, cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_NOCLOSEPROCESS, fMask: SEE_MASK_NOCLOSEPROCESS,
lpVerb: verb.0, lpVerb: verb.as_pcwstr(),
lpFile: exe.0, lpFile: exe.as_pcwstr(),
lpParameters: params, lpParameters: params,
lpDirectory: work_dir.0, lpDirectory: work_dir.as_ref().map(|d| d.as_pcwstr()).unwrap_or(PCWSTR::default()),
nShow: n_show, nShow: n_show,
..Default::default() ..Default::default()
}; };
@@ -291,20 +274,16 @@ pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>(
pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>( pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
exe_path: P1, exe_path: P1,
args: Vec<String>, args: Vec<OsString>,
work_dir: Option<P2>, work_dir: Option<P2>,
show_window: bool, show_window: bool,
set_env: Option<HashMap<String, String>>, set_env: Option<HashMap<String, String>>,
) -> IoResult<SafeProcessHandle> { ) -> IoResult<SafeProcessHandle> {
let exe_path = exe_path.as_ref(); let exe_path = string_to_wide(exe_path.as_ref());
let exe_path = OsString::from(exe_path); let dirp = string_to_wide_opt(work_dir.map(|w| w.as_ref().to_path_buf()));
let exe_name_ptr = os_to_pcwstr(&exe_path)?; let envp = make_envp(set_env)?;
let work_dir = work_dir.map(|d| d.as_ref().to_path_buf());
let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect(); let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect();
let mut params = make_command_line(Some(&exe_path), &wrapped_args, false)?; let mut params = make_command_line(Some(exe_path.as_os_str()), &wrapped_args, false)?;
let params = PWSTR(params.as_mut_ptr());
let mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default(); let mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default();
@@ -329,9 +308,6 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
hStdError: HANDLE(std::ptr::null_mut()), hStdError: HANDLE(std::ptr::null_mut()),
}; };
let envp = make_envp(set_env)?;
let dirp = pathopt_to_pcwstr(work_dir.as_deref())?;
let flags = if show_window { let flags = if show_window {
CREATE_UNICODE_ENVIRONMENT CREATE_UNICODE_ENVIRONMENT
} else { } else {
@@ -339,8 +315,19 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
}; };
unsafe { unsafe {
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, work_dir, args); info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, dirp, args);
CreateProcessW(exe_name_ptr.0, Option::Some(params), None, None, false, flags, envp.0, dirp.0, &si, &mut pi)?; CreateProcessW(
exe_path.as_pcwstr(),
Some(params.as_pwstr()),
None,
None,
false,
flags,
envp.map(|e| e.as_cvoid()),
dirp.map(|d| d.as_pcwstr()).unwrap_or(PCWSTR::default()),
&si,
&mut pi,
)?;
let _ = AllowSetForegroundWindow(pi.dwProcessId); let _ = AllowSetForegroundWindow(pi.dwProcessId);
let _ = CloseHandle(pi.hThread); let _ = CloseHandle(pi.hThread);
} }

View File

@@ -3,8 +3,8 @@ use std::{
sync::mpsc::Sender, sync::mpsc::Sender,
}; };
use crate::*;
use crate::bundle::Manifest; use crate::bundle::Manifest;
use crate::*;
/// Abstraction for finding and downloading updates from a package source / repository. /// Abstraction for finding and downloading updates from a package source / repository.
/// An implementation may copy a file from a local repository, download from a web address, /// An implementation may copy a file from a local repository, download from a web address,
@@ -14,7 +14,7 @@ pub trait UpdateSource: Send + Sync {
/// can subsequently be downloaded with download_release_entry. /// can subsequently be downloaded with download_release_entry.
fn get_release_feed(&self, channel: &str, app: &bundle::Manifest, staged_user_id: &str) -> Result<VelopackAssetFeed, Error>; fn get_release_feed(&self, channel: &str, app: &bundle::Manifest, staged_user_id: &str) -> Result<VelopackAssetFeed, Error>;
/// Download the specified VelopackAsset to the provided local file path. /// Download the specified VelopackAsset to the provided local file path.
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error>; fn download_release_entry(&self, asset: &VelopackAsset, local_file: &Path, progress_sender: Option<Sender<i16>>) -> Result<(), Error>;
/// Clone the source to create a new lifetime. /// Clone the source to create a new lifetime.
fn clone_boxed(&self) -> Box<dyn UpdateSource>; fn clone_boxed(&self) -> Box<dyn UpdateSource>;
} }
@@ -25,16 +25,21 @@ impl Clone for Box<dyn UpdateSource> {
} }
} }
/// A source that does not provide any update capability. /// A source that does not provide any update capability.
#[derive(Clone)] #[derive(Clone)]
pub struct NoneSource {} pub struct NoneSource {}
impl UpdateSource for NoneSource { impl UpdateSource for NoneSource {
fn get_release_feed(&self, _channel: &str, _app: &Manifest, _staged_user_id: &str) -> Result<VelopackAssetFeed, Error> { fn get_release_feed(&self, _channel: &str, _app: &Manifest, _staged_user_id: &str) -> Result<VelopackAssetFeed, Error> {
Err(Error::Generic("None source does not checking release feed".to_owned())) Err(Error::NotSupported("None source does not checking release feed".to_owned()))
} }
fn download_release_entry(&self, _asset: &VelopackAsset, _local_file: &str, _progress_sender: Option<Sender<i16>>) -> Result<(), Error> { fn download_release_entry(
Err(Error::Generic("None source does not support downloads".to_owned())) &self,
_asset: &VelopackAsset,
_local_file: &Path,
_progress_sender: Option<Sender<i16>>,
) -> Result<(), Error> {
Err(Error::NotSupported("None source does not support downloads".to_owned()))
} }
fn clone_boxed(&self) -> Box<dyn UpdateSource> { fn clone_boxed(&self) -> Box<dyn UpdateSource> {
Box::new(self.clone()) Box::new(self.clone())
@@ -72,7 +77,7 @@ impl UpdateSource for AutoSource {
self.source.get_release_feed(channel, app, staged_user_id) self.source.get_release_feed(channel, app, staged_user_id)
} }
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> { fn download_release_entry(&self, asset: &VelopackAsset, local_file: &Path, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
self.source.download_release_entry(asset, local_file, progress_sender) self.source.download_release_entry(asset, local_file, progress_sender)
} }
@@ -111,12 +116,12 @@ impl UpdateSource for HttpSource {
Ok(feed) Ok(feed)
} }
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> { fn download_release_entry(&self, asset: &VelopackAsset, local_file: &Path, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
let path = self.url.trim_end_matches('/').to_owned() + "/"; let path = self.url.trim_end_matches('/').to_owned() + "/";
let url = url::Url::parse(&path)?; let url = url::Url::parse(&path)?;
let asset_url = url.join(&asset.FileName)?; let asset_url = url.join(&asset.FileName)?;
info!("About to download from URL '{}' to file '{}'", asset_url, local_file); info!("About to download from URL '{}' to file '{:?}'", asset_url, local_file);
download::download_url_to_file(asset_url.as_str(), local_file, move |p| { download::download_url_to_file(asset_url.as_str(), local_file, move |p| {
if let Some(progress_sender) = &progress_sender { if let Some(progress_sender) = &progress_sender {
let _ = progress_sender.send(p); let _ = progress_sender.send(p);
@@ -150,15 +155,15 @@ impl UpdateSource for FileSource {
let releases_name = format!("releases.{}.json", channel); let releases_name = format!("releases.{}.json", channel);
let releases_path = self.path.join(&releases_name); let releases_path = self.path.join(&releases_name);
info!("Reading releases from file: {}", releases_path.display()); info!("Reading releases from file: {:?}", releases_path);
let json = std::fs::read_to_string(releases_path)?; let json = std::fs::read_to_string(releases_path)?;
let feed: VelopackAssetFeed = serde_json::from_str(&json)?; let feed: VelopackAssetFeed = serde_json::from_str(&json)?;
Ok(feed) Ok(feed)
} }
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> { fn download_release_entry(&self, asset: &VelopackAsset, local_file: &Path, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
let asset_path = self.path.join(&asset.FileName); let asset_path = self.path.join(&asset.FileName);
info!("About to copy from file '{}' to file '{}'", asset_path.display(), local_file); info!("About to copy from file '{:?}' to file '{:?}'", asset_path, local_file);
if let Some(progress_sender) = &progress_sender { if let Some(progress_sender) = &progress_sender {
let _ = progress_sender.send(50); let _ = progress_sender.send(50);
} }

View File

@@ -0,0 +1,190 @@
use std::{
ffi::{c_void, OsStr, OsString},
fmt::Debug,
os::windows::ffi::{OsStrExt, OsStringExt},
};
use windows::core::{PCWSTR, PWSTR};
#[derive(Clone, PartialEq, Eq)]
pub struct WideString {
str: OsString,
vec: Vec<u16>,
}
impl WideString {
pub fn as_slice(&self) -> &[u16] {
&self.vec
}
pub fn as_mut_slice(&mut self) -> &mut [u16] {
&mut self.vec
}
pub fn len(&self) -> usize {
self.vec.len()
}
pub fn as_os_str(&self) -> &OsStr {
self.str.as_os_str()
}
pub fn as_cvoid(&self) -> *const c_void {
self.as_ptr() as *const c_void
}
pub fn as_mut_cvoid(&mut self) -> *const c_void {
self.as_mut_ptr() as *const c_void
}
pub fn as_ptr(&self) -> *const u16 {
self.as_slice().as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut u16 {
self.as_mut_slice().as_mut_ptr()
}
pub fn as_pcwstr(&self) -> PCWSTR {
PCWSTR(self.as_ptr())
}
pub fn as_pwstr(&mut self) -> PWSTR {
PWSTR(self.as_mut_ptr())
}
}
impl Debug for WideString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "WideString({:?})", self.str)
}
}
impl From<Vec<u16>> for WideString {
fn from(inner: Vec<u16>) -> Self {
WideString { str: OsString::from_wide(&inner), vec: inner }
}
}
impl From<String> for WideString {
fn from(inner: String) -> Self {
string_to_wide(inner)
}
}
impl From<&str> for WideString {
fn from(inner: &str) -> Self {
string_to_wide(inner)
}
}
impl Into<Vec<u16>> for WideString {
fn into(self) -> Vec<u16> {
self.vec
}
}
impl AsRef<OsStr> for WideString {
fn as_ref(&self) -> &OsStr {
self.str.as_os_str()
}
}
impl AsRef<[u16]> for WideString {
fn as_ref(&self) -> &[u16] {
&self.vec
}
}
impl AsMut<[u16]> for WideString {
fn as_mut(&mut self) -> &mut [u16] {
&mut self.vec
}
}
pub fn string_to_wide<P: AsRef<OsStr>>(input: P) -> WideString {
let str = input.as_ref();
let str = OsString::from(str);
let vec = str
.encode_wide()
.filter(|f| *f != 0) // Filter out any null characters
.chain(Some(0))
.collect::<Vec<u16>>();
WideString { vec, str }
}
pub fn string_to_wide_opt<P: AsRef<OsStr>>(input: Option<P>) -> Option<WideString> {
input.map(string_to_wide)
}
pub trait ToWideSlice {
fn to_wide_slice(&self) -> &[u16];
}
impl ToWideSlice for PWSTR {
fn to_wide_slice(&self) -> &[u16] {
unsafe { self.as_wide() }
}
}
impl ToWideSlice for PCWSTR {
fn to_wide_slice(&self) -> &[u16] {
unsafe { self.as_wide() }
}
}
impl ToWideSlice for Vec<u16> {
fn to_wide_slice(&self) -> &[u16] {
self.as_ref()
}
}
impl ToWideSlice for &Vec<u16> {
fn to_wide_slice(&self) -> &[u16] {
self.as_ref()
}
}
impl ToWideSlice for &[u16] {
fn to_wide_slice(&self) -> &[u16] {
self
}
}
impl<const N: usize> ToWideSlice for [u16; N] {
fn to_wide_slice(&self) -> &[u16] {
self.as_ref()
}
}
pub fn wide_to_string_lossy<T: ToWideSlice>(input: T) -> String {
let slice = input.to_wide_slice();
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
let trimmed_slice = &slice[..null_pos];
String::from_utf16_lossy(trimmed_slice)
}
pub fn wide_to_string_lossy_opt<T: ToWideSlice>(input: Option<T>) -> Option<String> {
input.map(wide_to_string_lossy)
}
pub fn wide_to_string<T: ToWideSlice>(input: T) -> Result<String, std::string::FromUtf16Error> {
let slice = input.to_wide_slice();
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
let trimmed_slice = &slice[..null_pos];
Ok(String::from_utf16(trimmed_slice)?)
}
pub fn wide_to_string_opt<T: ToWideSlice>(input: Option<T>) -> Option<Result<String, std::string::FromUtf16Error>> {
input.map(wide_to_string)
}
pub fn wide_to_os_string<T: ToWideSlice>(input: T) -> OsString {
let slice = input.to_wide_slice();
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
let trimmed_slice = &slice[..null_pos];
OsString::from_wide(trimmed_slice)
}
pub fn wide_to_os_string_opt<T: ToWideSlice>(input: Option<T>) -> Option<OsString> {
input.map(wide_to_os_string)
}