feat(get_wine): make it async

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2026-01-12 11:19:21 +05:00
parent 1729f553bc
commit 091780bf9f

View File

@@ -9,7 +9,7 @@ from PySide6.QtWidgets import (QDialog, QTabWidget, QTableWidget,
QTableWidgetItem, QVBoxLayout, QWidget, QCheckBox,
QPushButton, QHeaderView, QMessageBox,
QLabel, QTextEdit, QHBoxLayout, QProgressBar,
QFrame, QSizePolicy, QAbstractItemView)
QFrame, QSizePolicy, QAbstractItemView, QStackedWidget)
from PySide6.QtCore import Qt, QThread, Signal, QMutex, QWaitCondition, QTimer
import urllib.parse
from portprotonqt.config_utils import read_proxy_config, get_portproton_start_command, read_theme_from_config
@@ -18,11 +18,42 @@ from portprotonqt.theme_manager import ThemeManager
from portprotonqt.localization import _
from portprotonqt.version_utils import version_sort_key
from portprotonqt.dialogs import create_dialog_hints_widget, update_dialog_hints
from portprotonqt.preloader import Preloader
logger = get_logger(__name__)
theme_manager = ThemeManager()
class WineLoadingThread(QThread):
"""Thread for loading wine metadata in the background"""
loading_complete = Signal(object) # Emits the metadata
loading_error = Signal(str) # Emits error message
def __init__(self, parent=None):
super().__init__(parent)
def run(self):
try:
json_url = "https://git.linux-gaming.ru/Boria138/PortProton-Wine-Metadata/raw/branch/main/wine_metadata.json"
# Create a session with proxy support
session = requests.Session()
proxy = read_proxy_config() or {}
if proxy:
session.proxies.update(proxy)
session.verify = True
response = session.get(json_url, timeout=30)
response.raise_for_status()
metadata = orjson.loads(response.content)
logger.info(f"Successfully loaded JSON metadata with {len(metadata)} entries")
self.loading_complete.emit(metadata)
except Exception as e:
logger.error(f"Error loading metadata: {e}")
self.loading_error.emit(str(e))
def get_cpu_level():
"""
Determine CPU level based on feature flags
@@ -343,6 +374,7 @@ class ProtonManager(QDialog):
self.portproton_location = portproton_location
self.input_manager = input_manager # Input manager for gamepad support
self.initial_command_executed = False # Track if --initial command has been executed
self.wine_loading_thread = None # Thread for loading wine data
# Find main window
self.main_window = None
@@ -354,9 +386,9 @@ class ProtonManager(QDialog):
parent_widget = parent_widget.parent()
self.initUI()
self.load_proton_data_from_json()
self.create_installed_tab()
# Start loading wine data in the background after UI is initialized
self.start_loading_wine_data()
# The installed tab will be created after wine data is loaded
# Enable gamepad support if input manager is provided
if self.input_manager:
self.enable_proton_manager_mode()
@@ -370,11 +402,48 @@ class ProtonManager(QDialog):
layout.setContentsMargins(5, 5, 5, 5)
layout.setSpacing(5)
# Create a stacked widget to hold preloader and content
self.content_stack = QStackedWidget()
# Preloader widget
self.preloader_widget = QWidget()
preloader_layout = QVBoxLayout(self.preloader_widget)
preloader_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
# Center the preloader
preloader_container = QWidget()
preloader_container_layout = QVBoxLayout(preloader_container)
preloader_container_layout.addStretch()
preloader_hlayout = QHBoxLayout()
preloader_hlayout.addStretch()
self.preloader = Preloader()
preloader_hlayout.addWidget(self.preloader)
preloader_hlayout.addStretch()
preloader_container_layout.addLayout(preloader_hlayout)
preloader_container_layout.addStretch()
preloader_container_layout.setContentsMargins(0, 0, 0, 0)
preloader_layout.addWidget(preloader_container)
# Content widget (tabs and controls)
self.content_widget = QWidget()
content_layout = QVBoxLayout(self.content_widget)
content_layout.setContentsMargins(5, 5, 5, 5)
content_layout.setSpacing(5)
# Tab widget - основной растягивающийся элемент
self.tab_widget = QTabWidget()
self.tab_widget.setStyleSheet(self.theme.GETWINE_WINDOW_STYLE)
self.tab_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
layout.addWidget(self.tab_widget, 1)
content_layout.addWidget(self.tab_widget, 1)
# Add widgets to stacked widget
self.content_stack.addWidget(self.preloader_widget) # Index 0: preloader
self.content_stack.addWidget(self.content_widget) # Index 1: content
self.content_stack.setCurrentIndex(0) # Show preloader initially
layout.addWidget(self.content_stack, 1)
# Инфо-блок для показа выбранного (компактный для информации по выбранным закачкам)
selection_widget = QWidget()
@@ -465,32 +534,52 @@ class ProtonManager(QDialog):
theme_manager, self.current_theme_name
)
def load_proton_data_from_json(self):
"""Загружаем данные по Протонам из файла JSON"""
json_url = "https://git.linux-gaming.ru/Boria138/PortProton-Wine-Metadata/raw/branch/main/wine_metadata.json"
def start_loading_wine_data(self):
"""Start loading wine data in a background thread"""
# Create and start the loading thread
self.wine_loading_thread = WineLoadingThread()
self.wine_loading_thread.loading_complete.connect(self.on_wine_data_loaded)
self.wine_loading_thread.loading_error.connect(self.on_wine_data_load_error)
self.wine_loading_thread.start()
try:
logger.debug(f"Loading JSON metadata from: {json_url}")
# Create a session with proxy support
session = requests.Session()
proxy = read_proxy_config() or {}
if proxy:
session.proxies.update(proxy)
session.verify = True
response = session.get(json_url, timeout=30)
response.raise_for_status()
def on_wine_data_loaded(self, metadata):
"""Handle when wine data is loaded successfully"""
# Process the metadata in the main thread
self.process_metadata(metadata)
metadata = orjson.loads(response.content)
logger.info(f"Successfully loaded JSON metadata with {len(metadata)} entries")
self.process_metadata(metadata)
# Create the installed tab after other tabs are created (to be last)
# First remove the existing installed tab if it exists
for i in range(self.tab_widget.count()):
if self.tab_widget.tabText(i) == _("Installed"):
self.tab_widget.removeTab(i)
break
# Then create the installed tab (will be added as the last tab)
self.create_installed_tab()
# Hide the preloader and show the content
if hasattr(self, 'content_stack'):
self.content_stack.setCurrentIndex(1) # Show content, hide preloader
except requests.exceptions.RequestException as e:
logger.error(f"Network error loading JSON: {e}")
except orjson.JSONDecodeError as e:
logger.error(f"JSON parsing error: {e}")
except Exception as e:
logger.error(f"Error loading metadata: {e}")
def on_wine_data_load_error(self, error_msg):
"""Handle when wine data loading fails"""
logger.error(f"Wine data loading failed: {error_msg}")
# Show error message but still allow the dialog to function
if hasattr(self, 'content_stack'):
self.content_stack.setCurrentIndex(1) # Show content even if loading failed
# Show error message to user
error_label = QLabel(_("Error loading wine data: {error}").format(error=error_msg))
error_label.setStyleSheet(self.theme.GETWINE_WINDOW_STYLE)
error_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# Add error message to the tab widget or replace empty content
if hasattr(self, 'tab_widget'):
# Add error as a new tab or replace empty content
error_tab = QWidget()
error_layout = QVBoxLayout(error_tab)
error_layout.addWidget(error_label)
self.tab_widget.addTab(error_tab, _("Error"))
def process_metadata(self, metadata):
"""Обработка JSON, создание Табов"""
@@ -504,11 +593,6 @@ class ProtonManager(QDialog):
tabs_dict = {}
for source_key, entries in metadata.items():
# Пропускаем таб "gdk_proton" (вроде ненужный протон, скипаем)
if source_key.lower() == 'gdk_proton':
logger.debug(f"Skipping tab: {source_key}")
continue
# Filter entries based on CPU compatibility
filtered_entries = self.filter_entries_by_cpu_level(entries, source_key)
tabs_dict[source_key] = filtered_entries
@@ -528,7 +612,11 @@ class ProtonManager(QDialog):
return successful_tabs
def filter_entries_by_cpu_level(self, entries, source_name):
"""Filter entries based on CPU compatibility"""
"""Filter entries based on CPU compatibility - only filter CachyOS Proton"""
# Only apply CPU filtering to CachyOS Proton, show all versions for other sources
if source_name.lower() != 'proton_cachyos':
return entries
if self.cpu_level >= 4:
# If CPU supports all features, return all entries
return entries
@@ -1518,7 +1606,7 @@ class ProtonManager(QDialog):
def show_proton_manager(parent=None, portproton_location=None, input_manager=None):
"""
Shows the Proton/WINE archive extractor dialog.
Shows the Proton/WINE archive extractor dialog asynchronously.
Args:
parent: Parent widget for the dialog
@@ -1529,5 +1617,5 @@ def show_proton_manager(parent=None, portproton_location=None, input_manager=Non
ProtonManager dialog instance
"""
dialog = ProtonManager(parent, portproton_location, input_manager=input_manager)
dialog.exec() # Use exec() for modal dialog
dialog.show() # Show the dialog without blocking
return dialog