feat: Enhance Help menu with About dialog and update check functionality
This commit is contained in:
parent
eb7ffe9969
commit
6278ef8eed
3 changed files with 196 additions and 16 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue