Utilities/BackgroundService.py

branch
BgService
changeset 3159
02cb2adb4868
child 3172
c0f78e9d0971
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utilities/BackgroundService.py	Tue Dec 31 18:03:31 2013 +0100
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a background service for the various checkers and other
+python interpreter dependent functions.
+"""
+
+from __future__ import unicode_literals
+
+import json
+import os
+import struct
+import sys
+import threading
+from zlib import adler32
+
+from PyQt4.QtCore import QProcess, pyqtSignal
+from PyQt4.QtGui import QApplication
+from PyQt4.QtNetwork import QTcpServer, QHostAddress
+
+from E5Gui.E5Application import e5App
+
+import Preferences
+import Utilities
+from Utilities.BackgroundClient import BackgroundClient
+
+from eric5config import getConfig
+
+
+class BackgroundService(QTcpServer):
+    """
+    Class implementing the main part of the background service.
+    """
+    syntaxChecked = pyqtSignal(str, bool, str, int, int, str, str, list)
+    #styleChecked = pyqtSignal(TBD)
+    #indentChecked = pyqtSignal(TBD)
+    
+    def __init__(self):
+        """
+        Constructor of the BackgroundService class.
+        """
+        self.processes = [None, None]
+        self.connections = [None, None]
+
+        super(BackgroundService, self).__init__()
+
+        networkInterface = Preferences.getDebugger("NetworkInterface")
+        if networkInterface == "all" or '.' in networkInterface:
+            self.hostAddress = '127.0.0.1'
+        else:
+            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('BackgroundService listening on: %i' % port)
+        for pyIdx, pyName in enumerate(['Python', 'Python3']):
+            interpreter = Preferences.getDebugger(
+                pyName + "Interpreter")
+            
+            if Utilities.samefilepath(interpreter, sys.executable):
+                process = self.__startInternalClient(port)
+            else:
+                process = self.__startExternalClient(interpreter, port)
+            self.processes[pyIdx] = process
+
+    def on_newConnection(self):
+        """
+        Slot for new incomming connections from the clients.
+        """
+        connection = self.nextPendingConnection()
+        if not connection.waitForReadyRead(1000):
+            return
+        ch = 0 if connection.read(1) == b'2' else 1
+        self.connections[ch] = connection
+        connection.readyRead.connect(
+            lambda x=ch: self.__receive(x))
+
+    def shutdown(self):
+        """
+        Cleanup the connections and processes when Eric is shuting down.
+        """
+        for connection in self.connections:
+            if connection:
+                connection.close()
+        
+        for process in self.processes:
+            if isinstance(process, QProcess):
+                process.close()
+                process = None
+            elif isinstance(process, threading.Thread):
+                process.join(0.1)
+                process = None
+
+    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 (string)
+        @param port socket port to which the interpreter should connect (int)
+        @return the process object (QProcess) or None
+        """
+        if interpreter == "" or not Utilities.isinpath(interpreter):
+            return None
+        
+        backgroundClient = os.path.join(
+            getConfig('ericDir'),
+            "Utilities", "BackgroundClient.py")
+        proc = QProcess()
+        args = [backgroundClient, self.hostAddress, str(port)]
+        proc.start(interpreter, args)
+        if not proc.waitForStarted(10000):
+            proc = None
+        return proc
+
+    def __startInternalClient(self, port):
+        """
+        Private method to start the background client as internal thread.
+        
+        @param port socket port to which the interpreter should connect (int)
+        @return the thread object (Thread) or None
+        """
+        self.backgroundClient = BackgroundClient(
+            self.hostAddress, port)
+        thread = threading.Thread(target=self.backgroundClient.run)
+        thread.start()
+        return thread
+
+    # TODO: Implement a queued processing of incomming events. Dublicate file
+    # checks should update an older request to avoid overrun or starving of
+    # the check.
+    def __send(self, fx, fn, data, isPy3):
+        """
+        Private method to send a job request to one of the clients.
+        
+        @param fx remote function name to execute (str)
+        @param fn filename for identification (str)
+        @param data function argument(s) (any basic datatype)
+        @param isPy3 flag for the required interpreter (boolean)
+        """
+        packedData = json.dumps([fx, fn, data])
+        if sys.version_info[0] == 3:
+            packedData = bytes(packedData, 'utf-8')
+        connection = self.connections[int(isPy3)]
+        if connection is None:
+            self.__postResult(
+                fx, fn, [
+                    True, fn, 0, 0, '',
+                    'No connection to Python{0} interpreter. '
+                    'Check your debugger settings.'.format(int(isPy3) + 2)])
+        else:
+            header = struct.pack(
+                b'!II', len(packedData), adler32(packedData) & 0xffffffff)
+            connection.write(header)
+            connection.write(packedData)
+
+    def __receive(self, channel):
+        """
+        Private method to receive the response from the clients.
+        
+        @param channel of the incomming connection (int: 0 or 1)
+        """
+        connection = self.connections[channel]
+        header = connection.read(8)
+        length, datahash = struct.unpack(b'!II', header)
+        
+        packedData = b''
+        while len(packedData) < length:
+            connection.waitForReadyRead(50)
+            packedData += connection.read(length - len(packedData))
+
+        assert adler32(packedData) & 0xffffffff == datahash, 'Hashes not equal'
+        if sys.version_info[0] == 3:
+            packedData = packedData.decode('utf-8')
+        # "check" if is's a tuple of 3 values
+        try:
+            fx, fn, data = json.loads(packedData)
+            self.__postResult(fx, fn, data)
+        except:
+            pass
+
+    def __postResult(self, fx, fn, data):
+        """
+        Private method to emit the correspondig signal for the returned
+        function.
+        
+        @param fx remote function name to execute (str)
+        @param fn filename for identification (str)
+        @param data function argument(s) (any basic datatype)
+        """
+        if fx == 'syntax':
+            self.syntaxChecked.emit(fn, *data)
+        elif fx == 'style':
+            pass
+        elif fx == 'indent':
+            pass
+        elif fx == 'exception':
+            # Call sys.excepthook(type, value, traceback) to emulate the
+            # exception which was caught on the client
+            sys.excepthook(*data)
+        
+        #QApplication.translate(packedData)
+        
+    # ggf. nach Utilities verschieben
+    def determinePythonVersion(self, filename, source):
+        """
+        Determine the python version of a given file.
+        
+        @param filename name of the file with extension (str)
+        @param source of the file (str)
+        @return flag if file is Python2 or Python3 (boolean)
+        """
+        flags = Utilities.extractFlags(source)
+        ext = os.path.splitext(filename)[1]
+        project = e5App().getObject('Project')
+        if "FileType" in flags:
+            isPy3 = flags["FileType"] not in ["Python", "Python2"]
+        elif (Preferences.getProject("DeterminePyFromProject") and
+              project.isOpen() and
+              project.isProjectFile(filename)):
+                    isPy3 = project.getProjectLanguage() == "Python3"
+        else:
+            isPy3 = ext in Preferences.getPython("PythonExtensions")
+        return isPy3
+
+    def syntaxCheck(self, filename, source="", checkFlakes=True,
+                    ignoreStarImportWarnings=False, isPy3=None):
+        """
+        Function to compile one Python source file to Python bytecode
+        and to perform a pyflakes check.
+        
+        @param filename source filename (string)
+        @keyparam source string containing the code to check (string)
+        @keyparam checkFlakes flag indicating to do a pyflakes check (boolean)
+        @keyparam ignoreStarImportWarnings flag indicating to
+            ignore 'star import' warnings (boolean)
+        @keyparam isPy3 flag sets the interpreter to use or None for autodetect
+            corresponding interpreter (boolean or None)
+        """
+        if isPy3 is None:
+            isPy3 = self.determinePythonVersion(filename, source)
+        
+        data = [source, checkFlakes, ignoreStarImportWarnings]
+        self.__send('syntax', filename, data, isPy3)

eric ide

mercurial