chore: clean all vulture 80% confidence dead code
All checks were successful
Code check / Check code (push) Successful in 1m45s

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-12-21 19:34:32 +05:00
parent 6457084d56
commit 58bbff8e69
10 changed files with 10 additions and 276 deletions

View File

@@ -77,22 +77,6 @@ def invalidate_config_cache(config_file: str = CONFIG_FILE):
del _config_last_modified[config_file]
logger.debug(f"Config cache invalidated for {config_file}")
def read_config():
"""Reads the configuration file and returns a dictionary of parameters.
Example line in config (no sections):
detail_level = detailed
"""
config_dict = {}
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
key, sep, value = line.partition("=")
if sep:
config_dict[key.strip()] = value.strip()
return config_dict
def read_theme_from_config():
"""Reads the theme from the [Appearance] section of the configuration file.

View File

@@ -29,7 +29,7 @@ class ContextMenuSignals(QObject):
class ContextMenuManager:
"""Manages context menu actions for game management in PortProtonQt."""
def __init__(self, parent, portproton_location, theme, load_games_callback, game_library_manager):
def __init__(self, parent, portproton_location, theme, game_library_manager):
"""
Initialize the ContextMenuManager.
@@ -44,7 +44,6 @@ class ContextMenuManager:
self.portproton_location = portproton_location
self.theme = theme
self.theme_manager = ThemeManager()
self.load_games = load_games_callback
self.game_library_manager = game_library_manager
self.update_game_grid = game_library_manager.update_game_grid
self.legendary_path = os.path.join(

View File

@@ -853,7 +853,6 @@ class AddGameDialog(QDialog):
from portprotonqt.context_menu_manager import CustomLineEdit # Локальный импорт
self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config())
self.edit_mode = edit_mode
self.original_name = game_name
self.last_exe_path = exe_path # Store last selected exe path
self.last_cover_path = cover_path # Store last selected cover path
self.downloader = Downloader(max_workers=4) # Initialize Downloader

View File

@@ -126,8 +126,6 @@ class Downloader(QObject):
self._has_internet = True
return self._has_internet
def reset_internet_check(self):
self._has_internet = None
def _get_url_lock(self, url):
with self._global_lock:
@@ -247,9 +245,6 @@ class Downloader(QObject):
with self._global_lock:
self._cache.clear()
def is_cached(self, url):
with self._global_lock:
return url in self._cache
def get_latest_legendary_release(self):
"""Get the latest legendary release info from GitHub API."""

View File

