mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Refactor string handling to use OsString where possible
This commit is contained in:
@@ -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());
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(¤t_dir, &temp_path_old), 1000, 10)
|
shared::retry_io_ex(|| fs::rename(¤t_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, ¤t_dir), 1000, 30)
|
shared::retry_io_ex(|| fs::rename(&temp_path_new, ¤t_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.")?;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())?;
|
||||||
|
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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(¤t_dir).join(name))
|
Ok(Path::new(¤t_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);
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()?);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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."))?;
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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::{
|
||||||
|
|||||||
@@ -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())
|
|
||||||
// }
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
190
src/lib-rust/src/wide_strings.rs
Normal file
190
src/lib-rust/src/wide_strings.rs
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user