2022-08-12 07:43:11 +12:00
|
|
|
import os
|
|
|
|
import shutil
|
2023-02-19 01:33:42 +13:00
|
|
|
from enum import auto
|
2022-08-12 07:43:11 +12:00
|
|
|
from logging import getLogger
|
2023-02-16 03:59:33 +13:00
|
|
|
from typing import Tuple, Optional
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot
|
2022-08-12 07:43:11 +12:00
|
|
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
|
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
from rare.models.game import RareGame
|
2023-02-19 01:33:42 +13:00
|
|
|
from rare.shared import RareCore
|
2023-02-18 00:29:59 +13:00
|
|
|
from rare.utils.misc import get_size
|
2023-02-16 06:58:28 +13:00
|
|
|
from rare.widgets.elide_label import ElideLabel
|
2023-02-19 04:29:41 +13:00
|
|
|
from rare.widgets.indicator_edit import PathEdit, IndicatorReasons, IndicatorReasonsCommon
|
2022-08-12 07:43:11 +12:00
|
|
|
|
|
|
|
logger = getLogger("MoveGame")
|
|
|
|
|
|
|
|
|
2023-02-19 01:33:42 +13:00
|
|
|
class MovePathEditReasons(IndicatorReasons):
|
|
|
|
DST_MISSING = auto()
|
|
|
|
NO_WRITE_PERM = auto()
|
|
|
|
SAME_DIR = auto()
|
|
|
|
DST_IN_SRC = auto()
|
|
|
|
NESTED_DIR = auto()
|
|
|
|
NO_SPACE = auto()
|
|
|
|
|
|
|
|
|
2022-08-12 07:43:11 +12:00
|
|
|
class MoveGamePopUp(QWidget):
|
|
|
|
move_clicked = pyqtSignal(str)
|
|
|
|
browse_done = pyqtSignal()
|
|
|
|
|
2022-12-27 02:18:48 +13:00
|
|
|
def __init__(self, parent=None):
|
|
|
|
super(MoveGamePopUp, self).__init__(parent=parent)
|
2023-02-19 01:33:42 +13:00
|
|
|
self.rcore = RareCore.instance()
|
|
|
|
self.core = RareCore.instance().core()
|
2023-02-16 03:59:33 +13:00
|
|
|
self.rgame: Optional[RareGame] = None
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-19 01:33:42 +13:00
|
|
|
self.path_edit = PathEdit("", QFileDialog.Directory, edit_func=self.path_edit_callback)
|
|
|
|
self.path_edit.extend_reasons({
|
2023-02-20 22:31:03 +13:00
|
|
|
MovePathEditReasons.DST_MISSING: self.tr("You need to provide the destination directory."),
|
2023-02-19 01:33:42 +13:00
|
|
|
MovePathEditReasons.NO_WRITE_PERM: self.tr("No write permission on destination."),
|
|
|
|
MovePathEditReasons.SAME_DIR: self.tr("Same directory or subdirectory selected."),
|
|
|
|
MovePathEditReasons.DST_IN_SRC: self.tr("Destination is inside source directory"),
|
|
|
|
MovePathEditReasons.NESTED_DIR: self.tr("Game install directories cannot be nested."),
|
|
|
|
MovePathEditReasons.NO_SPACE: self.tr("Not enough space available on drive."),
|
|
|
|
})
|
2023-02-16 03:59:33 +13:00
|
|
|
self.path_edit.path_select.clicked.connect(self.browse_done)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
self.button = QPushButton(self.tr("Move"))
|
2023-02-16 06:48:48 +13:00
|
|
|
self.button.setFixedSize(self.path_edit.path_select.sizeHint())
|
2023-02-16 03:59:33 +13:00
|
|
|
self.button.clicked.connect(lambda p: self.move_clicked.emit(self.path_edit.text()))
|
|
|
|
|
2023-02-16 06:58:28 +13:00
|
|
|
self.warn_label = ElideLabel("", parent=self)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
|
|
|
middle_layout = QHBoxLayout()
|
|
|
|
middle_layout.setAlignment(Qt.AlignRight)
|
2023-02-16 03:59:33 +13:00
|
|
|
middle_layout.addWidget(self.warn_label, stretch=1)
|
|
|
|
middle_layout.addWidget(self.button)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-18 00:29:59 +13:00
|
|
|
font = self.font()
|
|
|
|
font.setBold(True)
|
|
|
|
self.req_space_label = QLabel(self.tr("Required:"), self)
|
|
|
|
self.req_space = QLabel(self)
|
|
|
|
self.req_space.setFont(font)
|
|
|
|
self.avail_space_label = QLabel(self.tr("Available:"), self)
|
|
|
|
self.avail_space = QLabel(self)
|
|
|
|
self.avail_space.setFont(font)
|
|
|
|
|
|
|
|
bottom_layout = QHBoxLayout()
|
2022-08-12 07:43:11 +12:00
|
|
|
bottom_layout.addWidget(self.req_space_label)
|
2023-02-18 00:29:59 +13:00
|
|
|
bottom_layout.addWidget(self.req_space, stretch=1)
|
|
|
|
bottom_layout.addWidget(self.avail_space_label)
|
|
|
|
bottom_layout.addWidget(self.avail_space, stretch=1)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
layout: QVBoxLayout = QVBoxLayout(self)
|
|
|
|
layout.addWidget(self.path_edit)
|
2022-08-12 07:43:11 +12:00
|
|
|
layout.addLayout(middle_layout)
|
|
|
|
layout.addLayout(bottom_layout)
|
|
|
|
|
|
|
|
def refresh_indicator(self):
|
|
|
|
# needed so the edit_func gets run again
|
2023-02-16 03:59:33 +13:00
|
|
|
text = self.path_edit.text()
|
|
|
|
self.path_edit.setText(str())
|
|
|
|
self.path_edit.setText(text)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-19 01:33:42 +13:00
|
|
|
def path_edit_callback(self, path: str) -> Tuple[bool, str, int]:
|
2023-02-16 03:59:33 +13:00
|
|
|
self.button.setEnabled(True)
|
2023-02-16 06:48:48 +13:00
|
|
|
self.warn_label.setHidden(False)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-19 01:33:42 +13:00
|
|
|
def helper_func(reason: int) -> Tuple[bool, str, int]:
|
2023-02-16 03:59:33 +13:00
|
|
|
self.button.setEnabled(False)
|
2023-02-19 01:33:42 +13:00
|
|
|
return False, path, reason
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-18 00:29:59 +13:00
|
|
|
if not self.rgame.install_path or not path:
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(MovePathEditReasons.DST_MISSING)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-18 00:29:59 +13:00
|
|
|
src_path = os.path.realpath(self.rgame.install_path)
|
2023-02-16 03:59:33 +13:00
|
|
|
dst_path = os.path.realpath(path)
|
|
|
|
dst_install_path = os.path.realpath(os.path.join(dst_path, os.path.basename(src_path)))
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
if not os.path.isdir(dst_path):
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(IndicatorReasonsCommon.DIR_NOT_EXISTS)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
|
|
|
# Get free space on drive and size of game folder
|
2023-02-16 03:59:33 +13:00
|
|
|
_, _, free_space = shutil.disk_usage(dst_path)
|
|
|
|
source_size = sum(
|
|
|
|
os.stat(os.path.join(dp, f)).st_size
|
|
|
|
for dp, dn, filenames in os.walk(src_path)
|
|
|
|
for f in filenames
|
|
|
|
)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
|
|
|
# Calculate from bytes to gigabytes
|
2023-02-18 00:29:59 +13:00
|
|
|
# free_space = round(free_space / 1000 ** 3, 2)
|
|
|
|
# source_size = round(source_size / 1000 ** 3, 2)
|
|
|
|
self.req_space.setText(get_size(source_size))
|
|
|
|
self.avail_space.setText(get_size(free_space))
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-18 00:29:59 +13:00
|
|
|
if not os.access(path, os.W_OK) or not os.access(self.rgame.install_path, os.W_OK):
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(MovePathEditReasons.NO_WRITE_PERM)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
if src_path == dst_path or src_path == dst_install_path:
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(MovePathEditReasons.SAME_DIR)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
if str(src_path) in str(dst_path):
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(MovePathEditReasons.DST_IN_SRC)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
if str(dst_install_path) in str(src_path):
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(MovePathEditReasons.DST_IN_SRC)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-19 01:33:42 +13:00
|
|
|
for rgame in self.rcore.installed_games:
|
|
|
|
if not rgame.is_non_asset and rgame.install_path in path:
|
|
|
|
return helper_func(MovePathEditReasons.NESTED_DIR)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
is_existing_dir = is_game_dir(src_path, dst_install_path)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
for item in os.listdir(dst_path):
|
|
|
|
if os.path.basename(src_path) in os.path.basename(item):
|
|
|
|
if os.path.isdir(dst_install_path):
|
2022-08-12 07:43:11 +12:00
|
|
|
if not is_existing_dir:
|
2023-02-16 03:59:33 +13:00
|
|
|
self.warn_label.setHidden(False)
|
|
|
|
elif os.path.isfile(dst_install_path):
|
|
|
|
self.warn_label.setHidden(False)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
if free_space <= source_size and not is_existing_dir:
|
2023-02-19 01:33:42 +13:00
|
|
|
return helper_func(MovePathEditReasons.NO_SPACE)
|
2022-08-12 07:43:11 +12:00
|
|
|
|
|
|
|
# Fallback
|
2023-02-16 03:59:33 +13:00
|
|
|
self.button.setEnabled(True)
|
2023-02-19 01:33:42 +13:00
|
|
|
return True, path, IndicatorReasonsCommon.VALID
|
2023-02-16 03:59:33 +13:00
|
|
|
|
|
|
|
@pyqtSlot()
|
|
|
|
def __update_widget(self):
|
|
|
|
""" React to state updates from RareGame """
|
2023-02-19 01:33:42 +13:00
|
|
|
if not self.rgame.is_installed or self.rgame.is_non_asset:
|
2023-02-16 03:59:33 +13:00
|
|
|
self.setDisabled(True)
|
2022-08-12 07:43:11 +12:00
|
|
|
return
|
2022-09-11 04:04:40 +12:00
|
|
|
# FIXME: Make edit_func lighter instead of blocking signals
|
2023-02-16 03:59:33 +13:00
|
|
|
self.path_edit.line_edit.blockSignals(True)
|
2023-02-18 00:29:59 +13:00
|
|
|
self.path_edit.setText(self.rgame.install_path)
|
2022-09-11 04:04:40 +12:00
|
|
|
# FIXME: Make edit_func lighter instead of blocking signals
|
2023-02-16 03:59:33 +13:00
|
|
|
self.path_edit.line_edit.blockSignals(False)
|
|
|
|
self.warn_label.setText(
|
2023-02-16 06:48:48 +13:00
|
|
|
self.tr("Moving here will overwrite <b>{}</b>").format(os.path.basename(self.rgame.install_path))
|
2022-08-12 07:43:11 +12:00
|
|
|
)
|
2023-02-18 00:29:59 +13:00
|
|
|
self.refresh_indicator()
|
2023-02-16 03:59:33 +13:00
|
|
|
|
|
|
|
def update_game(self, rgame: RareGame):
|
|
|
|
if self.rgame is not None:
|
|
|
|
self.rgame.signals.widget.update.disconnect(self.__update_widget)
|
|
|
|
self.rgame = None
|
|
|
|
rgame.signals.widget.update.connect(self.__update_widget)
|
|
|
|
self.rgame = rgame
|
|
|
|
self.__update_widget()
|
2022-08-12 07:43:11 +12:00
|
|
|
|
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
def is_game_dir(src_path: str, dst_path: str):
|
2022-08-12 07:43:11 +12:00
|
|
|
# This iterates over the destination dir, then iterates over the current install dir and if the file names
|
|
|
|
# matches, we have an exisiting dir
|
2023-02-16 03:59:33 +13:00
|
|
|
if os.path.isdir(dst_path):
|
|
|
|
for dst_file in os.listdir(dst_path):
|
|
|
|
for src_file in os.listdir(src_path):
|
|
|
|
if dst_file == src_file:
|
2022-08-12 07:43:11 +12:00
|
|
|
return True
|
|
|
|
return False
|