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): |