1 # -*- coding: utf-8 -*- |
1 # -*- coding: utf-8 -*- |
2 |
2 |
3 # Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
3 # Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
4 # |
4 # |
5 # pylint: disable=C0103 |
|
6 |
5 |
7 """ |
6 """ |
8 Module implementing a background service for the various checkers and other |
7 Module implementing a background service for the various checkers and other |
9 python interpreter dependent functions. |
8 Python interpreter dependent functions. |
10 """ |
9 """ |
11 |
10 |
12 import json |
11 import json |
13 import os |
12 import os |
14 import struct |
13 import struct |
40 serviceNotAvailable = pyqtSignal(str, str, str, str) |
39 serviceNotAvailable = pyqtSignal(str, str, str, str) |
41 batchJobDone = pyqtSignal(str, str) |
40 batchJobDone = pyqtSignal(str, str) |
42 |
41 |
43 def __init__(self): |
42 def __init__(self): |
44 """ |
43 """ |
45 Constructor of the BackgroundService class. |
44 Constructor |
46 """ |
45 """ |
47 self.processes = {} |
46 self.processes = {} |
48 self.connections = {} |
47 self.connections = {} |
49 self.isWorking = None |
48 self.isWorking = None |
50 self.runningJob = [None, None, None, None] |
49 self.runningJob = [None, None, None, None] |
62 |
61 |
63 self.newConnection.connect(self.on_newConnection) |
62 self.newConnection.connect(self.on_newConnection) |
64 |
63 |
65 port = self.serverPort() |
64 port = self.serverPort() |
66 ## Note: Need the port if started external in debugger: |
65 ## Note: Need the port if started external in debugger: |
67 print('BackgroundService listening on: {0:d}'.format(port)) |
66 print('Background Service listening on: {0:d}'.format(port)) |
68 # __IGNORE_WARNING__ |
67 # __IGNORE_WARNING__ |
69 venvName = Preferences.getDebugger("Python3VirtualEnv") |
68 venvName = Preferences.getDebugger("Python3VirtualEnv") |
70 interpreter = e5App().getObject( |
69 interpreter = e5App().getObject( |
71 "VirtualEnvManager").getVirtualenvInterpreter(venvName) |
70 "VirtualEnvManager").getVirtualenvInterpreter(venvName) |
72 if not interpreter: |
71 if not interpreter: |
78 |
77 |
79 def __startExternalClient(self, interpreter, port): |
78 def __startExternalClient(self, interpreter, port): |
80 """ |
79 """ |
81 Private method to start the background client as external process. |
80 Private method to start the background client as external process. |
82 |
81 |
83 @param interpreter path and name of the executable to start (string) |
82 @param interpreter path and name of the executable to start |
84 @param port socket port to which the interpreter should connect (int) |
83 @type str |
85 @return the process object (QProcess or None) |
84 @param port socket port to which the interpreter should connect |
|
85 @type int |
|
86 @return the process object |
|
87 @rtype QProcess or None |
86 """ |
88 """ |
87 if interpreter == "" or not Utilities.isinpath(interpreter): |
89 if interpreter == "" or not Utilities.isinpath(interpreter): |
88 return None |
90 return None |
89 |
91 |
90 backgroundClient = os.path.join( |
92 backgroundClient = os.path.join( |
112 |
114 |
113 def __send(self, fx, lang, fn, data): |
115 def __send(self, fx, lang, fn, data): |
114 """ |
116 """ |
115 Private method to send a job request to one of the clients. |
117 Private method to send a job request to one of the clients. |
116 |
118 |
117 @param fx remote function name to execute (str) |
119 @param fx remote function name to execute |
118 @param lang language to connect to (str) |
120 @type str |
119 @param fn filename for identification (str) |
121 @param lang language to connect to |
120 @param data function argument(s) (any basic datatype) |
122 @type str |
|
123 @param fn filename for identification |
|
124 @type str |
|
125 @param data function argument(s) |
|
126 @type any basic datatype |
121 """ |
127 """ |
122 self.__cancelled = False |
128 self.__cancelled = False |
123 connection = self.connections.get(lang) |
129 connection = self.connections.get(lang) |
124 if connection is None: |
130 if connection is None: |
125 if fx != 'INIT': |
131 if fx != 'INIT': |
144 |
150 |
145 def __receive(self, lang): |
151 def __receive(self, lang): |
146 """ |
152 """ |
147 Private method to receive the response from the clients. |
153 Private method to receive the response from the clients. |
148 |
154 |
149 @param lang language of the incomming connection (str) |
155 @param lang language of the incoming connection |
|
156 @type str |
150 @exception RuntimeError raised if hashes don't match |
157 @exception RuntimeError raised if hashes don't match |
151 """ |
158 """ |
152 connection = self.connections[lang] |
159 connection = self.connections[lang] |
153 while connection.bytesAvailable(): |
160 while connection.bytesAvailable(): |
154 if self.__cancelled: |
161 if self.__cancelled: |
204 self.enqueueRequest(*self.runningJob) |
211 self.enqueueRequest(*self.runningJob) |
205 else: |
212 else: |
206 fx, lng, fn, data = self.runningJob |
213 fx, lng, fn, data = self.runningJob |
207 try: |
214 try: |
208 self.services[(fx, lng)][3](fx, lng, fn, self.tr( |
215 self.services[(fx, lng)][3](fx, lng, fn, self.tr( |
209 'An error in Erics background client stopped the' |
216 "An error in Eric's background client stopped the" |
210 ' service.') |
217 " service.") |
211 ) |
218 ) |
212 except (KeyError, TypeError): |
219 except (KeyError, TypeError): |
213 # ignore silently |
220 # ignore silently |
214 pass |
221 pass |
215 if res != E5MessageBox.No: |
222 if res != E5MessageBox.No: |
256 |
263 |
257 self.restartService('Python3') |
264 self.restartService('Python3') |
258 |
265 |
259 def restartService(self, language, forceKill=False): |
266 def restartService(self, language, forceKill=False): |
260 """ |
267 """ |
261 Public method to restart a given lanuage. |
268 Public method to restart a given language. |
262 |
269 |
263 @param language to restart (str) |
270 @param language to restart |
264 @keyparam forceKill flag to kill a running task (bool) |
271 @type str |
|
272 @param forceKill flag to kill a running task |
|
273 @type bool |
265 """ |
274 """ |
266 try: |
275 try: |
267 proc, interpreter = self.processes.pop(language) |
276 proc, interpreter = self.processes.pop(language) |
268 except KeyError: |
277 except KeyError: |
269 return |
278 return |
286 if process: |
295 if process: |
287 self.processes[language] = process, interpreter |
296 self.processes[language] = process, interpreter |
288 |
297 |
289 def enqueueRequest(self, fx, lang, fn, data): |
298 def enqueueRequest(self, fx, lang, fn, data): |
290 """ |
299 """ |
291 Public method implementing a queued processing of incomming events. |
300 Public method implementing a queued processing of incoming events. |
292 |
301 |
293 Dublicate service requests updates an older request to avoid overrun or |
302 Duplicate service requests update an older request to avoid overrun or |
294 starving of the services. |
303 starving of the services. |
295 @param fx function name of the service (str) |
304 |
296 @param lang language to connect to (str) |
305 @param fx function name of the service |
297 @param fn filename for identification (str) |
306 @type str |
298 @param data function argument(s) (any basic datatype(s)) |
307 @param lang language to connect to |
|
308 @type str |
|
309 @param fn filename for identification |
|
310 @type str |
|
311 @param data function argument(s) |
|
312 @type any basic datatype |
299 """ |
313 """ |
300 args = [fx, lang, fn, data] |
314 args = [fx, lang, fn, data] |
301 if fx == 'INIT': |
315 if fx == 'INIT': |
302 self.__queue.insert(0, args) |
316 self.__queue.insert(0, args) |
303 else: |
317 else: |
313 |
327 |
314 def requestCancel(self, fx, lang): |
328 def requestCancel(self, fx, lang): |
315 """ |
329 """ |
316 Public method to ask a batch job to terminate. |
330 Public method to ask a batch job to terminate. |
317 |
331 |
318 @param fx function name of the service (str) |
332 @param fx function name of the service |
319 @param lang language to connect to (str) |
333 @type str |
|
334 @param lang language to connect to |
|
335 @type str |
320 """ |
336 """ |
321 self.__cancelled = True |
337 self.__cancelled = True |
322 |
338 |
323 entriesToRemove = [] |
339 entriesToRemove = [] |
324 for pendingArg in self.__queue: |
340 for pendingArg in self.__queue: |
340 onErrorCallback=None, onBatchDone=None): |
356 onErrorCallback=None, onBatchDone=None): |
341 """ |
357 """ |
342 Public method to announce a new service to the background |
358 Public method to announce a new service to the background |
343 service/client. |
359 service/client. |
344 |
360 |
345 @param fx function name of the service (str) |
361 @param fx function name of the service |
346 @param lang language of the new service (str) |
362 @type str |
347 @param modulepath full path to the module (str) |
363 @param lang language of the new service |
348 @param module name to import (str) |
364 @type str |
349 @param callback function called on service response (function) |
365 @param modulepath full path to the module |
|
366 @type str |
|
367 @param module name to import |
|
368 @type str |
|
369 @param callback function called on service response |
|
370 @type function |
350 @param onErrorCallback function called, if client isn't available |
371 @param onErrorCallback function called, if client isn't available |
351 (function) |
372 (function) |
352 @param onBatchDone function called when a batch job is done (function) |
373 @param onBatchDone function called when a batch job is done |
|
374 @type function |
353 """ |
375 """ |
354 self.services[(fx, lang)] = ( |
376 self.services[(fx, lang)] = ( |
355 modulepath, module, callback, onErrorCallback |
377 modulepath, module, callback, onErrorCallback |
356 ) |
378 ) |
357 self.enqueueRequest('INIT', lang, fx, [modulepath, module]) |
379 self.enqueueRequest('INIT', lang, fx, [modulepath, module]) |
362 |
384 |
363 def serviceDisconnect(self, fx, lang): |
385 def serviceDisconnect(self, fx, lang): |
364 """ |
386 """ |
365 Public method to remove the service from the service list. |
387 Public method to remove the service from the service list. |
366 |
388 |
367 @param fx function name of the service (function) |
389 @param fx function name of the service |
368 @param lang language of the service (str) |
390 @type function |
|
391 @param lang language of the service |
|
392 @type str |
369 """ |
393 """ |
370 serviceArgs = self.services.pop((fx, lang), None) |
394 serviceArgs = self.services.pop((fx, lang), None) |
371 if serviceArgs and serviceArgs[3]: |
395 if serviceArgs and serviceArgs[3]: |
372 self.serviceNotAvailable.disconnect(serviceArgs[3]) |
396 self.serviceNotAvailable.disconnect(serviceArgs[3]) |
373 |
397 |
374 def on_newConnection(self): |
398 def on_newConnection(self): |
375 """ |
399 """ |
376 Private slot for new incomming connections from the clients. |
400 Private slot for new incoming connections from the clients. |
377 """ |
401 """ |
378 connection = self.nextPendingConnection() |
402 connection = self.nextPendingConnection() |
379 if not connection.waitForReadyRead(1000): |
403 if not connection.waitForReadyRead(1000): |
380 return |
404 return |
381 lang = connection.read(64) |
405 lang = connection.read(64) |
407 |
431 |
408 def on_disconnectSocket(self, lang): |
432 def on_disconnectSocket(self, lang): |
409 """ |
433 """ |
410 Private slot called when connection to a client is lost. |
434 Private slot called when connection to a client is lost. |
411 |
435 |
412 @param lang client language which connection is lost (str) |
436 @param lang client language which connection is lost |
|
437 @type str |
413 """ |
438 """ |
414 conn = self.connections.pop(lang, None) |
439 conn = self.connections.pop(lang, None) |
415 if conn: |
440 if conn: |
416 conn.close() |
441 conn.close() |
417 fx, lng, fn, data = self.runningJob |
442 fx, lng, fn, data = self.runningJob |