18 |
18 |
19 class EricJsonClient: |
19 class EricJsonClient: |
20 """ |
20 """ |
21 Class implementing a JSON based client base class. |
21 Class implementing a JSON based client base class. |
22 """ |
22 """ |
|
23 |
23 def __init__(self, host, port, idString=""): |
24 def __init__(self, host, port, idString=""): |
24 """ |
25 """ |
25 Constructor |
26 Constructor |
26 |
27 |
27 @param host IP address the background service is listening |
28 @param host IP address the background service is listening |
28 @type str |
29 @type str |
29 @param port port of the background service |
30 @param port port of the background service |
30 @type int |
31 @type int |
31 @param idString assigned client id to be sent back to the server in |
32 @param idString assigned client id to be sent back to the server in |
32 order to identify the connection |
33 order to identify the connection |
33 @type str |
34 @type str |
34 """ |
35 """ |
35 self.__connection = socket.create_connection((host, port)) |
36 self.__connection = socket.create_connection((host, port)) |
36 if idString: |
37 if idString: |
37 reply = idString + '\n' |
38 reply = idString + "\n" |
38 self.__connection.sendall(reply.encode('utf8', 'backslashreplace')) |
39 self.__connection.sendall(reply.encode("utf8", "backslashreplace")) |
39 |
40 |
40 def sendJson(self, command, params): |
41 def sendJson(self, command, params): |
41 """ |
42 """ |
42 Public method to send a single refactoring command to the server. |
43 Public method to send a single refactoring command to the server. |
43 |
44 |
44 @param command command name to be sent |
45 @param command command name to be sent |
45 @type str |
46 @type str |
46 @param params dictionary of named parameters for the command |
47 @param params dictionary of named parameters for the command |
47 @type dict |
48 @type dict |
48 """ |
49 """ |
49 commandDict = { |
50 commandDict = { |
50 "jsonrpc": "2.0", |
51 "jsonrpc": "2.0", |
51 "method": command, |
52 "method": command, |
52 "params": params, |
53 "params": params, |
53 } |
54 } |
54 cmd = json.dumps(commandDict) + '\n' |
55 cmd = json.dumps(commandDict) + "\n" |
55 self.__connection.sendall(cmd.encode('utf8', 'backslashreplace')) |
56 self.__connection.sendall(cmd.encode("utf8", "backslashreplace")) |
56 |
57 |
57 def __receiveJson(self): |
58 def __receiveJson(self): |
58 """ |
59 """ |
59 Private method to receive a JSON encode command and data from the |
60 Private method to receive a JSON encode command and data from the |
60 server. |
61 server. |
61 |
62 |
62 @return tuple containing the received command and a dictionary |
63 @return tuple containing the received command and a dictionary |
63 containing the associated data |
64 containing the associated data |
64 @rtype tuple of (str, dict) |
65 @rtype tuple of (str, dict) |
65 """ |
66 """ |
66 # step 1: receive the data |
67 # step 1: receive the data |
67 # The JSON RPC string is prefixed by a 9 character long length field. |
68 # The JSON RPC string is prefixed by a 9 character long length field. |
68 length = self.__connection.recv(9) |
69 length = self.__connection.recv(9) |
69 if len(length) < 9: |
70 if len(length) < 9: |
70 # invalid length string received |
71 # invalid length string received |
71 return None, None |
72 return None, None |
72 |
73 |
73 length = int(length) |
74 length = int(length) |
74 data = b'' |
75 data = b"" |
75 while len(data) < length: |
76 while len(data) < length: |
76 newData = self.__connection.recv(length - len(data)) |
77 newData = self.__connection.recv(length - len(data)) |
77 if not newData: |
78 if not newData: |
78 return None, None |
79 return None, None |
79 |
80 |
80 data += newData |
81 data += newData |
81 |
82 |
82 # step 2: decode and convert the data |
83 # step 2: decode and convert the data |
83 line = data.decode( |
84 line = data.decode("utf8", "backslashreplace") |
84 'utf8', 'backslashreplace') |
|
85 try: |
85 try: |
86 commandDict = json.loads(line.strip()) |
86 commandDict = json.loads(line.strip()) |
87 except (TypeError, ValueError) as err: |
87 except (TypeError, ValueError) as err: |
88 self.sendJson("ClientException", { |
88 self.sendJson( |
89 "ExceptionType": "ProtocolError", |
89 "ClientException", |
90 "ExceptionValue": str(err), |
90 { |
91 "ProtocolData": line.strip(), |
91 "ExceptionType": "ProtocolError", |
92 }) |
92 "ExceptionValue": str(err), |
|
93 "ProtocolData": line.strip(), |
|
94 }, |
|
95 ) |
93 return None, None |
96 return None, None |
94 |
97 |
95 method = commandDict["method"] |
98 method = commandDict["method"] |
96 params = commandDict["params"] |
99 params = commandDict["params"] |
97 |
100 |
98 return method, params |
101 return method, params |
99 |
102 |
100 def handleCall(self, method, params): |
103 def handleCall(self, method, params): |
101 """ |
104 """ |
102 Public method to handle a method call from the server. |
105 Public method to handle a method call from the server. |
103 |
106 |
104 Note: This is an empty implementation that must be overridden in |
107 Note: This is an empty implementation that must be overridden in |
105 derived classes. |
108 derived classes. |
106 |
109 |
107 @param method requested method name |
110 @param method requested method name |
108 @type str |
111 @type str |
109 @param params dictionary with method specific parameters |
112 @param params dictionary with method specific parameters |
110 @type dict |
113 @type dict |
111 """ |
114 """ |
112 pass |
115 pass |
113 |
116 |
114 def run(self): |
117 def run(self): |
115 """ |
118 """ |
116 Public method implementing the main loop of the client. |
119 Public method implementing the main loop of the client. |
117 """ |
120 """ |
118 try: |
121 try: |
119 selectErrors = 0 |
122 selectErrors = 0 |
120 while selectErrors <= 10: # selected arbitrarily |
123 while selectErrors <= 10: # selected arbitrarily |
121 try: |
124 try: |
122 rrdy, wrdy, xrdy = select.select( |
125 rrdy, wrdy, xrdy = select.select([self.__connection], [], []) |
123 [self.__connection], [], []) |
126 |
124 |
|
125 # Just waiting for self.__connection. Therefor no check |
127 # Just waiting for self.__connection. Therefor no check |
126 # needed. |
128 # needed. |
127 method, params = self.__receiveJson() |
129 method, params = self.__receiveJson() |
128 if method is None: |
130 if method is None: |
129 selectErrors += 1 |
131 selectErrors += 1 |
130 elif method == "Exit": |
132 elif method == "Exit": |
131 break |
133 break |
132 else: |
134 else: |
133 self.handleCall(method, params) |
135 self.handleCall(method, params) |
134 |
136 |
135 # reset select errors |
137 # reset select errors |
136 selectErrors = 0 |
138 selectErrors = 0 |
137 |
139 |
138 except (select.error, KeyboardInterrupt, socket.error): |
140 except (select.error, KeyboardInterrupt, socket.error): |
139 selectErrors += 1 |
141 selectErrors += 1 |
140 |
142 |
141 except Exception: |
143 except Exception: |
142 exctype, excval, exctb = sys.exc_info() |
144 exctype, excval, exctb = sys.exc_info() |
143 tbinfofile = io.StringIO() |
145 tbinfofile = io.StringIO() |
144 traceback.print_tb(exctb, None, tbinfofile) |
146 traceback.print_tb(exctb, None, tbinfofile) |
145 tbinfofile.seek(0) |
147 tbinfofile.seek(0) |
146 tbinfo = tbinfofile.read() |
148 tbinfo = tbinfofile.read() |
147 del exctb |
149 del exctb |
148 self.sendJson("ClientException", { |
150 self.sendJson( |
149 "ExceptionType": str(exctype), |
151 "ClientException", |
150 "ExceptionValue": str(excval), |
152 { |
151 "Traceback": tbinfo, |
153 "ExceptionType": str(exctype), |
152 }) |
154 "ExceptionValue": str(excval), |
|
155 "Traceback": tbinfo, |
|
156 }, |
|
157 ) |
153 |
158 |
154 # Give time to process latest response on server side |
159 # Give time to process latest response on server side |
155 with contextlib.suppress(socket.error, OSError): |
160 with contextlib.suppress(socket.error, OSError): |
156 self.__connection.shutdown(socket.SHUT_RDWR) |
161 self.__connection.shutdown(socket.SHUT_RDWR) |
157 self.__connection.close() |
162 self.__connection.close() |
158 |
163 |
159 def poll(self, waitMethod=""): |
164 def poll(self, waitMethod=""): |
160 """ |
165 """ |
161 Public method to check and receive one message (if available). |
166 Public method to check and receive one message (if available). |
162 |
167 |
163 @param waitMethod name of a method to wait for |
168 @param waitMethod name of a method to wait for |
164 @type str |
169 @type str |
165 @return dictionary containing the data of the waited for method |
170 @return dictionary containing the data of the waited for method |
166 @rtype dict |
171 @rtype dict |
167 """ |
172 """ |
168 try: |
173 try: |
169 if waitMethod: |
174 if waitMethod: |
170 rrdy, wrdy, xrdy = select.select( |
175 rrdy, wrdy, xrdy = select.select([self.__connection], [], []) |
171 [self.__connection], [], []) |
|
172 else: |
176 else: |
173 rrdy, wrdy, xrdy = select.select( |
177 rrdy, wrdy, xrdy = select.select([self.__connection], [], [], 0) |
174 [self.__connection], [], [], 0) |
178 |
175 |
|
176 if self.__connection in rrdy: |
179 if self.__connection in rrdy: |
177 method, params = self.__receiveJson() |
180 method, params = self.__receiveJson() |
178 if method is not None: |
181 if method is not None: |
179 if method == "Exit": |
182 if method == "Exit": |
180 self.__exitClient = True |
183 self.__exitClient = True |
181 elif method == waitMethod: |
184 elif method == waitMethod: |
182 return params |
185 return params |
183 else: |
186 else: |
184 self.handleCall(method, params) |
187 self.handleCall(method, params) |
185 |
188 |
186 except (select.error, KeyboardInterrupt, socket.error): |
189 except (select.error, KeyboardInterrupt, socket.error): |
187 # just ignore these |
190 # just ignore these |
188 pass |
191 pass |
189 |
192 |
190 except Exception: |
193 except Exception: |
191 exctype, excval, exctb = sys.exc_info() |
194 exctype, excval, exctb = sys.exc_info() |
192 tbinfofile = io.StringIO() |
195 tbinfofile = io.StringIO() |
193 traceback.print_tb(exctb, None, tbinfofile) |
196 traceback.print_tb(exctb, None, tbinfofile) |
194 tbinfofile.seek(0) |
197 tbinfofile.seek(0) |
195 tbinfo = tbinfofile.read() |
198 tbinfo = tbinfofile.read() |
196 del exctb |
199 del exctb |
197 self.sendJson("ClientException", { |
200 self.sendJson( |
198 "ExceptionType": str(exctype), |
201 "ClientException", |
199 "ExceptionValue": str(excval), |
202 { |
200 "Traceback": tbinfo, |
203 "ExceptionType": str(exctype), |
201 }) |
204 "ExceptionValue": str(excval), |
202 |
205 "Traceback": tbinfo, |
|
206 }, |
|
207 ) |
|
208 |
203 return None |
209 return None |