@@ -555,44 +555,6 @@ def get_egs_game_description_async(
cleaned = re.sub(r'[^a-z0-9 ]', '', title.lower()).strip()
return re.sub(r'\s+', '-', cleaned)
def get_product_slug(namespace: str) -> str:
"""Fetches the product slug using the namespace via GraphQL."""
search_query = {
"query": """
query {
Catalog {
catalogNs(namespace: $namespace) {
mappings(pageType: "productHome") {
pageSlug
pageType
}
}
}
}
""",
"variables": {"namespace": namespace}
}
try:
response = requests.post(
"https://launcher.store.epicgames.com/graphql",
json=search_query,
headers=headers,
timeout=10
)
response.raise_for_status()
data = orjson.loads(response.content)
mappings = data.get("data", {}).get("Catalog", {}).get("catalogNs", {}).get("mappings", [])
for mapping in mappings:
if mapping.get("pageType") == "productHome":
return mapping.get("pageSlug", "")
logger.warning("No productHome slug found for namespace %s", namespace)
return ""
except requests.RequestException as e:
logger.warning("Failed to fetch product slug for namespace %s: %s", namespace, str(e))
return ""
except orjson.JSONDecodeError:
logger.warning("Invalid JSON response for namespace %s", namespace)
return ""
def fetch_legacy_description(url: str) -> str:
"""Fetches description from the legacy API, handling DNS failures."""

View File

@@ -33,7 +33,6 @@ class MainWindowProtocol(Protocol):
# Required attributes
searchEdit: CustomLineEdit
_last_card_width: int
card_width: int
current_hovered_card: GameCard | None
current_focused_card: GameCard | None
@@ -134,7 +133,6 @@ class GameLibraryManager:
self.sizeSlider.setToolTip(f"{self.card_width} px")
save_card_size(self.card_width)
self.main_window.card_width = self.card_width
self.main_window._last_card_width = self.card_width
for card in self.game_card_cache.values():
card.update_card_size(self.card_width)
self.update_game_grid()
@@ -266,7 +264,7 @@ class GameLibraryManager:
if self.is_filtering:
# Filter mode: use the pre-computed filtered_games from optimized search
# This means we already have the exact games to show
self._update_search_results()
self._update_search_results(search_text)
else:
# Full update: sorting, removal/addition, reorganization
games_list = self.filtered_games if self.filtered_games else self.games
@@ -385,13 +383,11 @@ class GameLibraryManager:
if self.gamesListLayout is not None:
self.gamesListLayout.update()
self.gamesListWidget.updateGeometry()
self.main_window._last_card_width = self.card_width
self.force_update_cards_library()
self.is_filtering = False # Reset flag in any case
def _update_search_results(self):
def _update_search_results(self, search_text: str = ""):
"""Update the grid with pre-computed search results."""
if self.gamesListLayout is None or self.gamesListWidget is None:
return
@@ -449,35 +445,12 @@ class GameLibraryManager:
if self.gamesListLayout is not None:
self.gamesListLayout.update()
self.gamesListWidget.updateGeometry()
self.main_window._last_card_width = self.card_width
self.force_update_cards_library()
def _apply_filter_visibility(self, search_text: str):
"""Applies visibility to cards based on search, without changing the layout."""
# This method is used for simple substring matching
# For the new optimized search, we'll use a different approach in update_game_grid
# when is_filter=True
visible_count = 0
for game_key, card in self.game_card_cache.items():
game_name = card.name # Assume GameCard has 'name' attribute
should_be_visible = not search_text or search_text in game_name.lower()
if card.isVisible() != should_be_visible:
card.setVisible(should_be_visible)
if should_be_visible:
visible_count += 1
# Load image only for newly visible cards
if game_key in self.pending_images:
cover_path, width, height, callback = self.pending_images.pop(game_key)
load_pixmap_async(cover_path, width, height, callback)
# Force full relayout after visibility changes
if self.gamesListLayout is not None:
self.gamesListLayout.invalidate() # Принудительно инвалидируем для пересчёта
self.gamesListLayout.update()
if self.gamesListWidget is not None:
self.gamesListWidget.updateGeometry()
self.main_window._last_card_width = self.card_width
# If search is empty, load images for visible ones
if not search_text:

View File

@@ -57,7 +57,6 @@ class MainWindow(QMainWindow):
def __init__(self, app_name: str, version: str):
super().__init__()
self.theme_manager = ThemeManager()
self.is_exiting = False
selected_theme = read_theme_from_config()
self.current_theme_name = selected_theme
# Apply theme but defer heavy font loading
@@ -65,7 +64,6 @@ class MainWindow(QMainWindow):
self.tray_manager = TrayManager(self, app_name, self.current_theme_name)
self.card_width = read_card_size()
self.auto_card_width = read_auto_card_size()
self._last_card_width = self.card_width
self.setWindowTitle(f"{app_name} {version}")
self.setMinimumSize(800, 600)
@@ -85,7 +83,6 @@ class MainWindow(QMainWindow):
self,
self.portproton_location,
self.theme,
self.loadGames,
self.game_library_manager
)
@@ -144,7 +141,6 @@ class MainWindow(QMainWindow):
QApplication.processEvents() # Process to show progress bar immediately
self.installing = False
self.current_install_script = None
self.install_process = None
self.install_monitor_timer = None
@@ -647,7 +643,6 @@ class MainWindow(QMainWindow):
QMessageBox.warning(self, _("Warning"), _("Installation already in progress."))
return
self.installing = True
self.current_install_script = script_name
self.seen_progress = False
self.current_percent = 0.0
start_sh = self.start_sh
@@ -735,7 +730,6 @@ class MainWindow(QMainWindow):
QMessageBox.warning(self, _("Error"), f"Installation failed (code: {exit_code}).")
self.progress_bar.setVisible(False)
self.current_install_script = None
if self.install_process:
self.install_process.deleteLater()
self.install_process = None
@@ -1165,7 +1159,7 @@ class MainWindow(QMainWindow):
self.searchEdit = CustomLineEdit(self, theme=self.theme)
icon: QIcon = cast(QIcon, self.theme_manager.get_icon("search"))
action_pos = cast(QLineEdit.ActionPosition, QLineEdit.ActionPosition.LeadingPosition)
self.search_action = self.searchEdit.addAction(icon, action_pos)
self.searchEdit.addAction(icon, action_pos)
self.searchEdit.setMaximumWidth(200)
self.searchEdit.setPlaceholderText(_("Find Games ..."))
self.searchEdit.setClearButtonEnabled(True)
@@ -1452,7 +1446,7 @@ class MainWindow(QMainWindow):
self.autoInstallSearchLineEdit = CustomLineEdit(self, theme=self.theme)
icon: QIcon = cast(QIcon, self.theme_manager.get_icon("search"))
action_pos = QLineEdit.ActionPosition.LeadingPosition
self.search_action = self.autoInstallSearchLineEdit.addAction(icon, action_pos)
self.autoInstallSearchLineEdit.addAction(icon, action_pos)
self.autoInstallSearchLineEdit.setMaximumWidth(200)
self.autoInstallSearchLineEdit.setPlaceholderText(_("Find Games ..."))
self.autoInstallSearchLineEdit.setClearButtonEnabled(True)
@@ -1627,12 +1621,10 @@ class MainWindow(QMainWindow):
def filterAutoInstallGames(self):
"""Filter auto install game cards based on search text."""
search_text = self.autoInstallSearchLineEdit.text().lower().strip()
visible_count = 0
for card in self.allAutoInstallCards:
if search_text in card.name.lower():
card.setVisible(True)
visible_count += 1
else:
card.setVisible(False)
@@ -1781,7 +1773,7 @@ class MainWindow(QMainWindow):
self.update_status_message.emit(_("Launching tool..."), 0)
proc = QProcess(self)
proc.finished.connect(lambda exitCode, exitStatus: self._on_wine_tool_finished(exitCode, cli_arg))
proc.finished.connect(lambda exitCode: self._on_wine_tool_finished(exitCode, cli_arg))
proc.errorOccurred.connect(lambda error: self._on_wine_tool_error(error, cli_arg))
proc.start(cmd[0], cmd[1:])
@@ -1878,7 +1870,7 @@ class MainWindow(QMainWindow):
self.update_status_message.emit(_("Clearing prefix..."), 0)
self.clear_process = QProcess(self)
self.clear_process.finished.connect(lambda exitCode, exitStatus: self._on_clear_prefix_finished(exitCode))
self.clear_process.finished.connect(lambda exitCode: self._on_clear_prefix_finished(exitCode))
self.clear_process.errorOccurred.connect(lambda error: self._on_clear_prefix_error(error))
cmd = start_sh + ["cli", "--clear_pfx", selected_wine, selected_prefix]
self.clear_process.start(cmd[0], cmd[1:])
@@ -1916,7 +1908,7 @@ class MainWindow(QMainWindow):
return
start_sh = self.start_sh
self.backup_process = QProcess(self)
self.backup_process.finished.connect(lambda exitCode, exitStatus: self._on_backup_finished(exitCode))
self.backup_process.finished.connect(lambda exitCode: self._on_backup_finished(exitCode))
cmd = start_sh + ["--backup-prefix", prefix_name, backup_dir]
self.backup_process.start(cmd[0], cmd[1:])
if not self.backup_process.waitForStarted():
@@ -1934,7 +1926,7 @@ class MainWindow(QMainWindow):
return
start_sh = self.start_sh
self.restore_process = QProcess(self)
self.restore_process.finished.connect(lambda exitCode, exitStatus: self._on_restore_finished(exitCode))
self.restore_process.finished.connect(lambda exitCode: self._on_restore_finished(exitCode))
cmd = start_sh + ["--restore-prefix", file_path]
self.restore_process.start(cmd[0], cmd[1:])
if not self.restore_process.waitForStarted():
@@ -2684,7 +2676,6 @@ class MainWindow(QMainWindow):
if target_running:
# Игра стартовала устанавливаем флаг, обновляем кнопку на "Stop"
self._gameLaunched = True
if self.current_running_button is not None:
try:
self.current_running_button.setText(_("Stop"))
@@ -2693,7 +2684,6 @@ class MainWindow(QMainWindow):
#self._inhibit_screensaver()
elif not child_running:
# Игра завершилась сбрасываем флаг, сбрасываем кнопку и останавливаем таймер
self._gameLaunched = False
self.resetPlayButton()
#self._uninhibit_screensaver()
if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
@@ -2786,7 +2776,6 @@ class MainWindow(QMainWindow):
self.checkProcessTimer = None
self.current_running_button = None
self.target_exe = None
self._gameLaunched = False
else:
# Запускаем игру через PortProton
env_vars = os.environ.copy()
@@ -2883,7 +2872,6 @@ class MainWindow(QMainWindow):
self.checkProcessTimer = None
self.current_running_button = None
self.target_exe = None
self._gameLaunched = False
#self._uninhibit_screensaver()
else:
# Сохраняем ссылку на кнопку для сброса после завершения игры
@@ -2929,7 +2917,6 @@ class MainWindow(QMainWindow):
return
# Полное закрытие приложения
self.is_exiting = True
event.accept()
# Скрываем и удаляем иконку трея

