--- a/src/eric7/EricNetwork/EricJsonServer.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/EricNetwork/EricJsonServer.py Wed Jul 13 14:55:47 2022 +0200 @@ -11,8 +11,13 @@ import json from PyQt6.QtCore import ( - pyqtSlot, QProcess, QProcessEnvironment, QCoreApplication, QEventLoop, - QTimer, QThread + pyqtSlot, + QProcess, + QProcessEnvironment, + QCoreApplication, + QEventLoop, + QTimer, + QThread, ) from PyQt6.QtNetwork import QTcpServer, QHostAddress @@ -26,10 +31,11 @@ """ Class implementing a JSON based server base class. """ + def __init__(self, name="", multiplex=False, parent=None): """ Constructor - + @param name name of the server (used for output only) @type str @param multiplex flag indicating a multiplexing server @@ -38,7 +44,7 @@ @type QObject """ super().__init__(parent) - + self.__name = name self.__multiplex = multiplex if self.__multiplex: @@ -47,23 +53,26 @@ else: self.__clientProcess = None self.__connection = None - + # setup the network interface networkInterface = Preferences.getDebugger("NetworkInterface") - if networkInterface == "all" or '.' in networkInterface: + if networkInterface == "all" or "." in networkInterface: # IPv4 - self.__hostAddress = '127.0.0.1' + self.__hostAddress = "127.0.0.1" else: # IPv6 - self.__hostAddress = '::1' + self.__hostAddress = "::1" self.listen(QHostAddress(self.__hostAddress)) self.newConnection.connect(self.handleNewConnection) - + ## Note: Need the port if client is started external in debugger. - print('JSON server ({1}) listening on: {0:d}' # __IGNORE_WARNING__ - .format(self.serverPort(), self.__name)) - + print( # __IGNORE_WARNING_M801__ + "JSON server ({1}) listening on: {0:d}".format( + self.serverPort(), self.__name + ) + ) + @pyqtSlot() def handleNewConnection(self): """ @@ -72,12 +81,13 @@ connection = self.nextPendingConnection() if not connection.isValid(): return - + if self.__multiplex: if not connection.waitForReadyRead(3000): return - idString = bytes(connection.readLine()).decode( - "utf-8", 'backslashreplace').strip() + idString = ( + bytes(connection.readLine()).decode("utf-8", "backslashreplace").strip() + ) if idString in self.__connections: self.__connections[idString].close() self.__connections[idString] = connection @@ -85,19 +95,17 @@ idString = "" if self.__connection is not None: self.__connection.close() - + self.__connection = connection - - connection.readyRead.connect( - lambda: self.__receiveJson(idString)) - connection.disconnected.connect( - lambda: self.__handleDisconnect(idString)) - + + connection.readyRead.connect(lambda: self.__receiveJson(idString)) + connection.disconnected.connect(lambda: self.__handleDisconnect(idString)) + @pyqtSlot() def __handleDisconnect(self, idString): """ Private slot handling a disconnect of the client. - + @param idString id of the connection been disconnected @type str """ @@ -108,15 +116,15 @@ else: if self.__connection is not None: self.__connection.close() - + self.__connection = None - + def connectionNames(self): """ Public method to get the list of active connection names. - + If this is not a multiplexing server, an empty list is returned. - + @return list of active connection names @rtype list of str """ @@ -124,12 +132,12 @@ return list(self.__connections.keys()) else: return [] - + @pyqtSlot() def __receiveJson(self, idString): """ Private slot handling received data from the client. - + @param idString id of the connection @type str """ @@ -140,36 +148,38 @@ connection = None else: connection = self.__connection - + while connection and connection.canReadLine(): data = connection.readLine() - jsonLine = bytes(data).decode("utf-8", 'backslashreplace') - - #- print("JSON Server ({0}): {1}".format(self.__name, jsonLine)) - #- this is for debugging only - + jsonLine = bytes(data).decode("utf-8", "backslashreplace") + + # - print("JSON Server ({0}): {1}".format(self.__name, jsonLine)) + # - this is for debugging only + try: clientDict = json.loads(jsonLine.strip()) except (TypeError, ValueError) as err: EricMessageBox.critical( None, self.tr("JSON Protocol Error"), - self.tr("""<p>The response received from the client""" - """ could not be decoded. Please report""" - """ this issue with the received data to the""" - """ eric bugs email address.</p>""" - """<p>Error: {0}</p>""" - """<p>Data:<br/>{1}</p>""").format( - str(err), Utilities.html_encode(jsonLine.strip())), - EricMessageBox.Ok) + self.tr( + """<p>The response received from the client""" + """ could not be decoded. Please report""" + """ this issue with the received data to the""" + """ eric bugs email address.</p>""" + """<p>Error: {0}</p>""" + """<p>Data:<br/>{1}</p>""" + ).format(str(err), Utilities.html_encode(jsonLine.strip())), + EricMessageBox.Ok, + ) return - + self.handleCall(clientDict["method"], clientDict["params"]) - + def sendJson(self, command, params, flush=False, idString=""): """ Public method to send a single command to a client. - + @param command command name to be sent @type str @param params dictionary of named parameters for the command @@ -184,8 +194,8 @@ "method": command, "params": params, } - cmd = json.dumps(commandDict) + '\n' - + cmd = json.dumps(commandDict) + "\n" + if idString: try: connection = self.__connections[idString] @@ -193,19 +203,20 @@ connection = None else: connection = self.__connection - + if connection is not None: - data = cmd.encode('utf8', 'backslashreplace') + data = cmd.encode("utf8", "backslashreplace") length = "{0:09d}".format(len(data)) connection.write(length.encode() + data) if flush: connection.flush() - - def startClient(self, interpreter, clientScript, clientArgs, idString="", - environment=None): + + def startClient( + self, interpreter, clientScript, clientArgs, idString="", environment=None + ): """ Public method to start a client process. - + @param interpreter interpreter to be used for the client @type str @param clientScript path to the client script @@ -221,12 +232,11 @@ """ if interpreter == "" or not Utilities.isinpath(interpreter): return False - + exitCode = None - + proc = QProcess() - proc.setProcessChannelMode( - QProcess.ProcessChannelMode.ForwardedChannels) + proc.setProcessChannelMode(QProcess.ProcessChannelMode.ForwardedChannels) if environment is not None: env = QProcessEnvironment() for key, value in list(environment.items()): @@ -239,58 +249,57 @@ proc.start(interpreter, args) if not proc.waitForStarted(10000): proc = None - + if idString: self.__clientProcesses[idString] = proc if proc: timer = QTimer() timer.setSingleShot(True) - timer.start(30000) # 30s timeout - while ( - idString not in self.connectionNames() and - timer.isActive() - ): + timer.start(30000) # 30s timeout + while idString not in self.connectionNames() and timer.isActive(): # Give the event loop the chance to process the new # connection of the client (= slow start). QCoreApplication.processEvents( - QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) + QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents + ) QThread.msleep(100) - + # check if client exited prematurely if proc.state() == QProcess.ProcessState.NotRunning: exitCode = proc.exitCode() proc = None self.__clientProcesses[idString] = None break - + QThread.msleep(500) else: if proc: timer = QTimer() timer.setSingleShot(True) - timer.start(1000) # 1s timeout + timer.start(1000) # 1s timeout while timer.isActive(): # check if client exited prematurely QCoreApplication.processEvents( - QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) + QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents + ) QThread.msleep(100) if proc.state() == QProcess.ProcessState.NotRunning: exitCode = proc.exitCode() proc = None break self.__clientProcess = proc - + return proc is not None, exitCode - + def stopClient(self, idString=""): """ Public method to stop a client process. - + @param idString id of the client to be stopped @type str """ self.sendJson("Exit", {}, flush=True, idString=idString) - + if idString: try: connection = self.__connections[idString] @@ -300,17 +309,17 @@ connection = self.__connection if connection is not None: connection.waitForDisconnected() - + if idString: with contextlib.suppress(KeyError): - if self .__clientProcesses[idString] is not None: - self .__clientProcesses[idString].close() + if self.__clientProcesses[idString] is not None: + self.__clientProcesses[idString].close() del self.__clientProcesses[idString] else: if self.__clientProcess is not None: self.__clientProcess.close() self.__clientProcess = None - + def stopAllClients(self): """ Public method to stop all clients. @@ -318,18 +327,18 @@ clientNames = self.connectionNames()[:] for clientName in clientNames: self.stopClient(clientName) - + ####################################################################### ## The following methods should be overridden by derived classes ####################################################################### - + def handleCall(self, method, params): """ Public method to handle a method call from the client. - + Note: This is an empty implementation that must be overridden in derived classes. - + @param method requested method name @type str @param params dictionary with method specific parameters