mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Sync from development - prepare for v0.1.2
This commit is contained in:
@@ -174,13 +174,103 @@ class JackifyCLI:
|
||||
Returns:
|
||||
Dictionary of backend service instances
|
||||
"""
|
||||
# For now, create a basic modlist service
|
||||
# TODO: Add other services as needed
|
||||
# Initialize update service
|
||||
from jackify.backend.services.update_service import UpdateService
|
||||
update_service = UpdateService(jackify_version)
|
||||
|
||||
services = {
|
||||
'modlist_service': ModlistService(self.system_info)
|
||||
'modlist_service': ModlistService(self.system_info),
|
||||
'update_service': update_service
|
||||
}
|
||||
return services
|
||||
|
||||
def _check_for_updates_on_startup(self):
|
||||
"""Check for updates on startup in background thread"""
|
||||
try:
|
||||
self._debug_print("Checking for updates on startup...")
|
||||
|
||||
def update_check_callback(update_info):
|
||||
"""Handle update check results"""
|
||||
try:
|
||||
if update_info:
|
||||
print(f"\n{COLOR_INFO}Update available: v{update_info.version}{COLOR_RESET}")
|
||||
print(f"Current version: v{jackify_version}")
|
||||
print(f"Release date: {update_info.release_date}")
|
||||
if update_info.changelog:
|
||||
print(f"Changelog: {update_info.changelog[:200]}...")
|
||||
print(f"Download size: {update_info.file_size / (1024*1024):.1f} MB" if update_info.file_size else "Download size: Unknown")
|
||||
print(f"\nTo update, run: jackify --update")
|
||||
print("Or visit: https://github.com/Omni-guides/Jackify/releases")
|
||||
else:
|
||||
self._debug_print("No updates available")
|
||||
except Exception as e:
|
||||
self._debug_print(f"Error showing update info: {e}")
|
||||
|
||||
# Check for updates in background
|
||||
self.backend_services['update_service'].check_for_updates_async(update_check_callback)
|
||||
|
||||
except Exception as e:
|
||||
self._debug_print(f"Error checking for updates on startup: {e}")
|
||||
# Continue anyway - don't block startup on update check errors
|
||||
|
||||
def _handle_update(self):
|
||||
"""Handle manual update check and installation"""
|
||||
try:
|
||||
print("Checking for updates...")
|
||||
update_service = self.backend_services['update_service']
|
||||
|
||||
# Check if updating is possible
|
||||
if not update_service.can_update():
|
||||
print(f"{COLOR_ERROR}Update not possible: not running as AppImage or insufficient permissions{COLOR_RESET}")
|
||||
return 1
|
||||
|
||||
# Check for updates
|
||||
update_info = update_service.check_for_updates()
|
||||
|
||||
if update_info:
|
||||
print(f"{COLOR_INFO}Update available: v{update_info.version}{COLOR_RESET}")
|
||||
print(f"Current version: v{jackify_version}")
|
||||
print(f"Release date: {update_info.release_date}")
|
||||
if update_info.changelog:
|
||||
print(f"Changelog: {update_info.changelog}")
|
||||
print(f"Download size: {update_info.file_size / (1024*1024):.1f} MB" if update_info.file_size else "Download size: Unknown")
|
||||
|
||||
# Ask for confirmation
|
||||
response = input("\nDo you want to download and install this update? (y/N): ").strip().lower()
|
||||
if response in ['y', 'yes']:
|
||||
print("Downloading update...")
|
||||
|
||||
def progress_callback(downloaded, total):
|
||||
if total > 0:
|
||||
percentage = int((downloaded / total) * 100)
|
||||
downloaded_mb = downloaded / (1024 * 1024)
|
||||
total_mb = total / (1024 * 1024)
|
||||
print(f"\rDownloaded {downloaded_mb:.1f} MB of {total_mb:.1f} MB ({percentage}%)", end='', flush=True)
|
||||
|
||||
downloaded_path = update_service.download_update(update_info, progress_callback)
|
||||
|
||||
if downloaded_path:
|
||||
print(f"\nDownload completed. Installing update...")
|
||||
if update_service.apply_update(downloaded_path):
|
||||
print(f"{COLOR_INFO}Update applied successfully! Jackify will restart...{COLOR_RESET}")
|
||||
return 0
|
||||
else:
|
||||
print(f"{COLOR_ERROR}Failed to apply update{COLOR_RESET}")
|
||||
return 1
|
||||
else:
|
||||
print(f"\n{COLOR_ERROR}Failed to download update{COLOR_RESET}")
|
||||
return 1
|
||||
else:
|
||||
print("Update cancelled.")
|
||||
return 0
|
||||
else:
|
||||
print(f"{COLOR_INFO}You are already running the latest version (v{jackify_version}){COLOR_RESET}")
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"{COLOR_ERROR}Update failed: {e}{COLOR_RESET}")
|
||||
return 1
|
||||
|
||||
def _initialize_command_handlers(self):
|
||||
"""Initialize command handler instances.
|
||||
|
||||
@@ -271,6 +361,11 @@ class JackifyCLI:
|
||||
self._debug_print('JackifyCLI.run() called')
|
||||
self._debug_print(f'Parsed args: {self.args}')
|
||||
|
||||
# Handle update functionality
|
||||
if getattr(self.args, 'update', False):
|
||||
self._debug_print('Entering update workflow')
|
||||
return self._handle_update()
|
||||
|
||||
# Handle legacy restart-steam functionality (temporary)
|
||||
if getattr(self.args, 'restart_steam', False):
|
||||
self._debug_print('Entering restart_steam workflow')
|
||||
@@ -290,6 +385,9 @@ class JackifyCLI:
|
||||
if getattr(self.args, 'command', None):
|
||||
return self._run_command(self.args.command, self.args)
|
||||
|
||||
# Check for updates on startup (non-blocking)
|
||||
self._check_for_updates_on_startup()
|
||||
|
||||
# Run interactive mode (legacy for now)
|
||||
self._run_interactive()
|
||||
|
||||
@@ -303,6 +401,7 @@ class JackifyCLI:
|
||||
parser.add_argument("--resolution", type=str, help="Resolution to set (optional)")
|
||||
parser.add_argument('--restart-steam', action='store_true', help='Restart Steam (native, for GUI integration)')
|
||||
parser.add_argument('--dev', action='store_true', help='Enable development features (show hidden menu items)')
|
||||
parser.add_argument('--update', action='store_true', help='Check for and install updates')
|
||||
|
||||
# Add command-specific arguments
|
||||
self.commands['tuxborn'].add_args(parser)
|
||||
|
||||
293
jackify/frontends/gui/dialogs/update_dialog.py
Normal file
293
jackify/frontends/gui/dialogs/update_dialog.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""
|
||||
Update notification and download dialog for Jackify.
|
||||
|
||||
This dialog handles notifying users about available updates and
|
||||
managing the download/installation process.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QTextEdit, QProgressBar, QGroupBox, QCheckBox
|
||||
)
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
from PySide6.QtGui import QPixmap, QFont
|
||||
|
||||
from ....backend.services.update_service import UpdateService, UpdateInfo
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateDownloadThread(QThread):
|
||||
"""Background thread for downloading updates."""
|
||||
|
||||
progress_updated = Signal(int, int) # downloaded, total
|
||||
download_finished = Signal(object) # Path or None
|
||||
|
||||
def __init__(self, update_service: UpdateService, update_info: UpdateInfo):
|
||||
super().__init__()
|
||||
self.update_service = update_service
|
||||
self.update_info = update_info
|
||||
self.downloaded_path = None
|
||||
|
||||
def run(self):
|
||||
"""Download the update in background."""
|
||||
try:
|
||||
def progress_callback(downloaded: int, total: int):
|
||||
self.progress_updated.emit(downloaded, total)
|
||||
|
||||
self.downloaded_path = self.update_service.download_update(
|
||||
self.update_info, progress_callback
|
||||
)
|
||||
|
||||
self.download_finished.emit(self.downloaded_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in download thread: {e}")
|
||||
self.download_finished.emit(None)
|
||||
|
||||
|
||||
class UpdateDialog(QDialog):
|
||||
"""Dialog for notifying users about updates and handling downloads."""
|
||||
|
||||
def __init__(self, update_info: UpdateInfo, update_service: UpdateService, parent=None):
|
||||
super().__init__(parent)
|
||||
self.update_info = update_info
|
||||
self.update_service = update_service
|
||||
self.downloaded_path = None
|
||||
self.download_thread = None
|
||||
|
||||
self.setup_ui()
|
||||
self.setup_connections()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Set up the dialog UI."""
|
||||
self.setWindowTitle("Jackify Update Available")
|
||||
self.setModal(True)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.setMaximumSize(600, 600)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Header
|
||||
header_layout = QHBoxLayout()
|
||||
|
||||
# Update icon (if available)
|
||||
icon_label = QLabel()
|
||||
icon_label.setText("🔄") # Simple emoji for now
|
||||
icon_label.setStyleSheet("font-size: 32px;")
|
||||
header_layout.addWidget(icon_label)
|
||||
|
||||
# Update title
|
||||
title_layout = QVBoxLayout()
|
||||
title_label = QLabel(f"Update Available: v{self.update_info.version}")
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(14)
|
||||
title_font.setBold(True)
|
||||
title_label.setFont(title_font)
|
||||
title_layout.addWidget(title_label)
|
||||
|
||||
subtitle_label = QLabel(f"Current version: v{self.update_service.current_version}")
|
||||
subtitle_label.setStyleSheet("color: #666;")
|
||||
title_layout.addWidget(subtitle_label)
|
||||
|
||||
header_layout.addLayout(title_layout)
|
||||
header_layout.addStretch()
|
||||
|
||||
layout.addLayout(header_layout)
|
||||
|
||||
# File size info
|
||||
if self.update_info.file_size:
|
||||
size_mb = self.update_info.file_size / (1024 * 1024)
|
||||
size_label = QLabel(f"Download size: {size_mb:.1f} MB")
|
||||
size_label.setStyleSheet("color: #666; margin-bottom: 10px;")
|
||||
layout.addWidget(size_label)
|
||||
|
||||
# Changelog group
|
||||
changelog_group = QGroupBox("What's New")
|
||||
changelog_layout = QVBoxLayout(changelog_group)
|
||||
|
||||
self.changelog_text = QTextEdit()
|
||||
self.changelog_text.setPlainText(self.update_info.changelog or "No changelog available.")
|
||||
self.changelog_text.setMaximumHeight(150)
|
||||
self.changelog_text.setReadOnly(True)
|
||||
changelog_layout.addWidget(self.changelog_text)
|
||||
|
||||
layout.addWidget(changelog_group)
|
||||
|
||||
# Progress section (initially hidden)
|
||||
self.progress_group = QGroupBox("Download Progress")
|
||||
progress_layout = QVBoxLayout(self.progress_group)
|
||||
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setVisible(False)
|
||||
progress_layout.addWidget(self.progress_bar)
|
||||
|
||||
self.progress_label = QLabel("Preparing download...")
|
||||
self.progress_label.setVisible(False)
|
||||
progress_layout.addWidget(self.progress_label)
|
||||
|
||||
layout.addWidget(self.progress_group)
|
||||
self.progress_group.setVisible(False)
|
||||
|
||||
# Options
|
||||
options_group = QGroupBox("Update Options")
|
||||
options_layout = QVBoxLayout(options_group)
|
||||
|
||||
self.auto_restart_checkbox = QCheckBox("Automatically restart Jackify after update")
|
||||
self.auto_restart_checkbox.setChecked(True)
|
||||
options_layout.addWidget(self.auto_restart_checkbox)
|
||||
|
||||
layout.addWidget(options_group)
|
||||
|
||||
# Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
self.later_button = QPushButton("Remind Me Later")
|
||||
self.later_button.clicked.connect(self.remind_later)
|
||||
button_layout.addWidget(self.later_button)
|
||||
|
||||
self.skip_button = QPushButton("Skip This Version")
|
||||
self.skip_button.clicked.connect(self.skip_version)
|
||||
button_layout.addWidget(self.skip_button)
|
||||
|
||||
button_layout.addStretch()
|
||||
|
||||
self.download_button = QPushButton("Download & Install Update")
|
||||
self.download_button.setDefault(True)
|
||||
self.download_button.clicked.connect(self.start_download)
|
||||
button_layout.addWidget(self.download_button)
|
||||
|
||||
self.install_button = QPushButton("Install & Restart")
|
||||
self.install_button.setVisible(False)
|
||||
self.install_button.clicked.connect(self.install_update)
|
||||
button_layout.addWidget(self.install_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# Style the download button
|
||||
self.download_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #0d7377;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #14a085;
|
||||
}
|
||||
""")
|
||||
|
||||
def setup_connections(self):
|
||||
"""Set up signal connections."""
|
||||
pass
|
||||
|
||||
def start_download(self):
|
||||
"""Start downloading the update."""
|
||||
if not self.update_service.can_update():
|
||||
self.show_error("Update not possible",
|
||||
"Cannot update: not running as AppImage or insufficient permissions.")
|
||||
return
|
||||
|
||||
# Show progress UI
|
||||
self.progress_group.setVisible(True)
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_label.setVisible(True)
|
||||
self.progress_label.setText("Starting download...")
|
||||
|
||||
# Disable buttons during download
|
||||
self.download_button.setEnabled(False)
|
||||
self.later_button.setEnabled(False)
|
||||
self.skip_button.setEnabled(False)
|
||||
|
||||
# Start download thread
|
||||
self.download_thread = UpdateDownloadThread(self.update_service, self.update_info)
|
||||
self.download_thread.progress_updated.connect(self.update_progress)
|
||||
self.download_thread.download_finished.connect(self.download_completed)
|
||||
self.download_thread.start()
|
||||
|
||||
def update_progress(self, downloaded: int, total: int):
|
||||
"""Update download progress."""
|
||||
if total > 0:
|
||||
percentage = int((downloaded / total) * 100)
|
||||
self.progress_bar.setValue(percentage)
|
||||
|
||||
downloaded_mb = downloaded / (1024 * 1024)
|
||||
total_mb = total / (1024 * 1024)
|
||||
|
||||
self.progress_label.setText(f"Downloaded {downloaded_mb:.1f} MB of {total_mb:.1f} MB ({percentage}%)")
|
||||
else:
|
||||
self.progress_label.setText(f"Downloaded {downloaded / (1024 * 1024):.1f} MB...")
|
||||
|
||||
def download_completed(self, downloaded_path: Optional[Path]):
|
||||
"""Handle download completion."""
|
||||
if downloaded_path:
|
||||
self.downloaded_path = downloaded_path
|
||||
self.progress_label.setText("Download completed successfully!")
|
||||
self.progress_bar.setValue(100)
|
||||
|
||||
# Show install button
|
||||
self.download_button.setVisible(False)
|
||||
self.install_button.setVisible(True)
|
||||
|
||||
# Re-enable other buttons
|
||||
self.later_button.setEnabled(True)
|
||||
self.skip_button.setEnabled(True)
|
||||
|
||||
else:
|
||||
self.show_error("Download Failed", "Failed to download the update. Please try again later.")
|
||||
|
||||
# Reset UI
|
||||
self.progress_group.setVisible(False)
|
||||
self.download_button.setEnabled(True)
|
||||
self.later_button.setEnabled(True)
|
||||
self.skip_button.setEnabled(True)
|
||||
|
||||
def install_update(self):
|
||||
"""Install the downloaded update."""
|
||||
if not self.downloaded_path:
|
||||
self.show_error("No Download", "No update has been downloaded.")
|
||||
return
|
||||
|
||||
self.progress_label.setText("Installing update...")
|
||||
|
||||
if self.update_service.apply_update(self.downloaded_path):
|
||||
self.progress_label.setText("Update applied successfully! Jackify will restart...")
|
||||
|
||||
# Close dialog and exit application (update helper will restart)
|
||||
self.accept()
|
||||
|
||||
# The update helper script will handle the restart
|
||||
import sys
|
||||
sys.exit(0)
|
||||
|
||||
else:
|
||||
self.show_error("Installation Failed", "Failed to apply the update. Please try again.")
|
||||
|
||||
def remind_later(self):
|
||||
"""Close dialog and remind later."""
|
||||
self.reject()
|
||||
|
||||
def skip_version(self):
|
||||
"""Skip this version (could save preference)."""
|
||||
# TODO: Save preference to skip this version
|
||||
self.reject()
|
||||
|
||||
def show_error(self, title: str, message: str):
|
||||
"""Show error message to user."""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, title, message)
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Handle dialog close event."""
|
||||
if self.download_thread and self.download_thread.isRunning():
|
||||
# Cancel download if in progress
|
||||
self.download_thread.terminate()
|
||||
self.download_thread.wait()
|
||||
|
||||
event.accept()
|
||||
@@ -528,6 +528,10 @@ class JackifyMainWindow(QMainWindow):
|
||||
from jackify.backend.services.protontricks_detection_service import ProtontricksDetectionService
|
||||
self.protontricks_service = ProtontricksDetectionService(steamdeck=self.system_info.is_steamdeck)
|
||||
|
||||
# Initialize update service
|
||||
from jackify.backend.services.update_service import UpdateService
|
||||
self.update_service = UpdateService(__version__)
|
||||
|
||||
debug_print(f"GUI Backend initialized - Steam Deck: {self.system_info.is_steamdeck}")
|
||||
|
||||
def _is_steamdeck(self):
|
||||
@@ -735,6 +739,32 @@ class JackifyMainWindow(QMainWindow):
|
||||
print(f"Error checking protontricks: {e}")
|
||||
# Continue anyway - don't block startup on detection errors
|
||||
|
||||
def _check_for_updates_on_startup(self):
|
||||
"""Check for updates on startup in background thread"""
|
||||
try:
|
||||
debug_print("Checking for updates on startup...")
|
||||
|
||||
def update_check_callback(update_info):
|
||||
"""Handle update check results"""
|
||||
try:
|
||||
if update_info:
|
||||
debug_print(f"Update available: v{update_info.version}")
|
||||
# Show update dialog
|
||||
from jackify.frontends.gui.dialogs.update_dialog import UpdateDialog
|
||||
dialog = UpdateDialog(update_info, self.update_service, self)
|
||||
dialog.show() # Non-blocking
|
||||
else:
|
||||
debug_print("No updates available")
|
||||
except Exception as e:
|
||||
debug_print(f"Error showing update dialog: {e}")
|
||||
|
||||
# Check for updates in background
|
||||
self.update_service.check_for_updates_async(update_check_callback)
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"Error checking for updates on startup: {e}")
|
||||
# Continue anyway - don't block startup on update check errors
|
||||
|
||||
def cleanup_processes(self):
|
||||
"""Clean up any running processes before closing"""
|
||||
try:
|
||||
@@ -843,6 +873,9 @@ def main():
|
||||
window = JackifyMainWindow(dev_mode=dev_mode)
|
||||
window.show()
|
||||
|
||||
# Start background update check after window is shown
|
||||
window._check_for_updates_on_startup()
|
||||
|
||||
# Ensure cleanup on exit
|
||||
import atexit
|
||||
atexit.register(emergency_cleanup)
|
||||
|
||||
Reference in New Issue
Block a user