1
0
Fork 0
mirror of synced 2024-06-29 19:51:02 +12:00
Rare/rare/components/tabs/games/game_info/game_info.py

416 lines
15 KiB
Python
Raw Normal View History

import os
import platform
import shutil
from pathlib import Path
2021-11-17 10:54:23 +13:00
from logging import getLogger
from typing import Tuple
from PyQt5.QtCore import Qt, pyqtSignal, QThreadPool, pyqtSlot
from PyQt5.QtWidgets import (
QCheckBox,
QFileDialog,
QHBoxLayout,
QLabel,
QMenu,
QProgressBar,
QPushButton,
QVBoxLayout,
QWidget,
QMessageBox,
QWidgetAction,
)
from legendary.models.game import Game, InstalledGame
from rare.shared import (
LegendaryCoreSingleton,
GlobalSignalsSingleton,
ArgumentsSingleton,
)
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
from rare.utils.extra_widgets import PathEdit
from rare.utils.legendary_utils import VerifyWorker
2021-10-15 10:05:00 +13:00
from rare.utils.models import InstallOptionsModel
from rare.utils.steam_grades import SteamWorker
from rare.utils.utils import get_size, get_pixmap
2021-11-17 10:54:23 +13:00
logger = getLogger("GameInfo")
class GameInfo(QWidget, Ui_GameInfo):
igame: InstalledGame
2021-09-30 10:22:47 +13:00
game: Game = None
2021-09-16 10:29:12 +12:00
verify_threads = dict()
2021-10-15 10:05:00 +13:00
verification_finished = pyqtSignal(InstalledGame)
2021-11-17 10:54:23 +13:00
uninstalled = pyqtSignal(str)
2021-11-17 10:54:23 +13:00
def __init__(self, parent, game_utils):
super(GameInfo, self).__init__(parent=parent)
self.setupUi(self)
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton()
2021-11-17 10:54:23 +13:00
self.game_utils = game_utils
if platform.system() == "Windows":
self.lbl_grade.setVisible(False)
self.grade.setVisible(False)
else:
self.steam_worker = SteamWorker(self.core)
self.steam_worker.signals.rating_signal.connect(self.grade.setText)
self.steam_worker.setAutoDelete(False)
self.game_actions_stack.setCurrentIndex(0)
self.install_button.setText(self.tr("Link to Origin/Launch"))
self.game_actions_stack.resize(self.game_actions_stack.minimumSize())
2021-11-17 10:54:23 +13:00
self.uninstall_button.clicked.connect(self.uninstall)
self.verify_button.clicked.connect(self.verify)
self.verify_pool = QThreadPool()
self.verify_pool.setMaxThreadCount(2)
if self.args.offline:
self.repair_button.setDisabled(True)
else:
self.repair_button.clicked.connect(self.repair)
self.install_button.clicked.connect(
lambda: self.game_utils.launch_game(self.game.app_name)
)
self.move_game_pop_up = MoveGamePopUp()
self.move_action = QWidgetAction(self)
self.move_action.setDefaultWidget(self.move_game_pop_up)
self.move_button.setMenu(QMenu())
self.move_button.menu().addAction(self.move_action)
self.widget_container = QWidget()
box_layout = QHBoxLayout()
box_layout.setContentsMargins(0, 0, 0, 0)
box_layout.addWidget(self.move_button)
self.widget_container.setLayout(box_layout)
index = self.move_stack.addWidget(self.widget_container)
self.move_stack.setCurrentIndex(index)
self.move_game_pop_up.move_clicked.connect(self.move_game)
self.move_game_pop_up.browse_done.connect(self.show_menu_after_browse)
2022-01-19 09:46:12 +13:00
2021-11-17 10:54:23 +13:00
def uninstall(self):
if self.game_utils.uninstall_game(self.game.app_name):
self.game_utils.update_list.emit(self.game.app_name)
self.uninstalled.emit(self.game.app_name)
def repair(self):
2021-12-24 22:09:50 +13:00
repair_file = os.path.join(
self.core.lgd.get_tmp_path(), f"{self.game.app_name}.repair"
)
if not os.path.exists(repair_file):
2021-12-24 22:09:50 +13:00
QMessageBox.warning(
self,
"Warning",
self.tr(
"Repair file does not exist or game does not need a repair. Please verify game first"
),
)
return
2021-12-24 22:09:50 +13:00
self.signals.install_game.emit(
InstallOptionsModel(app_name=self.game.app_name, repair=True, update=True)
)
def verify(self):
2021-11-17 10:54:23 +13:00
if not os.path.exists(self.igame.install_path):
logger.error("Path does not exist")
2021-12-24 22:09:50 +13:00
QMessageBox.warning(
self,
"Warning",
self.tr("Installation path of {} does not exist. Cannot verify").format(
self.igame.title
),
)
2021-11-17 10:54:23 +13:00
return
self.verify_widget.setCurrentIndex(1)
2022-01-16 04:40:31 +13:00
verify_worker = VerifyWorker(self.game.app_name)
2022-05-14 08:17:50 +12:00
verify_worker.signals.status.connect(self.verify_statistics)
verify_worker.signals.summary.connect(self.finish_verify)
self.verify_progress.setValue(0)
self.verify_threads[self.game.app_name] = verify_worker
self.verify_pool.start(verify_worker)
2022-05-14 08:17:50 +12:00
def verify_statistics(self, num, total, app_name):
# checked, max, app_name
2022-01-16 04:40:31 +13:00
if app_name == self.game.app_name:
self.verify_progress.setValue(num * 100 // total)
2021-10-15 10:05:00 +13:00
def finish_verify(self, failed, missing, app_name):
if failed == missing == 0:
2021-12-24 22:09:50 +13:00
QMessageBox.information(
self,
"Summary",
"Game was verified successfully. No missing or corrupt files found",
)
2021-10-15 10:05:00 +13:00
igame = self.core.get_installed_game(app_name)
if igame.needs_verification:
igame.needs_verification = False
self.core.lgd.set_installed_game(self.igame.app_name, igame)
self.verification_finished.emit(igame)
elif failed == missing == -1:
QMessageBox.warning(self, "Warning", self.tr("Something went wrong"))
else:
2021-12-24 22:09:50 +13:00
ans = QMessageBox.question(
self,
"Summary",
self.tr(
"Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?"
).format(failed, missing),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes,
)
if ans == QMessageBox.Yes:
2021-12-24 22:09:50 +13:00
self.signals.install_game.emit(
InstallOptionsModel(
app_name=self.game.app_name, repair=True, update=True
)
)
self.verify_widget.setCurrentIndex(0)
self.verify_threads.pop(app_name)
@pyqtSlot(str)
def move_game(self, destination_path):
destination_path = Path(destination_path)
install_path = Path(self.igame.install_path)
destination_path_with_suffix = destination_path.joinpath(
install_path.stem
)
progress_of_moving = QProgressBar(self)
progress_of_moving.setValue(0)
for i in destination_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(destination_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 destination_path_with_suffix.is_dir():
shutil.rmtree(destination_path_with_suffix)
else:
destination_path_with_suffix.unlink()
else:
return
shutil.move(self.igame.install_path, destination_path)
self.install_path.setText(str(destination_path_with_suffix))
self.igame.install_path = str(destination_path_with_suffix)
self.core.lgd.set_installed_game(self.igame.app_name, self.igame)
self.move_game_pop_up.install_path = self.igame.install_path
self.move_game_pop_up.refresh_indicator()
progress_of_moving.setValue(100)
def show_menu_after_browse(self):
self.move_button.showMenu()
def update_game(self, app_name: str):
self.game = self.core.get_game(app_name)
self.igame = self.core.get_installed_game(self.game.app_name)
self.title.setTitle(self.game.app_title)
pixmap = get_pixmap(self.game.app_name)
2021-12-20 09:32:18 +13:00
if pixmap.isNull():
pixmap = get_pixmap(self.parent().parent().parent().ue_name)
w = 200
pixmap = pixmap.scaled(w, int(w * 4 / 3))
self.image.setPixmap(pixmap)
self.app_name.setText(self.game.app_name)
if self.igame:
self.version.setText(self.igame.version)
else:
2021-12-24 22:09:50 +13:00
self.version.setText(
self.game.app_version(self.igame.platform if self.igame else "Windows")
)
self.dev.setText(self.game.metadata["developer"])
2021-10-15 10:05:00 +13:00
if self.igame:
self.install_size.setText(get_size(self.igame.install_size))
self.install_path.setText(self.igame.install_path)
self.install_size.setVisible(True)
self.install_path.setVisible(True)
self.platform.setText(self.igame.platform)
else:
self.install_size.setVisible(False)
self.install_path.setVisible(False)
self.platform.setText("Windows")
if not self.igame:
# origin game
self.uninstall_button.setDisabled(True)
self.verify_button.setDisabled(True)
self.repair_button.setDisabled(True)
self.game_actions_stack.setCurrentIndex(1)
else:
self.uninstall_button.setDisabled(False)
self.verify_button.setDisabled(False)
if not self.args.offline:
self.repair_button.setDisabled(False)
self.game_actions_stack.setCurrentIndex(0)
2022-01-19 09:46:12 +13:00
try:
is_ue = self.core.get_asset(app_name).namespace == "ue"
except ValueError:
is_ue = False
self.grade.setVisible(not is_ue)
self.lbl_grade.setVisible(not is_ue)
if platform.system() != "Windows" and not is_ue:
self.grade.setText(self.tr("Loading"))
self.steam_worker.set_app_name(self.game.app_name)
QThreadPool.globalInstance().start(self.steam_worker)
2021-12-24 22:09:50 +13:00
if len(self.verify_threads.keys()) == 0 or not self.verify_threads.get(
self.game.app_name
2021-12-24 22:09:50 +13:00
):
self.verify_widget.setCurrentIndex(0)
elif self.verify_threads.get(self.game.app_name):
self.verify_widget.setCurrentIndex(1)
self.verify_progress.setValue(
int(
self.verify_threads[self.game.app_name].num
/ self.verify_threads[self.game.app_name].total
* 100
)
)
self.move_game_pop_up.update_game(app_name)
class MoveGamePopUp(QWidget):
move_clicked = pyqtSignal(str)
browse_done = pyqtSignal()
def __init__(self):
super(MoveGamePopUp, self).__init__()
layout: QVBoxLayout = QVBoxLayout()
self.install_path = str()
self.core = LegendaryCoreSingleton()
self.move_path_edit = PathEdit(
str(), QFileDialog.Directory, edit_func=self.edit_func_move_game
)
self.move_path_edit.path_select.clicked.connect(self.emit_browse_done_signal)
self.move_game = QPushButton(self.tr("Move"))
self.move_game.setMaximumWidth(50)
self.move_game.clicked.connect(self.emit_move_game_signal)
self.warn_overwriting = QLabel()
bottom_layout = QHBoxLayout()
bottom_layout.setAlignment(Qt.AlignRight)
bottom_layout.addWidget(self.warn_overwriting, stretch=1)
bottom_layout.addWidget(self.move_game)
layout.addWidget(self.move_path_edit)
layout.addLayout(bottom_layout)
self.setLayout(layout)
def emit_move_game_signal(self):
self.move_clicked.emit(self.move_path_edit.text())
def emit_browse_done_signal(self):
self.browse_done.emit()
def refresh_indicator(self):
# needed so the edit_func gets run again
text = self.move_path_edit.text()
self.move_path_edit.setText(str())
self.move_path_edit.setText(text)
# Thanks to lk.
def find_mount(self, path):
mount_point = path
while path != path.anchor:
if path.is_mount():
return path
else:
path = path.parent
return mount_point
def edit_func_move_game(self, dir_selected):
self.warn_overwriting.setHidden(True)
def helper_func(reason: str) -> Tuple[bool, str, str]:
self.move_game.setEnabled(False)
return False, dir_selected, self.tr(reason)
if not self.install_path or not dir_selected:
return helper_func("You need to provide a directory.")
current_path = Path(self.install_path).resolve()
destination_path = Path(dir_selected).resolve()
destination_path_with_suffix = destination_path.joinpath(
current_path.stem
).resolve()
if not destination_path.is_dir():
return helper_func("Directory doesn't exist or file selected.")
if not os.access(dir_selected, os.W_OK) or not os.access(self.install_path, os.W_OK):
return helper_func("No write permission on destination path/current install path.")
if (
current_path == destination_path
or current_path == destination_path_with_suffix
):
return helper_func("Same directory or parent directory selected.")
if str(current_path) in str(destination_path):
return helper_func(
"You can't select a directory that is inside the current install path."
)
if str(destination_path_with_suffix) in str(current_path):
return helper_func(
"You can't select a directory which contains the game installation."
)
if not platform.system() == "Windows":
if self.find_mount(destination_path) != self.find_mount(current_path):
return helper_func(
"Moving to a different drive is currently not supported."
)
else:
if current_path.drive != destination_path.drive:
return helper_func(
"Moving to a different drive is currently not supported."
)
for game in self.core.get_installed_list():
if game.install_path in dir_selected:
return helper_func(
"Game installations cannot be nested due to unintended sideeffects."
)
for i in destination_path.iterdir():
if current_path.stem in i.stem:
self.warn_overwriting.setHidden(False)
# Fallback
self.move_game.setEnabled(True)
return True, dir_selected, str()
def update_game(self, app_name):
igame = self.core.get_installed_game(app_name, False)
if igame is None:
return
self.install_path = igame.install_path
self.move_path_edit.setText(igame.install_path)
self.warn_overwriting.setText(self.tr("Moving here will overwrite the dir/file {}/").format(Path(self.install_path).stem))