"""UI components for the auto-update system. Provides 6 dialogs for update checking, downloading, and installation: 1. CheckingDialog - Shows while checking for updates 2. UpdateAvailableDialog - Shows when update is available 3. DownloadingDialog - Shows download progress 4. InstallDialog - Confirms installation and restart 5. NoUpdateDialog - Shows when no updates available 6. ErrorDialog - Shows when update check or install fails """ import logging from pathlib import Path from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QIcon from PySide6.QtWidgets import ( QDialog, QHBoxLayout, QLabel, QMessageBox, QProgressBar, QPushButton, QTextEdit, QVBoxLayout, ) logger = logging.getLogger(__name__) class CheckingDialog(QDialog): """Dialog shown while checking for updates. Shows an animated progress indicator and times out after 10 seconds. """ def __init__(self, parent=None): """Initialize checking dialog. Args: parent: Parent widget """ super().__init__(parent) self.setWindowTitle("Checking for Updates") self.setModal(True) self.setMinimumWidth(300) self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) layout = QVBoxLayout() # Status label self.label = QLabel("Checking for updates...") layout.addWidget(self.label) # Animated progress bar self.progress = QProgressBar() self.progress.setMaximum(0) # Makes it animated layout.addWidget(self.progress) # Timeout info info_label = QLabel("This may take up to 10 seconds") info_label.setStyleSheet("color: gray; font-size: 11px;") layout.addWidget(info_label) self.setLayout(layout) class UpdateAvailableDialog(QDialog): """Dialog shown when an update is available. Displays: - Current version - Available version - Changelog/release notes - Buttons: Update Now, Update Later, Skip This Version """ # Signals update_now = Signal() update_later = Signal() def __init__(self, version: str, changelog: str, parent=None): """Initialize update available dialog. Args: version: New version string (e.g., "0.0.2") changelog: Release notes text parent: Parent widget """ super().__init__(parent) self.setWindowTitle("Update Available") self.setModal(True) self.setMinimumWidth(400) self.setMinimumHeight(300) layout = QVBoxLayout() # Header header = QLabel(f"WebDrop Bridge v{version} is available") header.setStyleSheet("font-weight: bold; font-size: 14px;") layout.addWidget(header) # Changelog changelog_label = QLabel("Release Notes:") changelog_label.setStyleSheet("font-weight: bold; margin-top: 10px;") layout.addWidget(changelog_label) self.changelog = QTextEdit() self.changelog.setText(changelog) self.changelog.setReadOnly(True) layout.addWidget(self.changelog) # Buttons button_layout = QHBoxLayout() self.update_now_btn = QPushButton("Update Now") self.update_now_btn.clicked.connect(self._on_update_now) button_layout.addWidget(self.update_now_btn) self.update_later_btn = QPushButton("Later") self.update_later_btn.clicked.connect(self._on_update_later) button_layout.addWidget(self.update_later_btn) layout.addLayout(button_layout) self.setLayout(layout) def _on_update_now(self): """Handle update now button click.""" self.update_now.emit() self.accept() def _on_update_later(self): """Handle update later button click.""" self.update_later.emit() self.reject() class DownloadingDialog(QDialog): """Dialog shown while downloading the update. Displays: - Download progress bar - Current file being downloaded - Cancel button """ cancel_download = Signal() def __init__(self, parent=None): """Initialize downloading dialog. Args: parent: Parent widget """ super().__init__(parent) self.setWindowTitle("Downloading Update") self.setModal(True) self.setMinimumWidth(350) self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) layout = QVBoxLayout() # Header header = QLabel("Downloading update...") header.setStyleSheet("font-weight: bold;") layout.addWidget(header) # File label self.file_label = QLabel("Preparing download") layout.addWidget(self.file_label) # Progress bar self.progress = QProgressBar() self.progress.setMinimum(0) self.progress.setMaximum(100) self.progress.setValue(0) layout.addWidget(self.progress) # Size info self.size_label = QLabel("0 MB / 0 MB") self.size_label.setStyleSheet("color: gray; font-size: 11px;") layout.addWidget(self.size_label) # Cancel button self.cancel_btn = QPushButton("Cancel") self.cancel_btn.clicked.connect(self._on_cancel) layout.addWidget(self.cancel_btn) self.setLayout(layout) def set_progress(self, current: int, total: int): """Update progress bar. Args: current: Current bytes downloaded total: Total bytes to download """ if total > 0: percentage = int((current / total) * 100) self.progress.setValue(percentage) # Format size display current_mb = current / (1024 * 1024) total_mb = total / (1024 * 1024) self.size_label.setText(f"{current_mb:.1f} MB / {total_mb:.1f} MB") def set_filename(self, filename: str): """Set the filename being downloaded. Args: filename: Name of file being downloaded """ self.file_label.setText(f"Downloading: {filename}") def _on_cancel(self): """Handle cancel button click.""" self.cancel_download.emit() self.reject() class InstallDialog(QDialog): """Dialog shown before installing update and restarting. Displays: - Installation confirmation message - Warning about unsaved changes - Buttons: Install Now, Cancel """ install_now = Signal() def __init__(self, parent=None): """Initialize install dialog. Args: parent: Parent widget """ super().__init__(parent) self.setWindowTitle("Install Update") self.setModal(True) self.setMinimumWidth(350) layout = QVBoxLayout() # Header header = QLabel("Ready to Install") header.setStyleSheet("font-weight: bold; font-size: 14px;") layout.addWidget(header) # Message message = QLabel("The update is ready to install. The application will restart.") layout.addWidget(message) # Warning warning = QLabel( "⚠️ Please save any unsaved work before continuing.\n" "The application will close and restart." ) warning.setStyleSheet("background-color: #fff3cd; padding: 10px; border-radius: 4px;") warning.setWordWrap(True) layout.addWidget(warning) # Buttons button_layout = QHBoxLayout() self.install_btn = QPushButton("Install Now") self.install_btn.setStyleSheet("background-color: #28a745; color: white;") self.install_btn.clicked.connect(self._on_install) button_layout.addWidget(self.install_btn) self.cancel_btn = QPushButton("Cancel") self.cancel_btn.clicked.connect(self.reject) button_layout.addWidget(self.cancel_btn) layout.addLayout(button_layout) self.setLayout(layout) def _on_install(self): """Handle install now button click.""" self.install_now.emit() self.accept() class NoUpdateDialog(QDialog): """Dialog shown when no updates are available. Simple confirmation that the application is up to date. """ def __init__(self, parent=None): """Initialize no update dialog. Args: parent: Parent widget """ super().__init__(parent) self.setWindowTitle("No Updates Available") self.setModal(True) self.setMinimumWidth(300) layout = QVBoxLayout() # Message message = QLabel("✓ You're using the latest version") message.setStyleSheet("font-weight: bold; font-size: 12px; color: #28a745;") layout.addWidget(message) info = QLabel("WebDrop Bridge is up to date.") layout.addWidget(info) # Close button close_btn = QPushButton("OK") close_btn.clicked.connect(self.accept) layout.addWidget(close_btn) self.setLayout(layout) class ErrorDialog(QDialog): """Dialog shown when update check or installation fails. Displays: - Error message - Buttons: Retry, Manual Download, Cancel """ retry = Signal() manual_download = Signal() def __init__(self, error_message: str, parent=None): """Initialize error dialog. Args: error_message: Description of the error parent: Parent widget """ super().__init__(parent) self.setWindowTitle("Update Failed") self.setModal(True) self.setMinimumWidth(350) layout = QVBoxLayout() # Header header = QLabel("⚠️ Update Failed") header.setStyleSheet("font-weight: bold; font-size: 14px; color: #dc3545;") layout.addWidget(header) # Error message self.error_text = QTextEdit() self.error_text.setText(error_message) self.error_text.setReadOnly(True) self.error_text.setMaximumHeight(100) layout.addWidget(self.error_text) # Info message info = QLabel("Please try again or visit the website to download the update manually.") info.setWordWrap(True) info.setStyleSheet("color: gray; font-size: 11px;") layout.addWidget(info) # Buttons button_layout = QHBoxLayout() self.retry_btn = QPushButton("Retry") self.retry_btn.clicked.connect(self._on_retry) button_layout.addWidget(self.retry_btn) self.manual_btn = QPushButton("Download Manually") self.manual_btn.clicked.connect(self._on_manual) button_layout.addWidget(self.manual_btn) self.cancel_btn = QPushButton("Cancel") self.cancel_btn.clicked.connect(self.reject) button_layout.addWidget(self.cancel_btn) layout.addLayout(button_layout) self.setLayout(layout) def _on_retry(self): """Handle retry button click.""" self.retry.emit() self.accept() def _on_manual(self): """Handle manual download button click.""" self.manual_download.emit() self.accept()