diff -r eb7552dd73c8 -r 989cd767992b RefactoringRope/Refactoring.py --- a/RefactoringRope/Refactoring.py Mon Sep 11 13:34:20 2017 +0200 +++ b/RefactoringRope/Refactoring.py Mon Sep 11 18:51:54 2017 +0200 @@ -24,18 +24,21 @@ import rope.base.libutils import rope.base.exceptions -from PyQt5.QtCore import QObject +from PyQt5.QtCore import pyqtSlot, QProcess from PyQt5.QtWidgets import QMenu, QApplication, QDialog, QAction from PyQt5.Qsci import QsciScintilla +from PyQt5.QtNetwork import QTcpServer, QHostAddress from E5Gui.E5Application import e5App from E5Gui import E5MessageBox from E5Gui.E5Action import E5Action import Utilities +import Preferences -class Refactoring(QObject): +# TODO: rename this (and the module) to RefactoringServer once done +class Refactoring(QTcpServer): """ Class implementing the refactoring interface to rope. """ @@ -46,7 +49,7 @@ @param plugin reference to the plugin object @param parent parent (QObject) """ - QObject.__init__(self, parent) + super(Refactoring, self).__init__(parent) self.__plugin = plugin self.__ui = parent @@ -58,11 +61,33 @@ self.__mainMenu = None self.__helpDialog = None + self.__refactoringProcess = None + self.__refactoringConnection = None + # Rope objects + # TODO: move this to RefactoringClient self.__project = None + # TODO: split this between RefactoringClient and this server from FileSystemCommands import E5FileSystemCommands self.__fsCommands = E5FileSystemCommands(self.__e5project) + + # 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__ def initActions(self): """ @@ -2211,11 +2236,33 @@ self.__projectLanguage = self.__e5project.getProjectLanguage() if self.__projectLanguage.startswith("Python"): - import rope.base.project - self.__project = rope.base.project.Project( - self.__projectpath, fscommands=self.__fsCommands) - for act in self.actions: - act.setEnabled(True) + if self.__projectLanguage == "Python2": + interpreter = Preferences.getDebugger("PythonInterpreter") + elif self.__projectLanguage == "Python3": + interpreter = Preferences.getDebugger("Python3Interpreter") + else: + interpreter = "" + if interpreter: + process = self.__startRefactoringClient(interpreter) + if process is None: + self.__ui.appendToStderr(self.tr( + "Project language '{0}' is not supported because" + " the configured interpreter could not be started." + " Refactoring is disabled." + ).format(self.__projectLanguage)) + else: + self.__refactoringProcess = process +## import rope.base.project +## self.__project = rope.base.project.Project( +## self.__projectpath, fscommands=self.__fsCommands) + for act in self.actions: + act.setEnabled(True) + else: + self.__ui.appendToStderr(self.tr( + "Project language '{0}' is not supported because no" + " suitable interpreter is configured. Refactoring is" + " disabled." + ).format(self.__projectLanguage)) def projectClosed(self): """ @@ -2224,14 +2271,16 @@ for act in self.actions: act.setEnabled(False) - if self.__project is not None: - self.__project.close() - self.__project = None + self.__stopRefactoringClient() +## if self.__project is not None: +## self.__project.close() +## self.__project = None self.__projectopen = False self.__projectpath = '' self.__projectLanguage = "" + # TODO: delete this or move to client def getProject(self): """ Public method to get a reference to the rope project object. @@ -2306,3 +2355,175 @@ # this could come from trying to do PyQt4/PyQt5 mixed stuff # simply ignore it pass + + ####################################################################### + ## Methods below handle the network connection + ####################################################################### + + @pyqtSlot() + def __handleNewConnection(self): + """ + Private slot for new incomming connections from the refactoring client. + """ + if self.__refactoringConnection is not None: + self.__refactoringConnection.close() + self.__refactoringConnection = None + + connection = self.nextPendingConnection() + if not connection.isValid(): + return + + self.__refactoringConnection = connection + connection.readyRead.connect(self.__receiveJson) + connection.disconnected.connect(self.__handleDisconnect) + + self.__sendJson("ping", {}) + + @pyqtSlot() + def __handleDisconnect(self): + """ + Private slot handling a disconnect of the refactoring client. + """ + if self.__refactoringConnection is not None: + self.__refactoringConnection.close() + + self.__refactoringConnection = None + + @pyqtSlot() + def __receiveJson(self): + """ + Private slot handling received data from the refactoring client. + """ + while self.__refactoringConnection and \ + self.__refactoringConnection.canReadLine(): + data = self.__refactoringConnection.readLine() + jsonLine = bytes(data).decode() + + print("Refactoring Server: ", jsonLine) ##debug + + self.__processJson(jsonLine) + continue + + def __processJson(self, jsonStr): + """ + Private method to process the JSON serialized client data. + + @param jsonStr string containing the data structure received + from the refactoring client + @type str + """ + import json + + try: + clientDict = json.loads(jsonStr.strip()) + except (TypeError, ValueError) as err: + E5MessageBox.critical( + None, + self.tr("Refactoring Protocol Error"), + self.tr("""<p>The response received from the refactoring""" + """ 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(jsonStr.strip())), + E5MessageBox.StandardButtons( + E5MessageBox.Ok)) + return + + method = clientDict["method"] + params = clientDict["params"] + + print("Method:", method) + print("Params:", params) + + if method == "pong": + pass + + elif method == "ClientException": + if params["ExceptionType"] == "ProtocolError": + E5MessageBox.critical( + None, + self.tr("Refactoring Protocol Error"), + self.tr("""<p>The data received from the refactoring""" + """ server 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( + params["ExceptionValue"], + Utilities.html_encode(params["ProtocolData"])), + E5MessageBox.StandardButtons( + E5MessageBox.Ok)) + else: + E5MessageBox.critical( + None, + self.tr("Refactoring Client Error"), + self.tr("<p>An exception happened in the refactoring" + " client. Please report it to the eric bugs" + " email address.</p>" + "<p>Exception: {0}</p>" + "<p>Value: {1}</p>" + "Traceback: {2}</p>").format( + Utilities.html_encode(params["ExceptionType"]), + params["ExceptionValue"], + params["Traceback"].replace("\r\n", "<br/>") + .replace("\n", "<br/>").replace("\r", "<br/>"), + ), + E5MessageBox.StandardButtons( + E5MessageBox.Ok)) + return + + def __sendJson(self, command, params): + """ + Private method to send a single refactoring command to the client. + + @param command command name to be sent + @type str + @param params dictionary of named parameters for the command + @type dict + """ + import json + + commandDict = { + "jsonrpc": "2.0", + "method": command, + "params": params, + } + cmd = json.dumps(commandDict) + '\n' + if self.__refactoringConnection is not None: + self.__refactoringConnection.write( + cmd.encode('utf8', 'backslashreplace')) + + def __startRefactoringClient(self, interpreter): + """ + Private method to start the refactoring client. + + @param interpreter interpreter to be used for the refactoring client + @type str + @return reference to the refactoring client process + """ + if interpreter == "" or not Utilities.isinpath(interpreter): + return None + + client = os.path.join(os.path.dirname(__file__), + "RefactoringClient.py") + proc = QProcess() + proc.setProcessChannelMode(QProcess.ForwardedChannels) + args = [client, self.__hostAddress, str(self.serverPort()), + self.__projectpath] + proc.start(interpreter, args) + if not proc.waitForStarted(10000): + proc = None + + return proc + + def __stopRefactoringClient(self): + """ + Private method to stop the refactoring client process. + """ + self.__refactoringProcess.close() + self.__refactoringProcess = None + +# +# eflag: noqa = M801