Utilities/BackgroundService.py

branch
BgService
changeset 3173
1fb284abe46e
parent 3172
c0f78e9d0971
child 3209
c5432abceb25
equal deleted inserted replaced
3172:c0f78e9d0971 3173:1fb284abe46e
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> 3 # Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
4 # 4 #
5 # pylint: disable=C0103
5 6
6 """ 7 """
7 Module implementing a background service for the various checkers and other 8 Module implementing a background service for the various checkers and other
8 python interpreter dependent functions. 9 python interpreter dependent functions.
9 """ 10 """
16 import sys 17 import sys
17 import threading 18 import threading
18 from zlib import adler32 19 from zlib import adler32
19 20
20 from PyQt4.QtCore import QProcess, pyqtSignal 21 from PyQt4.QtCore import QProcess, pyqtSignal
21 from PyQt4.QtGui import QApplication
22 from PyQt4.QtNetwork import QTcpServer, QHostAddress 22 from PyQt4.QtNetwork import QTcpServer, QHostAddress
23
24 from E5Gui.E5Application import e5App
25 23
26 import Preferences 24 import Preferences
27 import Utilities 25 import Utilities
28 from Utilities.BackgroundClient import BackgroundClient 26 from Utilities.BackgroundClient import BackgroundClient
29 27
32 30
33 class BackgroundService(QTcpServer): 31 class BackgroundService(QTcpServer):
34 """ 32 """
35 Class implementing the main part of the background service. 33 Class implementing the main part of the background service.
36 """ 34 """
37 syntaxChecked = pyqtSignal(str, bool, str, int, int, str, str, list) 35 serviceNotAvailable = pyqtSignal(str, str, int, str)
38 #styleChecked = pyqtSignal(TBD)
39 #indentChecked = pyqtSignal(TBD)
40 36
41 def __init__(self): 37 def __init__(self):
42 """ 38 """
43 Constructor of the BackgroundService class. 39 Constructor of the BackgroundService class.
44 """ 40 """
45 self.processes = [None, None] 41 self.processes = [None, None]
46 self.connections = [None, None] 42 self.connections = [None, None]
43 self.isWorking = False
44 self.__queue = []
45 self.services = {}
47 46
48 super(BackgroundService, self).__init__() 47 super(BackgroundService, self).__init__()
49 48
50 networkInterface = Preferences.getDebugger("NetworkInterface") 49 networkInterface = Preferences.getDebugger("NetworkInterface")
51 if networkInterface == "all" or '.' in networkInterface: 50 if networkInterface == "all" or '.' in networkInterface:
71 process = self.__startInternalClient(port) 70 process = self.__startInternalClient(port)
72 else: 71 else:
73 process = self.__startExternalClient(interpreter, port) 72 process = self.__startExternalClient(interpreter, port)
74 self.processes[pyIdx] = process 73 self.processes[pyIdx] = process
75 74
76 def on_newConnection(self):
77 """
78 Slot for new incomming connections from the clients.
79 """
80 connection = self.nextPendingConnection()
81 if not connection.waitForReadyRead(1000):
82 return
83 ch = 0 if connection.read(1) == b'2' else 1
84 self.connections[ch] = connection
85 connection.readyRead.connect(
86 lambda x=ch: self.__receive(x))
87
88 def shutdown(self):
89 """
90 Cleanup the connections and processes when Eric is shuting down.
91 """
92 for connection in self.connections:
93 if connection:
94 connection.close()
95
96 for process in self.processes:
97 if isinstance(process, QProcess):
98 process.close()
99 process = None
100 elif isinstance(process, threading.Thread):
101 process.join(0.1)
102 process = None
103
104 def __startExternalClient(self, interpreter, port): 75 def __startExternalClient(self, interpreter, port):
105 """ 76 """
106 Private method to start the background client as external process. 77 Private method to start the background client as external process.
107 78
108 @param interpreter path and name of the executable to start (string) 79 @param interpreter path and name of the executable to start (string)
127 Private method to start the background client as internal thread. 98 Private method to start the background client as internal thread.
128 99
129 @param port socket port to which the interpreter should connect (int) 100 @param port socket port to which the interpreter should connect (int)
130 @return the thread object (Thread) or None 101 @return the thread object (Thread) or None
131 """ 102 """
132 self.backgroundClient = BackgroundClient( 103 backgroundClient = BackgroundClient(
133 self.hostAddress, port) 104 self.hostAddress, port)
134 thread = threading.Thread(target=self.backgroundClient.run) 105 thread = threading.Thread(target=backgroundClient.run)
135 thread.start() 106 thread.start()
136 return thread 107 return thread
137 108
138 # TODO: Implement a queued processing of incomming events. Dublicate file 109 def __processQueue(self):
139 # checks should update an older request to avoid overrun or starving of 110 """
140 # the check. 111 Private method to take the next service request and send it to the
141 def __send(self, fx, fn, data, isPy3): 112 client.
113 """
114 if self.__queue and self.isWorking is False:
115 self.isWorking = True
116 fx, fn, pyVer, data = self.__queue.pop(0)
117 self.__send(fx, fn, pyVer, data)
118
119 def __send(self, fx, fn, pyVer, data):
142 """ 120 """
143 Private method to send a job request to one of the clients. 121 Private method to send a job request to one of the clients.
144 122
145 @param fx remote function name to execute (str) 123 @param fx remote function name to execute (str)
146 @param fn filename for identification (str) 124 @param fn filename for identification (str)
125 @param pyVer version for the required interpreter (int)
147 @param data function argument(s) (any basic datatype) 126 @param data function argument(s) (any basic datatype)
148 @param isPy3 flag for the required interpreter (boolean)
149 """ 127 """
150 packedData = json.dumps([fx, fn, data]) 128 packedData = json.dumps([fx, fn, data])
151 if sys.version_info[0] == 3: 129 if sys.version_info[0] == 3:
152 packedData = bytes(packedData, 'utf-8') 130 packedData = bytes(packedData, 'utf-8')
153 connection = self.connections[int(isPy3)] 131 connection = self.connections[pyVer - 2]
154 if connection is None: 132 if connection is None:
155 self.__postResult( 133 if fx != 'INIT':
156 fx, fn, [ 134 self.serviceNotAvailable.emit(
157 True, fn, 0, 0, '', 135 fx, fn, pyVer, self.trUtf8(
158 'No connection to Python{0} interpreter. ' 136 'Python{0} interpreter not configured.').format(pyVer))
159 'Check your debugger settings.'.format(int(isPy3) + 2), 137 # Reset flag and continue processing queue
160 []]) 138 self.isWorking = False
139 self.__processQueue()
161 else: 140 else:
162 header = struct.pack( 141 header = struct.pack(
163 b'!II', len(packedData), adler32(packedData) & 0xffffffff) 142 b'!II', len(packedData), adler32(packedData) & 0xffffffff)
164 connection.write(header) 143 connection.write(header)
165 connection.write(packedData) 144 connection.write(packedData)
181 160
182 assert adler32(packedData) & 0xffffffff == datahash, 'Hashes not equal' 161 assert adler32(packedData) & 0xffffffff == datahash, 'Hashes not equal'
183 if sys.version_info[0] == 3: 162 if sys.version_info[0] == 3:
184 packedData = packedData.decode('utf-8') 163 packedData = packedData.decode('utf-8')
185 # "check" if is's a tuple of 3 values 164 # "check" if is's a tuple of 3 values
186 try: 165 fx, fn, data = json.loads(packedData)
187 fx, fn, data = json.loads(packedData) 166 self.__postResult(fx, fn, data)
188 self.__postResult(fx, fn, data) 167
189 except:
190 pass
191
192 def __postResult(self, fx, fn, data): 168 def __postResult(self, fx, fn, data):
193 """ 169 """
194 Private method to emit the correspondig signal for the returned 170 Private method to emit the correspondig signal for the returned
195 function. 171 function.
196 172
197 @param fx remote function name to execute (str) 173 @param fx remote function name to execute (str)
198 @param fn filename for identification (str) 174 @param fn filename for identification (str)
199 @param data function argument(s) (any basic datatype) 175 @param data function argument(s) (any basic datatype)
200 """ 176 """
201 if fx == 'syntax': 177 if fx == 'INIT':
202 self.syntaxChecked.emit(fn, *data)
203 elif fx == 'style':
204 pass
205 elif fx == 'indent':
206 pass 178 pass
207 elif fx == 'exception': 179 elif fx == 'exception':
208 # Call sys.excepthook(type, value, traceback) to emulate the 180 # Call sys.excepthook(type, value, traceback) to emulate the
209 # exception which was caught on the client 181 # exception which was caught on the client
210 sys.excepthook(*data) 182 #sys.excepthook(*data)
211 183 print(data)
212 #QApplication.translate(packedData) 184 else:
213 185 callback = self.services.get(fx)
214 # ggf. nach Utilities verschieben 186 if callback:
215 def determinePythonVersion(self, filename, source): 187 callback[2](fn, *data)
216 """ 188
217 Determine the python version of a given file. 189 self.isWorking = False
218 190 self.__processQueue()
219 @param filename name of the file with extension (str) 191
220 @param source of the file (str) 192 def enqueueRequest(self, fx, fn, pyVer, data):
221 @return flag if file is Python2 or Python3 (boolean) 193 """
222 """ 194 Implement a queued processing of incomming events.
223 flags = Utilities.extractFlags(source) 195
224 ext = os.path.splitext(filename)[1] 196 Dublicate file checks update an older request to avoid overrun or
225 project = e5App().getObject('Project') 197 starving of the check.
226 if "FileType" in flags: 198 @param fx function name of the service (str)
227 isPy3 = flags["FileType"] not in ["Python", "Python2"] 199 @param fn filename for identification (str)
228 elif (Preferences.getProject("DeterminePyFromProject") and 200 @param pyVer version for the required interpreter (int)
229 project.isOpen() and 201 @param data function argument(s) (any basic datatype)
230 project.isProjectFile(filename)): 202 """
231 isPy3 = project.getProjectLanguage() == "Python3" 203 args = [fx, fn, pyVer, data]
232 else: 204 if fx == 'INIT':
233 isPy3 = ext in Preferences.getPython("PythonExtensions") 205 self.__queue.insert(0, args)
234 return isPy3 206 else:
235 207 for pendingArg in self.__queue:
236 def syntaxCheck(self, filename, source="", checkFlakes=True, 208 if pendingArg[:3] == args[:3]:
237 ignoreStarImportWarnings=False, isPy3=None): 209 pendingArg[3] = args[3]
238 """ 210 break
239 Function to compile one Python source file to Python bytecode 211 else:
240 and to perform a pyflakes check. 212 self.__queue.append(args)
241 213 self.__processQueue()
242 @param filename source filename (string) 214
243 @keyparam source string containing the code to check (string) 215 def serviceConnect(
244 @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) 216 self, fx, modulepath, module, callback, onErrorCallback=None):
245 @keyparam ignoreStarImportWarnings flag indicating to 217 """
246 ignore 'star import' warnings (boolean) 218 Announce a new service to the background service/ client.
247 @keyparam isPy3 flag sets the interpreter to use or None for autodetect 219
248 corresponding interpreter (boolean or None) 220 @param fx function name of the service (str)
249 """ 221 @param modulepath full path to the module (str)
250 if isPy3 is None: 222 @param module name to import (str)
251 isPy3 = self.determinePythonVersion(filename, source) 223 @param callback function on service response (function)
252 224 @param onErrorCallback function if client isn't available (function)
253 data = [source, checkFlakes, ignoreStarImportWarnings] 225 """
254 self.__send('syntax', filename, data, isPy3) 226 self.services[fx] = modulepath, module, callback, onErrorCallback
227 self.enqueueRequest('INIT', fx, 0, [modulepath, module])
228 self.enqueueRequest('INIT', fx, 1, [modulepath, module])
229 if onErrorCallback:
230 self.serviceNotAvailable.connect(onErrorCallback)
231
232 def serviceDisconnect(self, fx):
233 """
234 Remove the service from the service list.
235
236 @param fx function name of the service
237 """
238 self.services.pop(fx, None)
239
240 def on_newConnection(self):
241 """
242 Slot for new incomming connections from the clients.
243 """
244 connection = self.nextPendingConnection()
245 if not connection.waitForReadyRead(1000):
246 return
247 ch = 0 if connection.read(1) == b'2' else 1
248 self.connections[ch] = connection
249 connection.readyRead.connect(
250 lambda x=ch: self.__receive(x))
251
252 for fx, args in self.services.items():
253 self.enqueueRequest('INIT', fx, ch, args[:2])
254
255 def shutdown(self):
256 """
257 Cleanup the connections and processes when Eric is shuting down.
258 """
259 for connection in self.connections:
260 if connection:
261 connection.close()
262
263 for process in self.processes:
264 if isinstance(process, QProcess):
265 process.close()
266 process = None
267 elif isinstance(process, threading.Thread):
268 process.join(0.1)
269 process = None

eric ide

mercurial