GameProcess: Move GameProcess out of GameUtils and into its own file under rare/shared
In the same vain, move `rare/game_launch_helper/message_models` into `rare/modes` since it is used in both the server and the client side. Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
This commit is contained in:
parent
426be3e403
commit
7fbd941c98
4 changed files with 124 additions and 111 deletions
|
@ -15,10 +15,10 @@ from PyQt5.QtNetwork import QLocalServer, QLocalSocket
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from rare.lgndr.core import LegendaryCore
|
from rare.lgndr.core import LegendaryCore
|
||||||
|
from rare.models.launcher import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
|
||||||
from rare.widgets.rare_app import RareApp
|
from rare.widgets.rare_app import RareApp
|
||||||
from .console import Console
|
from .console import Console
|
||||||
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
|
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
|
||||||
from .message_models import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
|
|
||||||
|
|
||||||
logger = logging.getLogger("RareLauncher")
|
logger = logging.getLogger("RareLauncher")
|
||||||
|
|
||||||
|
|
120
rare/shared/game_process.py
Normal file
120
rare/shared/game_process.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtSlot
|
||||||
|
from PyQt5.QtNetwork import QLocalSocket
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
from rare.models.game import RareGame
|
||||||
|
from rare.models.launcher import ErrorModel, Actions, FinishedModel, StateChangedModel
|
||||||
|
|
||||||
|
logger = logging.getLogger("GameProcess")
|
||||||
|
|
||||||
|
|
||||||
|
class GameProcess(QObject):
|
||||||
|
# str: app_name, int: exit_code
|
||||||
|
game_finished = pyqtSignal(RareGame, int)
|
||||||
|
# str: app_name
|
||||||
|
game_launched = pyqtSignal(RareGame)
|
||||||
|
|
||||||
|
def __init__(self, rgame: RareGame, on_startup=False, always_ask_sync: bool = False):
|
||||||
|
super(GameProcess, self).__init__()
|
||||||
|
self.rgame = rgame
|
||||||
|
self.on_startup = on_startup
|
||||||
|
self.tried_connections = 0
|
||||||
|
self.socket = QLocalSocket()
|
||||||
|
self.socket.connected.connect(self._socket_connected)
|
||||||
|
try:
|
||||||
|
self.socket.errorOccurred.connect(self._error_occurred)
|
||||||
|
except AttributeError:
|
||||||
|
QTimer.singleShot(100, lambda: self._error_occurred(QLocalSocket.UnknownSocketError) if self.socket.error() else None)
|
||||||
|
logger.warning("Do not handle errors on QLocalSocket, because of an old qt version")
|
||||||
|
self.socket.readyRead.connect(self._message_available)
|
||||||
|
self.always_ask_sync = always_ask_sync
|
||||||
|
|
||||||
|
def close_socket():
|
||||||
|
try:
|
||||||
|
self.socket.close()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.socket.disconnected.connect(close_socket)
|
||||||
|
self.timer = QTimer()
|
||||||
|
if not on_startup:
|
||||||
|
# wait a short time for process started
|
||||||
|
self.timer.timeout.connect(self.connect_to_server)
|
||||||
|
self.timer.start(200)
|
||||||
|
else:
|
||||||
|
# nothing happens, if no server available
|
||||||
|
self.connect_to_server()
|
||||||
|
|
||||||
|
def connect_to_server(self):
|
||||||
|
self.socket.connectToServer(f"rare_{self.rgame.app_name}")
|
||||||
|
self.tried_connections += 1
|
||||||
|
|
||||||
|
if self.tried_connections > 50: # 10 seconds
|
||||||
|
QMessageBox.warning(None, "Error", self.tr("Connection to game process failed (Timeout)"))
|
||||||
|
self.timer.stop()
|
||||||
|
self.game_finished.emit(self.rgame, 1)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _message_available(self):
|
||||||
|
message = self.socket.readAll().data()
|
||||||
|
if not message.startswith(b"{"):
|
||||||
|
logger.error(f"Received unsupported message: {message.decode('utf-8')}")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
data = json.loads(message)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(e)
|
||||||
|
logger.error("Could not load json data")
|
||||||
|
return
|
||||||
|
|
||||||
|
action = data.get("action", -1)
|
||||||
|
|
||||||
|
if action == -1:
|
||||||
|
logger.error("Got unexpected action")
|
||||||
|
elif action == Actions.finished:
|
||||||
|
logger.info(f"{self.rgame.app_name} {self.rgame.app_title} finished")
|
||||||
|
model = FinishedModel.from_json(data)
|
||||||
|
self.socket.close()
|
||||||
|
self._game_finished(model.exit_code)
|
||||||
|
elif action == Actions.error:
|
||||||
|
model = ErrorModel.from_json(data)
|
||||||
|
logger.error(f"Error in game {self.rgame.app_title}: {model.error_string}")
|
||||||
|
self.socket.close()
|
||||||
|
self._game_finished(1)
|
||||||
|
QMessageBox.warning(None, "Error", self.tr(
|
||||||
|
"Error in game {}: \n{}").format(self.rgame.app_title, model.error_string))
|
||||||
|
|
||||||
|
elif action == Actions.state_update:
|
||||||
|
model = StateChangedModel.from_json(data)
|
||||||
|
if model.new_state == StateChangedModel.States.started:
|
||||||
|
logger.info("Launched Game")
|
||||||
|
self.game_launched.emit(self.rgame)
|
||||||
|
meta_data = self.rgame.metadata
|
||||||
|
meta_data.last_played = datetime.datetime.now()
|
||||||
|
self.rgame.save_metadata()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _socket_connected(self):
|
||||||
|
self.timer.stop()
|
||||||
|
self.timer.deleteLater()
|
||||||
|
logger.info(f"Connection established for {self.rgame.app_name} ({self.rgame.app_title})")
|
||||||
|
if self.on_startup:
|
||||||
|
logger.info(f"Found {self.rgame.app_name} ({self.rgame.app_title}) running at startup")
|
||||||
|
|
||||||
|
# FIXME run this after startup, widgets do not exist at this time
|
||||||
|
QTimer.singleShot(1000, lambda: self.game_launched.emit(self.rgame))
|
||||||
|
|
||||||
|
@pyqtSlot(QLocalSocket.LocalSocketError)
|
||||||
|
def _error_occurred(self, _: QLocalSocket.LocalSocketError):
|
||||||
|
if self.on_startup:
|
||||||
|
self.socket.close()
|
||||||
|
self._game_finished(-1234) # 1234 is exit code for startup
|
||||||
|
logger.error(f"{self.rgame.app_name} ({self.rgame.app_title}): {self.socket.errorString()}")
|
||||||
|
|
||||||
|
def _game_finished(self, exit_code: int):
|
||||||
|
self.deleteLater()
|
||||||
|
self.game_finished.emit(self.rgame, exit_code)
|
|
@ -1,134 +1,26 @@
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QTimer, pyqtSlot
|
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, pyqtSlot
|
||||||
from PyQt5.QtCore import QStandardPaths
|
from PyQt5.QtCore import QStandardPaths
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtNetwork import QLocalSocket
|
|
||||||
from PyQt5.QtWidgets import QMessageBox, QPushButton
|
from PyQt5.QtWidgets import QMessageBox, QPushButton
|
||||||
from legendary.core import LegendaryCore
|
from legendary.core import LegendaryCore
|
||||||
|
|
||||||
from rare.components.dialogs.uninstall_dialog import UninstallDialog
|
from rare.components.dialogs.uninstall_dialog import UninstallDialog
|
||||||
from rare.game_launch_helper import message_models
|
|
||||||
from rare.lgndr.cli import LegendaryCLI
|
from rare.lgndr.cli import LegendaryCLI
|
||||||
from rare.lgndr.glue.arguments import LgndrUninstallGameArgs
|
from rare.lgndr.glue.arguments import LgndrUninstallGameArgs
|
||||||
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
|
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
|
||||||
from rare.models.game import RareGame
|
from rare.models.game import RareGame
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||||
|
from rare.shared.game_process import GameProcess
|
||||||
from rare.utils import config_helper, misc
|
from rare.utils import config_helper, misc
|
||||||
from .cloud_save_utils import CloudSaveUtils
|
from .cloud_save_utils import CloudSaveUtils
|
||||||
|
|
||||||
logger = getLogger("GameUtils")
|
logger = getLogger("GameUtils")
|
||||||
|
|
||||||
|
|
||||||
class GameProcess(QObject):
|
|
||||||
# str: app_name, int: exit_code
|
|
||||||
game_finished = pyqtSignal(RareGame, int)
|
|
||||||
# str: app_name
|
|
||||||
game_launched = pyqtSignal(RareGame)
|
|
||||||
|
|
||||||
def __init__(self, rgame: RareGame, on_startup=False, always_ask_sync: bool = False):
|
|
||||||
super(GameProcess, self).__init__()
|
|
||||||
self.rgame = rgame
|
|
||||||
self.on_startup = on_startup
|
|
||||||
self.tried_connections = 0
|
|
||||||
self.socket = QLocalSocket()
|
|
||||||
self.socket.connected.connect(self._socket_connected)
|
|
||||||
try:
|
|
||||||
self.socket.errorOccurred.connect(self._error_occurred)
|
|
||||||
except AttributeError:
|
|
||||||
QTimer.singleShot(100, lambda: self._error_occurred(None) if self.socket.error() else None)
|
|
||||||
logger.warning("Do not handle errors on QLocalSocket, because of an old qt version")
|
|
||||||
self.socket.readyRead.connect(self._message_available)
|
|
||||||
self.always_ask_sync = always_ask_sync
|
|
||||||
|
|
||||||
def close_socket():
|
|
||||||
try:
|
|
||||||
self.socket.close()
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.socket.disconnected.connect(close_socket)
|
|
||||||
self.timer = QTimer()
|
|
||||||
if not on_startup:
|
|
||||||
# wait a short time for process started
|
|
||||||
self.timer.timeout.connect(self.connect_to_server)
|
|
||||||
self.timer.start(200)
|
|
||||||
else:
|
|
||||||
# nothing happens, if no server available
|
|
||||||
self.connect_to_server()
|
|
||||||
|
|
||||||
def connect_to_server(self):
|
|
||||||
self.socket.connectToServer(f"rare_{self.rgame.app_name}")
|
|
||||||
self.tried_connections += 1
|
|
||||||
|
|
||||||
if self.tried_connections > 50: # 10 seconds
|
|
||||||
QMessageBox.warning(None, "Error", self.tr("Connection to game process failed (Timeout)"))
|
|
||||||
self.timer.stop()
|
|
||||||
self.game_finished.emit(self.rgame, 1)
|
|
||||||
|
|
||||||
def _message_available(self):
|
|
||||||
message = self.socket.readAll().data()
|
|
||||||
if not message.startswith(b"{"):
|
|
||||||
logger.error(f"Received unsupported message: {message.decode('utf-8')}")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
data = json.loads(message)
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
logger.error(e)
|
|
||||||
logger.error("Could not load json data")
|
|
||||||
return
|
|
||||||
|
|
||||||
action = data.get("action", -1)
|
|
||||||
|
|
||||||
if action == -1:
|
|
||||||
logger.error("Got unexpected action")
|
|
||||||
elif action == message_models.Actions.finished:
|
|
||||||
logger.info(f"{self.rgame.app_name} {self.rgame.app_title} finished")
|
|
||||||
model = message_models.FinishedModel.from_json(data)
|
|
||||||
self.socket.close()
|
|
||||||
self._game_finished(model.exit_code)
|
|
||||||
elif action == message_models.Actions.error:
|
|
||||||
model = message_models.ErrorModel.from_json(data)
|
|
||||||
logger.error(f"Error in game {self.rgame.app_title}: {model.error_string}")
|
|
||||||
self.socket.close()
|
|
||||||
self._game_finished(1)
|
|
||||||
QMessageBox.warning(None, "Error", self.tr(
|
|
||||||
"Error in game {}: \n{}").format(self.rgame.app_title, model.error_string))
|
|
||||||
|
|
||||||
elif action == message_models.Actions.state_update:
|
|
||||||
model = message_models.StateChangedModel.from_json(data)
|
|
||||||
if model.new_state == message_models.StateChangedModel.States.started:
|
|
||||||
logger.info("Launched Game")
|
|
||||||
self.game_launched.emit(self.rgame)
|
|
||||||
meta_data = self.rgame.metadata
|
|
||||||
meta_data.last_played = datetime.datetime.now()
|
|
||||||
self.rgame.save_metadata()
|
|
||||||
|
|
||||||
def _socket_connected(self):
|
|
||||||
self.timer.stop()
|
|
||||||
self.timer.deleteLater()
|
|
||||||
logger.info(f"Connection established for {self.rgame.app_name} ({self.rgame.app_title})")
|
|
||||||
if self.on_startup:
|
|
||||||
logger.info(f"Found {self.rgame.app_name} ({self.rgame.app_title}) running at startup")
|
|
||||||
|
|
||||||
# FIXME run this after startup, widgets do not exist at this time
|
|
||||||
QTimer.singleShot(1000, lambda: self.game_launched.emit(self.rgame))
|
|
||||||
|
|
||||||
def _error_occurred(self, _):
|
|
||||||
if self.on_startup:
|
|
||||||
self.socket.close()
|
|
||||||
self.deleteLater()
|
|
||||||
self._game_finished(-1234) # 1234 is exit code for startup
|
|
||||||
logger.error(f"{self.rgame.app_name} ({self.rgame.app_title}): {self.socket.errorString()}")
|
|
||||||
|
|
||||||
def _game_finished(self, exit_code: int):
|
|
||||||
self.game_finished.emit(self.rgame, exit_code)
|
|
||||||
|
|
||||||
|
|
||||||
def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False):
|
def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False):
|
||||||
igame = core.get_installed_game(app_name)
|
igame = core.get_installed_game(app_name)
|
||||||
|
|
||||||
|
@ -282,6 +174,7 @@ class GameUtils(QObject):
|
||||||
game_process.game_launched.connect(self.game_launched.emit)
|
game_process.game_launched.connect(self.game_launched.emit)
|
||||||
self.running_games[rgame.app_name] = game_process
|
self.running_games[rgame.app_name] = game_process
|
||||||
|
|
||||||
|
@pyqtSlot(RareGame, int)
|
||||||
def game_finished(self, rgame: RareGame, exit_code):
|
def game_finished(self, rgame: RareGame, exit_code):
|
||||||
if self.running_games.get(rgame.app_name):
|
if self.running_games.get(rgame.app_name):
|
||||||
self.running_games.pop(rgame.app_name)
|
self.running_games.pop(rgame.app_name)
|
||||||
|
|
Loading…
Reference in a new issue