687218d29b
With launchable DLCs we will need to replace the InstalledGame in the DLC with the InstalledGame from the base game so depend these properties solely on the Game attribute. Furthermore, since we do not need backwards compatibility any more, remove the `title` property and rename its uses to use `app_title`
385 lines
15 KiB
Python
385 lines
15 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 (
|
|
QMenu,
|
|
QWidget,
|
|
QMessageBox,
|
|
QWidgetAction,
|
|
)
|
|
|
|
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, icon
|
|
from rare.widgets.image_widget import ImageWidget, ImageSize
|
|
from rare.widgets.side_tab import SideTabContents
|
|
from .move_game import MoveGamePopUp, 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(icon("ri.install-line"))
|
|
self.ui.import_button.setIcon(icon("mdi.file-import"))
|
|
|
|
self.ui.modify_button.setIcon(icon("fa.gear"))
|
|
self.ui.verify_button.setIcon(icon("fa.check"))
|
|
self.ui.repair_button.setIcon(icon("fa.wrench"))
|
|
self.ui.move_button.setIcon(icon("mdi.folder-move"))
|
|
self.ui.uninstall_button.setIcon(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.uninstall_button.clicked.connect(self.__on_uninstall)
|
|
|
|
self.move_game_pop_up = MoveGamePopUp(self)
|
|
move_action = QWidgetAction(self)
|
|
move_action.setDefaultWidget(self.move_game_pop_up)
|
|
self.ui.move_button.setMenu(QMenu(self.ui.move_button))
|
|
self.ui.move_button.menu().addAction(move_action)
|
|
|
|
self.move_game_pop_up.browse_done.connect(self.ui.move_button.showMenu)
|
|
self.move_game_pop_up.move_clicked.connect(self.ui.move_button.menu().close)
|
|
self.move_game_pop_up.move_clicked.connect(self.__on_move)
|
|
|
|
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
|
|
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_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(str)
|
|
def __on_move(self, dst_path: str):
|
|
""" This method is to be called from the button only """
|
|
new_install_path = os.path.join(dst_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(dst_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
|
|
|
|
self.move_game(self.rgame, new_install_path, dir_exists)
|
|
|
|
def move_game(self, rgame: RareGame, dst_path, dst_exists):
|
|
worker = MoveWorker(
|
|
self.core, rgame=rgame, dst_path=dst_path, dst_exists=dst_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.image_manager.get_pixmap(self.rgame.app_name, True))
|
|
self.image.setPixmap(self.rgame.pixmap)
|
|
|
|
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 "Windows"
|
|
)
|
|
|
|
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(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 Game"))
|
|
|
|
self.move_game_pop_up.update_game(rgame)
|
|
|
|
self.rgame = rgame
|
|
self.__update_widget()
|