Utilities/BackgroundService.py

branch
BgService
changeset 3241
957673fc463a
parent 3209
c5432abceb25
child 3417
5a93c6cdc989
equal deleted inserted replaced
3228:f489068e51e8 3241:957673fc463a
30 30
31 class BackgroundService(QTcpServer): 31 class BackgroundService(QTcpServer):
32 """ 32 """
33 Class implementing the main part of the background service. 33 Class implementing the main part of the background service.
34 """ 34 """
35 serviceNotAvailable = pyqtSignal(str, str, int, str) 35 serviceNotAvailable = pyqtSignal(str, str, str, str)
36 36
37 def __init__(self): 37 def __init__(self):
38 """ 38 """
39 Constructor of the BackgroundService class. 39 Constructor of the BackgroundService class.
40 """ 40 """
41 self.processes = [None, None] 41 self.processes = []
42 self.connections = [None, None] 42 self.connections = {}
43 self.isWorking = None 43 self.isWorking = None
44 self.__queue = [] 44 self.__queue = []
45 self.services = {} 45 self.services = {}
46 46
47 super(BackgroundService, self).__init__() 47 super(BackgroundService, self).__init__()
56 self.newConnection.connect(self.on_newConnection) 56 self.newConnection.connect(self.on_newConnection)
57 port = self.serverPort() 57 port = self.serverPort()
58 ## NOTE: Need the port if started external in debugger: 58 ## NOTE: Need the port if started external in debugger:
59 print('BackgroundService listening on: %i' % port) 59 print('BackgroundService listening on: %i' % port)
60 if sys.platform == 'win32': 60 if sys.platform == 'win32':
61 pyCompare = Utilities.samefilepath 61 interpreterCompare = Utilities.samefilepath
62 else: 62 else:
63 pyCompare = Utilities.samepath 63 interpreterCompare = Utilities.samepath
64 64
65 for pyIdx, pyName in enumerate(['Python', 'Python3']): 65 for pyName in ['Python', 'Python3']:
66 interpreter = Preferences.getDebugger( 66 interpreter = Preferences.getDebugger(
67 pyName + "Interpreter") 67 pyName + "Interpreter")
68 68
69 if pyCompare(interpreter, sys.executable): 69 if interpreterCompare(interpreter, sys.executable):
70 process = self.__startInternalClient(port) 70 process = self.__startInternalClient(port)
71 else: 71 else:
72 process = self.__startExternalClient(interpreter, port) 72 process = self.__startExternalClient(interpreter, port)
73 self.processes[pyIdx] = process 73 if process:
74 self.processes.append(process)
74 75
75 def __startExternalClient(self, interpreter, port): 76 def __startExternalClient(self, interpreter, port):
76 """ 77 """
77 Private method to start the background client as external process. 78 Private method to start the background client as external process.
78 79
79 @param interpreter path and name of the executable to start (string) 80 @param interpreter path and name of the executable to start (string)
80 @param port socket port to which the interpreter should connect (int) 81 @param port socket port to which the interpreter should connect (int)
81 @return the process object (QProcess) or None 82 @return the process object (QProcess or None)
82 """ 83 """
83 if interpreter == "" or not Utilities.isinpath(interpreter): 84 if interpreter == "" or not Utilities.isinpath(interpreter):
84 return None 85 return None
85 86
86 backgroundClient = os.path.join( 87 backgroundClient = os.path.join(
110 """ 111 """
111 Private method to take the next service request and send it to the 112 Private method to take the next service request and send it to the
112 client. 113 client.
113 """ 114 """
114 if self.__queue and self.isWorking is None: 115 if self.__queue and self.isWorking is None:
115 fx, fn, pyVer, data = self.__queue.pop(0) 116 fx, lang, fn, data = self.__queue.pop(0)
116 self.isWorking = pyVer 117 self.isWorking = lang
117 self.__send(fx, fn, pyVer, data) 118 self.__send(fx, lang, fn, data)
118 119
119 def __send(self, fx, fn, pyVer, data): 120 def __send(self, fx, lang, fn, data):
120 """ 121 """
121 Private method to send a job request to one of the clients. 122 Private method to send a job request to one of the clients.
122 123
123 @param fx remote function name to execute (str) 124 @param fx remote function name to execute (str)
125 @param lang language to connect to (str)
124 @param fn filename for identification (str) 126 @param fn filename for identification (str)
125 @param pyVer version for the required interpreter (int)
126 @param data function argument(s) (any basic datatype) 127 @param data function argument(s) (any basic datatype)
127 """ 128 """
128 packedData = json.dumps([fx, fn, data]) 129 connection = self.connections.get(lang)
129 if sys.version_info[0] == 3:
130 packedData = bytes(packedData, 'utf-8')
131 connection = self.connections[pyVer - 2]
132 if connection is None: 130 if connection is None:
133 if fx != 'INIT': 131 if fx != 'INIT':
134 self.serviceNotAvailable.emit( 132 self.serviceNotAvailable.emit(
135 fx, fn, pyVer, self.trUtf8( 133 fx, lang, fn, self.trUtf8(
136 'Python{0} interpreter not configured.').format(pyVer)) 134 '{0} not configured.').format(lang))
137 # Reset flag and continue processing queue 135 # Reset flag and continue processing queue
138 self.isWorking = None 136 self.isWorking = None
139 self.__processQueue() 137 self.__processQueue()
140 else: 138 else:
139 packedData = json.dumps([fx, fn, data])
140 if sys.version_info[0] == 3:
141 packedData = bytes(packedData, 'utf-8')
141 header = struct.pack( 142 header = struct.pack(
142 b'!II', len(packedData), adler32(packedData) & 0xffffffff) 143 b'!II', len(packedData), adler32(packedData) & 0xffffffff)
143 connection.write(header) 144 connection.write(header)
144 connection.write(packedData) 145 connection.write(packedData)
145 146
146 def __receive(self, channel): 147 def __receive(self, lang):
147 """ 148 """
148 Private method to receive the response from the clients. 149 Private method to receive the response from the clients.
149 150
150 @param channel of the incomming connection (int: 0 or 1) 151 @param lang language of the incomming connection (str)
151 """ 152 """
152 connection = self.connections[channel] 153 connection = self.connections[lang]
153 header = connection.read(8) 154 header = connection.read(8)
154 length, datahash = struct.unpack(b'!II', header) 155 length, datahash = struct.unpack(b'!II', header)
155 156
156 packedData = b'' 157 packedData = b''
157 while len(packedData) < length: 158 while len(packedData) < length:
161 assert adler32(packedData) & 0xffffffff == datahash, 'Hashes not equal' 162 assert adler32(packedData) & 0xffffffff == datahash, 'Hashes not equal'
162 if sys.version_info[0] == 3: 163 if sys.version_info[0] == 3:
163 packedData = packedData.decode('utf-8') 164 packedData = packedData.decode('utf-8')
164 # "check" if is's a tuple of 3 values 165 # "check" if is's a tuple of 3 values
165 fx, fn, data = json.loads(packedData) 166 fx, fn, data = json.loads(packedData)
166 self.__postResult(fx, fn, data) 167
167
168 def __postResult(self, fx, fn, data):
169 """
170 Private method to emit the correspondig signal for the returned
171 function.
172
173 @param fx remote function name to execute (str)
174 @param fn filename for identification (str)
175 @param data function argument(s) (any basic datatype)
176 """
177 if fx == 'INIT': 168 if fx == 'INIT':
178 pass 169 pass
179 elif fx == 'exception': 170 elif fx == 'EXCEPTION':
180 # Call sys.excepthook(type, value, traceback) to emulate the 171 # Call sys.excepthook(type, value, traceback) to emulate the
181 # exception which was caught on the client 172 # exception which was caught on the client
182 #sys.excepthook(*data) 173 #sys.excepthook(*data)
183 print(data) 174 print(data)
184 else: 175 elif data == 'Unknown service.':
185 callback = self.services.get(fx) 176 callback = self.services.get((fx, lang))
177 if callback:
178 callback[3](fx, lang, fn, data)
179 else:
180 callback = self.services.get((fx, lang))
186 if callback: 181 if callback:
187 callback[2](fn, *data) 182 callback[2](fn, *data)
188 183
189 self.isWorking = None 184 self.isWorking = None
190 self.__processQueue() 185 self.__processQueue()
191 186
192 def enqueueRequest(self, fx, fn, pyVer, data): 187 def enqueueRequest(self, fx, lang, fn, data):
193 """ 188 """
194 Implement a queued processing of incomming events. 189 Implement a queued processing of incomming events.
195 190
196 Dublicate file checks update an older request to avoid overrun or 191 Dublicate service requests updates an older request to avoid overrun or
197 starving of the check. 192 starving of the services.
198 @param fx function name of the service (str) 193 @param fx function name of the service (str)
194 @param lang language to connect to (str)
199 @param fn filename for identification (str) 195 @param fn filename for identification (str)
200 @param pyVer version for the required interpreter (int) 196 @param data function argument(s) (any basic datatype(s))
201 @param data function argument(s) (any basic datatype) 197 """
202 """ 198 args = [fx, lang, fn, data]
203 args = [fx, fn, pyVer, data]
204 if fx == 'INIT': 199 if fx == 'INIT':
205 self.__queue.insert(0, args) 200 self.__queue.insert(0, args)
206 else: 201 else:
207 for pendingArg in self.__queue: 202 for pendingArg in self.__queue:
203 # Check if it's the same service request (fx, lang, fn equal)
208 if pendingArg[:3] == args[:3]: 204 if pendingArg[:3] == args[:3]:
205 # Update the data
209 pendingArg[3] = args[3] 206 pendingArg[3] = args[3]
210 break 207 break
211 else: 208 else:
212 self.__queue.append(args) 209 self.__queue.append(args)
213 self.__processQueue() 210 self.__processQueue()
214 211
215 def serviceConnect( 212 def serviceConnect(
216 self, fx, modulepath, module, callback, onErrorCallback=None): 213 self, fx, lang, modulepath, module, callback,
214 onErrorCallback=None):
217 """ 215 """
218 Announce a new service to the background service/ client. 216 Announce a new service to the background service/ client.
219 217
220 @param fx function name of the service (str) 218 @param fx function name of the service (str)
219 @param lang language of the new service (str)
221 @param modulepath full path to the module (str) 220 @param modulepath full path to the module (str)
222 @param module name to import (str) 221 @param module name to import (str)
223 @param callback function on service response (function) 222 @param callback function on service response (function)
224 @param onErrorCallback function if client isn't available (function) 223 @param onErrorCallback function if client isn't available (function)
225 """ 224 """
226 self.services[fx] = modulepath, module, callback, onErrorCallback 225 self.services[(fx, lang)] = \
227 self.enqueueRequest('INIT', fx, 0, [modulepath, module]) 226 modulepath, module, callback, onErrorCallback
228 self.enqueueRequest('INIT', fx, 1, [modulepath, module]) 227 self.enqueueRequest('INIT', lang, fx, [modulepath, module])
229 if onErrorCallback: 228 if onErrorCallback:
230 self.serviceNotAvailable.connect(onErrorCallback) 229 self.serviceNotAvailable.connect(onErrorCallback)
231 230
232 def serviceDisconnect(self, fx): 231 def serviceDisconnect(self, fx, lang):
233 """ 232 """
234 Remove the service from the service list. 233 Remove the service from the service list.
235 234
236 @param fx function name of the service 235 @param fx function name of the service (function)
237 """ 236 @param lang language of the service (str)
238 self.services.pop(fx, None) 237 """
238 serviceArgs = self.services.pop((fx, lang), None)
239 if serviceArgs and serviceArgs[3]:
240 self.serviceNotAvailable.disconnect(serviceArgs[3])
239 241
240 def on_newConnection(self): 242 def on_newConnection(self):
241 """ 243 """
242 Slot for new incomming connections from the clients. 244 Slot for new incomming connections from the clients.
243 """ 245 """
244 connection = self.nextPendingConnection() 246 connection = self.nextPendingConnection()
245 if not connection.waitForReadyRead(1000): 247 if not connection.waitForReadyRead(1000):
246 return 248 return
247 ch = 0 if connection.read(1) == b'2' else 1 249 lang = connection.read(64)
250 if sys.version_info[0] == 3:
251 lang = lang.decode('utf-8')
248 # Avoid hanging of eric on shutdown 252 # Avoid hanging of eric on shutdown
249 if self.connections[ch]: 253 if self.connections.get(lang):
250 self.connections[ch].close() 254 self.connections[lang].close()
251 if self.isWorking == ch + 2: 255 if self.isWorking == lang:
252 self.isWorking = None 256 self.isWorking = None
253 self.connections[ch] = connection 257 self.connections[lang] = connection
254 connection.readyRead.connect( 258 connection.readyRead.connect(
255 lambda x=ch: self.__receive(x)) 259 lambda x=lang: self.__receive(x))
256 260
257 for fx, args in self.services.items(): 261 for (fx, lng), args in self.services.items():
258 self.enqueueRequest('INIT', fx, ch, args[:2]) 262 if lng == lang:
263 # Register service with modulepath and module
264 self.enqueueRequest('INIT', lng, fx, args[:2])
259 265
260 def shutdown(self): 266 def shutdown(self):
261 """ 267 """
262 Cleanup the connections and processes when Eric is shuting down. 268 Cleanup the connections and processes when Eric is shuting down.
263 """ 269 """
264 for connection in self.connections: 270 for connection in self.connections.values():
265 if connection: 271 if connection:
266 connection.close() 272 connection.close()
267 273
268 for process in self.processes: 274 for process in self.processes:
269 if isinstance(process, QProcess): 275 if isinstance(process, QProcess):

eric ide

mercurial