View File

@@ -1,15 +1,11 @@
"""
Utility module for search optimizations including Trie, hash tables, and fuzzy matching.
"""
import concurrent.futures
import threading
from collections.abc import Callable
from typing import Any
from rapidfuzz import fuzz
from threading import Lock
from portprotonqt.logger import get_logger
from PySide6.QtCore import QThread, QRunnable, Signal, QObject, QTimer
import requests
from PySide6.QtCore import QThread, Signal, QObject
logger = get_logger(__name__)
@@ -139,99 +135,12 @@ class SearchOptimizer:
return []
class RequestRunnable(QRunnable):
"""Runnable for executing HTTP requests in a thread."""
def __init__(self, method: str, url: str, on_success=None, on_error=None, **kwargs):
super().__init__()
self.method = method
self.url = url
self.kwargs = kwargs
self.result = None
self.error = None
self.on_success: Callable | None = on_success
self.on_error: Callable | None = on_error
def run(self):
try:
if self.method.lower() == 'get':
self.result = requests.get(self.url, **self.kwargs)
elif self.method.lower() == 'post':
self.result = requests.post(self.url, **self.kwargs)
else:
raise ValueError(f"Unsupported HTTP method: {self.method}")
# Execute success callback if provided
if self.on_success is not None:
success_callback = self.on_success # Capture the callback
def success_handler():
if success_callback is not None: # Re-check to satisfy Pyright
success_callback(self.result)
QTimer.singleShot(0, success_handler)
except Exception as e:
self.error = e
# Execute error callback if provided
if self.on_error is not None:
error_callback = self.on_error # Capture the callback
captured_error = e # Capture the exception
def error_handler():
error_callback(captured_error)
QTimer.singleShot(0, error_handler)
def run_request_in_thread(method: str, url: str, on_success: Callable | None = None, on_error: Callable | None = None, **kwargs):
"""Run HTTP request in a separate thread using Qt's thread system."""
runnable = RequestRunnable(method, url, on_success=on_success, on_error=on_error, **kwargs)
# Use QThreadPool to execute the runnable
from PySide6.QtCore import QThreadPool
thread_pool = QThreadPool.globalInstance()
thread_pool.start(runnable)
return runnable # Return the runnable to allow for potential cancellation if needed
def run_function_in_thread(func, *args, on_success: Callable | None = None, on_error: Callable | None = None, **kwargs):
"""Run a function in a separate thread."""
def execute():
try:
result = func(*args, **kwargs)
if on_success:
on_success(result)
except Exception as e:
if on_error:
on_error(e)
thread = threading.Thread(target=execute)
thread.daemon = True
thread.start()
return thread
def run_in_thread(func, *args, **kwargs):
"""Run a function in a separate thread."""
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(func, *args, **kwargs)
return future.result()
def run_in_thread_async(func, *args, callback: Callable | None = None, **kwargs):
"""Run a function in a separate thread asynchronously."""
import threading
def target():
try:
result = func(*args, **kwargs)
if callback:
callback(result)
except Exception as e:
if callback:
callback(None) # or handle error in callback
logger.error(f"Error in threaded operation: {e}")
thread = threading.Thread(target=target)
thread.daemon = True
thread.start()
return thread
# Threaded search implementation using QThread for performance optimization
@@ -320,11 +229,6 @@ class ThreadedSearch(QThread):
self.worker.search_finished.connect(self.search_finished)
self.worker.search_error.connect(self.search_error)
def set_search_params(self, search_text: str, games_data: list, search_type: str = "auto"):
"""Set parameters for the search operation."""
self.search_text = search_text
self.games_data = games_data
self.search_type = search_type
def set_games_data(self, games_data: list):
"""Set the games data to be searched."""
@@ -334,46 +238,3 @@ class ThreadedSearch(QThread):
def run(self):
"""Run the search operation in the thread."""
self.worker.execute_search(self.search_text, self.search_type)
class SearchThreadPool:
"""
A simple thread pool for managing multiple search operations.
"""
def __init__(self, max_threads: int = 3):
self.max_threads = max_threads
self.active_threads = []
self.thread_queue = []
def submit_search(self, search_text: str, games_data: list, search_type: str = "auto",
on_start: Callable | None = None, on_finish: Callable | None = None, on_error: Callable | None = None):
"""
Submit a search operation to the pool.
Args:
search_text: Text to search for
games_data: List of game data tuples to search in
search_type: Type of search ("exact", "prefix", "fuzzy", "auto")
on_start: Callback when search starts
on_finish: Callback when search finishes (receives results)
on_error: Callback when search errors (receives error message)
"""
search_thread = ThreadedSearch()
search_thread.set_search_params(search_text, games_data, search_type)
# Connect callbacks if provided
if on_start:
search_thread.search_started.connect(on_start)
if on_finish:
search_thread.search_finished.connect(on_finish)
if on_error:
search_thread.search_error.connect(on_error)
# Start the thread
search_thread.start()
self.active_threads.append(search_thread)
# Clean up finished threads
self.active_threads = [thread for thread in self.active_threads if thread.isRunning()]
return search_thread

