83 self.__serviceInterfaces = {} |
84 self.__serviceInterfaces = {} |
84 # no specific service interfaces have been created yet |
85 # no specific service interfaces have been created yet |
85 |
86 |
86 self.__connection = None |
87 self.__connection = None |
87 self.__callbacks = {} # callback references indexed by UUID |
88 self.__callbacks = {} # callback references indexed by UUID |
|
89 self.__messageQueue = collections.deque() |
88 |
90 |
89 self.connectionStateChanged.connect(self.__connectionStateChanged) |
91 self.connectionStateChanged.connect(self.__connectionStateChanged) |
90 |
92 |
91 def getServiceInterface(self, name): |
93 def getServiceInterface(self, name): |
92 """ |
94 """ |
95 |
97 |
96 @param name service name |
98 @param name service name |
97 @type str |
99 @type str |
98 @return reference to the service interface |
100 @return reference to the service interface |
99 @rtype QObject |
101 @rtype QObject |
|
102 @exception ValueError raised to indicate an unsupported server interface |
|
103 was requested |
100 """ |
104 """ |
101 lname = name.lower() |
105 lname = name.lower() |
102 try: |
106 try: |
103 return self.__serviceInterfaces[lname] |
107 return self.__serviceInterfaces[lname] |
104 except KeyError: |
108 except KeyError: |
105 # instantiate the service interface |
109 if lname not in ("debugger", "filesystem", "project"): |
106 if lname == "filesystem": |
110 raise ValueError(f"no such service supported ({name})") |
107 from .EricServerFileSystemInterface import EricServerFileSystemInterface |
|
108 self.__serviceInterfaces[lname] = EricServerFileSystemInterface(self) |
|
109 elif lname == "debugger": |
|
110 from .EricServerDebuggerInterface import EricServerDebuggerInterface |
|
111 self.__serviceInterfaces[lname] = EricServerDebuggerInterface(self) |
|
112 elif lname == "project": |
|
113 # TODO: 'Project Interface' not implemented yet |
|
114 pass |
|
115 else: |
111 else: |
116 raise ValueError(f"no such service supported ({name})") |
112 # instantiate the service interface |
|
113 if lname == "filesystem": |
|
114 from .EricServerFileSystemInterface import ( # noqa: I101 |
|
115 EricServerFileSystemInterface |
|
116 ) |
|
117 self.__serviceInterfaces[lname] = ( |
|
118 EricServerFileSystemInterface(self) |
|
119 ) |
|
120 elif lname == "debugger": |
|
121 from .EricServerDebuggerInterface import EricServerDebuggerInterface |
|
122 # noqa: I101 |
|
123 self.__serviceInterfaces[lname] = EricServerDebuggerInterface(self) |
|
124 elif lname == "project": |
|
125 # TODO: 'Project Interface' not implemented yet |
|
126 pass |
117 |
127 |
118 return self.__serviceInterfaces[lname] |
128 return self.__serviceInterfaces[lname] |
119 |
129 |
120 ####################################################################### |
130 ####################################################################### |
121 ## Methods for handling the server connection. |
131 ## Methods for handling the server connection. |
122 ####################################################################### |
132 ####################################################################### |
123 |
133 |
124 def connectToServer(self, host, port=None, timeout=None): |
134 def connectToServer(self, host, port=None, timeout=None): |
125 """ |
135 """ |
126 Public method to connect to the given host and port |
136 Public method to connect to the given host and port. |
127 |
137 |
128 @param host host name or IP address of the eric remote server |
138 @param host host name or IP address of the eric remote server |
129 @type str |
139 @type str |
130 @param port port number to connect to (defaults to None) |
140 @param port port number to connect to (defaults to None) |
131 @type int (optional) |
141 @type int (optional) |
132 @param timeout timeout im seconds for the connection attempt |
142 @param timeout timeout im seconds for the connection attempt |
133 (defaults to None) |
143 (defaults to None) |
134 @type int (optional) |
144 @type int (optional) |
|
145 @return flag indicating success |
|
146 @rtype bool |
135 """ |
147 """ |
136 if not bool(port): # None or 0 |
148 if not bool(port): # None or 0 |
137 # use default port |
149 # use default port |
138 port = 42024 |
150 port = 42024 |
139 |
151 |
140 if not bool(timeout): # None or 0 |
152 if not bool(timeout): # None or 0 |
141 # use configured default timeout |
153 # use configured default timeout |
142 timeout = Preferences.getEricServer("ConnectionTimeout") |
154 timeout = Preferences.getEricServer("ConnectionTimeout") |
143 timeout = timeout * 1000 # convert to milliseconds |
155 timeout *= 1000 # convert to milliseconds |
144 |
156 |
145 if self.__connection is not None: |
157 if self.__connection is not None: |
146 self.disconnectFromServer() |
158 self.disconnectFromServer() |
147 |
159 |
148 self.__connection = QTcpSocket(self) |
160 self.__connection = QTcpSocket(self) |
223 |
235 |
224 @pyqtSlot() |
236 @pyqtSlot() |
225 def __receiveJson(self): |
237 def __receiveJson(self): |
226 """ |
238 """ |
227 Private slot handling received data from the eric remote server. |
239 Private slot handling received data from the eric remote server. |
228 |
|
229 @param idString id of the connection |
|
230 @type str |
|
231 """ |
240 """ |
232 while self.__connection and self.__connection.bytesAvailable(): |
241 while self.__connection and self.__connection.bytesAvailable(): |
233 header = self.__connection.read(struct.calcsize(b"!II")) |
242 header = self.__connection.read(struct.calcsize(b"!II")) |
234 length, datahash = struct.unpack(b"!II", header) |
243 length, datahash = struct.unpack(b"!II", header) |
235 |
244 |
236 data = bytearray() |
245 data = bytearray() |
237 while len(data) < length: |
246 while len(data) < length: |
238 maxSize = length - len(data) |
247 maxSize = length - len(data) |
239 if self.__connection.bytesAvailable() < maxSize: |
248 if self.__connection.bytesAvailable() < maxSize: |
240 self.__connection.waitForReadyRead(50) |
249 self.__connection.waitForReadyRead(50) |
241 data += self.__connection.read(maxSize) |
250 newData = self.__connection.read(maxSize) |
|
251 if newData: |
|
252 data += newData |
242 |
253 |
243 if zlib.adler32(data) & 0xFFFFFFFF != datahash: |
254 if zlib.adler32(data) & 0xFFFFFFFF != datahash: |
244 # corrupted data -> discard and continue |
255 # corrupted data -> discard and continue |
245 continue |
256 continue |
246 |
257 |
247 jsonString = data.decode("utf-8", "backslashreplace") |
258 jsonString = data.decode("utf-8", "backslashreplace") |
248 |
259 |
249 # - print("Remote Server Interface: {0}".format(jsonString)) |
260 # - print("Remote Server Interface Receive: {0}".format(jsonString)) |
250 # - this is for debugging only |
261 # - this is for debugging only |
251 |
262 |
252 try: |
263 try: |
253 serverDataDict = json.loads(jsonString.strip()) |
264 serverDataDict = json.loads(jsonString.strip()) |
254 except (TypeError, ValueError) as err: |
265 except (TypeError, ValueError) as err: |
266 EricMessageBox.Ok, |
277 EricMessageBox.Ok, |
267 ) |
278 ) |
268 return |
279 return |
269 |
280 |
270 reqUuid = serverDataDict["uuid"] |
281 reqUuid = serverDataDict["uuid"] |
271 try: |
282 if reqUuid: |
|
283 # It is a response to a synchronous request -> handle the call back |
|
284 # immediately. |
272 self.__callbacks[reqUuid]( |
285 self.__callbacks[reqUuid]( |
273 serverDataDict["reply"], serverDataDict["params"] |
286 serverDataDict["reply"], serverDataDict["params"] |
274 ) |
287 ) |
275 del self.__callbacks[reqUuid] |
288 del self.__callbacks[reqUuid] |
|
289 else: |
|
290 self.__messageQueue.append(serverDataDict) |
|
291 |
|
292 while self.__messageQueue: |
|
293 serverDataDict = self.__messageQueue.popleft() # get the first message |
|
294 try: |
|
295 self.__categorySignalMapping[serverDataDict["category"]].emit( |
|
296 serverDataDict["reply"], serverDataDict["params"] |
|
297 ) |
276 except KeyError: |
298 except KeyError: |
277 # no callback for this UUID exists, send a signal |
299 if serverDataDict["category"] == EricRequestCategory.Error: |
278 try: |
300 # handle server errors in here |
279 self.__categorySignalMapping[serverDataDict["category"]].emit( |
301 self.__handleServerError( |
280 serverDataDict["reply"], serverDataDict["params"] |
302 serverDataDict["reply"], serverDataDict["params"] |
281 ) |
303 ) |
282 except KeyError: |
304 else: |
283 if serverDataDict["category"] == EricRequestCategory.Error: |
305 self.remoteReply.emit( |
284 # handle server errors in here |
306 serverDataDict["category"], |
285 self.__handleServerError( |
307 serverDataDict["reply"], |
286 serverDataDict["reply"], serverDataDict["params"] |
308 serverDataDict["params"], |
287 ) |
309 ) |
288 else: |
|
289 self.remoteReply.emit( |
|
290 serverDataDict["category"], |
|
291 serverDataDict["reply"], |
|
292 serverDataDict["params"], |
|
293 ) |
|
294 |
310 |
295 def sendJson(self, category, request, params, callback=None, flush=False): |
311 def sendJson(self, category, request, params, callback=None, flush=False): |
296 """ |
312 """ |
297 Public method to send a single command to a client. |
313 Public method to send a single command to a client. |
298 |
314 |
307 @type function (optional) |
323 @type function (optional) |
308 @param flush flag indicating to flush the data to the socket |
324 @param flush flag indicating to flush the data to the socket |
309 (defaults to False) |
325 (defaults to False) |
310 @type bool (optional) |
326 @type bool (optional) |
311 """ |
327 """ |
312 reqUuid = str(uuid.uuid4()) |
|
313 if callback: |
328 if callback: |
|
329 reqUuid = str(uuid.uuid4()) |
314 self.__callbacks[reqUuid] = callback |
330 self.__callbacks[reqUuid] = callback |
|
331 else: |
|
332 reqUuid = "" |
315 |
333 |
316 serviceDict = { |
334 serviceDict = { |
317 "jsonrpc": "2.0", |
335 "jsonrpc": "2.0", |
318 "category": category, |
336 "category": category, |
319 "request": request, |
337 "request": request, |
320 "params": params, |
338 "params": params, |
321 "uuid": reqUuid, |
339 "uuid": reqUuid, |
322 } |
340 } |
323 jsonString = json.dumps(serviceDict) + "\n" |
341 jsonString = json.dumps(serviceDict) + "\n" |
324 |
342 |
|
343 # - print("Remote Server Interface Send: {0}".format(jsonString)) |
|
344 # - this is for debugging only |
|
345 |
325 if self.__connection is not None: |
346 if self.__connection is not None: |
326 data = jsonString.encode("utf8", "backslashreplace") |
347 data = jsonString.encode("utf8", "backslashreplace") |
327 header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF) |
348 header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF) |
328 self.__connection.write(header) |
349 self.__connection.write(header) |
329 self.__connection.write(data) |
350 self.__connection.write(data) |
387 None, |
411 None, |
388 self.tr("eric-ide Server Versions"), |
412 self.tr("eric-ide Server Versions"), |
389 versionText, |
413 versionText, |
390 ) |
414 ) |
391 |
415 |
392 else: |
|
393 raise ValueError(f"unsupported reply received ({reply})") |
|
394 |
|
395 ####################################################################### |
416 ####################################################################### |
396 ## Reply handler methods |
417 ## Reply handler methods |
397 ####################################################################### |
418 ####################################################################### |
398 |
419 |
399 def __handleServerError(self, reply, params): |
420 def __handleServerError(self, reply, params): |
400 """ |
421 """ |
401 Public method handling server error replies. |
422 Private method handling server error replies. |
402 |
423 |
403 @param reply name of the error reply |
424 @param reply name of the error reply |
404 @type str |
425 @type str |
405 @param params dictionary containing the specific reply data |
426 @param params dictionary containing the specific reply data |
406 @type dict |
427 @type dict |
530 Public slot to initialize the eric-ide server menu. |
551 Public slot to initialize the eric-ide server menu. |
531 |
552 |
532 @return the menu generated |
553 @return the menu generated |
533 @rtype QMenu |
554 @rtype QMenu |
534 """ |
555 """ |
535 self.__serverProfilesMenu = QMenu(self.tr("Connect to"))##, self.__ui) |
556 self.__serverProfilesMenu = QMenu(self.tr("Connect to")) |
536 self.__serverProfilesMenu.aboutToShow.connect(self.__showServerProfilesMenu) |
557 self.__serverProfilesMenu.aboutToShow.connect(self.__showServerProfilesMenu) |
537 self.__serverProfilesMenu.triggered.connect(self.__serverProfileTriggered) |
558 self.__serverProfilesMenu.triggered.connect(self.__serverProfileTriggered) |
538 |
559 |
539 menu = QMenu(self.tr("eric-ide Server"), self.__ui) |
560 menu = QMenu(self.tr("eric-ide Server"), self.__ui) |
540 menu.setTearOffEnabled(True) |
561 menu.setTearOffEnabled(True) |