Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Install new required dependencies when updating
Browse files Browse the repository at this point in the history
  • Loading branch information
caesay committed Dec 25, 2023
1 parent 3ae9844 commit 437c342
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 73 deletions.
10 changes: 9 additions & 1 deletion src/Rust/src/commands/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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")]
Expand Down
72 changes: 9 additions & 63 deletions src/Rust/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<dyn runtimes::RuntimeInfo>> = 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...");
Expand Down Expand Up @@ -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!");
Expand Down Expand Up @@ -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, &current_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,
Expand Down
22 changes: 21 additions & 1 deletion src/Rust/src/shared/dialogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Rust/src/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod download;
pub mod prerequisite;
pub mod runtimes;

mod self_delete;
mod shortcuts;
pub mod splash;
Expand Down
77 changes: 77 additions & 0 deletions src/Rust/src/windows/prerequisite.rs
Original file line number Diff line number Diff line change
@@ -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<bool> {
info!("Checking application pre-requisites...");
let dependencies = super::runtimes::parse_dependency_list(&app.runtime_dependencies);
let mut missing: Vec<&Box<dyn runtimes::RuntimeInfo>> = 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)
}
24 changes: 18 additions & 6 deletions src/Rust/src/windows/splash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>, delay: bool) -> Sender<i16> {
pub fn show_progress_dialog<T1: AsRef<str>, T2: AsRef<str>>(window_title: T1, content: T2) -> Sender<i16> {
let window_title = window_title.as_ref().to_string();
let content = content.as_ref().to_string();
let (tx, rx) = mpsc::channel::<i16>();
thread::spawn(move || {
show_com_ctl_progress_dialog(rx, &window_title, &content);
});
tx
}

pub fn show_splash_dialog(app_name: String, imgstream: Option<Vec<u8>>, delay: bool) -> Sender<i16> {
let (tx, rx) = mpsc::channel::<i16>();
let tx2 = tx.clone();
thread::spawn(move || {
Expand Down Expand Up @@ -59,7 +69,9 @@ pub fn show_splash_in_new_thread(app_name: String, imgstream: Option<Vec<u8>>, 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
Expand Down Expand Up @@ -289,14 +301,14 @@ pub struct ComCtlProgressWindow {
rx: Rc<Receiver<i16>>,
}

fn show_com_ctl_progress_dialog(app_name: String, rx: Receiver<i16>) {
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<i16>, 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) {
Expand Down
6 changes: 4 additions & 2 deletions src/Rust/src/windows/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Foundation::HANDLE> {
let encoded = name.encode_utf16().chain([0u16]).collect::<Vec<u16>>();
pub fn create_global_mutex(app: &shared::bundle::Manifest) -> Result<Foundation::HANDLE> {
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::<Vec<u16>>();
let pw = PCWSTR(encoded.as_ptr());
let mutex = unsafe { CreateMutexW(None, true, pw) }?;
match unsafe { GetLastError() } {
Expand Down

0 comments on commit 437c342

Please sign in to comment.