1
0
Fork 0
mirror of synced 2024-05-18 11:32:50 +12:00
Rare/rare/components/tabs/games/game_info/game_info.py
2024-02-21 13:30:41 +02:00

397 lines
16 KiB
Python

import os
import platform
import shutil
from logging import getLogger
from typing import Optional
from PyQt5.QtCore import (
Qt,
pyqtSlot,
pyqtSignal,
)
from PyQt5.QtWidgets import (
QWidget,
QMessageBox,
)
from rare.models.install import SelectiveDownloadsModel, MoveGameModel
from rare.components.dialogs.selective_dialog import SelectiveDialog
from rare.models.game import RareGame
from rare.shared import RareCore
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 format_size, qta_icon, style_hyperlink
from rare.widgets.image_widget import ImageWidget, ImageSize
from rare.widgets.side_tab import SideTabContents
from rare.components.dialogs.move_dialog import MoveDialog, is_game_dir
logger = getLogger("GameInfo")
class GameInfo(QWidget, SideTabContents):
# str: app_name
import_clicked = pyqtSignal(str)
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.modify_button.setObjectName("InstallButton")
self.ui.uninstall_button.setObjectName("UninstallButton")
self.ui.install_button.setIcon(qta_icon("ri.install-line"))
self.ui.import_button.setIcon(qta_icon("mdi.application-import"))
self.ui.modify_button.setIcon(qta_icon("fa.gear"))
self.ui.verify_button.setIcon(qta_icon("fa.check"))
self.ui.repair_button.setIcon(qta_icon("fa.wrench"))
self.ui.move_button.setIcon(qta_icon("mdi.folder-move-outline"))
self.ui.uninstall_button.setIcon(qta_icon("ri.uninstall-line"))
self.rcore = RareCore.instance()
self.core = RareCore.instance().core()
self.args = RareCore.instance().args()
# self.image_manager = RareCore.instance().image_manager()
self.rgame: Optional[RareGame] = None
self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Display)
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)
self.ui.install_button.clicked.connect(self.__on_install)
self.ui.import_button.clicked.connect(self.__on_import)
self.ui.modify_button.clicked.connect(self.__on_modify)
self.ui.verify_button.clicked.connect(self.__on_verify)
self.ui.repair_button.clicked.connect(self.__on_repair)
self.ui.move_button.clicked.connect(self.__on_move)
self.ui.uninstall_button.clicked.connect(self.__on_uninstall)
self.steam_grade_ratings = {
"platinum": self.tr("Platinum"),
"gold": self.tr("Gold"),
"silver": self.tr("Silver"),
"bronze": self.tr("Bronze"),
"borked": self.tr("Borked"),
"fail": self.tr("Failed to get rating"),
"pending": self.tr("Loading..."),
"na": self.tr("Not applicable"),
}
# lk: hide unfinished things
self.ui.tags_group.setVisible(False)
self.ui.requirements_group.setVisible(False)
@pyqtSlot()
def __on_install(self):
if self.rgame.is_non_asset:
self.rgame.launch()
else:
self.rgame.install()
@pyqtSlot()
def __on_import(self):
self.import_clicked.emit(self.rgame.app_name)
@pyqtSlot()
def __on_uninstall(self):
""" This method is to be called from the button only """
self.rgame.uninstall()
@pyqtSlot()
def __on_modify(self):
""" This method is to be called from the button only """
self.rgame.modify()
@pyqtSlot()
def __on_repair(self):
""" This method 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.app_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? - {}").format(self.rgame.app_title),
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.app_title, rgame.version, rgame.remote_version),
) == QMessageBox.Yes
rgame.repair(repair_and_update=ans)
@pyqtSlot(RareGame, str)
def __on_worker_error(self, rgame: RareGame, message: str):
QMessageBox.warning(
self,
self.tr("Error - {}").format(rgame.app_title),
message
)
@pyqtSlot()
def __on_verify(self):
""" This method 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.app_title} does not exist")
QMessageBox.warning(
self,
self.tr("Error - {}").format(self.rgame.app_title),
self.tr("Installation path for <b>{}</b> does not exist. Cannot continue.").format(self.rgame.app_title),
)
return
if self.rgame.sdl_name is not None:
selective_dialog = SelectiveDialog(
self.rgame, parent=self
)
selective_dialog.result_ready.connect(self.verify_game)
selective_dialog.open()
else:
self.verify_game(self.rgame)
@pyqtSlot(RareGame, SelectiveDownloadsModel)
def verify_game(self, rgame: RareGame, sdl_model: SelectiveDownloadsModel = None):
if sdl_model is not None:
if not sdl_model.accepted or sdl_model.install_tag is None:
return
self.core.lgd.config.set(rgame.app_name, "install_tags", ','.join(sdl_model.install_tag))
self.core.lgd.save_config()
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_worker_error)
self.rcore.enqueue_worker(rgame, worker)
@pyqtSlot(RareGame, int, int, float, float)
def __on_verify_progress(self, rgame: RareGame, num, total, percentage, speed):
# lk: the check is NOT REQUIRED because signals are disconnected but protect against it anyway
if rgame is not self.rgame:
return
self.ui.verify_progress.setValue(num * 100 // total)
@pyqtSlot(RareGame, bool, int, int)
def __on_verify_result(self, rgame: RareGame, success, failed, missing):
self.ui.repair_button.setDisabled(success)
if success:
QMessageBox.information(
self,
self.tr("Summary - {}").format(rgame.app_title),
self.tr("<b>{}</b> has been verified successfully. "
"No missing or corrupt files found").format(rgame.app_title),
)
else:
ans = QMessageBox.question(
self,
self.tr("Summary - {}").format(rgame.app_title),
self.tr(
"<b>{}</b> failed verification, <b>{}</b> file(s) corrupted, <b>{}</b> file(s) are missing. "
"Do you want to repair them?"
).format(rgame.app_title, failed, missing),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes,
)
if ans == QMessageBox.Yes:
self.repair_game(rgame)
@pyqtSlot()
def __on_move(self):
""" This method is to be called from the button only """
move_dialog = MoveDialog(self.rgame, parent=self)
move_dialog.result_ready.connect(self.move_game)
move_dialog.open()
def move_game(self, rgame: RareGame, model: MoveGameModel):
if not model.accepted:
return
new_install_path = os.path.join(model.target_path, os.path.basename(self.rgame.install_path))
dir_exists = False
if os.path.isdir(new_install_path):
dir_exists = is_game_dir(self.rgame.install_path, new_install_path)
if not dir_exists:
for item in os.listdir(model.target_path):
if os.path.basename(self.rgame.install_path) in os.path.basename(item):
ans = QMessageBox.question(
self,
self.tr("Move game? - {}").format(self.rgame.app_title),
self.tr(
"Destination <b>{}</b> already exists. "
"Are you sure you want to overwrite it?"
).format(new_install_path),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes,
)
if ans == QMessageBox.Yes:
if os.path.isdir(new_install_path):
shutil.rmtree(new_install_path)
else:
os.remove(new_install_path)
else:
return
worker = MoveWorker(
self.core, rgame=rgame, dst_path=model.target_path, dst_exists=dir_exists
)
worker.signals.progress.connect(self.__on_move_progress)
worker.signals.result.connect(self.__on_move_result)
worker.signals.error.connect(self.__on_worker_error)
self.rcore.enqueue_worker(self.rgame, worker)
@pyqtSlot(RareGame, int, object, object)
def __on_move_progress(self, rgame: RareGame, progress: int, total_size: int, copied_size: int):
# lk: the check is NOT REQUIRED because signals are disconnected but protect against it anyway
if rgame is not self.rgame:
return
self.ui.move_progress.setValue(progress)
@pyqtSlot(RareGame, str)
def __on_move_result(self, rgame: RareGame, dst_path: str):
QMessageBox.information(
self,
self.tr("Summary - {}").format(rgame.app_title),
self.tr("<b>{}</b> successfully moved to <b>{}<b>.").format(rgame.app_title, dst_path),
)
@pyqtSlot()
def __update_widget(self):
""" React to state updates from RareGame """
self.image.setPixmap(self.rgame.get_pixmap(True))
self.ui.lbl_version.setDisabled(self.rgame.is_non_asset)
self.ui.version.setDisabled(self.rgame.is_non_asset)
self.ui.version.setText(
self.rgame.version if not self.rgame.is_non_asset else "N/A"
)
self.ui.lbl_install_size.setEnabled(bool(self.rgame.install_size))
self.ui.install_size.setEnabled(bool(self.rgame.install_size))
self.ui.install_size.setText(
format_size(self.rgame.install_size) if self.rgame.install_size else "N/A"
)
self.ui.lbl_install_path.setEnabled(bool(self.rgame.install_path))
self.ui.install_path.setEnabled(bool(self.rgame.install_path))
self.ui.install_path.setText(
self.rgame.install_path if self.rgame.install_path else "N/A"
)
self.ui.platform.setText(
self.rgame.igame.platform
if self.rgame.is_installed and not self.rgame.is_non_asset
else self.rgame.default_platform
)
self.ui.lbl_grade.setDisabled(
self.rgame.is_unreal or platform.system() == "Windows"
)
self.ui.grade.setDisabled(
self.rgame.is_unreal or platform.system() == "Windows"
)
self.ui.grade.setText(
style_hyperlink(
f"https://www.protondb.com/app/{self.rgame.steam_appid}",
self.steam_grade_ratings[self.rgame.steam_grade()]
)
)
self.ui.install_button.setEnabled(
(not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle
)
self.ui.import_button.setEnabled(
(not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle
)
self.ui.modify_button.setEnabled(
self.rgame.is_installed
and (not self.rgame.is_non_asset)
and self.rgame.is_idle
and self.rgame.sdl_name is not None
)
self.ui.verify_button.setEnabled(
self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
)
self.ui.verify_progress.setValue(self.rgame.progress if self.rgame.state == RareGame.State.VERIFYING else 0)
if self.rgame.state == RareGame.State.VERIFYING:
self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page)
else:
self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)
self.ui.repair_button.setEnabled(
self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
and self.rgame.needs_repair
and not self.args.offline
)
self.ui.move_button.setEnabled(
self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
)
self.ui.move_progress.setValue(self.rgame.progress if self.rgame.state == RareGame.State.MOVING else 0)
if self.rgame.state == RareGame.State.MOVING:
self.ui.move_stack.setCurrentWidget(self.ui.move_progress_page)
else:
self.ui.move_stack.setCurrentWidget(self.ui.move_button_page)
self.ui.uninstall_button.setEnabled(
self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
)
if self.rgame.is_installed and not self.rgame.is_non_asset:
self.ui.game_actions_stack.setCurrentWidget(self.ui.installed_page)
else:
self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page)
@pyqtSlot(RareGame)
def update_game(self, rgame: RareGame):
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)
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)
except TypeError as e:
logger.warning(f"{self.rgame.app_name} move worker: {e}")
self.rgame.signals.widget.update.disconnect(self.__update_widget)
self.rgame = None
rgame.signals.widget.update.connect(self.__update_widget)
if (worker := rgame.worker()) is not None:
if isinstance(worker, VerifyWorker):
worker.signals.progress.connect(self.__on_verify_progress)
if isinstance(worker, MoveWorker):
worker.signals.progress.connect(self.__on_move_progress)
self.set_title.emit(rgame.app_title)
self.ui.app_name.setText(rgame.app_name)
self.ui.dev.setText(rgame.developer)
if rgame.is_non_asset:
self.ui.install_button.setText(self.tr("Link/Launch"))
self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page)
else:
self.ui.install_button.setText(self.tr("Install"))
self.rgame = rgame
self.__update_widget()