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 velopack::{locator, locator::VelopackLocator, constants};
|
||||
use anyhow::{bail, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
use velopack::{constants, locator, locator::VelopackLocator};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::apply_linux_impl::apply_package_impl;
|
||||
@@ -15,7 +15,7 @@ pub fn apply<'a>(
|
||||
restart: bool,
|
||||
wait: OperationWait,
|
||||
package: Option<&PathBuf>,
|
||||
exe_args: Option<Vec<&str>>,
|
||||
exe_args: Option<Vec<OsString>>,
|
||||
run_hooks: bool,
|
||||
) -> Result<VelopackLocator> {
|
||||
shared::operation_wait(wait);
|
||||
@@ -25,10 +25,12 @@ pub fn apply<'a>(
|
||||
|
||||
match package {
|
||||
Some(package) => {
|
||||
info!("Getting ready to apply package to {} ver {}: {}",
|
||||
locator.get_manifest_id(),
|
||||
locator.get_manifest_version_full_string(),
|
||||
package.to_string_lossy());
|
||||
info!(
|
||||
"Getting ready to apply package to {} ver {}: {:?}",
|
||||
locator.get_manifest_id(),
|
||||
locator.get_manifest_version_full_string(),
|
||||
package
|
||||
);
|
||||
match apply_package_impl(&locator, &package, run_hooks) {
|
||||
Ok(applied_locator) => {
|
||||
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> {
|
||||
// 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 manifest = bundle.read_manifest()?;
|
||||
let temp_path = locator.get_temp_dir_rand16().to_string_lossy().to_string();
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
windows::{self, splash},
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::{ffi::OsString, fs, path::PathBuf};
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
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 {
|
||||
info!("Re-launching as administrator to update in {:?}", root_path);
|
||||
|
||||
let package_string = package.to_string_lossy().to_string();
|
||||
let args = vec!["--norestart".to_string(), "--package".to_string(), package_string];
|
||||
let args: Vec<OsString> = vec!["--norestart".into(), "--package".into(), package.into()];
|
||||
let exe_path = std::env::current_exe()?;
|
||||
let work_dir: Option<String> = None; // same as this process
|
||||
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
|
||||
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)
|
||||
.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
|
||||
// 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)
|
||||
.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},
|
||||
windows,
|
||||
};
|
||||
use velopack::bundle::BundleZip;
|
||||
use velopack::locator::*;
|
||||
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 pretty_bytes_rust::pretty_bytes;
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fs::{self},
|
||||
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
|
||||
info!("Reading package manifest...");
|
||||
let app = pkg.read_manifest()?;
|
||||
|
||||
|
||||
info!("Package manifest loaded successfully.");
|
||||
info!(" Package ID: {}", &app.id);
|
||||
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))?;
|
||||
}
|
||||
|
||||
let root_path_str = root_path.to_str().unwrap();
|
||||
info!("Installation Directory: {:?}", root_path_str);
|
||||
info!("Installation Directory: {:?}", root_path);
|
||||
|
||||
// do we have enough disk space?
|
||||
let (compressed_size, extracted_size) = pkg.calculate_size();
|
||||
let required_space = compressed_size + extracted_size + (50 * 1000 * 1000); // archive + velopack overhead
|
||||
|
||||
let mut free_space: u64 = 0;
|
||||
let root_pcwstr = windows::strings::string_to_u16(root_path_str);
|
||||
let root_pcwstr: PCWSTR = PCWSTR(root_pcwstr.as_ptr());
|
||||
if let Ok(()) = unsafe { GetDiskFreeSpaceExW(root_pcwstr, None, None, Some(&mut free_space)) } {
|
||||
let root_pcwstr = string_to_wide(&root_path);
|
||||
if let Ok(()) = unsafe { GetDiskFreeSpaceExW(root_pcwstr.as_pcwstr(), None, None, Some(&mut free_space)) } {
|
||||
if free_space < required_space {
|
||||
bail!(
|
||||
"{} 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);
|
||||
}
|
||||
|
||||
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)
|
||||
if !shared::is_dir_empty(&root_path) {
|
||||
// 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)
|
||||
})?;
|
||||
|
||||
root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(16));
|
||||
info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed);
|
||||
let renamed = root_path.with_extension(shared::random_string(16));
|
||||
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!(
|
||||
"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."
|
||||
)
|
||||
})?;
|
||||
|
||||
root_path_renamed = Some(renamed);
|
||||
}
|
||||
|
||||
info!("Preparing and cleaning installation directory...");
|
||||
remove_dir_all::ensure_empty_dir(&root_path)?;
|
||||
|
||||
|
||||
info!("Acquiring lock...");
|
||||
let paths = create_config_from_root_dir(&root_path);
|
||||
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() {
|
||||
info!("Installation completed successfully!");
|
||||
if !root_path_renamed.is_empty() {
|
||||
if let Some(renamed) = root_path_renamed {
|
||||
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 {
|
||||
error!("Installation failed!");
|
||||
if !root_path_renamed.is_empty() {
|
||||
if let Some(renamed) = root_path_renamed {
|
||||
info!("Rolling back installation...");
|
||||
let _ = shared::force_stop_package(&root_path);
|
||||
let _ = shared::retry_io(|| fs::remove_dir_all(&root_path));
|
||||
let _ = shared::retry_io(|| fs::rename(&root_path_renamed, &root_path));
|
||||
let _ = shared::retry_io(|| remove_dir_all::remove_dir_all(&root_path));
|
||||
let _ = shared::retry_io(|| fs::rename(&renamed, &root_path));
|
||||
}
|
||||
install_result?;
|
||||
}
|
||||
@@ -152,7 +151,12 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
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!");
|
||||
|
||||
// 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();
|
||||
|
||||
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() {
|
||||
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)?;
|
||||
@@ -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();
|
||||
|
||||
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() {
|
||||
@@ -71,13 +71,13 @@ pub fn delta<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>>(
|
||||
|
||||
for delta_file in &delta_files {
|
||||
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();
|
||||
|
||||
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");
|
||||
fs::create_dir_all(&work_dir)?;
|
||||
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());
|
||||
|
||||
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));
|
||||
fs::create_dir_all(&delta_dir)?;
|
||||
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())?;
|
||||
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
use crate::shared::{self, OperationWait};
|
||||
use anyhow::Result;
|
||||
use std::ffi::OsString;
|
||||
use velopack::locator::LocationContext;
|
||||
|
||||
#[allow(unused_variables, unused_imports)]
|
||||
#[allow(unused_variables)]
|
||||
pub fn start(
|
||||
wait: OperationWait,
|
||||
context: LocationContext,
|
||||
exe_name: Option<&String>,
|
||||
exe_args: Option<Vec<&str>>,
|
||||
legacy_args: Option<&String>,
|
||||
exe_name: Option<&OsString>,
|
||||
exe_args: Option<Vec<OsString>>,
|
||||
legacy_args: Option<&OsString>,
|
||||
) -> 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);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -4,19 +4,16 @@ use crate::{
|
||||
windows as win,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::{
|
||||
fs,
|
||||
path::Path,
|
||||
path::PathBuf,
|
||||
process::Command as Process,
|
||||
ffi::{OsStr, OsString},
|
||||
os::windows::process::CommandExt,
|
||||
};
|
||||
use velopack::{bundle::Manifest, constants};
|
||||
use std::{fs, path::Path, path::PathBuf, process::Command as Process};
|
||||
use velopack::locator::{self, LocationContext, VelopackLocator};
|
||||
use velopack::{bundle::Manifest, constants};
|
||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||
|
||||
enum LocatorResult
|
||||
{
|
||||
enum LocatorResult {
|
||||
Normal(VelopackLocator),
|
||||
Legacy(PathBuf, Manifest),
|
||||
}
|
||||
@@ -34,18 +31,18 @@ impl LocatorResult {
|
||||
LocatorResult::Legacy(_, manifest) => manifest.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_current_dir(&self) -> PathBuf {
|
||||
match self {
|
||||
LocatorResult::Normal(locator) => locator.get_current_bin_dir(),
|
||||
LocatorResult::Legacy(path, _) => path.join("current"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_exe_to_start(&self, name: Option<&String>) -> Result<PathBuf> {
|
||||
|
||||
pub fn get_exe_to_start<P: AsRef<OsStr>>(&self, name: Option<P>) -> Result<PathBuf> {
|
||||
let current_dir = self.get_current_dir();
|
||||
if let Some(name) = name {
|
||||
Ok(Path::new(¤t_dir).join(name))
|
||||
Ok(Path::new(¤t_dir).join(name.as_ref()))
|
||||
} else {
|
||||
match self {
|
||||
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 packages_dir = parent_dir.join("packages");
|
||||
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))
|
||||
} 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(
|
||||
context: LocationContext,
|
||||
exe_name: Option<&String>,
|
||||
exe_args: Option<Vec<&str>>,
|
||||
legacy_args: Option<&String>,
|
||||
exe_name: Option<&OsString>,
|
||||
exe_args: Option<Vec<OsString>>,
|
||||
legacy_args: Option<&OsString>,
|
||||
) -> 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 root_dir = locator.get_root_dir();
|
||||
let manifest = locator.get_manifest();
|
||||
@@ -113,17 +114,17 @@ pub fn start_impl(
|
||||
|
||||
fn start_regular(
|
||||
locator: LocatorResult,
|
||||
exe_name: Option<&String>,
|
||||
exe_args: Option<Vec<&str>>,
|
||||
legacy_args: Option<&String>,
|
||||
exe_name: Option<&OsString>,
|
||||
exe_args: Option<Vec<OsString>>,
|
||||
legacy_args: Option<&OsString>,
|
||||
) -> 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
|
||||
let exe_to_execute = locator.get_exe_to_start(exe_name)?;
|
||||
if !exe_to_execute.exists() {
|
||||
bail!("Unable to find executable to start: '{:?}'", exe_to_execute);
|
||||
}
|
||||
|
||||
|
||||
let current = locator.get_current_dir();
|
||||
info!("About to launch: '{:?}' in dir '{:?}'", exe_to_execute, current);
|
||||
|
||||
@@ -143,13 +144,14 @@ fn start_regular(
|
||||
|
||||
fn try_legacy_migration(root_dir: &PathBuf, manifest: &Manifest) -> Result<VelopackLocator> {
|
||||
info!("This is a legacy app. Will try and upgrade it now.");
|
||||
|
||||
|
||||
// if started by legacy Squirrel, the working dir of Update.exe may be inside the app-* folder,
|
||||
// meaning we can not clean up properly.
|
||||
std::env::set_current_dir(&root_dir)?;
|
||||
let 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...");
|
||||
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
|
||||
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);
|
||||
|
||||
if let Err(e) = windows::registry::remove_uninstall_entry(&locator) {
|
||||
|
||||
@@ -7,6 +7,7 @@ extern crate log;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{arg, value_parser, Command};
|
||||
use memmap2::Mmap;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::{env, path::PathBuf};
|
||||
use velopack_bins::*;
|
||||
@@ -83,7 +84,7 @@ fn main_inner() -> Result<()> {
|
||||
|
||||
let debug = matches.get_one::<PathBuf>("debug");
|
||||
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!(" Location: {:?}", env::current_exe()?);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::{dialogs_common::*, dialogs_const::*};
|
||||
use crate::windows::strings::string_to_wide;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use velopack::{
|
||||
bundle::Manifest,
|
||||
locator::{auto_locate_app_manifest, LocationContext},
|
||||
wide_strings::{string_to_wide, string_to_wide_opt},
|
||||
};
|
||||
use windows::{
|
||||
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 instruction = string_to_wide(format!("{} uninstall has completed with errors.", app_title));
|
||||
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();
|
||||
@@ -224,7 +224,7 @@ pub fn generate_confirm(
|
||||
ico: DialogIcon,
|
||||
) -> Result<DialogResult> {
|
||||
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 td_btn = TASKDIALOG_BUTTON { nButtonID: IDOK.0, pszButtonText: ok_text_buf.as_pcwstr() };
|
||||
vec![td_btn]
|
||||
@@ -246,7 +246,7 @@ pub fn generate_confirm(
|
||||
let title_buf = string_to_wide(title);
|
||||
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() {
|
||||
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<()> {
|
||||
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);
|
||||
Process::new("/usr/bin/osascript").arg("-e").arg(command).spawn().map_err(|z| anyhow!("Failed to stop application ({}).", z))?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::windows::strings;
|
||||
use ::windows::{
|
||||
core::PWSTR,
|
||||
Win32::{
|
||||
@@ -11,10 +10,11 @@ use regex::Regex;
|
||||
use semver::Version;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
fs,
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
let full_path = strings::u16_to_string(&full_path_vec);
|
||||
if let Err(_) = full_path {
|
||||
continue;
|
||||
}
|
||||
|
||||
let full_path = PathBuf::from(full_path.unwrap());
|
||||
let slice = &full_path_vec[..full_path_len as usize];
|
||||
let full_path = wide_to_os_string(slice);
|
||||
let full_path = PathBuf::from(full_path);
|
||||
if let Ok(is_subpath) = crate::windows::is_sub_path(&full_path, dir) {
|
||||
if is_subpath {
|
||||
oup.push((pid, full_path, process));
|
||||
@@ -93,21 +90,20 @@ fn _force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
||||
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 exe_to_execute = locator.get_main_exe_path();
|
||||
|
||||
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();
|
||||
if let Some(env_var) = set_env {
|
||||
debug!("Setting environment variable: {}={}", env_var, "true");
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ fn main() -> ExitCode {
|
||||
args.insert(0, "start".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;
|
||||
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 clap::{arg, value_parser, ArgAction, ArgMatches, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::{env, path::PathBuf};
|
||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
||||
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!(--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!([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")
|
||||
.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!(--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_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..))
|
||||
.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..).value_parser(value_parser!(OsString)))
|
||||
.long_flag_aliases(vec!["processStart", "processStartAndWait"])
|
||||
)
|
||||
.subcommand(Command::new("patch")
|
||||
@@ -158,8 +159,8 @@ fn main() -> Result<()> {
|
||||
|
||||
info!("--");
|
||||
info!("Starting Velopack Updater ({})", env!("NGBV_VERSION"));
|
||||
info!(" Location: {}", env::current_exe()?.to_string_lossy());
|
||||
info!(" CWD: {}", env::current_dir()?.to_string_lossy());
|
||||
info!(" Location: {:?}", env::current_exe()?);
|
||||
info!(" CWD: {:?}", env::current_dir()?);
|
||||
info!(" Verbose: {}", verbose);
|
||||
info!(" Silent: {}", silent);
|
||||
info!(" Log File: {:?}", log_file);
|
||||
@@ -220,11 +221,11 @@ fn patch(_context: LocationContext, matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_exe_args(matches: &ArgMatches) -> Option<Vec<&str>> {
|
||||
matches.get_many::<String>("EXE_ARGS").map(|v| v.map(|f| f.as_str()).collect())
|
||||
fn get_exe_args(matches: &ArgMatches) -> Option<Vec<OsString>> {
|
||||
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 package = matches.get_one::<PathBuf>("package");
|
||||
let exe_args = get_exe_args(matches);
|
||||
@@ -246,9 +247,9 @@ fn apply(context: LocationContext, matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_start_args(matches: &ArgMatches) -> (OperationWait, Option<&String>, Option<&String>, Option<Vec<&str>>) {
|
||||
let legacy_args = matches.get_one::<String>("args");
|
||||
let exe_name = matches.get_one::<String>("EXE_NAME");
|
||||
fn get_start_args(matches: &ArgMatches) -> (OperationWait, Option<&OsString>, Option<&OsString>, Option<Vec<OsString>>) {
|
||||
let legacy_args = matches.get_one::<OsString>("args");
|
||||
let exe_name = matches.get_one::<OsString>("EXE_NAME");
|
||||
let exe_args = get_exe_args(matches);
|
||||
let wait = get_op_wait(&matches);
|
||||
(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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
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 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());
|
||||
assert_eq!(wait_for_parent, OperationWait::WaitParent);
|
||||
assert_eq!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".into()));
|
||||
assert_eq!(exe_args, None);
|
||||
|
||||
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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".into()));
|
||||
assert_eq!(exe_args, None);
|
||||
|
||||
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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".into()));
|
||||
assert_eq!(exe_args, None);
|
||||
|
||||
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 (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!(exe_name, Some(&"hello.exe".to_string()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".to_string()));
|
||||
assert_eq!(exe_name, Some(&"hello.exe".into()));
|
||||
assert_eq!(legacy_args, Some(&"myarg".into()));
|
||||
assert_eq!(exe_args, None);
|
||||
|
||||
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());
|
||||
assert_eq!(wait_for_parent, OperationWait::WaitParent);
|
||||
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);
|
||||
|
||||
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());
|
||||
assert_eq!(wait_for_parent, OperationWait::WaitParent);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use anyhow::{bail, Result};
|
||||
use std::path::PathBuf;
|
||||
use velopack::wide_strings::wide_to_os_string;
|
||||
use windows::{
|
||||
core::GUID,
|
||||
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 {
|
||||
let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0);
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn get_roaming_app_data() -> Result<String> {
|
||||
pub fn get_roaming_app_data() -> Result<PathBuf> {
|
||||
get_known_folder(&FOLDERID_RoamingAppData)
|
||||
}
|
||||
|
||||
pub fn get_user_desktop() -> Result<String> {
|
||||
pub fn get_user_desktop() -> Result<PathBuf> {
|
||||
get_known_folder(&FOLDERID_Desktop)
|
||||
}
|
||||
|
||||
pub fn get_user_profile() -> Result<String> {
|
||||
pub fn get_user_profile() -> Result<PathBuf> {
|
||||
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 programs_path = Path::new(&start_menu).join("Programs");
|
||||
Ok(programs_path.to_string_lossy().to_string())
|
||||
let programs_path = start_menu.join("Programs");
|
||||
Ok(programs_path)
|
||||
}
|
||||
|
||||
pub fn get_startup() -> Result<String> {
|
||||
pub fn get_startup() -> Result<PathBuf> {
|
||||
get_known_folder(&FOLDERID_Startup)
|
||||
}
|
||||
|
||||
pub fn get_downloads() -> Result<String> {
|
||||
pub fn get_downloads() -> Result<PathBuf> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn get_program_files_x86() -> Result<String> {
|
||||
pub fn get_program_files_x86() -> Result<PathBuf> {
|
||||
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_path = Path::new(&pinned_str).join("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned");
|
||||
Ok(pinned_path.to_string_lossy().to_string())
|
||||
let pinned_path = pinned_str.join("Microsoft").join("Internet Explorer").join("Quick Launch").join("User Pinned");
|
||||
Ok(pinned_path)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ pub mod runtimes;
|
||||
pub mod splash;
|
||||
pub mod known_path;
|
||||
|
||||
pub mod strings;
|
||||
pub mod registry;
|
||||
pub mod webview2;
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use super::{runtimes, splash};
|
||||
use crate::shared::dialogs;
|
||||
use velopack::{bundle, download};
|
||||
|
||||
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> {
|
||||
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 = Path::new(downloads.as_str());
|
||||
|
||||
info!("Downloading {} missing pre-requisites...", missing.len());
|
||||
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() {
|
||||
let dep = &missing[i];
|
||||
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() {
|
||||
let window_title = if updating_from.is_some() { format!("{} Update", dep.display_name()) } else { format!("{} Setup", dep.display_name()) };
|
||||
if !exe_path.exists() {
|
||||
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());
|
||||
info!(" {}", 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);
|
||||
});
|
||||
|
||||
@@ -64,7 +65,7 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
|
||||
}
|
||||
|
||||
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 {
|
||||
warn!("A restart is required to complete the installation of {}.", dep.display_name());
|
||||
dialogs::show_restart_required(&app);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use chrono::{Datelike, Local as DateTime};
|
||||
use std::ffi::OsString;
|
||||
use velopack::locator::VelopackLocator;
|
||||
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_authors = locator.get_manifest_authors();
|
||||
|
||||
let root_path_str = locator.get_root_dir_as_string();
|
||||
let main_exe_path = locator.get_main_exe_path_as_string();
|
||||
let updater_path = locator.get_update_path_as_string();
|
||||
let root_path = locator.get_root_dir();
|
||||
let main_exe_path = locator.get_main_exe_path();
|
||||
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_kb = folder_size / 1024;
|
||||
@@ -23,8 +24,13 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
||||
let now = DateTime::now();
|
||||
let formatted_date = format!("{}{:02}{:02}", now.year(), now.month(), now.day());
|
||||
|
||||
let uninstall_cmd = format!("\"{}\" --uninstall", updater_path);
|
||||
let uninstall_quiet: String = format!("\"{}\" --uninstall --silent", updater_path);
|
||||
let mut uninstall_cmd = OsString::from("\"");
|
||||
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 (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 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("DisplayVersion", &short_version)?;
|
||||
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("QuietUninstallString", &uninstall_quiet)?;
|
||||
reg_app.set_value("UninstallString", &uninstall_cmd)?;
|
||||
|
||||
@@ -67,7 +67,7 @@ pub enum RuntimeInstallResult {
|
||||
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();
|
||||
args.push("/norestart");
|
||||
if quiet {
|
||||
@@ -77,7 +77,7 @@ fn def_installer_routine(installer_path: &str, quiet: bool) -> Result<RuntimeIns
|
||||
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 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 is_installed(&self) -> bool;
|
||||
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)]
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ impl RuntimeInfo for VCRedistInfo {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -460,7 +460,7 @@ impl RuntimeInfo for DotnetInfo {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
vec!["/silent", "/install"]
|
||||
} else {
|
||||
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 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 glob::glob;
|
||||
use same_file::is_same_file;
|
||||
use velopack::{locator::ShortcutLocationFlags, locator::VelopackLocator};
|
||||
use windows::core::{Interface, GUID, PCWSTR};
|
||||
use velopack::{locator::ShortcutLocationFlags, locator::VelopackLocator, wide_strings::*};
|
||||
use windows::core::{Interface, GUID};
|
||||
use windows::Win32::Storage::EnhancedStorage::PKEY_AppUserModel_ID;
|
||||
use windows::Win32::System::Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, StructuredStorage::InitPropVariantFromStringVector,
|
||||
@@ -17,7 +17,7 @@ use windows::Win32::UI::Shell::{
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
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";
|
||||
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_authors = next_app.get_manifest_authors();
|
||||
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_work_dir = next_app.get_current_bin_dir_as_string();
|
||||
let app_main_exe = next_app.get_main_exe_path();
|
||||
let app_work_dir = next_app.get_current_bin_dir();
|
||||
|
||||
info!("App Model ID: {:?}", app_model_id);
|
||||
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
|
||||
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) {
|
||||
warn!("Failed to update shortcut target: {}", e);
|
||||
}
|
||||
@@ -213,7 +217,7 @@ unsafe fn unsafe_update_app_manifest_lnks(next_app: &VelopackLocator, previous_a
|
||||
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);
|
||||
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)> {
|
||||
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();
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
match known::get_start_menu() {
|
||||
// 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),
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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);
|
||||
if let Ok(glob_paths) = glob(&search_glob) {
|
||||
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) => {
|
||||
if let Ok(target) = properties.get_target_path() {
|
||||
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));
|
||||
}
|
||||
} else if let Ok(work_dir) = properties.get_working_directory() {
|
||||
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));
|
||||
}
|
||||
} 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) => {
|
||||
@@ -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);
|
||||
for (flag, properties) in shortcuts {
|
||||
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;
|
||||
if let Err(e) = unsafe_delete_lnk_file(&path, remove_parent_if_empty) {
|
||||
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(());
|
||||
}
|
||||
|
||||
let path = string_to_u16(path.to_string_lossy());
|
||||
let item_result: IShellItem = SHCreateItemFromParsingName(PCWSTR(path.as_ptr()), None)?;
|
||||
let path = string_to_wide(path);
|
||||
let item_result: IShellItem = SHCreateItemFromParsingName(path.as_pcwstr(), None)?;
|
||||
let pinned_list: IStartMenuPinnedList = create_instance(&StartMenuPin)?;
|
||||
pinned_list.RemoveFromList(&item_result)?;
|
||||
Ok(())
|
||||
@@ -422,89 +427,86 @@ where
|
||||
struct Lnk {
|
||||
me: IShellLinkW,
|
||||
pf: IPersistFile,
|
||||
my_path: String,
|
||||
my_path: PathBuf,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Lnk {
|
||||
pub unsafe fn get_link_path(&self) -> String {
|
||||
pub unsafe fn get_link_path(&self) -> PathBuf {
|
||||
self.my_path.clone()
|
||||
}
|
||||
|
||||
pub unsafe fn get_arguments(&self) -> Result<String> {
|
||||
let mut pszargs = [0u16; 1024];
|
||||
self.me.GetArguments(&mut pszargs)?;
|
||||
let args = u16_to_string(pszargs)?;
|
||||
let args = wide_to_string(pszargs)?;
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
pub unsafe fn get_description(&self) -> Result<String> {
|
||||
let mut pszdesc = [0u16; 1024];
|
||||
self.me.GetDescription(&mut pszdesc)?;
|
||||
let desc = u16_to_string(pszdesc)?;
|
||||
let desc = wide_to_string(pszdesc)?;
|
||||
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 pindex = 0;
|
||||
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))
|
||||
}
|
||||
|
||||
pub unsafe fn get_target_path(&self) -> Result<String> {
|
||||
pub unsafe fn get_target_path(&self) -> Result<PathBuf> {
|
||||
let mut pszfile = [0u16; 1024];
|
||||
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)
|
||||
}
|
||||
|
||||
pub unsafe fn get_working_directory(&self) -> Result<String> {
|
||||
pub unsafe fn get_working_directory(&self) -> Result<PathBuf> {
|
||||
let mut pszdir = [0u16; 1024];
|
||||
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)
|
||||
}
|
||||
|
||||
pub unsafe fn set_arguments(&mut self, path: &str) -> Result<()> {
|
||||
let args = string_to_u16(path);
|
||||
let args = PCWSTR(args.as_ptr());
|
||||
Ok(self.me.SetArguments(args)?)
|
||||
pub unsafe fn set_arguments<P: AsRef<str>>(&mut self, path: P) -> Result<()> {
|
||||
let args = string_to_wide(path.as_ref());
|
||||
Ok(self.me.SetArguments(args.as_pcwstr())?)
|
||||
}
|
||||
|
||||
pub unsafe fn set_description(&mut self, path: &str) -> Result<()> {
|
||||
let desc = string_to_u16(path);
|
||||
let desc = PCWSTR(desc.as_ptr());
|
||||
Ok(self.me.SetDescription(desc)?)
|
||||
pub unsafe fn set_description<P: AsRef<str>>(&mut self, path: P) -> Result<()> {
|
||||
let desc = string_to_wide(path.as_ref());
|
||||
Ok(self.me.SetDescription(desc.as_pcwstr())?)
|
||||
}
|
||||
|
||||
pub unsafe fn set_icon_location(&mut self, path: &str, index: i32) -> Result<()> {
|
||||
let icon = string_to_u16(path);
|
||||
let icon = PCWSTR(icon.as_ptr());
|
||||
Ok(self.me.SetIconLocation(icon, index)?)
|
||||
pub unsafe fn set_icon_location<P: AsRef<Path>>(&mut self, path: P, index: i32) -> Result<()> {
|
||||
let icon = string_to_wide(path.as_ref());
|
||||
Ok(self.me.SetIconLocation(icon.as_pcwstr(), index)?)
|
||||
}
|
||||
|
||||
pub unsafe fn set_target_path(&mut self, path: &str) -> Result<()> {
|
||||
let target = string_to_u16(path);
|
||||
let target = PCWSTR(target.as_ptr());
|
||||
Ok(self.me.SetPath(target)?)
|
||||
pub unsafe fn set_target_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
let target = string_to_wide(path.as_ref());
|
||||
Ok(self.me.SetPath(target.as_pcwstr())?)
|
||||
}
|
||||
|
||||
pub unsafe fn set_working_directory(&mut self, path: &str) -> Result<()> {
|
||||
let work_dir = string_to_u16(path);
|
||||
let work_dir = PCWSTR(work_dir.as_ptr());
|
||||
Ok(self.me.SetWorkingDirectory(work_dir)?)
|
||||
pub unsafe fn set_working_directory<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
let work_dir = string_to_wide(path.as_ref());
|
||||
Ok(self.me.SetWorkingDirectory(work_dir.as_pcwstr())?)
|
||||
}
|
||||
|
||||
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
|
||||
// Docs: https://docs.microsoft.com/windows/win32/properties/props-system-appusermodel-id
|
||||
let store: IPropertyStore = self.me.cast()?;
|
||||
if let Some(app_model_id) = app_model_id {
|
||||
let id = string_to_u16(app_model_id);
|
||||
let id = PCWSTR(id.as_ptr());
|
||||
let variant = InitPropVariantFromStringVector(Some(&[id]))?;
|
||||
let id = string_to_wide(app_model_id.as_ref());
|
||||
let variant = InitPropVariantFromStringVector(Some(&[id.as_pcwstr()]))?;
|
||||
store.SetValue(&PKEY_AppUserModel_ID, &variant)?;
|
||||
} else {
|
||||
let prop_variant = PROPVARIANT::default(); // defaults to VT_EMPTY
|
||||
@@ -515,26 +517,26 @@ impl Lnk {
|
||||
}
|
||||
|
||||
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)?)
|
||||
}
|
||||
|
||||
pub unsafe fn save_as(&mut self, path: &str) -> Result<()> {
|
||||
let output = string_to_u16(path);
|
||||
let output = PCWSTR(output.as_ptr());
|
||||
self.my_path = path.to_string();
|
||||
Ok(self.pf.Save(output, true)?)
|
||||
pub unsafe fn save_as<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
let output = string_to_wide(path.as_ref());
|
||||
self.my_path = path.as_ref().to_path_buf();
|
||||
Ok(self.pf.Save(output.as_pcwstr(), true)?)
|
||||
}
|
||||
|
||||
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 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 = PCWSTR(link_pcwstr.as_ptr());
|
||||
|
||||
persist.Load(link_pcwstr, STGM_READWRITE)?;
|
||||
let link_pcwstr = string_to_wide(link_path);
|
||||
persist.Load(link_pcwstr.as_pcwstr(), STGM_READWRITE)?;
|
||||
|
||||
// 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
|
||||
@@ -548,19 +550,19 @@ impl Lnk {
|
||||
// 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> {
|
||||
let link: IShellLinkW = create_instance(&ShellLink)?;
|
||||
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 {
|
||||
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_run_delegate_in_com_context(move || {
|
||||
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");
|
||||
Ok(())
|
||||
})
|
||||
@@ -696,8 +698,8 @@ fn test_shortcut_full_integration() {
|
||||
assert_eq!(shortcuts[2].0, ShortcutLocationFlags::START_MENU);
|
||||
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_working_directory().unwrap(), work);
|
||||
assert_eq!(shortcuts[0].1.get_target_path().unwrap().to_string_lossy().to_string(), target);
|
||||
assert_eq!(shortcuts[0].1.get_working_directory().unwrap().to_string_lossy().to_string(), work);
|
||||
|
||||
unsafe_remove_all_shortcuts_for_root_dir(root);
|
||||
assert!(!link1.exists());
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::strings::string_to_wide;
|
||||
use anyhow::{bail, Result};
|
||||
use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, ImageFormat, ImageReader};
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use std::{io::Cursor, thread};
|
||||
use velopack::wide_strings::string_to_wide;
|
||||
use windows::{
|
||||
core::HRESULT,
|
||||
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::{
|
||||
shared::{self, runtime_arch::RuntimeArch},
|
||||
windows::strings::{string_to_u16, u16_to_string},
|
||||
};
|
||||
use crate::shared::{self, runtime_arch::RuntimeArch};
|
||||
use anyhow::{anyhow, Result};
|
||||
use normpath::PathExt;
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
path::{Path, PathBuf},
|
||||
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::{
|
||||
core::PCWSTR,
|
||||
Win32::Storage::FileSystem::GetLongPathNameW,
|
||||
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 main_exe_path = locator.get_main_exe_path();
|
||||
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;
|
||||
|
||||
info!("Running {} hook...", hook_name);
|
||||
let cmd = process::run_process(
|
||||
main_exe_path,
|
||||
args.iter().map(|f| f.to_string()).collect(),
|
||||
Some(current_path.to_string_lossy().to_string()),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
let cmd = process::run_process(main_exe_path, args, Some(current_path), false, None);
|
||||
|
||||
if let Err(e) = cmd {
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
let encoded_u16 = super::strings::string_to_u16(input);
|
||||
let encoded = PCWSTR(encoded_u16.as_ptr());
|
||||
let mut buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, None) };
|
||||
let input = input.as_ref();
|
||||
let encoded = string_to_wide(input);
|
||||
let mut buffer_size = unsafe { ExpandEnvironmentStringsW(encoded.as_pcwstr(), None) };
|
||||
if buffer_size == 0 {
|
||||
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||
}
|
||||
|
||||
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 {
|
||||
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||
}
|
||||
|
||||
super::strings::u16_to_string(buffer)
|
||||
Ok(wide_to_os_string(buffer))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -91,24 +86,23 @@ fn test_expand_environment_strings() {
|
||||
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> {
|
||||
let str = str.as_ref().to_string();
|
||||
let str = string_to_u16(str);
|
||||
let str = PCWSTR(str.as_ptr());
|
||||
pub fn get_long_path<P: AsRef<OsStr>>(str: P) -> Result<OsString> {
|
||||
let str = str.as_ref();
|
||||
let str = string_to_wide(str);
|
||||
|
||||
// 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 {
|
||||
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||
}
|
||||
|
||||
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 {
|
||||
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||
}
|
||||
|
||||
let result = u16_to_string(vec)?;
|
||||
Ok(result)
|
||||
Ok(wide_to_os_string(vec))
|
||||
}
|
||||
|
||||
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 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
|
||||
match get_long_path(&path) {
|
||||
Ok(p) => path = p,
|
||||
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),
|
||||
}
|
||||
let mut path = get_long_path(&path).unwrap_or(path.into());
|
||||
let mut parent = get_long_path(&parent).unwrap_or(parent.into());
|
||||
|
||||
path = path.to_lowercase();
|
||||
parent = parent.to_lowercase();
|
||||
path = path.to_ascii_lowercase();
|
||||
parent = parent.to_ascii_lowercase();
|
||||
|
||||
let path = PathBuf::from(path);
|
||||
let parent = PathBuf::from(parent);
|
||||
@@ -309,7 +294,11 @@ pub fn is_os_version_or_greater(version: &str) -> Result<bool> {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ use libc::{c_void, size_t};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CString,
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::Sender,
|
||||
@@ -43,7 +44,7 @@ impl UpdateSource for CCallbackUpdateSource {
|
||||
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 = 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 {
|
||||
(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)?;
|
||||
Ok(feed)
|
||||
} 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 {
|
||||
let local_file = local_file.to_string_lossy().to_string();
|
||||
let local_file_cstr = CString::new(local_file).unwrap();
|
||||
let asset_ptr = unsafe { allocate_VelopackAsset(asset) };
|
||||
|
||||
@@ -76,12 +78,12 @@ impl UpdateSource for CCallbackUpdateSource {
|
||||
}
|
||||
|
||||
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(())
|
||||
} 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 libc::{c_char, c_void, size_t};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::path::PathBuf;
|
||||
use std::mem::size_of;
|
||||
use std::path::PathBuf;
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fs::{self, File},
|
||||
@@ -7,15 +8,14 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::io::Cursor;
|
||||
|
||||
use regex::Regex;
|
||||
use semver::Version;
|
||||
use xml::EventReader;
|
||||
use xml::reader::XmlEvent;
|
||||
use xml::EventReader;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::{Error, misc};
|
||||
use crate::{misc, Error};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
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> {
|
||||
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 cursor: Box<dyn ReadSeek> = Box::new(file);
|
||||
let zip = ZipArchive::new(cursor)?;
|
||||
Ok(BundleZip {
|
||||
zip: Rc::new(RefCell::new(zip)),
|
||||
zip_from_file: true,
|
||||
file_path: Some(file_name.to_owned()),
|
||||
Ok(BundleZip {
|
||||
zip: Rc::new(RefCell::new(zip)),
|
||||
zip_from_file: true,
|
||||
file_path: Some(file_name.to_owned()),
|
||||
zip_range: 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...");
|
||||
let cursor: Box<dyn ReadSeek> = Box::new(Cursor::new(zip_range));
|
||||
let zip = ZipArchive::new(cursor)?;
|
||||
Ok(BundleZip {
|
||||
zip: Rc::new(RefCell::new(zip)),
|
||||
zip_from_file: false,
|
||||
zip_range: Some(zip_range),
|
||||
file_path: None,
|
||||
manifest: None,
|
||||
})
|
||||
Ok(BundleZip { zip: Rc::new(RefCell::new(zip)), zip_from_file: false, zip_range: Some(zip_range), file_path: None, manifest: None })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -75,7 +69,7 @@ impl BundleZip<'_> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn calculate_size(&self) -> (u64, u64) {
|
||||
let mut total_uncompressed_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> {
|
||||
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 parent = p.parent().unwrap();
|
||||
|
||||
@@ -172,7 +166,7 @@ impl BundleZip<'_> {
|
||||
{
|
||||
let idx = self.find_zip_file(predicate);
|
||||
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();
|
||||
self.extract_zip_idx_to_path(idx, path)?;
|
||||
@@ -183,15 +177,15 @@ impl BundleZip<'_> {
|
||||
if let Some(manifest) = &self.manifest {
|
||||
return Ok(manifest.clone());
|
||||
}
|
||||
|
||||
let nuspec_idx = self.find_zip_file(|name| name.ends_with(".nuspec"))
|
||||
.ok_or(Error::MissingNuspec)?;
|
||||
|
||||
let nuspec_idx =
|
||||
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 archive = self.zip.borrow_mut();
|
||||
archive.by_index(nuspec_idx)?.read_to_string(&mut contents)?;
|
||||
let app = read_manifest_from_string(&contents)?;
|
||||
|
||||
|
||||
self.manifest = Some(app.clone());
|
||||
Ok(app)
|
||||
}
|
||||
@@ -218,19 +212,23 @@ impl BundleZip<'_> {
|
||||
{
|
||||
let absolute_path = link_path.parent().unwrap().join(&target_path);
|
||||
trace!(
|
||||
"Creating symlink '{}' -> '{}', target isfile={}, isdir={}, relative={}",
|
||||
link_path.to_string_lossy(),
|
||||
absolute_path.to_string_lossy(),
|
||||
"Creating symlink '{:?}' -> '{:?}', target isfile={}, isdir={}, relative={:?}",
|
||||
link_path,
|
||||
absolute_path,
|
||||
absolute_path.is_file(),
|
||||
absolute_path.is_dir(),
|
||||
target_path.to_string_lossy()
|
||||
target_path
|
||||
);
|
||||
if absolute_path.is_file() {
|
||||
std::os::windows::fs::symlink_file(target_path, link_path)?;
|
||||
} else if absolute_path.is_dir() {
|
||||
std::os::windows::fs::symlink_dir(target_path, link_path)?;
|
||||
} 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"))]
|
||||
@@ -246,7 +244,7 @@ impl BundleZip<'_> {
|
||||
let files = self.get_file_names()?;
|
||||
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 stub_regex = Regex::new("_ExecutionStub.exe$").unwrap();
|
||||
let symlink_regex = Regex::new(".__symlink$").unwrap();
|
||||
@@ -259,7 +257,7 @@ impl BundleZip<'_> {
|
||||
let nuspec_path = current_path.join("sq.version");
|
||||
let _ = self
|
||||
.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.
|
||||
@@ -294,16 +292,16 @@ impl BundleZip<'_> {
|
||||
#[cfg(target_os = "windows")]
|
||||
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)?;
|
||||
|
||||
// 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.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
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 contents = String::new();
|
||||
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('/');
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -418,16 +416,16 @@ pub fn read_manifest_from_string(xml: &str) -> Result<Manifest, Error> {
|
||||
}
|
||||
|
||||
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) {
|
||||
return Err(Error::MissingNuspecProperty("version".to_owned()));
|
||||
return Err(Error::InvalidPackage("Missing required manifest property: version".into()));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
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() {
|
||||
@@ -485,7 +483,11 @@ fn parse_package_file_name<T: AsRef<str>>(name: T) -> Option<EntryNameInfo> {
|
||||
let mut entry = EntryNameInfo::default();
|
||||
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);
|
||||
if ver_idx.is_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.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::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{misc, Error};
|
||||
|
||||
/// 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
|
||||
A: FnMut(i16),
|
||||
{
|
||||
@@ -74,7 +75,7 @@ fn test_download_file_reports_progress() {
|
||||
let tmpfile = tempfile::NamedTempFile::new().unwrap();
|
||||
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);
|
||||
prog_count += 1;
|
||||
last_prog = p;
|
||||
@@ -117,11 +118,7 @@ fn test_interrupted_download() {
|
||||
});
|
||||
|
||||
let tmpfile = tempfile::NamedTempFile::new().unwrap();
|
||||
let result = download_url_to_file(
|
||||
&format!("http://{}", addr),
|
||||
tmpfile.path().to_str().unwrap(),
|
||||
|_| {}
|
||||
);
|
||||
let result = download_url_to_file(&format!("http://{}", addr), tmpfile.path(), |_| {});
|
||||
|
||||
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 _ = download_url_to_file(
|
||||
&format!("http://{}", addr),
|
||||
tmpfile.path().to_str().unwrap(),
|
||||
|_| {},
|
||||
).unwrap();
|
||||
let _ = download_url_to_file(&format!("http://{}", addr), tmpfile.path(), |_| {}).unwrap();
|
||||
|
||||
// Verify that the downloaded file has the expected size
|
||||
let metadata = tmpfile.path().metadata().unwrap();
|
||||
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;
|
||||
pub use app::*;
|
||||
|
||||
@@ -128,6 +130,7 @@ pub mod locator;
|
||||
/// Sources are abstractions for custom update sources (eg. url, local file, github releases, etc).
|
||||
pub mod sources;
|
||||
|
||||
maybe_pub!(wide_strings);
|
||||
maybe_pub!(download, bundle, constants, lockfile, logging, misc);
|
||||
maybe_pub_os!(process, "process_win.rs", "process_unix.rs");
|
||||
|
||||
@@ -147,13 +150,13 @@ pub enum NetworkError {
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
#[error("File does not exist: {0}")]
|
||||
FileNotFound(String),
|
||||
FileNotFound(PathBuf),
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[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})")]
|
||||
SizeInvalid(String, u64, u64),
|
||||
SizeInvalid(PathBuf, u64, u64),
|
||||
#[error("Zip error: {0}")]
|
||||
Zip(#[from] zip::result::ZipError),
|
||||
#[error("Network error: {0}")]
|
||||
@@ -162,16 +165,14 @@ pub enum Error {
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("Semver parse error: {0}")]
|
||||
Semver(#[from] semver::Error),
|
||||
#[error("This application is missing a package manifest (.nuspec) or it could not be parsed.")]
|
||||
MissingNuspec,
|
||||
#[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 update package is invalid: {0}.")]
|
||||
InvalidPackage(String),
|
||||
#[error("This application is not properly installed: {0}")]
|
||||
NotInstalled(String),
|
||||
#[error("Generic error: {0}")]
|
||||
Generic(String),
|
||||
#[error("This is not supported: {0}")]
|
||||
NotSupported(String),
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
#[cfg(target_os = "windows")]
|
||||
#[error("Win32 error: {0}")]
|
||||
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.
|
||||
pub fn new(config: &VelopackLocatorConfig) -> Result<VelopackLocator, Error> {
|
||||
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() {
|
||||
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)?;
|
||||
@@ -152,11 +152,6 @@ impl VelopackLocator {
|
||||
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.
|
||||
pub fn get_ideal_local_nupkg_path(&self, id: Option<&str>, version: Option<Version>) -> PathBuf {
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn get_temp_dir_root(&self) -> PathBuf {
|
||||
self.paths.PackagesDir.join("VelopackTemp")
|
||||
@@ -179,51 +169,26 @@ impl VelopackLocator {
|
||||
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.
|
||||
pub fn get_root_dir(&self) -> PathBuf {
|
||||
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.
|
||||
pub fn get_update_path(&self) -> PathBuf {
|
||||
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.
|
||||
pub fn get_main_exe_path(&self) -> PathBuf {
|
||||
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.
|
||||
pub fn get_current_bin_dir(&self) -> PathBuf {
|
||||
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.
|
||||
pub fn get_manifest(&self) -> Manifest {
|
||||
self.manifest.clone()
|
||||
@@ -311,10 +276,6 @@ impl VelopackLocator {
|
||||
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 {
|
||||
let packages_dir = self.get_packages_dir();
|
||||
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);
|
||||
}
|
||||
}
|
||||
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.
|
||||
pub fn find_latest_full_package(packages_dir: &PathBuf) -> Option<(PathBuf, Manifest)> {
|
||||
let packages_dir = packages_dir.to_string_lossy();
|
||||
|
||||
info!("Attempting to auto-detect package in: {}", packages_dir);
|
||||
info!("Attempting to auto-detect package in: {:?}", packages_dir);
|
||||
let mut package: Option<(PathBuf, Manifest)> = None;
|
||||
|
||||
let search_glob = format!("{}/*-full.nupkg", packages_dir);
|
||||
if let Ok(paths) = glob::glob(search_glob.as_str()) {
|
||||
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(mani) = bun.read_manifest() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use semver::Version;
|
||||
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};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -396,7 +397,7 @@ impl UpdateManager {
|
||||
let partial_file = final_target_file.with_extension("partial");
|
||||
|
||||
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(());
|
||||
}
|
||||
|
||||
@@ -405,11 +406,11 @@ impl UpdateManager {
|
||||
let delta_pattern = format!("{}/*-delta.nupkg", packages_dir.to_string_lossy());
|
||||
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) {
|
||||
Ok(paths) => {
|
||||
for path in paths.into_iter().flatten() {
|
||||
to_delete.push(path.to_string_lossy().to_string());
|
||||
to_delete.push(path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -423,20 +424,19 @@ impl UpdateManager {
|
||||
|
||||
if update.BaseRelease.is_some() && !update.DeltasToTarget.is_empty() {
|
||||
info!("Beginning delta update process.");
|
||||
if let Err(e) = self.download_and_apply_delta_updates(update, &partial_file, progress.clone()) {
|
||||
error!("Error downloading delta updates: {}", e);
|
||||
if self.download_and_apply_delta_updates(update, &partial_file, progress.clone()).is_err() {
|
||||
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)?;
|
||||
info!("Successfully downloaded file: '{}'", partial_file.to_string_lossy());
|
||||
info!("Successfully downloaded file: '{:?}'", partial_file);
|
||||
}
|
||||
} 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)?;
|
||||
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)?;
|
||||
|
||||
find_files_to_delete(&delta_pattern, &mut to_delete);
|
||||
@@ -457,7 +457,7 @@ impl UpdateManager {
|
||||
}
|
||||
|
||||
for path in to_delete {
|
||||
info!("Deleting up old package: '{}'", path);
|
||||
info!("Deleting up old package: '{:?}'", path);
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
@@ -467,23 +467,21 @@ impl UpdateManager {
|
||||
fn download_and_apply_delta_updates(
|
||||
&self,
|
||||
update: &UpdateInfo,
|
||||
target_file: &PathBuf,
|
||||
output_file: &PathBuf,
|
||||
progress: Option<Sender<i16>>,
|
||||
) -> Result<(), Error> {
|
||||
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 = base_release_path.to_string_lossy().to_string();
|
||||
let output_path = target_file.to_string_lossy().to_string();
|
||||
|
||||
let mut args: Vec<String> =
|
||||
["patch", "--old", &base_release_path, "--output", &output_path].iter().map(|s| s.to_string()).collect();
|
||||
let mut args: Vec<OsString> =
|
||||
vec!["patch".into(), "--old".into(), base_release_path.clone().into(), "--output".into(), output_file.clone().into()];
|
||||
|
||||
for (i, delta) in update.DeltasToTarget.iter().enumerate() {
|
||||
let delta_file = packages_dir.join(&delta.FileName);
|
||||
let partial_file = delta_file.with_extension("partial");
|
||||
|
||||
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)?;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
args.push("--delta".to_string());
|
||||
args.push(delta_file.to_string_lossy().to_string());
|
||||
args.push("--delta".into());
|
||||
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 {
|
||||
let _ = progress.send(70);
|
||||
@@ -508,7 +506,7 @@ impl UpdateManager {
|
||||
} else {
|
||||
let error_message = String::from_utf8_lossy(&output.stderr);
|
||||
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 {
|
||||
@@ -517,17 +515,17 @@ impl UpdateManager {
|
||||
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();
|
||||
if file_size != asset.Size {
|
||||
error!("File size mismatch for file '{}': expected {}, got {}", file.to_string_lossy(), asset.Size, file_size);
|
||||
return Err(Error::SizeInvalid(file.to_string_lossy().to_string(), asset.Size, file_size));
|
||||
error!("File size mismatch for file '{:?}': expected {}, got {}", file, asset.Size, file_size);
|
||||
return Err(Error::SizeInvalid(file.to_path_buf(), asset.Size, file_size));
|
||||
}
|
||||
|
||||
let (sha1, _) = misc::calculate_sha1_sha256(file)?;
|
||||
if !sha1.eq_ignore_ascii_case(&asset.SHA1) {
|
||||
error!("SHA1 checksum mismatch for file '{}': expected '{}', got '{}'", file.to_string_lossy(), asset.SHA1, sha1);
|
||||
return Err(Error::ChecksumInvalid(file.to_string_lossy().to_string(), asset.SHA1.clone(), sha1));
|
||||
error!("SHA1 checksum mismatch for file '{:?}': expected '{}', got '{}'", file, asset.SHA1, sha1);
|
||||
return Err(Error::ChecksumInvalid(file.to_path_buf(), asset.SHA1.clone(), sha1));
|
||||
}
|
||||
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>
|
||||
where
|
||||
A: AsRef<VelopackAsset>,
|
||||
S: AsRef<str>,
|
||||
S: AsRef<OsStr>,
|
||||
C: IntoIterator<Item = S>,
|
||||
{
|
||||
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>
|
||||
where
|
||||
A: AsRef<VelopackAsset>,
|
||||
S: AsRef<str>,
|
||||
S: AsRef<OsStr>,
|
||||
C: IntoIterator<Item = S>,
|
||||
{
|
||||
self.unsafe_apply_updates(to_apply, silent, ApplyWaitMode::WaitCurrentProcess, restart, restart_args)?;
|
||||
@@ -622,50 +620,49 @@ impl UpdateManager {
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
A: AsRef<VelopackAsset>,
|
||||
S: AsRef<str>,
|
||||
S: AsRef<OsStr>,
|
||||
C: IntoIterator<Item = S>,
|
||||
{
|
||||
let to_apply = to_apply.as_ref();
|
||||
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() {
|
||||
error!("Package does not exist on disk: '{}'", &pkg_path_str);
|
||||
return Err(Error::FileNotFound(pkg_path_str.to_string()));
|
||||
error!("Package does not exist on disk: '{:?}'", &pkg_path);
|
||||
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 {
|
||||
ApplyWaitMode::NoWait => {}
|
||||
ApplyWaitMode::WaitCurrentProcess => {
|
||||
args.push("--waitPid".to_string());
|
||||
args.push(format!("{}", std::process::id()));
|
||||
args.push("--waitPid".into());
|
||||
args.push(format!("{}", std::process::id()).into());
|
||||
}
|
||||
ApplyWaitMode::WaitPid(pid) => {
|
||||
args.push("--waitPid".to_string());
|
||||
args.push(format!("{}", pid));
|
||||
args.push("--waitPid".into());
|
||||
args.push(format!("{}", pid).into());
|
||||
}
|
||||
}
|
||||
|
||||
if silent {
|
||||
args.push("--silent".to_string());
|
||||
args.push("--silent".into());
|
||||
}
|
||||
|
||||
if !restart {
|
||||
args.push("--norestart".to_string());
|
||||
args.push("--norestart".into());
|
||||
}
|
||||
|
||||
args.push("--root".to_string());
|
||||
args.push(self.locator.get_root_dir_as_string());
|
||||
args.push("--root".into());
|
||||
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() {
|
||||
args.push("--".to_string());
|
||||
args.push("--".into());
|
||||
for arg in restart_args {
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::wide_strings::*;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{OsStr, OsString},
|
||||
io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult},
|
||||
os::{raw::c_void, windows::ffi::OsStrExt},
|
||||
os::windows::ffi::OsStrExt,
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -101,7 +102,7 @@ fn append_arg(cmd: &mut Vec<u16>, arg: &Arg, force_quotes: bool) -> IoResult<()>
|
||||
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
|
||||
// that the spawned process may recover them using CommandLineToArgvW.
|
||||
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);
|
||||
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
|
||||
// rather a concatenation of null-terminated k=v\0 sequences, with a final
|
||||
// \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);
|
||||
Ok((Some(blk.as_ptr() as *mut c_void), blk))
|
||||
Ok(Some(blk.into()))
|
||||
} 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>>(
|
||||
exe_path: P1,
|
||||
args: Vec<String>,
|
||||
args: Vec<OsString>,
|
||||
work_dir: Option<P2>,
|
||||
show_window: bool,
|
||||
) -> IoResult<SafeProcessHandle> {
|
||||
let verb = os_to_pcwstr("runas")?;
|
||||
let exe = os_to_pcwstr(exe_path.as_ref())?;
|
||||
let verb = string_to_wide("runas");
|
||||
let exe = string_to_wide(exe_path.as_ref());
|
||||
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 = 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 {
|
||||
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 {
|
||||
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
|
||||
fMask: SEE_MASK_NOCLOSEPROCESS,
|
||||
lpVerb: verb.0,
|
||||
lpFile: exe.0,
|
||||
lpVerb: verb.as_pcwstr(),
|
||||
lpFile: exe.as_pcwstr(),
|
||||
lpParameters: params,
|
||||
lpDirectory: work_dir.0,
|
||||
lpDirectory: work_dir.as_ref().map(|d| d.as_pcwstr()).unwrap_or(PCWSTR::default()),
|
||||
nShow: n_show,
|
||||
..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>>(
|
||||
exe_path: P1,
|
||||
args: Vec<String>,
|
||||
args: Vec<OsString>,
|
||||
work_dir: Option<P2>,
|
||||
show_window: bool,
|
||||
set_env: Option<HashMap<String, String>>,
|
||||
) -> IoResult<SafeProcessHandle> {
|
||||
let exe_path = exe_path.as_ref();
|
||||
let exe_path = OsString::from(exe_path);
|
||||
let exe_name_ptr = os_to_pcwstr(&exe_path)?;
|
||||
|
||||
let work_dir = work_dir.map(|d| d.as_ref().to_path_buf());
|
||||
|
||||
let exe_path = string_to_wide(exe_path.as_ref());
|
||||
let dirp = string_to_wide_opt(work_dir.map(|w| w.as_ref().to_path_buf()));
|
||||
let envp = make_envp(set_env)?;
|
||||
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 params = PWSTR(params.as_mut_ptr());
|
||||
let mut params = make_command_line(Some(exe_path.as_os_str()), &wrapped_args, false)?;
|
||||
|
||||
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()),
|
||||
};
|
||||
|
||||
let envp = make_envp(set_env)?;
|
||||
let dirp = pathopt_to_pcwstr(work_dir.as_deref())?;
|
||||
|
||||
let flags = if show_window {
|
||||
CREATE_UNICODE_ENVIRONMENT
|
||||
} else {
|
||||
@@ -339,8 +315,19 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||
};
|
||||
|
||||
unsafe {
|
||||
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, work_dir, args);
|
||||
CreateProcessW(exe_name_ptr.0, Option::Some(params), None, None, false, flags, envp.0, dirp.0, &si, &mut pi)?;
|
||||
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, dirp, args);
|
||||
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 _ = CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::{
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
use crate::bundle::Manifest;
|
||||
use crate::*;
|
||||
|
||||
/// 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,
|
||||
@@ -14,7 +14,7 @@ pub trait UpdateSource: Send + Sync {
|
||||
/// 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>;
|
||||
/// 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.
|
||||
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)]
|
||||
pub struct NoneSource {}
|
||||
|
||||
impl UpdateSource for NoneSource {
|
||||
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> {
|
||||
Err(Error::Generic("None source does not support downloads".to_owned()))
|
||||
fn download_release_entry(
|
||||
&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> {
|
||||
Box::new(self.clone())
|
||||
@@ -72,7 +77,7 @@ impl UpdateSource for AutoSource {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -111,12 +116,12 @@ impl UpdateSource for HttpSource {
|
||||
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 url = url::Url::parse(&path)?;
|
||||
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| {
|
||||
if let Some(progress_sender) = &progress_sender {
|
||||
let _ = progress_sender.send(p);
|
||||
@@ -150,15 +155,15 @@ impl UpdateSource for FileSource {
|
||||
let releases_name = format!("releases.{}.json", channel);
|
||||
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 feed: VelopackAssetFeed = serde_json::from_str(&json)?;
|
||||
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);
|
||||
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 {
|
||||
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