--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RefactoringRope/JsonClient.py Tue Sep 12 18:55:25 2017 +0200 @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the JSON based client base class. +""" + +from __future__ import unicode_literals +try: + bytes = unicode + import StringIO as io # __IGNORE_EXCEPTION__ +except NameError: + import io # __IGNORE_WARNING__ + +import sys +import socket +import select +import traceback +import time +import json + + +class JsonClient(object): + """ + Class implementing the JSON based client base class. + """ + def __init__(self, host, port): + """ + Constructor + + @param host ip address the background service is listening + @type str + @param port port of the background service + @type int + """ + self.__connection = socket.create_connection((host, port)) + + def sendJson(self, command, params): + """ + Public method to send a single refactoring command to the server. + + @param command command name to be sent + @type str + @param params dictionary of named parameters for the command + @type dict + """ + commandDict = { + "jsonrpc": "2.0", + "method": command, + "params": params, + } + cmd = json.dumps(commandDict) + '\n' + self.__connection.sendall(cmd.encode('utf8', 'backslashreplace')) + + def __receiveJson(self): + """ + Private method to receive a JSON encode command and data from the + server. + """ + line = self.__connection.recv(1024 * 1024, socket.MSG_PEEK) # 1M buffer + + eol = line.find(b'\n') + + if eol >= 0: + size = eol + 1 + + # Now we know how big the line is, read it for real. + line = self.__connection.recv(size).decode( + 'utf8', 'backslashreplace') + try: + commandDict = json.loads(line.strip()) + except (TypeError, ValueError) as err: + self.sendJson("ClientException", { + "ExceptionType": "ProtocolError", + "ExceptionValue": str(err), + "ProtocolData": line.strip(), + }) + return + + method = commandDict["method"] + params = commandDict["params"] + self.handleCall(method, params) + + def handleCall(self, method, params): + """ + Public method to handle a method call from the server. + + 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 + + def run(self): + """ + Public method implementing the main loop of the client. + """ + try: + while True: + try: + rrdy, wrdy, xrdy = select.select( + [self.__connection], [], []) + except (select.error, KeyboardInterrupt, socket.error): + # just carry on + continue + + if self.__connection in rrdy: + self.__receiveJson() + + except Exception: + exctype, excval, exctb = sys.exc_info() + tbinfofile = io.StringIO() + traceback.print_tb(exctb, None, tbinfofile) + tbinfofile.seek(0) + tbinfo = tbinfofile.read() + del exctb + self.sendJson("ClientException", { + "ExceptionType": str(exctype), + "ExceptionValue": str(excval), + "Traceback": tbinfo, + }) + + # Give time to process latest response on server side + time.sleep(0.5) + self.__connection.shutdown(socket.SHUT_RDWR) + self.__connection.close()