24 |
29 |
25 class EricJsonServer(QTcpServer): |
30 class EricJsonServer(QTcpServer): |
26 """ |
31 """ |
27 Class implementing a JSON based server base class. |
32 Class implementing a JSON based server base class. |
28 """ |
33 """ |
|
34 |
29 def __init__(self, name="", multiplex=False, parent=None): |
35 def __init__(self, name="", multiplex=False, parent=None): |
30 """ |
36 """ |
31 Constructor |
37 Constructor |
32 |
38 |
33 @param name name of the server (used for output only) |
39 @param name name of the server (used for output only) |
34 @type str |
40 @type str |
35 @param multiplex flag indicating a multiplexing server |
41 @param multiplex flag indicating a multiplexing server |
36 @type bool |
42 @type bool |
37 @param parent parent object |
43 @param parent parent object |
38 @type QObject |
44 @type QObject |
39 """ |
45 """ |
40 super().__init__(parent) |
46 super().__init__(parent) |
41 |
47 |
42 self.__name = name |
48 self.__name = name |
43 self.__multiplex = multiplex |
49 self.__multiplex = multiplex |
44 if self.__multiplex: |
50 if self.__multiplex: |
45 self.__clientProcesses = {} |
51 self.__clientProcesses = {} |
46 self.__connections = {} |
52 self.__connections = {} |
47 else: |
53 else: |
48 self.__clientProcess = None |
54 self.__clientProcess = None |
49 self.__connection = None |
55 self.__connection = None |
50 |
56 |
51 # setup the network interface |
57 # setup the network interface |
52 networkInterface = Preferences.getDebugger("NetworkInterface") |
58 networkInterface = Preferences.getDebugger("NetworkInterface") |
53 if networkInterface == "all" or '.' in networkInterface: |
59 if networkInterface == "all" or "." in networkInterface: |
54 # IPv4 |
60 # IPv4 |
55 self.__hostAddress = '127.0.0.1' |
61 self.__hostAddress = "127.0.0.1" |
56 else: |
62 else: |
57 # IPv6 |
63 # IPv6 |
58 self.__hostAddress = '::1' |
64 self.__hostAddress = "::1" |
59 self.listen(QHostAddress(self.__hostAddress)) |
65 self.listen(QHostAddress(self.__hostAddress)) |
60 |
66 |
61 self.newConnection.connect(self.handleNewConnection) |
67 self.newConnection.connect(self.handleNewConnection) |
62 |
68 |
63 ## Note: Need the port if client is started external in debugger. |
69 ## Note: Need the port if client is started external in debugger. |
64 print('JSON server ({1}) listening on: {0:d}' # __IGNORE_WARNING__ |
70 print( # __IGNORE_WARNING_M801__ |
65 .format(self.serverPort(), self.__name)) |
71 "JSON server ({1}) listening on: {0:d}".format( |
66 |
72 self.serverPort(), self.__name |
|
73 ) |
|
74 ) |
|
75 |
67 @pyqtSlot() |
76 @pyqtSlot() |
68 def handleNewConnection(self): |
77 def handleNewConnection(self): |
69 """ |
78 """ |
70 Public slot for new incoming connections from a client. |
79 Public slot for new incoming connections from a client. |
71 """ |
80 """ |
72 connection = self.nextPendingConnection() |
81 connection = self.nextPendingConnection() |
73 if not connection.isValid(): |
82 if not connection.isValid(): |
74 return |
83 return |
75 |
84 |
76 if self.__multiplex: |
85 if self.__multiplex: |
77 if not connection.waitForReadyRead(3000): |
86 if not connection.waitForReadyRead(3000): |
78 return |
87 return |
79 idString = bytes(connection.readLine()).decode( |
88 idString = ( |
80 "utf-8", 'backslashreplace').strip() |
89 bytes(connection.readLine()).decode("utf-8", "backslashreplace").strip() |
|
90 ) |
81 if idString in self.__connections: |
91 if idString in self.__connections: |
82 self.__connections[idString].close() |
92 self.__connections[idString].close() |
83 self.__connections[idString] = connection |
93 self.__connections[idString] = connection |
84 else: |
94 else: |
85 idString = "" |
95 idString = "" |
86 if self.__connection is not None: |
96 if self.__connection is not None: |
87 self.__connection.close() |
97 self.__connection.close() |
88 |
98 |
89 self.__connection = connection |
99 self.__connection = connection |
90 |
100 |
91 connection.readyRead.connect( |
101 connection.readyRead.connect(lambda: self.__receiveJson(idString)) |
92 lambda: self.__receiveJson(idString)) |
102 connection.disconnected.connect(lambda: self.__handleDisconnect(idString)) |
93 connection.disconnected.connect( |
103 |
94 lambda: self.__handleDisconnect(idString)) |
|
95 |
|
96 @pyqtSlot() |
104 @pyqtSlot() |
97 def __handleDisconnect(self, idString): |
105 def __handleDisconnect(self, idString): |
98 """ |
106 """ |
99 Private slot handling a disconnect of the client. |
107 Private slot handling a disconnect of the client. |
100 |
108 |
101 @param idString id of the connection been disconnected |
109 @param idString id of the connection been disconnected |
102 @type str |
110 @type str |
103 """ |
111 """ |
104 if idString: |
112 if idString: |
105 if idString in self.__connections: |
113 if idString in self.__connections: |
106 self.__connections[idString].close() |
114 self.__connections[idString].close() |
107 del self.__connections[idString] |
115 del self.__connections[idString] |
108 else: |
116 else: |
109 if self.__connection is not None: |
117 if self.__connection is not None: |
110 self.__connection.close() |
118 self.__connection.close() |
111 |
119 |
112 self.__connection = None |
120 self.__connection = None |
113 |
121 |
114 def connectionNames(self): |
122 def connectionNames(self): |
115 """ |
123 """ |
116 Public method to get the list of active connection names. |
124 Public method to get the list of active connection names. |
117 |
125 |
118 If this is not a multiplexing server, an empty list is returned. |
126 If this is not a multiplexing server, an empty list is returned. |
119 |
127 |
120 @return list of active connection names |
128 @return list of active connection names |
121 @rtype list of str |
129 @rtype list of str |
122 """ |
130 """ |
123 if self.__multiplex: |
131 if self.__multiplex: |
124 return list(self.__connections.keys()) |
132 return list(self.__connections.keys()) |
125 else: |
133 else: |
126 return [] |
134 return [] |
127 |
135 |
128 @pyqtSlot() |
136 @pyqtSlot() |
129 def __receiveJson(self, idString): |
137 def __receiveJson(self, idString): |
130 """ |
138 """ |
131 Private slot handling received data from the client. |
139 Private slot handling received data from the client. |
132 |
140 |
133 @param idString id of the connection |
141 @param idString id of the connection |
134 @type str |
142 @type str |
135 """ |
143 """ |
136 if idString: |
144 if idString: |
137 try: |
145 try: |
138 connection = self.__connections[idString] |
146 connection = self.__connections[idString] |
139 except KeyError: |
147 except KeyError: |
140 connection = None |
148 connection = None |
141 else: |
149 else: |
142 connection = self.__connection |
150 connection = self.__connection |
143 |
151 |
144 while connection and connection.canReadLine(): |
152 while connection and connection.canReadLine(): |
145 data = connection.readLine() |
153 data = connection.readLine() |
146 jsonLine = bytes(data).decode("utf-8", 'backslashreplace') |
154 jsonLine = bytes(data).decode("utf-8", "backslashreplace") |
147 |
155 |
148 #- print("JSON Server ({0}): {1}".format(self.__name, jsonLine)) |
156 # - print("JSON Server ({0}): {1}".format(self.__name, jsonLine)) |
149 #- this is for debugging only |
157 # - this is for debugging only |
150 |
158 |
151 try: |
159 try: |
152 clientDict = json.loads(jsonLine.strip()) |
160 clientDict = json.loads(jsonLine.strip()) |
153 except (TypeError, ValueError) as err: |
161 except (TypeError, ValueError) as err: |
154 EricMessageBox.critical( |
162 EricMessageBox.critical( |
155 None, |
163 None, |
156 self.tr("JSON Protocol Error"), |
164 self.tr("JSON Protocol Error"), |
157 self.tr("""<p>The response received from the client""" |
165 self.tr( |
158 """ could not be decoded. Please report""" |
166 """<p>The response received from the client""" |
159 """ this issue with the received data to the""" |
167 """ could not be decoded. Please report""" |
160 """ eric bugs email address.</p>""" |
168 """ this issue with the received data to the""" |
161 """<p>Error: {0}</p>""" |
169 """ eric bugs email address.</p>""" |
162 """<p>Data:<br/>{1}</p>""").format( |
170 """<p>Error: {0}</p>""" |
163 str(err), Utilities.html_encode(jsonLine.strip())), |
171 """<p>Data:<br/>{1}</p>""" |
164 EricMessageBox.Ok) |
172 ).format(str(err), Utilities.html_encode(jsonLine.strip())), |
|
173 EricMessageBox.Ok, |
|
174 ) |
165 return |
175 return |
166 |
176 |
167 self.handleCall(clientDict["method"], clientDict["params"]) |
177 self.handleCall(clientDict["method"], clientDict["params"]) |
168 |
178 |
169 def sendJson(self, command, params, flush=False, idString=""): |
179 def sendJson(self, command, params, flush=False, idString=""): |
170 """ |
180 """ |
171 Public method to send a single command to a client. |
181 Public method to send a single command to a client. |
172 |
182 |
173 @param command command name to be sent |
183 @param command command name to be sent |
174 @type str |
184 @type str |
175 @param params dictionary of named parameters for the command |
185 @param params dictionary of named parameters for the command |
176 @type dict |
186 @type dict |
177 @param flush flag indicating to flush the data to the socket |
187 @param flush flag indicating to flush the data to the socket |
237 args.append(idString) |
247 args.append(idString) |
238 args.extend(clientArgs) |
248 args.extend(clientArgs) |
239 proc.start(interpreter, args) |
249 proc.start(interpreter, args) |
240 if not proc.waitForStarted(10000): |
250 if not proc.waitForStarted(10000): |
241 proc = None |
251 proc = None |
242 |
252 |
243 if idString: |
253 if idString: |
244 self.__clientProcesses[idString] = proc |
254 self.__clientProcesses[idString] = proc |
245 if proc: |
255 if proc: |
246 timer = QTimer() |
256 timer = QTimer() |
247 timer.setSingleShot(True) |
257 timer.setSingleShot(True) |
248 timer.start(30000) # 30s timeout |
258 timer.start(30000) # 30s timeout |
249 while ( |
259 while idString not in self.connectionNames() and timer.isActive(): |
250 idString not in self.connectionNames() and |
|
251 timer.isActive() |
|
252 ): |
|
253 # Give the event loop the chance to process the new |
260 # Give the event loop the chance to process the new |
254 # connection of the client (= slow start). |
261 # connection of the client (= slow start). |
255 QCoreApplication.processEvents( |
262 QCoreApplication.processEvents( |
256 QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) |
263 QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents |
|
264 ) |
257 QThread.msleep(100) |
265 QThread.msleep(100) |
258 |
266 |
259 # check if client exited prematurely |
267 # check if client exited prematurely |
260 if proc.state() == QProcess.ProcessState.NotRunning: |
268 if proc.state() == QProcess.ProcessState.NotRunning: |
261 exitCode = proc.exitCode() |
269 exitCode = proc.exitCode() |
262 proc = None |
270 proc = None |
263 self.__clientProcesses[idString] = None |
271 self.__clientProcesses[idString] = None |
264 break |
272 break |
265 |
273 |
266 QThread.msleep(500) |
274 QThread.msleep(500) |
267 else: |
275 else: |
268 if proc: |
276 if proc: |
269 timer = QTimer() |
277 timer = QTimer() |
270 timer.setSingleShot(True) |
278 timer.setSingleShot(True) |
271 timer.start(1000) # 1s timeout |
279 timer.start(1000) # 1s timeout |
272 while timer.isActive(): |
280 while timer.isActive(): |
273 # check if client exited prematurely |
281 # check if client exited prematurely |
274 QCoreApplication.processEvents( |
282 QCoreApplication.processEvents( |
275 QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) |
283 QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents |
|
284 ) |
276 QThread.msleep(100) |
285 QThread.msleep(100) |
277 if proc.state() == QProcess.ProcessState.NotRunning: |
286 if proc.state() == QProcess.ProcessState.NotRunning: |
278 exitCode = proc.exitCode() |
287 exitCode = proc.exitCode() |
279 proc = None |
288 proc = None |
280 break |
289 break |
281 self.__clientProcess = proc |
290 self.__clientProcess = proc |
282 |
291 |
283 return proc is not None, exitCode |
292 return proc is not None, exitCode |
284 |
293 |
285 def stopClient(self, idString=""): |
294 def stopClient(self, idString=""): |
286 """ |
295 """ |
287 Public method to stop a client process. |
296 Public method to stop a client process. |
288 |
297 |
289 @param idString id of the client to be stopped |
298 @param idString id of the client to be stopped |
290 @type str |
299 @type str |
291 """ |
300 """ |
292 self.sendJson("Exit", {}, flush=True, idString=idString) |
301 self.sendJson("Exit", {}, flush=True, idString=idString) |
293 |
302 |
294 if idString: |
303 if idString: |
295 try: |
304 try: |
296 connection = self.__connections[idString] |
305 connection = self.__connections[idString] |
297 except KeyError: |
306 except KeyError: |
298 connection = None |
307 connection = None |
299 else: |
308 else: |
300 connection = self.__connection |
309 connection = self.__connection |
301 if connection is not None: |
310 if connection is not None: |
302 connection.waitForDisconnected() |
311 connection.waitForDisconnected() |
303 |
312 |
304 if idString: |
313 if idString: |
305 with contextlib.suppress(KeyError): |
314 with contextlib.suppress(KeyError): |
306 if self .__clientProcesses[idString] is not None: |
315 if self.__clientProcesses[idString] is not None: |
307 self .__clientProcesses[idString].close() |
316 self.__clientProcesses[idString].close() |
308 del self.__clientProcesses[idString] |
317 del self.__clientProcesses[idString] |
309 else: |
318 else: |
310 if self.__clientProcess is not None: |
319 if self.__clientProcess is not None: |
311 self.__clientProcess.close() |
320 self.__clientProcess.close() |
312 self.__clientProcess = None |
321 self.__clientProcess = None |
313 |
322 |
314 def stopAllClients(self): |
323 def stopAllClients(self): |
315 """ |
324 """ |
316 Public method to stop all clients. |
325 Public method to stop all clients. |
317 """ |
326 """ |
318 clientNames = self.connectionNames()[:] |
327 clientNames = self.connectionNames()[:] |
319 for clientName in clientNames: |
328 for clientName in clientNames: |
320 self.stopClient(clientName) |
329 self.stopClient(clientName) |
321 |
330 |
322 ####################################################################### |
331 ####################################################################### |
323 ## The following methods should be overridden by derived classes |
332 ## The following methods should be overridden by derived classes |
324 ####################################################################### |
333 ####################################################################### |
325 |
334 |
326 def handleCall(self, method, params): |
335 def handleCall(self, method, params): |
327 """ |
336 """ |
328 Public method to handle a method call from the client. |
337 Public method to handle a method call from the client. |
329 |
338 |
330 Note: This is an empty implementation that must be overridden in |
339 Note: This is an empty implementation that must be overridden in |
331 derived classes. |
340 derived classes. |
332 |
341 |
333 @param method requested method name |
342 @param method requested method name |
334 @type str |
343 @type str |
335 @param params dictionary with method specific parameters |
344 @param params dictionary with method specific parameters |
336 @type dict |
345 @type dict |
337 """ |
346 """ |