src/eric7/Utilities/BackgroundService.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9317
bf8135417cd3
--- a/src/eric7/Utilities/BackgroundService.py	Wed Jul 13 11:16:20 2022 +0200
+++ b/src/eric7/Utilities/BackgroundService.py	Wed Jul 13 14:55:47 2022 +0200
@@ -29,20 +29,21 @@
 class BackgroundService(QTcpServer):
     """
     Class implementing the main part of the background service.
-    
+
     @signal serviceNotAvailable(function, language, filename, message)
         emitted to indicate the non-availability of a service function
         (str, str, str, str)
     @signal batchJobDone(function, language) emitted to indicate the end of
         a batch job (str, str)
     """
+
     serviceNotAvailable = pyqtSignal(str, str, str, str)
     batchJobDone = pyqtSignal(str, str)
-    
+
     def __init__(self, parent=None):
         """
         Constructor
-        
+
         @param parent reference to the parent object
         @type QObject
         """
@@ -56,32 +57,33 @@
         self.services = {}
 
         networkInterface = Preferences.getDebugger("NetworkInterface")
-        if networkInterface == "all" or '.' in networkInterface:
-            self.hostAddress = '127.0.0.1'
+        if networkInterface == "all" or "." in networkInterface:
+            self.hostAddress = "127.0.0.1"
         else:
-            self.hostAddress = '::1'
+            self.hostAddress = "::1"
         self.listen(QHostAddress(self.hostAddress))
 
         self.newConnection.connect(self.on_newConnection)
-        
+
         port = self.serverPort()
         ## Note: Need the port if started external in debugger:
-        print('Background Service listening on: {0:d}'.format(port))
+        print("Background Service listening on: {0:d}".format(port))
         # __IGNORE_WARNING__
         venvName = Preferences.getDebugger("Python3VirtualEnv")
-        interpreter = ericApp().getObject(
-            "VirtualEnvManager").getVirtualenvInterpreter(venvName)
+        interpreter = (
+            ericApp().getObject("VirtualEnvManager").getVirtualenvInterpreter(venvName)
+        )
         if not interpreter:
             interpreter = Globals.getPythonExecutable()
         if interpreter:
             process = self.__startExternalClient(interpreter, port)
             if process:
-                self.processes['Python3'] = process, interpreter
+                self.processes["Python3"] = process, interpreter
 
     def __startExternalClient(self, interpreter, port):
         """
         Private method to start the background client as external process.
-        
+
         @param interpreter path and name of the executable to start
         @type str
         @param port socket port to which the interpreter should connect
@@ -91,12 +93,12 @@
         """
         if interpreter == "" or not Utilities.isinpath(interpreter):
             return None
-        
+
         backgroundClient = os.path.join(
-            os.path.dirname(__file__), "BackgroundClient.py")
+            os.path.dirname(__file__), "BackgroundClient.py"
+        )
         proc = QProcess(self)
-        proc.setProcessChannelMode(
-            QProcess.ProcessChannelMode.ForwardedChannels)
+        proc.setProcessChannelMode(QProcess.ProcessChannelMode.ForwardedChannels)
         args = [
             backgroundClient,
             self.hostAddress,
@@ -108,7 +110,7 @@
         if not proc.waitForStarted(10000):
             proc = None
         return proc
-    
+
     def __processQueue(self):
         """
         Private method to take the next service request and send it to the
@@ -119,11 +121,11 @@
             self.isWorking = lang
             self.runningJob = fx, lang, fn, data
             self.__send(fx, lang, fn, data)
-    
+
     def __send(self, fx, lang, fn, data):
         """
         Private method to send a job request to one of the clients.
-        
+
         @param fx remote function name to execute
         @type str
         @param lang language to connect to
@@ -136,30 +138,32 @@
         self.__cancelled = False
         connection = self.connections.get(lang)
         if connection is None:
-            if fx != 'INIT':
+            if fx != "INIT":
                 # Avoid growing recursion depth which could itself result in an
                 # exception
                 QTimer.singleShot(
                     0,
                     lambda: self.serviceNotAvailable.emit(
-                        fx, lang, fn, self.tr(
-                            '{0} not configured.').format(lang)))
+                        fx, lang, fn, self.tr("{0} not configured.").format(lang)
+                    ),
+                )
             # Reset flag and continue processing queue
             self.isWorking = None
             self.__processQueue()
         else:
             packedData = json.dumps([fx, fn, data])
