summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--installer-downloader/src/controller.rs4
-rw-r--r--installer-downloader/src/delegate.rs3
-rw-r--r--installer-downloader/src/ui_downloader.rs19
-rw-r--r--installer-downloader/src/winapi_impl/delegate.rs4
-rw-r--r--installer-downloader/tests/controller.rs40
-rw-r--r--installer-downloader/tests/snapshots/controller__download-2.snap1
-rw-r--r--installer-downloader/tests/snapshots/controller__download-3.snap2
-rw-r--r--installer-downloader/tests/snapshots/controller__download.snap1
-rw-r--r--installer-downloader/tests/snapshots/controller__failed_verification.snap1
-rw-r--r--installer-downloader/tests/snapshots/controller__fetch_version-2.snap1
-rw-r--r--installer-downloader/tests/snapshots/controller__fetch_version.snap1
-rw-r--r--mullvad-update/src/app.rs35
12 files changed, 98 insertions, 14 deletions
diff --git a/installer-downloader/src/controller.rs b/installer-downloader/src/controller.rs
index c5aebaa371..e7b8a6bb40 100644
--- a/installer-downloader/src/controller.rs
+++ b/installer-downloader/src/controller.rs
@@ -161,9 +161,7 @@ where
});
let ui_downloader = UiAppDownloader::new(self_, downloader);
- let _ = tx.send(tokio::spawn(async move {
- let _ = app::install_and_upgrade(ui_downloader).await;
- }));
+ let _ = tx.send(tokio::spawn(app::install_and_upgrade(ui_downloader)));
});
active_download = rx.await.ok();
}
diff --git a/installer-downloader/src/delegate.rs b/installer-downloader/src/delegate.rs
index beca2e6282..3e82d2ef31 100644
--- a/installer-downloader/src/delegate.rs
+++ b/installer-downloader/src/delegate.rs
@@ -62,6 +62,9 @@ pub trait AppDelegate {
/// Hide beta text
fn hide_beta_text(&mut self);
+ /// Exit the application
+ fn quit(&mut self);
+
/// Create queue for scheduling actions on UI thread
fn queue(&self) -> Self::Queue;
}
diff --git a/installer-downloader/src/ui_downloader.rs b/installer-downloader/src/ui_downloader.rs
index 5658297082..7ae37df3e5 100644
--- a/installer-downloader/src/ui_downloader.rs
+++ b/installer-downloader/src/ui_downloader.rs
@@ -88,6 +88,25 @@ impl<Delegate: AppDelegate, Downloader: AppDownloader + Send + 'static> AppDownl
}
}
}
+
+ async fn install(&mut self) -> Result<(), app::DownloadError> {
+ match self.downloader.install().await {
+ Ok(()) => {
+ self.queue.queue_main(move |self_| {
+ // Success!
+ self_.quit();
+ });
+ Ok(())
+ }
+ Err(error) => {
+ self.queue.queue_main(move |self_| {
+ self_.set_download_text("ERROR: Failed to launch installer!");
+ });
+
+ Err(error)
+ }
+ }
+ }
}
/// Implementation of [fetch::ProgressUpdater] that updates some [AppDelegate].
diff --git a/installer-downloader/src/winapi_impl/delegate.rs b/installer-downloader/src/winapi_impl/delegate.rs
index 4414559e7c..353dff9691 100644
--- a/installer-downloader/src/winapi_impl/delegate.rs
+++ b/installer-downloader/src/winapi_impl/delegate.rs
@@ -91,6 +91,10 @@ impl AppDelegate for AppWindow {
self.beta_link.set_visible(false);
}
+ fn quit(&mut self) {
+ nwg::stop_thread_dispatch();
+ }
+
fn queue(&self) -> Self::Queue {
Queue {
main_wnd: self.window.handle,
diff --git a/installer-downloader/tests/controller.rs b/installer-downloader/tests/controller.rs
index af46452e89..460e1c168a 100644
--- a/installer-downloader/tests/controller.rs
+++ b/installer-downloader/tests/controller.rs
@@ -36,16 +36,16 @@ impl VersionInfoProvider for FakeVersionInfoProvider {
}
/// Downloader for which all steps immediately succeed
-pub type FakeAppDownloaderHappyPath = FakeAppDownloader<true, true>;
+pub type FakeAppDownloaderHappyPath = FakeAppDownloader<true, true, true>;
/// Downloader for which the download step fails
-pub type FakeAppDownloaderDownloadFail = FakeAppDownloader<false, false>;
+pub type FakeAppDownloaderDownloadFail = FakeAppDownloader<false, false, false>;
-/// Downloader for which the final verification step fails
-pub type FakeAppDownloaderVerifyFail = FakeAppDownloader<true, false>;
+/// Downloader for which the verification step fails
+pub type FakeAppDownloaderVerifyFail = FakeAppDownloader<true, false, false>;
-impl<const A: bool, const B: bool> From<UiAppDownloaderParameters<FakeAppDelegate>>
- for FakeAppDownloader<A, B>
+impl<const A: bool, const B: bool, const C: bool> From<UiAppDownloaderParameters<FakeAppDelegate>>
+ for FakeAppDownloader<A, B, C>
{
fn from(params: UiAppDownloaderParameters<FakeAppDelegate>) -> Self {
FakeAppDownloader { params }
@@ -57,13 +57,18 @@ impl<const A: bool, const B: bool> From<UiAppDownloaderParameters<FakeAppDelegat
/// Parameters:
/// * EXE_SUCCEED - whether fetching the binary succeeds
/// * VERIFY_SUCCEED - whether verifying the binary succeeds
-pub struct FakeAppDownloader<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool> {
+/// * LAUNCH_SUCCEED - whether launching the binary succeeds
+pub struct FakeAppDownloader<
+ const EXE_SUCCEED: bool,
+ const VERIFY_SUCCEED: bool,
+ const LAUNCH_SUCCEED: bool,
+> {
params: UiAppDownloaderParameters<FakeAppDelegate>,
}
#[async_trait::async_trait]
-impl<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool> AppDownloader
- for FakeAppDownloader<EXE_SUCCEED, VERIFY_SUCCEED>
+impl<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool, const LAUNCH_SUCCEED: bool> AppDownloader
+ for FakeAppDownloader<EXE_SUCCEED, VERIFY_SUCCEED, LAUNCH_SUCCEED>
{
async fn create_cache_dir(&mut self) -> Result<(), DownloadError> {
Ok(())
@@ -91,6 +96,16 @@ impl<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool> AppDownloader
)))
}
}
+
+ async fn install(&mut self) -> Result<(), DownloadError> {
+ if LAUNCH_SUCCEED {
+ Ok(())
+ } else {
+ Err(DownloadError::InstallFailed(anyhow::anyhow!(
+ "install failed"
+ )))
+ }
+ }
}
/// A fake queue that stores callbacks so that tests can run them later.
@@ -140,6 +155,7 @@ pub struct DelegateState {
pub download_progress: u32,
pub download_progress_visible: bool,
pub beta_text_visible: bool,
+ pub quit: bool,
/// Record of method calls.
pub call_log: Vec<String>,
}
@@ -244,6 +260,11 @@ impl AppDelegate for FakeAppDelegate {
self.state.beta_text_visible = false;
}
+ fn quit(&mut self) {
+ self.state.call_log.push("quit".into());
+ self.state.quit = true;
+ }
+
fn queue(&self) -> Self::Queue {
self.queue.clone()
}
@@ -311,6 +332,7 @@ async fn test_download() {
queue.run_callbacks(&mut delegate);
// Everything including verification should have succeeded
+ // Downloader should have quit
assert_yaml_snapshot!(delegate.state);
}
diff --git a/installer-downloader/tests/snapshots/controller__download-2.snap b/installer-downloader/tests/snapshots/controller__download-2.snap
index e86d9055d7..390e172778 100644
--- a/installer-downloader/tests/snapshots/controller__download-2.snap
+++ b/installer-downloader/tests/snapshots/controller__download-2.snap
@@ -12,6 +12,7 @@ download_button_enabled: true
download_progress: 0
download_progress_visible: true
beta_text_visible: false
+quit: false
call_log:
- hide_download_progress
- show_download_button
diff --git a/installer-downloader/tests/snapshots/controller__download-3.snap b/installer-downloader/tests/snapshots/controller__download-3.snap
index d7c86160de..6d918cf341 100644
--- a/installer-downloader/tests/snapshots/controller__download-3.snap
+++ b/installer-downloader/tests/snapshots/controller__download-3.snap
@@ -12,6 +12,7 @@ download_button_enabled: true
download_progress: 100
download_progress_visible: true
beta_text_visible: false
+quit: true
call_log:
- hide_download_progress
- show_download_button
@@ -36,3 +37,4 @@ call_log:
- "set_download_text: Download complete. Verifying..."
- disable_cancel_button
- "set_download_text: Verification successful. Starting install..."
+ - quit
diff --git a/installer-downloader/tests/snapshots/controller__download.snap b/installer-downloader/tests/snapshots/controller__download.snap
index 45bd1796ad..3d79a1922e 100644
--- a/installer-downloader/tests/snapshots/controller__download.snap
+++ b/installer-downloader/tests/snapshots/controller__download.snap
@@ -12,6 +12,7 @@ download_button_enabled: true
download_progress: 0
download_progress_visible: false
beta_text_visible: false
+quit: false
call_log:
- hide_download_progress
- show_download_button
diff --git a/installer-downloader/tests/snapshots/controller__failed_verification.snap b/installer-downloader/tests/snapshots/controller__failed_verification.snap
index d345795b7e..b7825fb1d5 100644
--- a/installer-downloader/tests/snapshots/controller__failed_verification.snap
+++ b/installer-downloader/tests/snapshots/controller__failed_verification.snap
@@ -12,6 +12,7 @@ download_button_enabled: true
download_progress: 100
download_progress_visible: true
beta_text_visible: false
+quit: false
call_log:
- hide_download_progress
- show_download_button
diff --git a/installer-downloader/tests/snapshots/controller__fetch_version-2.snap b/installer-downloader/tests/snapshots/controller__fetch_version-2.snap
index 45bd1796ad..3d79a1922e 100644
--- a/installer-downloader/tests/snapshots/controller__fetch_version-2.snap
+++ b/installer-downloader/tests/snapshots/controller__fetch_version-2.snap
@@ -12,6 +12,7 @@ download_button_enabled: true
download_progress: 0
download_progress_visible: false
beta_text_visible: false
+quit: false
call_log:
- hide_download_progress
- show_download_button
diff --git a/installer-downloader/tests/snapshots/controller__fetch_version.snap b/installer-downloader/tests/snapshots/controller__fetch_version.snap
index a8c1896189..24c54ce95c 100644
--- a/installer-downloader/tests/snapshots/controller__fetch_version.snap
+++ b/installer-downloader/tests/snapshots/controller__fetch_version.snap
@@ -12,6 +12,7 @@ download_button_enabled: false
download_progress: 0
download_progress_visible: false
beta_text_visible: false
+quit: false
call_log:
- hide_download_progress
- show_download_button
diff --git a/mullvad-update/src/app.rs b/mullvad-update/src/app.rs
index f8b0ec3414..b693520bf1 100644
--- a/mullvad-update/src/app.rs
+++ b/mullvad-update/src/app.rs
@@ -1,18 +1,24 @@
//! This module implements the flow of downloading and verifying the app.
-use std::path::PathBuf;
+use std::{path::PathBuf, time::Duration};
+
+use tokio::{process::Command, time::timeout};
use crate::{
fetch::{self, ProgressUpdater},
verify::{AppVerifier, Sha256Verifier},
};
+const INSTALLER_STARTUP_TIMEOUT: Duration = Duration::from_millis(500);
+
#[derive(Debug)]
pub enum DownloadError {
CreateDir(anyhow::Error),
FetchSignature(anyhow::Error),
FetchApp(anyhow::Error),
Verification(anyhow::Error),
+ Launch(std::io::Error),
+ InstallFailed(anyhow::Error),
}
/// Parameters required to construct an [AppDownloader].
@@ -36,13 +42,17 @@ pub trait AppDownloader: Send {
/// Verify the app signature.
async fn verify(&mut self) -> Result<(), DownloadError>;
+
+ /// Execute installer.
+ async fn install(&mut self) -> Result<(), DownloadError>;
}
/// Download the app and signature, and verify the app's signature
pub async fn install_and_upgrade(mut downloader: impl AppDownloader) -> Result<(), DownloadError> {
downloader.create_cache_dir().await?;
downloader.download_executable().await?;
- downloader.verify().await
+ downloader.verify().await?;
+ downloader.install().await
}
#[derive(Clone)]
@@ -92,6 +102,27 @@ impl<AppProgress: ProgressUpdater> AppDownloader for HttpAppDownloader<AppProgre
.await
.map_err(DownloadError::Verification)
}
+
+ async fn install(&mut self) -> Result<(), DownloadError> {
+ let bin_path = self.bin_path().expect("Performed after 'create_cache_dir'");
+
+ // Launch process
+ // TODO: move to launch.rs?
+ let mut cmd = Command::new(bin_path);
+ let mut child = cmd.spawn().map_err(DownloadError::Launch)?;
+
+ // Wait to see if the installer fails
+ match timeout(INSTALLER_STARTUP_TIMEOUT, child.wait()).await {
+ // Timeout: Quit and let the installer take over
+ Err(_timeout) => Ok(()),
+ // No timeout: Incredibly quick but successful (or wrong exit code, probably)
+ Ok(Ok(status)) if status.success() => Ok(()),
+ // Installer failed
+ Ok(Ok(status)) => Err(DownloadError::InstallFailed(anyhow::anyhow!("Install failed with status: {status}"))),
+ // Installer failed
+ Ok(Err(err)) => Err(DownloadError::InstallFailed(anyhow::anyhow!("Install failed : {err}"))),
+ }
+ }
}
impl<AppProgress> HttpAppDownloader<AppProgress> {