Mon, 25 Sep 2017 20:08:59 +0200
Continued implementing the distributed Code Assist.
# -*- coding: utf-8 -*- # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the JSON based server base class. """ from __future__ import unicode_literals import json from PyQt5.QtCore import pyqtSlot, QProcess from PyQt5.QtNetwork import QTcpServer, QHostAddress from E5Gui import E5MessageBox import Preferences import Utilities class JsonServer(QTcpServer): """ Class implementing the JSON based server base class. """ def __init__(self, multiplex=False, parent=None): """ Constructor @param multiplex flag indicating a multiplexing server @type bool @param parent parent object @type QObject """ super(JsonServer, self).__init__(parent) self.__multiplex = multiplex if self.__multiplex: self.__clientProcesses = {} self.__connections = {} else: self.__clientProcess = None self.__connection = None # setup the network interface networkInterface = Preferences.getDebugger("NetworkInterface") if networkInterface == "all" or '.' in networkInterface: # IPv4 self.__hostAddress = '127.0.0.1' else: # IPv6 self.__hostAddress = '::1' self.listen(QHostAddress(self.__hostAddress)) self.newConnection.connect(self.handleNewConnection) port = self.serverPort() ## Note: Need the port if started external in debugger: print('Refactoring server listening on: {0:d}'.format(port)) # __IGNORE_WARNING__ @pyqtSlot() def handleNewConnection(self): """ Public slot for new incoming connections from a client. """ 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() if idString in self.__connections: self.__connections[idString].close() self.__connections[idString] = connection else: 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)) @pyqtSlot() def __handleDisconnect(self, idString): """ Private slot handling a disconnect of the client. @param idString id of the connection been disconnected @type str """ if idString: if idString in self.__connections: self.__connections[idString].close() del self.__connections[id] 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 """ return list(self.__connections.keys()) @pyqtSlot() def __receiveJson(self, idString): """ Private slot handling received data from the client. @param idString id of the connection been disconnected @type str """ if idString: connection = self.__connections[idString] else: connection = self.__connection while connection and connection.canReadLine(): data = connection.readLine() jsonLine = bytes(data).decode("utf-8", 'backslashreplace') ## print("JSON Server: ", jsonLine) ##debug try: clientDict = json.loads(jsonLine.strip()) except (TypeError, ValueError) as err: E5MessageBox.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/>{0}</p>""").format( str(err), Utilities.html_encode(jsonLine.strip())), E5MessageBox.StandardButtons( E5MessageBox.Ok)) return method = clientDict["method"] params = clientDict["params"] self.handleCall(method, 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 @type dict @param flush flag indicating to flush the data to the socket @type bool @param idString id of the connection to send data to @type str """ commandDict = { "jsonrpc": "2.0", "method": command, "params": params, } cmd = json.dumps(commandDict) + '\n' if idString: connection = self.__connections[idString] else: connection = self.__connection if connection is not None: connection.write(cmd.encode('utf8', 'backslashreplace')) if flush: connection.flush() def startClient(self, interpreter, clientScript, clientArgs, idString=""): """ 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 @type str @param clientArgs list of arguments for the client @param idString id of the client to be started @type str @return flag indicating a successful client start @rtype bool """ if interpreter == "" or not Utilities.isinpath(interpreter): return False proc = QProcess() proc.setProcessChannelMode(QProcess.ForwardedChannels) args = [clientScript, self.__hostAddress, str(self.serverPort())] if idString: args.append(idString) args.extend(clientArgs) proc.start(interpreter, args) if not proc.waitForStarted(10000): proc = None if idString: self.__clientProcesses[idString] = proc else: self.__clientProcess = proc return proc is not None 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: connection = self.__connections[idString] else: connection = self.__connection if connection is not None: connection.waitForDisconnected() if idString: self .__clientProcesses[idString].close() del self.__clientProcesses[idString] else: if self.__clientProcess is not None: self.__clientProcess.close() self.__clientProcess = None ####################################################################### ## 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 @type dict """ pass