From 437c342bd91ca1291dfdf0b09e7dd70abd1c6f7a Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Mon, 25 Dec 2023 14:08:07 +0000 Subject: [PATCH] Install new required dependencies when updating --- src/Rust/src/commands/apply.rs | 10 +++- src/Rust/src/setup.rs | 72 ++++---------------------- src/Rust/src/shared/dialogs.rs | 22 +++++++- src/Rust/src/windows/mod.rs | 2 + src/Rust/src/windows/prerequisite.rs | 77 ++++++++++++++++++++++++++++ src/Rust/src/windows/splash.rs | 24 ++++++--- src/Rust/src/windows/util.rs | 6 ++- 7 files changed, 140 insertions(+), 73 deletions(-) create mode 100644 src/Rust/src/windows/prerequisite.rs diff --git a/src/Rust/src/commands/apply.rs b/src/Rust/src/commands/apply.rs index 34f7ef57a..4f928a89b 100644 --- a/src/Rust/src/commands/apply.rs +++ b/src/Rust/src/commands/apply.rs @@ -31,6 +31,9 @@ fn apply_package<'a>(package: Option<&PathBuf>) -> Result<()> { let (root_path, app) = shared::detect_current_manifest()?; + #[cfg(target_os = "windows")] + let _mutex = crate::windows::create_global_mutex(&app)?; + if let Some(pkg) = package { info!("Loading package from argument '{}'.", pkg.to_string_lossy()); let bun = bundle::load_bundle_from_file(&pkg)?; @@ -63,11 +66,16 @@ fn apply_package<'a>(package: Option<&PathBuf>) -> Result<()> { let package_manifest = package_manifest.unwrap(); - let found_version = package_manifest.clone().version; + let found_version = (&package_manifest.version).to_owned(); if found_version <= app.version { bail!("Latest package found is {}, which is not newer than current version {}.", found_version, app.version); } + #[cfg(target_os = "windows")] + if !crate::windows::prerequisite::prompt_and_install_all_missing(&package_manifest, Some(&app.version))? { + bail!("Stopping apply. Pre-requisites are missing."); + } + info!("Applying package to current: {}", found_version); #[cfg(target_os = "windows")] diff --git a/src/Rust/src/setup.rs b/src/Rust/src/setup.rs index d25b81eeb..07a238a74 100644 --- a/src/Rust/src/setup.rs +++ b/src/Rust/src/setup.rs @@ -12,7 +12,6 @@ extern crate simplelog; extern crate lazy_static; use shared::{bundle, dialogs}; -use windows::{download, runtimes, splash}; use anyhow::{anyhow, bail, Result}; use clap::{arg, value_parser, Command}; @@ -96,66 +95,11 @@ fn run(debug_pkg: &Option<&PathBuf>, install_to: &Option<&PathBuf>) -> Result<() info!(" Package Machine Architecture: {}", &app.machine_architecture); info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies); - let mutex_name = format!("clowdsquirrel-{}", &app.id); - info!("Attempting to open global system mutex: '{}'", &mutex_name); - let _mutex = windows::create_global_mutex(&mutex_name)?; + let _mutex = windows::create_global_mutex(&app)?; - info!("Checking application pre-requisites..."); - let dependencies = runtimes::parse_dependency_list(&app.runtime_dependencies); - let mut missing: Vec<&Box> = Vec::new(); - let mut missing_str = String::new(); - - for i in 0..dependencies.len() { - let dep = &dependencies[i]; - if dep.is_installed() { - info!(" {} is already installed.", dep.display_name()); - continue; - } - info!(" {} is missing.", dep.display_name()); - if !missing.is_empty() { - missing_str += ", "; - } - missing.push(dep); - missing_str += dep.display_name(); - } - - let splash_bytes = pkg.get_splash_bytes(); - - if !missing.is_empty() { - if !dialogs::show_missing_dependencies_dialog(&app, &missing_str) { - error!("User cancelled pre-requisite installation."); - return Ok(()); - } - - let downloads = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Downloads, co::KF::DONT_UNEXPAND, None)?; - let downloads = Path::new(downloads.as_str()); - - info!("Downloading {} missing pre-requisites...", missing.len()); - let quiet = dialogs::get_silent(); - - 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()); - - if !exe_name.exists() { - let tx = splash::show_splash_in_new_thread(app.title.to_owned(), splash_bytes.clone(), true); - info!(" Downloading {}...", dep.display_name()); - let result = download::download_url_to_file(&url, &exe_name.to_str().unwrap(), |p| { - let _ = tx.send(p); - }); - let _ = tx.send(splash::MSG_CLOSE); - result?; - } - - info!(" Installing {}...", dep.display_name()); - let result = dep.install(exe_name.to_str().unwrap(), quiet)?; - if result == runtimes::RuntimeInstallResult::RestartRequired { - warn!("A restart is required to complete the installation of {}.", dep.display_name()); - dialogs::show_restart_required(&app); - return Ok(()); - } - } + if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? { + info!("Cancelling setup. Pre-requisites not installed."); + return Ok(()); } info!("Determining install directory..."); @@ -226,9 +170,11 @@ fn run(debug_pkg: &Option<&PathBuf>, install_to: &Option<&PathBuf>) -> Result<() info!("Preparing and cleaning installation directory..."); remove_dir_all::ensure_empty_dir(&root_path)?; - let tx = splash::show_splash_in_new_thread(app.title.to_owned(), splash_bytes.clone(), true); + 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(splash::MSG_CLOSE); + let _ = tx.send(windows::splash::MSG_CLOSE); if install_result.is_ok() { info!("Installation completed successfully!"); @@ -294,7 +240,7 @@ fn install_app(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::mp 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(splash::MSG_CLOSE); + let _ = tx.send(windows::splash::MSG_CLOSE); dialogs::show_warning( format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e), setup_name, diff --git a/src/Rust/src/shared/dialogs.rs b/src/Rust/src/shared/dialogs.rs index d96a6a464..d6cf6e4c7 100644 --- a/src/Rust/src/shared/dialogs.rs +++ b/src/Rust/src/shared/dialogs.rs @@ -56,7 +56,27 @@ pub fn show_restart_required(app: &Manifest) { ); } -pub fn show_missing_dependencies_dialog(app: &Manifest, depedency_string: &str) -> bool { +pub fn show_update_missing_dependencies_dialog(app: &Manifest, depedency_string: &str, from: &semver::Version, to: &semver::Version) -> bool { + if get_silent() { + // this has different behavior to show_setup_missing_dependencies_dialog, + // if silent is true then we will bail because the app is probably exiting + // and installing dependencies may result in a UAC prompt. + warn!("Cancelling pre-requisite installation because silent flag is true."); + return false; + } + + let hwnd = w::HWND::GetDesktopWindow(); + ok_cancel( + &hwnd, + format!("{} Update", app.title).as_str(), + Some(format!("{} would like to update from {} to {}", app.title, from, to).as_str()), + format!("{} {to} has missing dependencies which need to be installed: {}, would you like to continue?", app.title, depedency_string).as_str(), + Some("Install & Update"), + ) + .unwrap_or(false) +} + +pub fn show_setup_missing_dependencies_dialog(app: &Manifest, depedency_string: &str) -> bool { if get_silent() { return true; } diff --git a/src/Rust/src/windows/mod.rs b/src/Rust/src/windows/mod.rs index e45529a5f..b0eebcc4d 100644 --- a/src/Rust/src/windows/mod.rs +++ b/src/Rust/src/windows/mod.rs @@ -1,5 +1,7 @@ pub mod download; +pub mod prerequisite; pub mod runtimes; + mod self_delete; mod shortcuts; pub mod splash; diff --git a/src/Rust/src/windows/prerequisite.rs b/src/Rust/src/windows/prerequisite.rs new file mode 100644 index 000000000..242be11dc --- /dev/null +++ b/src/Rust/src/windows/prerequisite.rs @@ -0,0 +1,77 @@ +use super::{download, runtimes, splash}; +use crate::shared::{bundle, dialogs}; + +use anyhow::Result; +use std::path::Path; +use winsafe::{self as w, co}; + +pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result { + info!("Checking application pre-requisites..."); + let dependencies = super::runtimes::parse_dependency_list(&app.runtime_dependencies); + let mut missing: Vec<&Box> = Vec::new(); + let mut missing_str = String::new(); + + for i in 0..dependencies.len() { + let dep = &dependencies[i]; + if dep.is_installed() { + info!(" {} is already installed.", dep.display_name()); + continue; + } + info!(" {} is missing.", dep.display_name()); + if !missing.is_empty() { + missing_str += ", "; + } + missing.push(dep); + missing_str += dep.display_name(); + } + + if !missing.is_empty() { + if let Some(from_version) = updating_from { + if !dialogs::show_update_missing_dependencies_dialog(&app, &missing_str, &from_version, &app.version) { + error!("User cancelled pre-requisite installation."); + return Ok(false); + } + } else { + if !dialogs::show_setup_missing_dependencies_dialog(&app, &missing_str) { + error!("User cancelled pre-requisite installation."); + return Ok(false); + } + } + + let downloads = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Downloads, co::KF::DONT_UNEXPAND, None)?; + let downloads = Path::new(downloads.as_str()); + + info!("Downloading {} missing pre-requisites...", missing.len()); + let quiet = dialogs::get_silent(); + + 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()); + + if !exe_name.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 _ = tx.send(p); + }); + + let _ = tx.send(splash::MSG_CLOSE); + result?; + } + + info!(" Installing {}...", dep.display_name()); + let result = dep.install(exe_name.to_str().unwrap(), quiet)?; + if result == runtimes::RuntimeInstallResult::RestartRequired { + warn!("A restart is required to complete the installation of {}.", dep.display_name()); + dialogs::show_restart_required(&app); + return Ok(false); + } + } + } + + Ok(true) +} diff --git a/src/Rust/src/windows/splash.rs b/src/Rust/src/windows/splash.rs index db2d33b52..d344d5612 100644 --- a/src/Rust/src/windows/splash.rs +++ b/src/Rust/src/windows/splash.rs @@ -17,7 +17,17 @@ const MSG_NOMESSAGE: i16 = -99; pub const MSG_CLOSE: i16 = -1; // pub const MSG_INDEFINITE: i16 = -2; -pub fn show_splash_in_new_thread(app_name: String, imgstream: Option>, delay: bool) -> Sender { +pub fn show_progress_dialog, T2: AsRef>(window_title: T1, content: T2) -> Sender { + let window_title = window_title.as_ref().to_string(); + let content = content.as_ref().to_string(); + let (tx, rx) = mpsc::channel::(); + thread::spawn(move || { + show_com_ctl_progress_dialog(rx, &window_title, &content); + }); + tx +} + +pub fn show_splash_dialog(app_name: String, imgstream: Option>, delay: bool) -> Sender { let (tx, rx) = mpsc::channel::(); let tx2 = tx.clone(); thread::spawn(move || { @@ -59,7 +69,9 @@ pub fn show_splash_in_new_thread(app_name: String, imgstream: Option>, d Ok(()) }); } else { - show_com_ctl_progress_dialog(app_name, rx); + let setup_name = format!("{} Setup", app_name); + let content = "Please Wait..."; + show_com_ctl_progress_dialog(rx, setup_name.as_str(), content); } }); tx @@ -289,14 +301,14 @@ pub struct ComCtlProgressWindow { rx: Rc>, } -fn show_com_ctl_progress_dialog(app_name: String, rx: Receiver) { - let mut setup_name = WString::from_str(format!("{} Setup", app_name)); - let mut content = WString::from_str("Please Wait..."); +fn show_com_ctl_progress_dialog(rx: Receiver, window_title: &str, content: &str) { + let mut window_title = WString::from_str(window_title); + let mut content = WString::from_str(content); let mut config: w::TASKDIALOGCONFIG = Default::default(); config.dwFlags = co::TDF::SIZE_TO_CONTENT | co::TDF::SHOW_PROGRESS_BAR | co::TDF::CALLBACK_TIMER; config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION)); - config.set_pszWindowTitle(Some(&mut setup_name)); + config.set_pszWindowTitle(Some(&mut window_title)); config.set_pszMainInstruction(Some(&mut content)); // if (_icon != null) { diff --git a/src/Rust/src/windows/util.rs b/src/Rust/src/windows/util.rs index cdcd3b46e..48246a86b 100644 --- a/src/Rust/src/windows/util.rs +++ b/src/Rust/src/windows/util.rs @@ -34,8 +34,10 @@ pub fn run_hook(app: &shared::bundle::Manifest, root_path: &PathBuf, hook_name: let _ = shared::force_stop_package(&root_path); } -pub fn create_global_mutex(name: &str) -> Result { - let encoded = name.encode_utf16().chain([0u16]).collect::>(); +pub fn create_global_mutex(app: &shared::bundle::Manifest) -> Result { + let mutex_name = format!("clowdsquirrel-{}", &app.id); + info!("Attempting to open global system mutex: '{}'", &mutex_name); + let encoded = mutex_name.encode_utf16().chain([0u16]).collect::>(); let pw = PCWSTR(encoded.as_ptr()); let mutex = unsafe { CreateMutexW(None, true, pw) }?; match unsafe { GetLastError() } {