mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Restructure Rust into single lib.rs
This commit is contained in:
@@ -1,2 +1,8 @@
|
||||
[alias]
|
||||
bx = "build"
|
||||
bw = "build --features windows"
|
||||
tx = "test"
|
||||
tw = "test --features windows"
|
||||
|
||||
[build]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
@@ -6,11 +6,20 @@ edition = "2021"
|
||||
[features]
|
||||
windows = []
|
||||
|
||||
[lib]
|
||||
name = "velopack"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "setup"
|
||||
path = "src/setup.rs"
|
||||
required-features = ["windows"]
|
||||
|
||||
[[bin]]
|
||||
name = "stub"
|
||||
path = "src/stub.rs"
|
||||
required-features = ["windows"]
|
||||
|
||||
[[bin]]
|
||||
name = "update"
|
||||
path = "src/update.rs"
|
||||
@@ -18,17 +27,10 @@ path = "src/update.rs"
|
||||
[[bin]]
|
||||
name = "testapp"
|
||||
path = "src/testapp.rs"
|
||||
required-features = ["windows"]
|
||||
|
||||
[[bin]]
|
||||
name = "testawareapp"
|
||||
path = "src/testawareapp.rs"
|
||||
required-features = ["windows"]
|
||||
|
||||
[[bin]]
|
||||
name = "stub"
|
||||
path = "src/stub.rs"
|
||||
required-features = ["windows"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z" # optimize for size
|
||||
|
||||
202
src/Rust/src/commands/install.rs
Normal file
202
src/Rust/src/commands/install.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use crate::{dialogs, shared, shared::bundle, windows};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use memmap2::Mmap;
|
||||
use pretty_bytes_rust::pretty_bytes;
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, File},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use winsafe::{self as w, co};
|
||||
|
||||
pub fn install(debug_pkg: &Option<&PathBuf>, install_to: &Option<&PathBuf>) -> Result<()> {
|
||||
let osinfo = os_info::get();
|
||||
info!("OS: {}, Arch={}", osinfo, osinfo.architecture().unwrap_or("unknown"));
|
||||
|
||||
if !w::IsWindowsVersionOrGreater(6, 1, 1)? {
|
||||
bail!("This installer requires Windows 7 SP1 or later and cannot run.");
|
||||
}
|
||||
|
||||
let file = File::open(env::current_exe()?)?;
|
||||
let mmap = unsafe { Mmap::map(&file)? };
|
||||
let pkg = bundle::load_bundle_from_mmap(&mmap, debug_pkg)?;
|
||||
info!("Bundle loaded successfully.");
|
||||
|
||||
// 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);
|
||||
info!(" Package Title: {}", &app.title);
|
||||
info!(" Package Authors: {}", &app.authors);
|
||||
info!(" Package Description: {}", &app.description);
|
||||
info!(" Package Machine Architecture: {}", &app.machine_architecture);
|
||||
info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies);
|
||||
|
||||
let _mutex = windows::create_global_mutex(&app)?;
|
||||
|
||||
if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? {
|
||||
info!("Cancelling setup. Pre-requisites not installed.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Determining install directory...");
|
||||
let (root_path, root_is_default) = if install_to.is_some() {
|
||||
(install_to.unwrap().clone(), false)
|
||||
} else {
|
||||
let appdata = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::LocalAppData, co::KF::DONT_UNEXPAND, None)?;
|
||||
(Path::new(&appdata).join(&app.id), true)
|
||||
};
|
||||
|
||||
// path needs to exist for future operations (disk space etc)
|
||||
if !root_path.exists() {
|
||||
shared::retry_io(|| fs::create_dir_all(&root_path))?;
|
||||
}
|
||||
|
||||
let root_path_str = root_path.to_str().unwrap();
|
||||
info!("Installation Directory: {:?}", root_path_str);
|
||||
|
||||
// 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;
|
||||
w::GetDiskFreeSpaceEx(Some(&root_path_str), None, None, Some(&mut free_space))?;
|
||||
if free_space < required_space {
|
||||
bail!(
|
||||
"{} requires at least {} disk space to be installed. There is only {} available.",
|
||||
&app.title,
|
||||
pretty_bytes(required_space, None),
|
||||
pretty_bytes(free_space, None)
|
||||
);
|
||||
}
|
||||
|
||||
info!("There is {} free space available at destination, this package requires {}.", pretty_bytes(free_space, None), pretty_bytes(required_space, None));
|
||||
|
||||
// does this app support this OS / architecture?
|
||||
if !app.os_min_version.is_empty() && !windows::is_os_version_or_greater(&app.os_min_version)? {
|
||||
bail!("This application requires Windows {} or later.", &app.os_min_version);
|
||||
}
|
||||
|
||||
if !app.machine_architecture.is_empty() && !windows::is_cpu_architecture_supported(&app.machine_architecture)? {
|
||||
bail!("This application ({}) does not support your CPU architecture.", &app.machine_architecture);
|
||||
}
|
||||
|
||||
let mut root_path_renamed = String::new();
|
||||
// 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
|
||||
if !dialogs::show_overwrite_repair_dialog(&app, &root_path, root_is_default) {
|
||||
// user cancelled overwrite prompt
|
||||
error!("Directory exists, and user cancelled overwrite.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
shared::force_stop_package(&root_path)
|
||||
.map_err(|z| anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z))?;
|
||||
|
||||
root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(8));
|
||||
info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed);
|
||||
|
||||
shared::retry_io(|| fs::rename(&root_path, &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."
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
info!("Preparing and cleaning installation directory...");
|
||||
remove_dir_all::ensure_empty_dir(&root_path)?;
|
||||
|
||||
info!("Reading splash image...");
|
||||
let splash_bytes = pkg.get_splash_bytes();
|
||||
let tx = windows::splash::show_splash_dialog(app.title.to_owned(), splash_bytes, true);
|
||||
let install_result = install_impl(&pkg, &root_path, &tx);
|
||||
let _ = tx.send(windows::splash::MSG_CLOSE);
|
||||
|
||||
if install_result.is_ok() {
|
||||
info!("Installation completed successfully!");
|
||||
if !root_path_renamed.is_empty() {
|
||||
info!("Removing rollback directory...");
|
||||
let _ = shared::retry_io(|| fs::remove_dir_all(&root_path_renamed));
|
||||
}
|
||||
} else {
|
||||
error!("Installation failed!");
|
||||
if !root_path_renamed.is_empty() {
|
||||
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));
|
||||
}
|
||||
install_result?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_impl(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::mpsc::Sender<i16>) -> Result<()> {
|
||||
info!("Starting installation!");
|
||||
|
||||
let app = pkg.read_manifest()?;
|
||||
|
||||
// all application paths
|
||||
let updater_path = app.get_update_path(root_path);
|
||||
let packages_path = app.get_packages_path(root_path);
|
||||
let current_path = app.get_current_path(root_path);
|
||||
let nupkg_path = app.get_target_nupkg_path(root_path);
|
||||
let main_exe_path = app.get_main_exe_path(root_path);
|
||||
|
||||
info!("Extracting Update.exe...");
|
||||
let _ = pkg
|
||||
.extract_zip_predicate_to_path(|name| name.ends_with("Squirrel.exe"), updater_path)
|
||||
.map_err(|_| anyhow!("This installer is missing a critical binary (Update.exe). Please contact the application author."))?;
|
||||
let _ = tx.send(5);
|
||||
|
||||
info!("Copying nupkg to packages directory...");
|
||||
shared::retry_io(|| fs::create_dir_all(&packages_path))?;
|
||||
pkg.copy_bundle_to_file(&nupkg_path)?;
|
||||
let _ = tx.send(10);
|
||||
|
||||
pkg.extract_lib_contents_to_path(¤t_path, |p| {
|
||||
let _ = tx.send(((p as f32) / 100.0 * 80.0 + 10.0) as i16);
|
||||
})?;
|
||||
|
||||
if !Path::new(&main_exe_path).exists() {
|
||||
bail!("The main executable could not be found in the package. Please contact the application author.");
|
||||
}
|
||||
|
||||
info!("Creating start menu shortcut...");
|
||||
let startmenu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None)?;
|
||||
let lnk_path = Path::new(&startmenu).join("Programs").join(format!("{}.lnk", &app.title));
|
||||
if let Err(e) = windows::create_lnk(&lnk_path.to_string_lossy(), &main_exe_path, ¤t_path, None) {
|
||||
warn!("Failed to create start menu shortcut: {}", e);
|
||||
}
|
||||
|
||||
let ver_string = app.version.to_string();
|
||||
info!("Starting process install hook: \"{}\" --veloapp-install {}", &main_exe_path, &ver_string);
|
||||
let args = vec!["--veloapp-install", &ver_string];
|
||||
if let Err(e) = windows::run_process_no_console_and_wait(&main_exe_path, args, ¤t_path, Some(Duration::from_secs(30))) {
|
||||
let setup_name = format!("{} Setup {}", app.title, app.version);
|
||||
error!("Process install hook failed: {}", e);
|
||||
let _ = tx.send(windows::splash::MSG_CLOSE);
|
||||
dialogs::show_warn(
|
||||
&setup_name,
|
||||
None,
|
||||
format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e).as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
let _ = tx.send(100);
|
||||
|
||||
app.write_uninstall_entry(root_path)?;
|
||||
|
||||
if !dialogs::get_silent() {
|
||||
info!("Starting app...");
|
||||
shared::start_package(&app, &root_path, None, Some("VELOPACK_FIRSTRUN"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -9,6 +9,11 @@ mod start;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use start::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod install;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use install::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod uninstall;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn uninstall(log_file: &PathBuf) -> Result<()> {
|
||||
pub fn uninstall() -> Result<()> {
|
||||
info!("Command: Uninstall");
|
||||
let (root_path, app) = shared::detect_current_manifest()?;
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn uninstall(log_file: &PathBuf) -> Result<()> {
|
||||
shared::dialogs::show_info(format!("{} Uninstall", app.title).as_str(), None, "The application was successfully uninstalled.");
|
||||
} else {
|
||||
error!("Finished with errors.");
|
||||
shared::dialogs::show_uninstall_complete_with_errors_dialog(&app, &log_file);
|
||||
shared::dialogs::show_uninstall_complete_with_errors_dialog(&app, None);
|
||||
}
|
||||
|
||||
let dead_path = root_path.join(".dead");
|
||||
|
||||
13
src/Rust/src/lib.rs
Normal file
13
src/Rust/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub mod commands;
|
||||
pub mod logging;
|
||||
pub mod shared;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
pub use shared::{bundle, dialogs};
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
@@ -1,10 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use simplelog::*;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
pub fn trace_logger() {
|
||||
TermLogger::init(LevelFilter::Trace, Config::default(), TerminalMode::Mixed, ColorChoice::Never).unwrap();
|
||||
}
|
||||
pub fn default_logging(verbose: bool, nocolor: bool) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let default_log_file = {
|
||||
let mut my_dir = env::current_exe().unwrap();
|
||||
my_dir.pop();
|
||||
my_dir.join("Velopack.log")
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let default_log_file = {
|
||||
let (_root, manifest) = shared::detect_current_manifest().expect("Unable to load app manfiest.");
|
||||
std::path::Path::new(format!("/tmp/velopack/{}.log", manifest.id).as_str()).to_path_buf()
|
||||
};
|
||||
|
||||
setup_logging(Some(&default_log_file), true, verbose, nocolor)
|
||||
}
|
||||
|
||||
pub fn setup_logging(file: Option<&PathBuf>, console: bool, verbose: bool, nocolor: bool) -> Result<()> {
|
||||
let mut loggers: Vec<Box<dyn SharedLogger>> = Vec::new();
|
||||
@@ -29,4 +45,4 @@ pub fn setup_logging(file: Option<&PathBuf>, console: bool, verbose: bool, nocol
|
||||
|
||||
CombinedLogger::init(loggers)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod logging;
|
||||
mod shared;
|
||||
mod windows;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use shared::{bundle, dialogs};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::{arg, value_parser, Command};
|
||||
use memmap2::Mmap;
|
||||
use pretty_bytes_rust::pretty_bytes;
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, File},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use winsafe::{self as w, co};
|
||||
use std::{env, path::PathBuf};
|
||||
use velopack::*;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut arg_config = Command::new("Setup")
|
||||
@@ -59,7 +43,7 @@ fn main() -> Result<()> {
|
||||
info!(" Debug: {:?}", debug);
|
||||
}
|
||||
|
||||
let res = run(&debug, &installto);
|
||||
let res = commands::install(&debug, &installto);
|
||||
if let Err(e) = &res {
|
||||
error!("An error has occurred: {}", e);
|
||||
dialogs::show_error("Setup Error", None, format!("An error has occurred: {}", e).as_str());
|
||||
@@ -68,194 +52,3 @@ fn main() -> Result<()> {
|
||||
res?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(debug_pkg: &Option<&PathBuf>, install_to: &Option<&PathBuf>) -> Result<()> {
|
||||
let osinfo = os_info::get();
|
||||
info!("OS: {}, Arch={}", osinfo, osinfo.architecture().unwrap_or("unknown"));
|
||||
|
||||
if !w::IsWindowsVersionOrGreater(6, 1, 1)? {
|
||||
bail!("This installer requires Windows 7 SP1 or later and cannot run.");
|
||||
}
|
||||
|
||||
let file = File::open(env::current_exe()?)?;
|
||||
let mmap = unsafe { Mmap::map(&file)? };
|
||||
let pkg = bundle::load_bundle_from_mmap(&mmap, debug_pkg)?;
|
||||
info!("Bundle loaded successfully.");
|
||||
|
||||
// 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);
|
||||
info!(" Package Title: {}", &app.title);
|
||||
info!(" Package Authors: {}", &app.authors);
|
||||
info!(" Package Description: {}", &app.description);
|
||||
info!(" Package Machine Architecture: {}", &app.machine_architecture);
|
||||
info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies);
|
||||
|
||||
let _mutex = windows::create_global_mutex(&app)?;
|
||||
|
||||
if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? {
|
||||
info!("Cancelling setup. Pre-requisites not installed.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Determining install directory...");
|
||||
let (root_path, root_is_default) = if install_to.is_some() {
|
||||
(install_to.unwrap().clone(), false)
|
||||
} else {
|
||||
let appdata = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::LocalAppData, co::KF::DONT_UNEXPAND, None)?;
|
||||
(Path::new(&appdata).join(&app.id), true)
|
||||
};
|
||||
|
||||
// path needs to exist for future operations (disk space etc)
|
||||
if !root_path.exists() {
|
||||
shared::retry_io(|| fs::create_dir_all(&root_path))?;
|
||||
}
|
||||
|
||||
let root_path_str = root_path.to_str().unwrap();
|
||||
info!("Installation Directory: {:?}", root_path_str);
|
||||
|
||||
// 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;
|
||||
w::GetDiskFreeSpaceEx(Some(&root_path_str), None, None, Some(&mut free_space))?;
|
||||
if free_space < required_space {
|
||||
bail!(
|
||||
"{} requires at least {} disk space to be installed. There is only {} available.",
|
||||
&app.title,
|
||||
pretty_bytes(required_space, None),
|
||||
pretty_bytes(free_space, None)
|
||||
);
|
||||
}
|
||||
|
||||
info!("There is {} free space available at destination, this package requires {}.", pretty_bytes(free_space, None), pretty_bytes(required_space, None));
|
||||
|
||||
// does this app support this OS / architecture?
|
||||
if !app.os_min_version.is_empty() && !windows::is_os_version_or_greater(&app.os_min_version)? {
|
||||
bail!("This application requires Windows {} or later.", &app.os_min_version);
|
||||
}
|
||||
|
||||
if !app.machine_architecture.is_empty() && !windows::is_cpu_architecture_supported(&app.machine_architecture)? {
|
||||
bail!("This application ({}) does not support your CPU architecture.", &app.machine_architecture);
|
||||
}
|
||||
|
||||
let mut root_path_renamed = String::new();
|
||||
// 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
|
||||
if !dialogs::show_overwrite_repair_dialog(&app, &root_path, root_is_default) {
|
||||
// user cancelled overwrite prompt
|
||||
error!("Directory exists, and user cancelled overwrite.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
shared::force_stop_package(&root_path)
|
||||
.map_err(|z| anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z))?;
|
||||
|
||||
root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(8));
|
||||
info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed);
|
||||
|
||||
shared::retry_io(|| fs::rename(&root_path, &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."
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
info!("Preparing and cleaning installation directory...");
|
||||
remove_dir_all::ensure_empty_dir(&root_path)?;
|
||||
|
||||
info!("Reading splash image...");
|
||||
let splash_bytes = pkg.get_splash_bytes();
|
||||
let tx = windows::splash::show_splash_dialog(app.title.to_owned(), splash_bytes, true);
|
||||
let install_result = install_app(&pkg, &root_path, &tx);
|
||||
let _ = tx.send(windows::splash::MSG_CLOSE);
|
||||
|
||||
if install_result.is_ok() {
|
||||
info!("Installation completed successfully!");
|
||||
if !root_path_renamed.is_empty() {
|
||||
info!("Removing rollback directory...");
|
||||
let _ = shared::retry_io(|| fs::remove_dir_all(&root_path_renamed));
|
||||
}
|
||||
} else {
|
||||
error!("Installation failed!");
|
||||
if !root_path_renamed.is_empty() {
|
||||
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));
|
||||
}
|
||||
install_result?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_app(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::mpsc::Sender<i16>) -> Result<()> {
|
||||
info!("Starting installation!");
|
||||
|
||||
let app = pkg.read_manifest()?;
|
||||
|
||||
// all application paths
|
||||
let updater_path = app.get_update_path(root_path);
|
||||
let packages_path = app.get_packages_path(root_path);
|
||||
let current_path = app.get_current_path(root_path);
|
||||
let nupkg_path = app.get_target_nupkg_path(root_path);
|
||||
let main_exe_path = app.get_main_exe_path(root_path);
|
||||
|
||||
info!("Extracting Update.exe...");
|
||||
let _ = pkg
|
||||
.extract_zip_predicate_to_path(|name| name.ends_with("Squirrel.exe"), updater_path)
|
||||
.map_err(|_| anyhow!("This installer is missing a critical binary (Update.exe). Please contact the application author."))?;
|
||||
let _ = tx.send(5);
|
||||
|
||||
info!("Copying nupkg to packages directory...");
|
||||
shared::retry_io(|| fs::create_dir_all(&packages_path))?;
|
||||
pkg.copy_bundle_to_file(&nupkg_path)?;
|
||||
let _ = tx.send(10);
|
||||
|
||||
pkg.extract_lib_contents_to_path(¤t_path, |p| {
|
||||
let _ = tx.send(((p as f32) / 100.0 * 80.0 + 10.0) as i16);
|
||||
})?;
|
||||
|
||||
if !Path::new(&main_exe_path).exists() {
|
||||
bail!("The main executable could not be found in the package. Please contact the application author.");
|
||||
}
|
||||
|
||||
info!("Creating start menu shortcut...");
|
||||
let startmenu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None)?;
|
||||
let lnk_path = Path::new(&startmenu).join("Programs").join(format!("{}.lnk", &app.title));
|
||||
if let Err(e) = windows::create_lnk(&lnk_path.to_string_lossy(), &main_exe_path, ¤t_path, None) {
|
||||
warn!("Failed to create start menu shortcut: {}", e);
|
||||
}
|
||||
|
||||
let ver_string = app.version.to_string();
|
||||
info!("Starting process install hook: \"{}\" --veloapp-install {}", &main_exe_path, &ver_string);
|
||||
let args = vec!["--veloapp-install", &ver_string];
|
||||
if let Err(e) = windows::run_process_no_console_and_wait(&main_exe_path, args, ¤t_path, Some(Duration::from_secs(30))) {
|
||||
let setup_name = format!("{} Setup {}", app.title, app.version);
|
||||
error!("Process install hook failed: {}", e);
|
||||
let _ = tx.send(windows::splash::MSG_CLOSE);
|
||||
dialogs::show_warn(
|
||||
&setup_name,
|
||||
None,
|
||||
format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e).as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
let _ = tx.send(100);
|
||||
|
||||
app.write_uninstall_entry(root_path)?;
|
||||
|
||||
if !dialogs::get_silent() {
|
||||
info!("Starting app...");
|
||||
shared::start_package(&app, &root_path, None, Some("VELOPACK_FIRSTRUN"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn show_setup_missing_dependencies_dialog(app: &Manifest, depedency_string:
|
||||
)
|
||||
}
|
||||
|
||||
pub fn show_uninstall_complete_with_errors_dialog(app: &Manifest, log_path: &PathBuf) {
|
||||
pub fn show_uninstall_complete_with_errors_dialog(app: &Manifest, log_path: Option<&PathBuf>) {
|
||||
if get_silent() {
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +51,6 @@ pub fn show_uninstall_complete_with_errors_dialog(app: &Manifest, log_path: &Pat
|
||||
let mut content = WString::from_str(
|
||||
"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 footer = WString::from_str(format!("Log file: '<A HREF=\"na\">{}</A>'", log_path.display()));
|
||||
|
||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
||||
config.dwFlags = co::TDF::ENABLE_HYPERLINKS | co::TDF::SIZE_TO_CONTENT;
|
||||
@@ -61,14 +60,17 @@ pub fn show_uninstall_complete_with_errors_dialog(app: &Manifest, log_path: &Pat
|
||||
config.set_pszMainInstruction(Some(&mut instruction));
|
||||
config.set_pszContent(Some(&mut content));
|
||||
|
||||
if log_path.exists() {
|
||||
config.set_pszFooterIcon(w::IconId::Id(co::TD_ICON::INFORMATION.into()));
|
||||
config.set_pszFooter(Some(&mut footer));
|
||||
let footer_path = log_path.map(|p| p.to_string_lossy().to_string()).unwrap_or("".to_string());
|
||||
let mut footer = WString::from_str(format!("Log file: '<A HREF=\"na\">{}</A>'", footer_path));
|
||||
if let Some(log_path) = log_path {
|
||||
if log_path.exists() {
|
||||
config.set_pszFooterIcon(w::IconId::Id(co::TD_ICON::INFORMATION.into()));
|
||||
config.set_pszFooter(Some(&mut footer));
|
||||
config.lpCallbackData = log_path as *const PathBuf as usize;
|
||||
config.pfCallback = Some(task_dialog_callback);
|
||||
}
|
||||
}
|
||||
|
||||
config.lpCallbackData = log_path as *const PathBuf as usize;
|
||||
config.pfCallback = Some(task_dialog_callback);
|
||||
|
||||
let _ = w::TaskDialogIndirect(&config, None);
|
||||
}
|
||||
|
||||
@@ -169,4 +171,4 @@ pub fn generate_confirm(title: &str, header: Option<&str>, body: &str, ok_text:
|
||||
pub fn generate_alert(title: &str, header: Option<&str>, body: &str, ok_text: Option<&str>, btns: DialogButton, ico: DialogIcon) -> Result<()> {
|
||||
let _ = generate_confirm(title, header, body, ok_text, btns, ico).map(|_| ())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,7 @@ fn main() -> ExitCode {
|
||||
let my_path = std::env::current_exe().unwrap();
|
||||
let my_name = my_path.file_name().unwrap().to_string_lossy();
|
||||
|
||||
let mut log_path = my_path.clone();
|
||||
log_path.pop();
|
||||
log_path.push("Velopack.log");
|
||||
|
||||
let _ = logging::setup_logging(Some(&log_path), false, true, true);
|
||||
let _ = logging::default_logging(false, false);
|
||||
|
||||
let mut update_exe = my_path.clone();
|
||||
update_exe.pop();
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod commands;
|
||||
mod logging;
|
||||
mod shared;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use clap::{arg, value_parser, ArgMatches, Command};
|
||||
use shared::dialogs;
|
||||
use std::{env, path::PathBuf};
|
||||
use velopack::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn root_command() -> Command {
|
||||
@@ -80,37 +70,29 @@ fn main() -> Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let matches = root_command().get_matches();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let default_log_file = {
|
||||
let mut my_dir = env::current_exe().unwrap();
|
||||
my_dir.pop();
|
||||
my_dir.join("Velopack.log")
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let default_log_file = {
|
||||
let (_root, manifest) = shared::detect_current_manifest().expect("Unable to load app manfiest.");
|
||||
std::path::Path::new(format!("/tmp/velopack/{}.log", manifest.id).as_str()).to_path_buf()
|
||||
};
|
||||
|
||||
let verbose = matches.get_flag("verbose");
|
||||
let silent = matches.get_flag("silent");
|
||||
let nocolor = matches.get_flag("nocolor");
|
||||
let log_file = matches.get_one("log").unwrap_or(&default_log_file);
|
||||
let log_file = matches.get_one("log");
|
||||
|
||||
dialogs::set_silent(silent);
|
||||
logging::setup_logging(Some(&log_file), true, verbose, nocolor)?;
|
||||
|
||||
if let Some(log_file) = log_file {
|
||||
logging::setup_logging(Some(&log_file), true, verbose, nocolor)?;
|
||||
} else {
|
||||
logging::default_logging(verbose, nocolor)?;
|
||||
}
|
||||
|
||||
info!("Starting Velopack Updater ({})", env!("NGBV_VERSION"));
|
||||
info!(" Location: {}", env::current_exe()?.to_string_lossy());
|
||||
info!(" Verbose: {}", verbose);
|
||||
info!(" Silent: {}", silent);
|
||||
info!(" Log File: {}", log_file.to_string_lossy());
|
||||
info!(" Log File: {:?}", log_file);
|
||||
|
||||
let (subcommand, subcommand_matches) = matches.subcommand().ok_or_else(|| anyhow!("No subcommand was used. Try `--help` for more information."))?;
|
||||
let result = match subcommand {
|
||||
#[cfg(target_os = "windows")]
|
||||
"uninstall" => uninstall(subcommand_matches, log_file).map_err(|e| anyhow!("Uninstall error: {}", e)),
|
||||
"uninstall" => uninstall(subcommand_matches).map_err(|e| anyhow!("Uninstall error: {}", e)),
|
||||
#[cfg(target_os = "windows")]
|
||||
"start" => start(&subcommand_matches).map_err(|e| anyhow!("Start error: {}", e)),
|
||||
"apply" => apply(subcommand_matches).map_err(|e| anyhow!("Apply error: {}", e)),
|
||||
@@ -176,9 +158,9 @@ fn start(matches: &ArgMatches) -> Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn uninstall(_matches: &ArgMatches, log_file: &PathBuf) -> Result<()> {
|
||||
fn uninstall(_matches: &ArgMatches) -> Result<()> {
|
||||
info!("Command: Uninstall");
|
||||
commands::uninstall(log_file)
|
||||
commands::uninstall()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -372,19 +372,19 @@ pub fn assert_can_run_binary_authenticode<P: AsRef<Path>>(path: P) -> Result<()>
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_authenticode() {
|
||||
fn verify_authenticode_against_powershell(path: &str) -> bool {
|
||||
let command = format!("Get-AuthenticodeSignature \"{}\" | select Status -expandproperty Status", path);
|
||||
let args = command.split_whitespace().collect();
|
||||
let ps_output = super::run_process_no_console_and_wait("powershell", args, std::env::current_dir().unwrap(), None).unwrap();
|
||||
let ps_result = ps_output.trim() == "Valid";
|
||||
let my_result = check_authenticode_signature(path).unwrap_or(false);
|
||||
assert!(ps_result == my_result);
|
||||
return my_result;
|
||||
}
|
||||
|
||||
assert!(verify_authenticode_against_powershell(r"C:\Windows\System32\notepad.exe"));
|
||||
assert!(verify_authenticode_against_powershell(r"C:\Windows\System32\cmd.exe"));
|
||||
assert!(verify_authenticode_against_powershell(r"C:\Users\Caelan\AppData\Local\Programs\Microsoft VS Code\Code.exe"));
|
||||
assert!(!verify_authenticode_against_powershell(r"C:\Users\Caelan\AppData\Local\Clowd\Update.exe"));
|
||||
assert!(!verify_authenticode_against_powershell(r"C:\Users\Caelan\.cargo\bin\cargo.exe"));
|
||||
}
|
||||
|
||||
fn verify_authenticode_against_powershell(path: &str) -> bool {
|
||||
let command = format!("Get-AuthenticodeSignature \"{}\" | select Status -expandproperty Status", path);
|
||||
let args = command.split_whitespace().collect();
|
||||
let ps_output = super::run_process_no_console_and_wait("powershell", args, std::env::current_dir().unwrap(), None).unwrap();
|
||||
let ps_result = ps_output.trim() == "Valid";
|
||||
let my_result = check_authenticode_signature(path).unwrap_or(false);
|
||||
assert!(ps_result == my_result);
|
||||
return my_result;
|
||||
}
|
||||
Reference in New Issue
Block a user