From e9ddf8b37547bed7dac72f264c33c104852843c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Wed, 30 Apr 2025 19:32:30 +0200 Subject: [PATCH] Add a way to cancel an ongoing container upgrade This might happen if people in a hurry want to do a conversion without having to wait for the new container to be ready. --- dangerzone/gui/main_window.py | 65 ++++++++++++++++++++++++-------- dangerzone/updater/releases.py | 9 ++--- dangerzone/updater/signatures.py | 4 +- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 05d6aa6..3fe84bb 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -462,7 +462,9 @@ class InstallContainerThread(QtCore.QThread): def run(self) -> None: error = None try: - should_upgrade = bool(self.dangerzone.settings.get("updater_check_all")) + should_upgrade = bool( + self.dangerzone.settings.get("updater_container_needs_update") + ) installed = self.dangerzone.isolation_provider.install( should_upgrade=should_upgrade, callback=self.process_stdout.emit ) @@ -526,6 +528,26 @@ class WaitingWidgetContainer(WaitingWidget): # Linux states # - "install_container" + def _create_button( + self, label: str, event: QtCore.Signal, hide: bool = False + ) -> QtWidgets.QWidget: + button = QtWidgets.QPushButton(label) + button.clicked.connect(event) + buttons_layout = QtWidgets.QHBoxLayout() + buttons_layout.addStretch() + buttons_layout.addWidget(button) + buttons_layout.addStretch() + + widget = QtWidgets.QWidget() + widget.setLayout(buttons_layout) + if hide: + widget.hide() + return widget + + def _hide_buttons(self) -> None: + self.button_check.hide() + self.button_cancel.hide() + def __init__(self, dangerzone: DangerzoneGui) -> None: super(WaitingWidgetContainer, self).__init__() self.dangerzone = dangerzone @@ -537,14 +559,10 @@ class WaitingWidgetContainer(WaitingWidget): self.label.setStyleSheet("QLabel { font-size: 20px; }") # Buttons - check_button = QtWidgets.QPushButton("Check Again") - check_button.clicked.connect(self.check_state) - buttons_layout = QtWidgets.QHBoxLayout() - buttons_layout.addStretch() - buttons_layout.addWidget(check_button) - buttons_layout.addStretch() - self.buttons = QtWidgets.QWidget() - self.buttons.setLayout(buttons_layout) + self.button_check = self._create_button("Check Again", self.check_state) + self.button_cancel = self._create_button( + "Cancel", self.cancel_install, hide=True + ) self.traceback = TracebackWidget() @@ -554,7 +572,8 @@ class WaitingWidgetContainer(WaitingWidget): layout.addWidget(self.label) layout.addWidget(self.traceback) layout.addStretch() - layout.addWidget(self.buttons) + layout.addWidget(self.button_check) + layout.addWidget(self.button_cancel) layout.addStretch() self.setLayout(layout) @@ -584,18 +603,22 @@ class WaitingWidgetContainer(WaitingWidget): # Update the state self.state_change(state, error) + def cancel_install(self) -> None: + self.install_container_t.terminate() + self.finished.emit() + def show_error(self, msg: str, details: Optional[str] = None) -> None: self.label.setText(msg) show_traceback = details is not None if show_traceback: self.traceback.set_content(details) self.traceback.setVisible(show_traceback) - self.buttons.show() + self.button_check.show() def show_message(self, msg: str) -> None: self.label.setText(msg) self.traceback.setVisible(False) - self.buttons.hide() + self._hide_buttons() def installation_finished(self, error: Optional[str] = None) -> None: if error: @@ -649,11 +672,23 @@ class WaitingWidgetContainer(WaitingWidget): error, ) else: - self.show_message( - "Installing the Dangerzone container image.

" - "This might take a few minutes..." + needs_update = bool( + self.dangerzone.settings.get("updater_container_needs_update") ) + if needs_update: + message = ( + "Downloading and upgrading the Dangerzone container image.

" + "This might take a few minutes..." + ) + else: + message = ( + "Installing the Dangerzone container image.

" + "This might take a few minutes..." + ) + self.show_message(message) self.traceback.setVisible(True) + self.button_cancel.show() + self.button_check.hide() self.install_container_t = InstallContainerThread(self.dangerzone) self.install_container_t.finished.connect(self.installation_finished) diff --git a/dangerzone/updater/releases.py b/dangerzone/updater/releases.py index 77cb012..30933c1 100644 --- a/dangerzone/updater/releases.py +++ b/dangerzone/updater/releases.py @@ -169,8 +169,9 @@ def check_for_updates(settings: Settings) -> UpdaterReport: slightly different answer: 1. No new updates: Return an empty update report. - 2. Updates are available: Return an update report with the latest version and - changelog, in HTML format. + 2. Updates are available: + Return an update report with the latest version and changelog, or with the + information the container image needs to be updated. 3. Update check failed: Return an update report that holds just the error message. """ @@ -215,9 +216,7 @@ def check_for_updates(settings: Settings) -> UpdaterReport: report.changelog = gh_changelog container_name = container_utils.expected_image_name() - container_needs_update, _ = is_container_update_available( - container_name, DEFAULT_PUBKEY_LOCATION - ) + container_needs_update, _ = is_container_update_available(container_name) report.container_needs_update = container_needs_update settings.set( diff --git a/dangerzone/updater/signatures.py b/dangerzone/updater/signatures.py index 3fad8a4..c9be39f 100644 --- a/dangerzone/updater/signatures.py +++ b/dangerzone/updater/signatures.py @@ -133,7 +133,9 @@ class Signature: return full_digest.replace("sha256:", "") -def is_update_available(image_str: str, pubkey: Path) -> Tuple[bool, Optional[str]]: +def is_update_available( + image_str: str, pubkey: Path = DEFAULT_PUBKEY_LOCATION +) -> Tuple[bool, Optional[str]]: """ Check if a new image is available, doing all the necessary checks ensuring it would be safe to upgrade.