feat: Enhance Help menu with About dialog and update check functionality

This commit is contained in:
claudi 2026-01-29 09:09:04 +01:00
parent eb7ffe9969
commit 6278ef8eed
3 changed files with 196 additions and 16 deletions

View file

@ -364,7 +364,14 @@ class MainWindow(QMainWindow):
# Check for Updates action
check_updates_action = help_menu.addAction("Check for Updates...")
check_updates_action.triggered.connect(self._on_check_for_updates)
check_updates_action.triggered.connect(self._on_manual_check_for_updates)
# Separator
help_menu.addSeparator()
# About action
about_action = help_menu.addAction("About WebDrop Bridge...")
about_action.triggered.connect(self._show_about_dialog)
def _create_status_bar(self) -> None:
"""Create status bar with update status indicator."""
@ -387,13 +394,30 @@ class MainWindow(QMainWindow):
else:
self.update_status_label.setText(status)
def _on_check_for_updates(self) -> None:
"""Handle check for updates menu action.
def _on_manual_check_for_updates(self) -> None:
"""Handle manual check for updates from menu.
Emits the check_for_updates signal to allow the main application
to perform the update check asynchronously.
Triggers an immediate update check (bypass cache).
"""
self.check_for_updates.emit()
logger.info("Manual update check requested from menu")
# Same as startup check, but user-initiated
self.check_for_updates_startup()
def _show_about_dialog(self) -> None:
"""Show About dialog with version and information."""
from PySide6.QtWidgets import QMessageBox
about_text = (
f"<b>{self.config.app_name}</b><br>"
f"Version: {self.config.app_version}<br>"
f"<br>"
f"A professional Qt-based desktop application that converts "
f"web-based drag-and-drop text paths into native file operations.<br>"
f"<br>"
f"<small>© 2026 WebDrop Bridge Contributors</small>"
)
QMessageBox.about(self, f"About {self.config.app_name}", about_text)
def _navigate_home(self) -> None:
"""Navigate to the home (start) URL."""
@ -490,8 +514,147 @@ class MainWindow(QMainWindow):
# Update status to show update available
self.set_update_status(f"Update available: v{release.version}", emoji="")
# Emit signal for main app to show dialog
self.update_available.emit(release)
# Show update available dialog
from webdrop_bridge.ui.update_manager_ui import UpdateAvailableDialog
dialog = UpdateAvailableDialog(
version=release.version,
changelog=release.body,
parent=self
)
# Connect dialog signals
dialog.update_now.connect(lambda: self._on_user_update_now(release))
dialog.update_later.connect(lambda: self._on_user_update_later())
dialog.skip_version.connect(lambda: self._on_user_skip_version(release.version))
# Show dialog (modal)
dialog.exec()
def _on_user_update_now(self, release) -> None:
"""Handle user clicking 'Update Now' button.
Args:
release: Release object to download and install
"""
logger.info(f"User clicked 'Update Now' for v{release.version}")
# Start download
self._start_update_download(release)
def _on_user_update_later(self) -> None:
"""Handle user clicking 'Later' button."""
logger.info("User deferred update")
self.set_update_status("Update deferred", emoji="")
def _on_user_skip_version(self, version: str) -> None:
"""Handle user clicking 'Skip Version' button.
Args:
version: Version to skip
"""
logger.info(f"User skipped version {version}")
# Store skipped version in preferences
skipped_file = Path.home() / ".webdrop-bridge" / "skipped_version.txt"
skipped_file.parent.mkdir(parents=True, exist_ok=True)
skipped_file.write_text(version)
self.set_update_status(f"Skipped v{version}", emoji="")
def _start_update_download(self, release) -> None:
"""Start downloading the update.
Args:
release: Release object to download
"""
logger.info(f"Starting download for v{release.version}")
self.set_update_status(f"Downloading v{release.version}", emoji="⬇️")
# For now, just start installer directly (simplified)
# In production, would show download progress dialog
self._perform_update(release)
def _perform_update(self, release) -> None:
"""Download and install the update.
Args:
release: Release object to download and install
"""
from webdrop_bridge.core.updater import UpdateManager
from webdrop_bridge.ui.update_manager_ui import InstallDialog
try:
logger.info(f"Downloading and installing v{release.version}")
# Create update manager
manager = UpdateManager(
current_version=self.config.app_version,
config_dir=Path.home() / ".webdrop-bridge"
)
# Download synchronously for simplicity
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
installer_path = loop.run_until_complete(
manager.download_update(release)
)
if not installer_path:
self.set_update_status("Download failed", emoji="")
logger.error("Download failed - no installer found")
return
logger.info(f"Downloaded to {installer_path}")
# Verify checksum
checksum_ok = loop.run_until_complete(
manager.verify_checksum(installer_path, release)
)
loop.close()
if not checksum_ok:
self.set_update_status("Checksum verification failed", emoji="")
logger.error("Checksum verification failed")
return
logger.info("Checksum verification passed")
self.set_update_status(f"Ready to install v{release.version}", emoji="")
# Show install confirmation dialog
install_dialog = InstallDialog(parent=self)
install_dialog.install_now.connect(
lambda: self._do_install(installer_path)
)
install_dialog.exec()
except Exception as e:
logger.error(f"Update failed: {e}")
self.set_update_status(f"Update failed: {str(e)[:30]}", emoji="")
def _do_install(self, installer_path: Path) -> None:
"""Execute the installer.
Args:
installer_path: Path to installer executable
"""
logger.info(f"Installing from {installer_path}")
from webdrop_bridge.core.updater import UpdateManager
manager = UpdateManager(
current_version=self.config.app_version,
config_dir=Path.home() / ".webdrop-bridge"
)
if manager.install_update(installer_path):
self.set_update_status("Installation started", emoji="")
logger.info("Update installer launched successfully")
else:
self.set_update_status("Installation failed", emoji="")
logger.error("Failed to launch update installer")
class UpdateCheckWorker:

View file

@ -346,13 +346,22 @@ class TestMainWindowMenuBar:
assert callable(window.check_for_updates.emit)
def test_on_check_for_updates_method_exists(self, qtbot, sample_config):
"""Test _on_check_for_updates method exists."""
"""Test _on_manual_check_for_updates method exists."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Test that the method exists
assert hasattr(window, "_on_check_for_updates")
assert callable(window._on_check_for_updates)
assert hasattr(window, "_on_manual_check_for_updates")
assert callable(window._on_manual_check_for_updates)
def test_show_about_dialog_method_exists(self, qtbot, sample_config):
"""Test _show_about_dialog method exists."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Test that the method exists
assert hasattr(window, "_show_about_dialog")
assert callable(window._show_about_dialog)
class TestMainWindowStatusBar:

View file

@ -99,8 +99,9 @@ class TestMainWindowStartupCheck:
assert "" in window.update_status_label.text()
def test_on_update_available_emits_signal(self, qtbot, sample_config):
"""Test _on_update_available emits update_available signal."""
"""Test _on_update_available shows dialog and updates status."""
from webdrop_bridge.ui.main_window import MainWindow
from unittest.mock import patch
window = MainWindow(sample_config)
qtbot.addWidget(window)
@ -108,13 +109,17 @@ class TestMainWindowStartupCheck:
# Create mock release
mock_release = MagicMock()
mock_release.version = "0.0.2"
mock_release.body = "Bug fixes"
with qtbot.waitSignal(window.update_available):
# Mock the dialog creation to avoid showing it
with patch('webdrop_bridge.ui.update_manager_ui.UpdateAvailableDialog'):
window._on_update_available(mock_release)
assert "0.0.2" in window.update_status_label.text()
def test_on_update_available_updates_status(self, qtbot, sample_config):
"""Test _on_update_available updates status bar."""
from webdrop_bridge.ui.main_window import MainWindow
from unittest.mock import patch
window = MainWindow(sample_config)
qtbot.addWidget(window)
@ -122,7 +127,10 @@ class TestMainWindowStartupCheck:
# Create mock release
mock_release = MagicMock()
mock_release.version = "0.0.2"
mock_release.body = "Bug fixes"
# Mock the dialog creation to avoid showing it
with patch('webdrop_bridge.ui.update_manager_ui.UpdateAvailableDialog'):
window._on_update_available(mock_release)
assert "0.0.2" in window.update_status_label.text()
assert "" in window.update_status_label.text()