1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00

Implement moving game installations (#193)

Implement moving game installations
This commit is contained in:
aznd 2022-04-10 21:14:46 +02:00 committed by GitHub
parent 48562f6527
commit 7a82091285
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 288 additions and 12 deletions

View file

@ -1,13 +1,33 @@
import os
import platform
import shutil
from pathlib import Path
from logging import getLogger
from typing import Tuple
from PyQt5.QtCore import pyqtSignal, QThreadPool
from PyQt5.QtWidgets import QWidget, QMessageBox
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.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
from rare.utils.models import InstallOptionsModel
from rare.utils.steam_grades import SteamWorker
@ -53,7 +73,24 @@ class GameInfo(QWidget, Ui_GameInfo):
else:
self.repair_button.clicked.connect(self.repair)
self.install_button.clicked.connect(lambda: self.game_utils.launch_game(self.game.app_name))
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)
def uninstall(self):
if self.game_utils.uninstall_game(self.game.app_name):
@ -135,6 +172,53 @@ class GameInfo(QWidget, Ui_GameInfo):
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)
@ -193,13 +277,139 @@ class GameInfo(QWidget, Ui_GameInfo):
QThreadPool.globalInstance().start(self.steam_worker)
if len(self.verify_threads.keys()) == 0 or not self.verify_threads.get(
self.game.app_name
self.game.app_name
):
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
int(
self.verify_threads[self.game.app_name].num
/ self.verify_threads[self.game.app_name].total
* 100)
* 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))

View file

@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_GameInfo(object):
def setupUi(self, GameInfo):
GameInfo.setObjectName("GameInfo")
GameInfo.resize(414, 340)
GameInfo.resize(791, 583)
self.layout_game_info = QtWidgets.QHBoxLayout(GameInfo)
self.layout_game_info.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.layout_game_info.setObjectName("layout_game_info")
@ -136,6 +136,28 @@ class Ui_GameInfo(object):
self.repair_button = QtWidgets.QPushButton(self.installed_page)
self.repair_button.setObjectName("repair_button")
self.installed_layout.addWidget(self.repair_button)
self.move_stack = QtWidgets.QStackedWidget(self.installed_page)
self.move_stack.setMinimumSize(QtCore.QSize(0, 20))
self.move_stack.setObjectName("move_stack")
self.page = QtWidgets.QWidget()
self.page.setObjectName("page")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.page)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.move_button = QtWidgets.QToolButton(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.move_button.sizePolicy().hasHeightForWidth())
self.move_button.setSizePolicy(sizePolicy)
self.move_button.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.move_button.setObjectName("move_button")
self.horizontalLayout_2.addWidget(self.move_button)
self.move_stack.addWidget(self.page)
self.page_2 = QtWidgets.QWidget()
self.page_2.setObjectName("page_2")
self.move_stack.addWidget(self.page_2)
self.installed_layout.addWidget(self.move_stack)
self.uninstall_button = QtWidgets.QPushButton(self.installed_page)
self.uninstall_button.setStyleSheet("")
self.uninstall_button.setObjectName("uninstall_button")
@ -219,7 +241,8 @@ class Ui_GameInfo(object):
self.lbl_install_path.setText(_translate("GameInfo", "Installation Path"))
self.lbl_install_size.setText(_translate("GameInfo", "Installation Size"))
self.verify_button.setText(_translate("GameInfo", "Verify Installation"))
self.repair_button.setText(_translate("GameInfo", "Repair Instalation"))
self.repair_button.setText(_translate("GameInfo", "Repair Installation"))
self.move_button.setText(_translate("GameInfo", "Move Installation"))
self.uninstall_button.setText(_translate("GameInfo", "Uninstall Game"))
self.install_button.setText(_translate("GameInfo", "Install Game"))
self.lbl_version.setText(_translate("GameInfo", "Version"))

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>340</height>
<width>791</width>
<height>583</height>
</rect>
</property>
<property name="windowTitle">
@ -283,10 +283,53 @@
<item>
<widget class="QPushButton" name="repair_button">
<property name="text">
<string>Repair Instalation</string>
<string>Repair Installation</string>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="move_stack">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<widget class="QWidget" name="page">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="move_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Move Installation</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2"/>
</widget>
</item>
<item>
<widget class="QPushButton" name="uninstall_button">
<property name="styleSheet">