src/eric7/RemoteServerInterface/EricServerInterface.py

branch
server
changeset 10561
be23a662d709
parent 10555
08e853c0c77b
child 10565
3583a10ce4d4
equal deleted inserted replaced
10560:28b14d2df6a1 10561:be23a662d709
5 5
6 """ 6 """
7 Module implementing the interface to the eric remote server. 7 Module implementing the interface to the eric remote server.
8 """ 8 """
9 9
10 import collections
10 import json 11 import json
11 import struct 12 import struct
12 import uuid 13 import uuid
13 import zlib 14 import zlib
14 15
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)
366 @type str 387 @type str
367 @param params dictionary containing the reply data 388 @param params dictionary containing the reply data
368 @type dict 389 @type dict
369 @exception ValueError raised in case of an unsupported reply 390 @exception ValueError raised in case of an unsupported reply
370 """ 391 """
371 if reply == "Versions": 392 if reply != "Versions":
393 raise ValueError(f"unsupported reply received ({reply})")
394
395 else:
372 versionText = self.tr("""<h2>Server Version Numbers</h2><table>""") 396 versionText = self.tr("""<h2>Server Version Numbers</h2><table>""")
373 397
374 # Python version 398 # Python version
375 versionText += ( 399 versionText += (
376 """<tr><td><b>Python</b></td><td>{0}, {1}</td></tr>""" 400 """<tr><td><b>Python</b></td><td>{0}, {1}</td></tr>"""
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)
552 self.__menus = { 573 self.__menus = {
553 "Main": menu, 574 "Main": menu,
554 ##"Recent": self.recentMenu, 575 ##"Recent": self.recentMenu,
555 } 576 }
556 577
557
558 return menu 578 return menu
559 579
560 def initToolbar(self, toolbarManager): 580 def initToolbar(self, toolbarManager):
561 """ 581 """
562 Public slot to initialize the eric-ide server toolbar. 582 Public slot to initialize the eric-ide server toolbar.

eric ide

mercurial