Do not move a game that can't be moved; abort before making any changes instead.

However, we handle the exception here and show a warning dialog about this. The user can choose to just set the location without moving or updating anything - they'll have to fix things up themselves.

Should help with #5223
This commit is contained in:
Daniel Johnson 2024-01-12 19:44:27 -05:00
parent 6f283f21a4
commit bf169bc0ef
3 changed files with 49 additions and 29 deletions

View file

@ -95,6 +95,10 @@ class MissingGameExecutableError(MissingExecutableError):
self.filename = filename
class InvalidGameMoveError(LutrisError):
"""Raised when a game can't be moved as desired; we may have to just set the location."""
class EsyncLimitError(Exception):
"""Raised when the ESYNC limit is not set correctly."""

View file

@ -20,7 +20,7 @@ from lutris.database import categories as categories_db
from lutris.database import games as games_db
from lutris.database import sql
from lutris.exception_backstops import watch_game_errors
from lutris.exceptions import GameConfigError, MissingExecutableError
from lutris.exceptions import GameConfigError, InvalidGameMoveError, MissingExecutableError
from lutris.runner_interpreter import export_bash_script, get_launch_parameters
from lutris.runners import InvalidRunnerError, import_runner
from lutris.runners.runner import Runner
@ -960,16 +960,22 @@ class Game(GObject.Object):
logger.info("Moving %s to %s", self, new_location)
new_config = ""
old_location = self.directory
if os.path.exists(old_location):
game_directory = os.path.basename(old_location)
target_directory = os.path.join(new_location, game_directory)
else:
target_directory = new_location
target_directory = self._get_move_target_directory(new_location)
# Raise errors before actually changing anything!
if not old_location:
raise InvalidGameMoveError(_("The game has no location currently set, so it can't be moved."))
if not system.path_exists(old_location):
raise InvalidGameMoveError(
_("The location '%s' does not exist, perhaps the files have already been moved?") % old_location)
if new_location.startswith(old_location):
raise InvalidGameMoveError(
_("Lutris can't move '%s' to a location inside of itself, '%s'.") % (old_location, new_location))
self.directory = target_directory
self.save()
if not old_location:
logger.info("Previous location wasn't set. Cannot continue moving")
return target_directory
with open(self.config.game_config_path, encoding='utf-8') as config_file:
for line in config_file.readlines():
@ -980,12 +986,6 @@ class Game(GObject.Object):
with open(self.config.game_config_path, "w", encoding='utf-8') as config_file:
config_file.write(new_config)
if not system.path_exists(old_location):
logger.warning("Location %s doesn't exist, files already moved?", old_location)
return target_directory
if new_location.startswith(old_location):
logger.warning("Can't move %s to one of its children %s", old_location, new_location)
return target_directory
try:
shutil.move(old_location, new_location)
except OSError as ex:
@ -995,6 +995,20 @@ class Game(GObject.Object):
)
return target_directory
def set_location(self, new_location):
target_directory = self._get_move_target_directory(new_location)
self.directory = target_directory
self.save()
return target_directory
def _get_move_target_directory(self, new_location):
old_location = self.directory
if old_location and os.path.exists(old_location):
game_directory = os.path.basename(old_location)
return os.path.join(new_location, game_directory)
return new_location
def export_game(slug, dest_dir):
"""Export a full game folder along with some lutris metadata"""

View file

@ -11,6 +11,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gdk, GLib, GObject, Gtk
from lutris import api, settings
from lutris.exceptions import InvalidGameMoveError
from lutris.gui.widgets.log_text_view import LogTextView
from lutris.util import datapath
from lutris.util.jobs import AsyncCall
@ -522,19 +523,6 @@ class InstallerSourceDialog(ModelessDialog):
self.show_all()
class WarningMessageDialog(Gtk.MessageDialog):
def __init__(self, message, secondary_message="", parent=None):
super().__init__(type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK, parent=parent)
self.set_default_response(Gtk.ResponseType.OK)
self.set_markup("<b>%s</b>" % message)
if secondary_message:
self.props.secondary_use_markup = True
self.props.secondary_text = secondary_message
self.run()
self.destroy()
class MoveDialog(ModelessDialog):
__gsignals__ = {
"game-moved": (GObject.SIGNAL_RUN_FIRST, None, ()),
@ -564,7 +552,7 @@ class MoveDialog(ModelessDialog):
GLib.source_remove(self.progress_source_id)
def move(self):
AsyncCall(self._move_game, self.on_game_moved)
AsyncCall(self._move_game, self._move_game_cb)
def show_progress(self):
self.progress.pulse()
@ -573,6 +561,20 @@ class MoveDialog(ModelessDialog):
def _move_game(self):
self.new_directory = self.game.move(self.destination)
def _move_game_cb(self, _result, error):
if error and isinstance(error, InvalidGameMoveError):
secondary = _("Do you want to change the game location anyway? No files can be moved, "
"and the game configuration may need to be adjusted.")
dlg = WarningDialog(str(error), secondary=secondary, parent=self)
if dlg.result == Gtk.ResponseType.OK:
self.new_directory = self.game.set_location(self.destination)
self.on_game_moved(None, None)
else:
self.destroy()
return
self.on_game_moved(_result, error)
def on_game_moved(self, _result, error):
if error:
ErrorDialog(error, parent=self)