1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00
Rare/rare/components/tabs/games/game_info/game_info.py

403 lines
16 KiB
Python

import os
import platform
import shutil
from logging import getLogger
from pathlib import Path
from typing import Optional, Union
from PyQt5.QtCore import (
Qt,
QThreadPool,
pyqtSlot,
)
from PyQt5.QtWidgets import (
QMenu,
QPushButton,
QWidget,
QMessageBox,
QWidgetAction,
)
from rare.models.game import RareGame
from rare.shared import (
RareCore,
LegendaryCoreSingleton,
GlobalSignalsSingleton,
ArgumentsSingleton,
ImageManagerSingleton,
)
from rare.shared.image_manager import ImageSize
from rare.shared.workers import VerifyWorker, MoveWorker
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
from rare.utils.misc import get_size
from rare.utils.steam_grades import SteamWorker
from rare.widgets.image_widget import ImageWidget
from .move_game import MoveGamePopUp, is_game_dir
logger = getLogger("GameInfo")
class GameInfo(QWidget):
def __init__(self, parent=None):
super(GameInfo, self).__init__(parent=parent)
self.ui = Ui_GameInfo()
self.ui.setupUi(self)
# lk: set object names for CSS properties
self.ui.install_button.setObjectName("InstallButton")
self.ui.uninstall_button.setObjectName("UninstallButton")
self.rcore = RareCore.instance()
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton()
self.image_manager = ImageManagerSingleton()
self.rgame: Optional[RareGame] = None
self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Display)
self.ui.layout_game_info.insertWidget(0, self.image, alignment=Qt.AlignTop)
if platform.system() == "Windows":
self.ui.lbl_grade.setVisible(False)
self.ui.grade.setVisible(False)
self.ui.game_actions_stack.setCurrentWidget(self.ui.installed_page)
self.ui.uninstall_button.clicked.connect(self.__on_uninstall)
self.ui.verify_button.clicked.connect(self.__on_verify)
self.verify_pool = QThreadPool()
self.verify_pool.setMaxThreadCount(2)
if self.args.offline:
self.ui.repair_button.setDisabled(True)
else:
self.ui.repair_button.clicked.connect(self.__on_repair)
self.ui.install_button.clicked.connect(self.__on_install)
self.move_game_pop_up = MoveGamePopUp()
self.move_action = QWidgetAction(self)
self.move_action.setDefaultWidget(self.move_game_pop_up)
self.ui.move_button.setMenu(QMenu())
self.ui.move_button.menu().addAction(self.move_action)
self.existing_game_dir = False
self.is_moving = False
self.game_moving = None
self.dest_path_with_suffix = None
self.move_game_pop_up.browse_done.connect(self.show_menu_after_browse)
self.move_game_pop_up.move_clicked.connect(self.ui.move_button.menu().close)
self.move_game_pop_up.move_clicked.connect(self.move_game)
@pyqtSlot()
def __on_install(self):
if self.rgame.is_origin:
self.rgame.launch()
else:
self.rgame.install()
# FIXME: Move to RareGame
@pyqtSlot()
def __on_uninstall(self):
""" This function is to be called from the button only """
self.rgame.uninstall()
@pyqtSlot()
def __on_repair(self):
""" This function is to be called from the button only """
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f"{self.rgame.app_name}.repair")
if not os.path.exists(repair_file):
QMessageBox.warning(
self,
self.tr("Error - {}").format(self.rgame.title),
self.tr(
"Repair file does not exist or game does not need a repair. Please verify game first"
),
)
return
self.repair_game(self.rgame)
def repair_game(self, rgame: RareGame):
rgame.update_game()
ans = False
if rgame.has_update:
ans = QMessageBox.question(
self,
self.tr("Repair and update?"),
self.tr(
"There is an update for <b>{}</b> from <b>{}</b> to <b>{}</b>. "
"Do you want to update the game while repairing it?"
).format(rgame.title, rgame.version, rgame.remote_version),
) == QMessageBox.Yes
rgame.repair(repair_and_update=ans)
@pyqtSlot()
def __on_verify(self):
""" This function is to be called from the button only """
if not os.path.exists(self.rgame.igame.install_path):
logger.error(f"Installation path {self.rgame.igame.install_path} for {self.rgame.title} does not exist")
QMessageBox.warning(
self,
self.tr("Error - {}").format(self.rgame.title),
self.tr("Installation path for <b>{}</b> does not exist. Cannot continue.").format(self.rgame.title),
)
return
self.verify_game(self.rgame)
def verify_game(self, rgame: RareGame):
worker = VerifyWorker(self.core, self.args, rgame)
worker.signals.progress.connect(self.__on_verify_progress)
worker.signals.result.connect(self.__on_verify_result)
worker.signals.error.connect(self.__on_verify_error)
self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page)
self.ui.verify_progress.setValue(0)
self.ui.move_button.setEnabled(False)
self.rcore.enqueue_worker(rgame, worker)
def verify_cleanup(self, rgame: RareGame):
if rgame is not self.rgame:
return
self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)
self.ui.move_button.setEnabled(True)
self.ui.verify_button.setEnabled(True)
@pyqtSlot(RareGame, str)
def __on_verify_error(self, rgame: RareGame, message):
self.verify_cleanup(rgame)
QMessageBox.warning(
self,
self.tr("Error - {}").format(rgame.title),
message
)
@pyqtSlot(RareGame, int, int, float, float)
def __on_verify_progress(self, rgame: RareGame, num, total, percentage, speed):
self.ui.verify_progress.setValue(num * 100 // total)
@pyqtSlot(RareGame, bool, int, int)
def __on_verify_result(self, rgame: RareGame, success, failed, missing):
self.verify_cleanup(rgame)
self.ui.repair_button.setDisabled(success)
if success:
QMessageBox.information(
self,
self.tr("Summary - {}").format(rgame.title),
self.tr("<b>{}</b> has been verified successfully. "
"No missing or corrupt files found").format(rgame.title),
)
else:
ans = QMessageBox.question(
self,
self.tr("Summary - {}").format(rgame.title),
self.tr(
"Verification failed, <b>{}</b> file(s) corrupted, <b>{}</b> file(s) are missing. "
"Do you want to repair them?"
).format(failed, missing),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes,
)
if ans == QMessageBox.Yes:
self.repair_game(rgame)
@pyqtSlot(str)
def move_game(self, dest_path):
dest_path = Path(dest_path)
install_path = Path(self.rgame.igame.install_path)
self.dest_path_with_suffix = dest_path.joinpath(install_path.stem)
if self.dest_path_with_suffix.is_dir():
self.existing_game_dir = is_game_dir(install_path, self.dest_path_with_suffix)
if not self.existing_game_dir:
for i in dest_path.iterdir():
if install_path.stem in i.stem:
warn_msg = QMessageBox()
warn_msg.setText(self.tr("Destination file/directory exists."))
warn_msg.setInformativeText(
self.tr("Do you really want to overwrite it? This will delete {}").format(
self.dest_path_with_suffix
)
)
warn_msg.addButton(QPushButton(self.tr("Yes")), QMessageBox.YesRole)
warn_msg.addButton(QPushButton(self.tr("No")), QMessageBox.NoRole)
response = warn_msg.exec()
if response == 0:
# Not using pathlib, since we can't delete not-empty folders. With shutil we can.
if self.dest_path_with_suffix.is_dir():
shutil.rmtree(self.dest_path_with_suffix)
else:
self.dest_path_with_suffix.unlink()
else:
return
self.ui.move_stack.setCurrentWidget(self.ui.move_progress)
self.game_moving = self.rgame.app_name
self.is_moving = True
self.ui.verify_button.setEnabled(False)
if self.move_game_pop_up.is_different_drive(str(dest_path), str(install_path)):
# Destination dir on different drive
self.start_copy_diff_drive()
else:
# Destination dir on same drive
shutil.move(self.rgame.igame.install_path, dest_path)
self.set_new_game(self.dest_path_with_suffix)
def __on_move_progress(self, progress_int):
self.ui.move_progress.setValue(progress_int)
def start_copy_diff_drive(self):
worker = MoveWorker(
self.core,
install_path=self.rgame.igame.install_path,
dest_path=self.dest_path_with_suffix,
is_existing_dir=self.existing_game_dir,
igame=self.rgame.igame,
)
worker.signals.progress.connect(self.__on_move_progress)
worker.signals.result.connect(self.set_new_game)
worker.signals.error.connect(self.warn_no_space_left)
self.rcore.enqueue_worker(self.rgame, worker)
def move_helper_clean_up(self):
self.ui.move_stack.setCurrentWidget(self.ui.move_button_page)
self.move_game_pop_up.refresh_indicator()
self.is_moving = False
self.game_moving = None
self.ui.verify_button.setEnabled(True)
self.ui.move_button.setEnabled(True)
# This func does the needed UI changes, e.g. changing back to the initial move tool button and other stuff
def warn_no_space_left(self):
err_msg = QMessageBox()
err_msg.setText(self.tr("Out of space or unknown OS error occured."))
err_msg.exec()
self.move_helper_clean_up()
# Sets all needed variables to the new path.
def set_new_game(self, dest_path_with_suffix):
self.ui.install_path.setText(str(dest_path_with_suffix))
self.rgame.igame.install_path = str(dest_path_with_suffix)
self.core.lgd.set_installed_game(self.rgame.app_name, self.rgame.igame)
self.move_game_pop_up.install_path = self.rgame.igame.install_path
self.move_helper_clean_up()
# We need to re-show the menu, as after clicking on browse, the whole menu gets closed.
# Otherwise, the user would need to click on the move button again to open it again.
def show_menu_after_browse(self):
self.ui.move_button.showMenu()
@pyqtSlot()
def __update_ui(self):
pass
@pyqtSlot(str)
@pyqtSlot(RareGame)
def update_game(self, rgame: Union[RareGame, str]):
if isinstance(rgame, str):
rgame = self.rcore.get_game(rgame)
if self.rgame is not None:
if (worker := self.rgame.worker()) is not None:
if isinstance(worker, VerifyWorker):
try:
worker.signals.progress.disconnect(self.__on_verify_progress)
self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)
except TypeError as e:
logger.warning(f"{self.rgame.app_name} verify worker: {e}")
if isinstance(worker, MoveWorker):
try:
worker.signals.progress.disconnect(self.__on_move_progress)
self.ui.move_stack.setCurrentWidget(self.ui.move_button_page)
except TypeError as e:
logger.warning(f"{self.rgame.app_name} move worker: {e}")
self.rgame.signals.widget.update.disconnect(self.__update_ui)
self.rgame.signals.game.installed.disconnect(self.update_game)
self.rgame.signals.game.uninstalled.disconnect(self.update_game)
self.rgame = None
rgame.signals.widget.update.connect(self.__update_ui)
rgame.signals.game.installed.connect(self.update_game)
rgame.signals.game.uninstalled.connect(self.update_game)
if (worker := rgame.worker()) is not None:
if isinstance(worker, VerifyWorker):
self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page)
self.ui.verify_progress.setValue(rgame.progress)
worker.signals.progress.connect(self.__on_verify_progress)
else:
self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)
if isinstance(worker, MoveWorker):
self.ui.move_stack.setCurrentWidget(self.ui.move_progress_page)
self.ui.move_progress.setValue(rgame.progress)
worker.signals.progress.connect(self.__on_move_progress)
else:
self.ui.move_stack.setCurrentWidget(self.ui.move_button_page)
self.title.setTitle(rgame.app_title)
self.image.setPixmap(rgame.pixmap)
self.ui.app_name.setText(rgame.app_name)
self.ui.version.setText(rgame.version)
self.ui.dev.setText(rgame.developer)
if rgame.igame:
self.ui.install_size.setText(get_size(rgame.igame.install_size))
self.ui.install_path.setText(rgame.igame.install_path)
self.ui.platform.setText(rgame.igame.platform)
else:
self.ui.install_size.setText("N/A")
self.ui.install_path.setText("N/A")
self.ui.platform.setText("Windows")
self.ui.install_size.setEnabled(bool(rgame.igame))
self.ui.lbl_install_size.setEnabled(bool(rgame.igame))
self.ui.install_path.setEnabled(bool(rgame.igame))
self.ui.lbl_install_path.setEnabled(bool(rgame.igame))
self.ui.uninstall_button.setEnabled(bool(rgame.igame))
self.ui.verify_button.setEnabled(bool(rgame.igame))
self.ui.repair_button.setEnabled(bool(rgame.igame))
if not rgame.is_installed or rgame.is_origin:
self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page)
if rgame.is_origin:
self.ui.version.setText("N/A")
self.ui.version.setEnabled(False)
self.ui.install_button.setText(self.tr("Link to Origin/Launch"))
else:
self.ui.install_button.setText(self.tr("Install Game"))
else:
if not self.args.offline:
self.ui.repair_button.setDisabled(
not os.path.exists(os.path.join(self.core.lgd.get_tmp_path(), f"{rgame.app_name}.repair"))
)
self.ui.game_actions_stack.setCurrentWidget(self.ui.installed_page)
grade_visible = not rgame.is_unreal and platform.system() != "Windows"
self.ui.grade.setVisible(grade_visible)
self.ui.lbl_grade.setVisible(grade_visible)
if platform.system() != "Windows" and not rgame.is_unreal:
self.ui.grade.setText(self.tr("Loading"))
# TODO: Handle result emitted after quickly changing between game information
steam_worker: SteamWorker = SteamWorker(self.core, rgame.app_name)
steam_worker.signals.rating.connect(self.ui.grade.setText)
QThreadPool.globalInstance().start(steam_worker)
self.ui.verify_button.setEnabled(rgame.is_idle)
self.ui.move_button.setEnabled(rgame.is_idle)
self.move_game_pop_up.update_game(rgame.app_name)
self.rgame = rgame