-            packedData = bytes(packedData, 'utf-8')
+            packedData = bytes(packedData, "utf-8")
             header = struct.pack(
-                b'!II', len(packedData), adler32(packedData) & 0xffffffff)
+                b"!II", len(packedData), adler32(packedData) & 0xFFFFFFFF
+            )
             connection.write(header)
-            connection.write(b'JOB   ')    # 6 character message type
+            connection.write(b"JOB   ")  # 6 character message type
             connection.write(packedData)
 
     def __receive(self, lang):
         """
         Private method to receive the response from the clients.
-        
+
         @param lang language of the incoming connection
         @type str
         @exception RuntimeError raised if hashes don't match
@@ -169,34 +173,34 @@
             if self.__cancelled:
                 connection.readAll()
                 continue
-            
-            header = connection.read(struct.calcsize(b'!II'))
-            length, datahash = struct.unpack(b'!II', header)
-            
-            packedData = b''
+
+            header = connection.read(struct.calcsize(b"!II"))
+            length, datahash = struct.unpack(b"!II", header)
+
+            packedData = b""
             while len(packedData) < length:
                 maxSize = length - len(packedData)
                 if connection.bytesAvailable() < maxSize:
                     connection.waitForReadyRead(50)
                 packedData += connection.read(maxSize)
 
-            if adler32(packedData) & 0xffffffff != datahash:
-                raise RuntimeError('Hashes not equal')
-            packedData = packedData.decode('utf-8')
+            if adler32(packedData) & 0xFFFFFFFF != datahash:
+                raise RuntimeError("Hashes not equal")
+            packedData = packedData.decode("utf-8")
             # "check" if is's a tuple of 3 values
             fx, fn, data = json.loads(packedData)
-            
-            if fx == 'INIT':
+
+            if fx == "INIT":
                 if data != "ok":
                     EricMessageBox.critical(
                         None,
                         self.tr("Initialization of Background Service"),
                         self.tr(
                             "<p>Initialization of Background Service"
-                            " <b>{0}</b> failed.</p><p>Reason: {1}</p>")
-                        .format(fn, data)
+                            " <b>{0}</b> failed.</p><p>Reason: {1}</p>"
+                        ).format(fn, data),
                     )
-            elif fx == 'EXCEPTION':
+            elif fx == "EXCEPTION":
                 # Remove connection because it'll close anyway
                 self.connections.pop(lang, None)
                 # Call sys.excepthook(type, value, traceback) to emulate the
@@ -219,26 +223,31 @@
                         "</ul></p>"
                         "<p>Note: The client can be restarted by opening and"
                         " accepting the preferences dialog or reloading/"
-                        "changing the project.</p>").format(lang),
-                    EricMessageBox.Yes |
-                    EricMessageBox.No |
-                    EricMessageBox.Retry,
-                    EricMessageBox.Yes)
-                
+                        "changing the project.</p>"
+                    ).format(lang),
+                    EricMessageBox.Yes | EricMessageBox.No | EricMessageBox.Retry,
+                    EricMessageBox.Yes,
+                )
+
                 if res == EricMessageBox.Retry:
                     self.enqueueRequest(*self.runningJob)
                 else:
                     fx, lng, fn, data = self.runningJob
                     with contextlib.suppress(KeyError, TypeError):
-                        self.services[(fx, lng)][3](fx, lng, fn, self.tr(
-                            "An error in Eric's background client stopped the"
-                            " service.")
+                        self.services[(fx, lng)][3](
+                            fx,
+                            lng,
+                            fn,
+                            self.tr(
+                                "An error in Eric's background client stopped the"
+                                " service."
+                            ),
                         )
                 if res != EricMessageBox.No:
                     self.isWorking = None
                     self.restartService(lang, True)
                     return
-            elif data == 'Unknown service.':
+            elif data == "Unknown service.":
                 callback = self.services.get((fx, lang))
                 if callback:
                     callback[3](fx, lang, fn, data)
@@ -251,7 +260,7 @@
                             callback[2](fn, *data)
                         elif isinstance(data, str):
                             callback[3](fx, lang, fn, data)
-                    if data == 'Unknown batch service.':
+                    if data == "Unknown batch service.":
                         self.batchJobDone.emit(fx, lang)
                         self.__cancelled = True
                 else:
@@ -260,7 +269,7 @@
                 callback = self.services.get((fx, lang))
                 if callback:
                     callback[2](fn, *data)
-        
+
         self.isWorking = None
         self.__processQueue()
 
@@ -269,21 +278,22 @@
         Public slot to restart the built in languages.
         """
         venvName = Preferences.getDebugger("Python3VirtualEnv")