View File

@@ -6,7 +6,6 @@ current_theme_name = read_theme_from_config()
# КОНСТАНТЫ
favoriteLabelSize = 48, 48
pixmapsScaledSize = 60, 60
# VARS
font_family = "Play"
@@ -652,26 +651,6 @@ PLAY_BUTTON_STYLE = f"""
}}
"""
# СТИЛЬ КНОПКИ "ОБЗОР..." В ДИАЛОГЕ "ДОБАВИТЬ ИГРУ"
DIALOG_BROWSE_BUTTON_STYLE = f"""
QPushButton {{
background: rgba(20, 20, 20, 0.40);
border: {border_a} rgba(255, 255, 255, 0.20);
border-radius: {border_radius_b};
color: {color_f};
font-size: {font_size_a};
padding: 5px 10px;
}}
QPushButton:hover {{
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 rgba(0,122,255,0.20),
stop:1 rgba(0,122,255,0.15));
}}
QPushButton:pressed {{
background: rgba(20, 20, 20, 0.60);
border: {border_a} rgba(255, 255, 255, 0.25);
}}
"""
ADDGAME_INPUT_STYLE = f"""
QLineEdit {{

View File

@@ -72,8 +72,6 @@ class TrayManager:
self.tray_icon.setContextMenu(self.tray_menu)
self.tray_icon.show()
self.main_window.is_exiting = False
self.click_count = 0
self.click_timer = QTimer()
self.click_timer.setSingleShot(True)
@@ -231,7 +229,6 @@ class TrayManager:
executable = sys.executable
args = sys.argv
self.main_window.is_exiting = True
QApplication.quit()
subprocess.Popen([executable] + args)
@@ -241,11 +238,9 @@ class TrayManager:
save_theme_to_config("standart")
executable = sys.executable
args = sys.argv
self.main_window.is_exiting = True
QApplication.quit()
subprocess.Popen([executable] + args)
def force_exit(self):
self.main_window.is_exiting = True
self.main_window.close()
sys.exit(0)