-        interpreter = ericApp().getObject(
-            "VirtualEnvManager").getVirtualenvInterpreter(venvName)
+        interpreter = (
+            ericApp().getObject("VirtualEnvManager").getVirtualenvInterpreter(venvName)
+        )
         if not interpreter:
             interpreter = Globals.getPythonExecutable()
-        
+
         # Tweak the processes list to reflect the changed interpreter
-        proc, inter = self.processes.pop('Python3', [None, None])
-        self.processes['Python3'] = proc, interpreter
-        
-        self.restartService('Python3')
+        proc, inter = self.processes.pop("Python3", [None, None])
+        self.processes["Python3"] = proc, interpreter
+
+        self.restartService("Python3")
 
     def restartService(self, language, forceKill=False):
         """
         Public method to restart a given language.
-        
+
         @param language to restart
         @type str
         @param forceKill flag to kill a running task
@@ -293,20 +303,20 @@
             proc, interpreter = self.processes.pop(language)
         except KeyError:
             return
-        
+
         # Don't kill a process if it's still working
         if not forceKill:
             while self.isWorking is not None:
                 QThread.msleep(100)
                 QApplication.processEvents()
-        
+
         conn = self.connections.pop(language, None)
         if conn:
             conn.blockSignals(True)
             conn.close()
         if proc:
             proc.close()
-        
+
         if interpreter:
             port = self.serverPort()
             process = self.__startExternalClient(interpreter, port)
@@ -316,10 +326,10 @@
     def enqueueRequest(self, fx, lang, fn, data):
         """
         Public method implementing a queued processing of incoming events.
-        
+
         Duplicate service requests update an older request to avoid overrun or
         starving of the services.
-        
+
         @param fx function name of the service
         @type str
         @param lang language to connect to
@@ -330,7 +340,7 @@
         @type any basic datatype
         """
         args = [fx, lang, fn, data]
-        if fx == 'INIT':
+        if fx == "INIT":
             self.__queue.insert(0, args)
         else:
             for pendingArg in self.__queue:
@@ -342,40 +352,47 @@
             else:
                 self.__queue.append(args)
         self.__processQueue()
-    
+
     def requestCancel(self, fx, lang):
         """
         Public method to ask a batch job to terminate.
-        
+
         @param fx function name of the service
         @type str
         @param lang language to connect to
         @type str
         """
         self.__cancelled = True
-        
+
         entriesToRemove = []
         for pendingArg in self.__queue:
             if pendingArg[:2] == [fx, lang]:
                 entriesToRemove.append(pendingArg)
         for entryToRemove in entriesToRemove:
             self.__queue.remove(entryToRemove)
-        
+
         connection = self.connections.get(lang)
         if connection is None:
             return
         else:
-            header = struct.pack(b'!II', 0, 0)
+            header = struct.pack(b"!II", 0, 0)
             connection.write(header)
-            connection.write(b'CANCEL')    # 6 character message type
-    
+            connection.write(b"CANCEL")  # 6 character message type
+
     def serviceConnect(
-            self, fx, lang, modulepath, module, callback,
-            onErrorCallback=None, onBatchDone=None):
+        self,
+        fx,
+        lang,
+        modulepath,
+        module,
+        callback,
+        onErrorCallback=None,
+        onBatchDone=None,
+    ):
         """
         Public method to announce a new service to the background
         service/client.
-        
+
         @param fx function name of the service
         @type str
         @param lang language of the new service
@@ -391,19 +408,17 @@
         @param onBatchDone function called when a batch job is done
         @type function
         """
-        self.services[(fx, lang)] = (
-            modulepath, module, callback, onErrorCallback
-        )
-        self.enqueueRequest('INIT', lang, fx, [modulepath, module])
+        self.services[(fx, lang)] = (modulepath, module, callback, onErrorCallback)
+        self.enqueueRequest("INIT", lang, fx, [modulepath, module])
         if onErrorCallback:
             self.serviceNotAvailable.connect(onErrorCallback)
         if onBatchDone:
             self.batchJobDone.connect(onBatchDone)
-    
+
     def serviceDisconnect(self, fx, lang):
         """
         Public method to remove the service from the service list.
-        
+
         @param fx function name of the service
         @type function
         @param lang language of the service
@@ -421,23 +436,21 @@
         if not connection.waitForReadyRead(1000):
             return
         lang = connection.read(64)
-        lang = lang.decode('utf-8')
+        lang = lang.decode("utf-8")
         # Avoid hanging of eric on shutdown
         if self.connections.get(lang):
             self.connections[lang].close()
         if self.isWorking == lang:
             self.isWorking = None
         self.connections[lang] = connection
-        connection.readyRead.connect(
-            lambda: self.__receive(lang))
-        connection.disconnected.connect(
-            lambda: self.on_disconnectSocket(lang))
-            
+        connection.readyRead.connect(lambda: self.__receive(lang))
+        connection.disconnected.connect(lambda: self.on_disconnectSocket(lang))
+
         for (fx, lng), args in self.services.items():
             if lng == lang:
                 # Register service with modulepath and module
-                self.enqueueRequest('INIT', lng, fx, args[:2])
-        
+                self.enqueueRequest("INIT", lng, fx, args[:2])
+
         # Syntax check the open editors again
         try:
             vm = ericApp().getObject("ViewManager")
@@ -450,7 +463,7 @@
     def on_disconnectSocket(self, lang):
         """
         Private slot called when connection to a client is lost.
-        
+
         @param lang client language which connection is lost
         @type str
         """
@@ -458,21 +471,27 @@
         if conn:
             conn.close()
             fx, lng, fn, data = self.runningJob
-            if fx != 'INIT' and lng == lang:
-                self.services[(fx, lng)][3](fx, lng, fn, self.tr(
-                    "Eric's background client disconnected because of an"
-                    " unknown reason.")
+            if fx != "INIT" and lng == lang:
+                self.services[(fx, lng)][3](
+                    fx,
+                    lng,
+                    fn,
+                    self.tr(
+                        "Eric's background client disconnected because of an"
+                        " unknown reason."
+                    ),
                 )
             self.isWorking = None
-            
+
             res = EricMessageBox.yesNo(
                 None,
-                self.tr('Background client disconnected.'),
+                self.tr("Background client disconnected."),
                 self.tr(
-                    'The background client for <b>{0}</b> disconnected because'
-                    ' of an unknown reason.<br>Should it be restarted?'
+                    "The background client for <b>{0}</b> disconnected because"
+                    " of an unknown reason.<br>Should it be restarted?"
                 ).format(lang),
-                yesDefault=True)
+                yesDefault=True,
+            )
             if res:
                 self.restartService(lang)
 
@@ -482,13 +501,13 @@
         shutting down.
         """
         self.close()
-        
+
         for connection in self.connections.values():
             connection.readyRead.disconnect()
             connection.disconnected.disconnect()
             connection.close()
             connection.deleteLater()
-        
+
         for process, _interpreter in self.processes.values():
             process.close()
             if not process.waitForFinished(10000):

eric ide

mercurial