15 import os |
15 import os |
16 import struct |
16 import struct |
17 import sys |
17 import sys |
18 from zlib import adler32 |
18 from zlib import adler32 |
19 |
19 |
20 from PyQt4.QtCore import QProcess, pyqtSignal |
20 from PyQt4.QtCore import QProcess, pyqtSignal, QTimer |
21 from PyQt4.QtGui import QApplication |
21 from PyQt4.QtGui import QApplication |
22 from PyQt4.QtNetwork import QTcpServer, QHostAddress |
22 from PyQt4.QtNetwork import QTcpServer, QHostAddress |
23 |
23 |
|
24 from E5Gui import E5MessageBox |
|
25 from E5Gui.E5Application import e5App |
24 import Preferences |
26 import Preferences |
25 import Utilities |
27 import Utilities |
26 |
28 |
27 from eric5config import getConfig |
29 from eric5config import getConfig |
28 |
30 |
60 for pyName in ['Python', 'Python3']: |
63 for pyName in ['Python', 'Python3']: |
61 interpreter = Preferences.getDebugger( |
64 interpreter = Preferences.getDebugger( |
62 pyName + "Interpreter") |
65 pyName + "Interpreter") |
63 process = self.__startExternalClient(interpreter, port) |
66 process = self.__startExternalClient(interpreter, port) |
64 if process: |
67 if process: |
65 self.processes.append(process) |
68 if pyName == 'Python': |
|
69 pyName = 'Python2' |
|
70 self.processes[pyName] = process, interpreter |
66 |
71 |
67 def __startExternalClient(self, interpreter, port): |
72 def __startExternalClient(self, interpreter, port): |
68 """ |
73 """ |
69 Private method to start the background client as external process. |
74 Private method to start the background client as external process. |
70 |
75 |
91 client. |
96 client. |
92 """ |
97 """ |
93 if self.__queue and self.isWorking is None: |
98 if self.__queue and self.isWorking is None: |
94 fx, lang, fn, data = self.__queue.pop(0) |
99 fx, lang, fn, data = self.__queue.pop(0) |
95 self.isWorking = lang |
100 self.isWorking = lang |
|
101 self.runningJob = fx, lang, fn, data |
96 self.__send(fx, lang, fn, data) |
102 self.__send(fx, lang, fn, data) |
97 |
103 |
98 def __send(self, fx, lang, fn, data): |
104 def __send(self, fx, lang, fn, data): |
99 """ |
105 """ |
100 Private method to send a job request to one of the clients. |
106 Private method to send a job request to one of the clients. |
105 @param data function argument(s) (any basic datatype) |
111 @param data function argument(s) (any basic datatype) |
106 """ |
112 """ |
107 connection = self.connections.get(lang) |
113 connection = self.connections.get(lang) |
108 if connection is None: |
114 if connection is None: |
109 if fx != 'INIT': |
115 if fx != 'INIT': |
110 self.serviceNotAvailable.emit( |
116 # Avoid growing recursion deep which could itself result in an |
111 fx, lang, fn, self.tr( |
117 # exception |
112 '{0} not configured.').format(lang)) |
118 QTimer.singleShot( |
|
119 0, |
|
120 lambda: self.serviceNotAvailable.emit( |
|
121 fx, lang, fn, self.tr( |
|
122 '{0} not configured.').format(lang))) |
113 # Reset flag and continue processing queue |
123 # Reset flag and continue processing queue |
114 self.isWorking = None |
124 self.isWorking = None |
115 self.__processQueue() |
125 self.__processQueue() |
116 else: |
126 else: |
117 packedData = json.dumps([fx, fn, data]) |
127 packedData = json.dumps([fx, fn, data]) |
144 fx, fn, data = json.loads(packedData) |
154 fx, fn, data = json.loads(packedData) |
145 |
155 |
146 if fx == 'INIT': |
156 if fx == 'INIT': |
147 pass |
157 pass |
148 elif fx == 'EXCEPTION': |
158 elif fx == 'EXCEPTION': |
|
159 # Remove connection because it'll close anyway |
|
160 self.connections.pop(lang, None) |
149 # Call sys.excepthook(type, value, traceback) to emulate the |
161 # Call sys.excepthook(type, value, traceback) to emulate the |
150 # exception which was caught on the client |
162 # exception which was caught on the client |
151 sys.excepthook(*data) |
163 sys.excepthook(*data) |
152 QApplication.processEvents() |
164 res = E5MessageBox.question( |
|
165 None, |
|
166 self.tr("Restart background client?"), |
|
167 self.tr( |
|
168 "<p>The background client for <b>{0}</b> has stopped" |
|
169 " due to an exception. It's used by various plug-ins like" |
|
170 " the different checkers.</p>" |
|
171 "<p>Select<br>" |
|
172 "<b>'Yes'</b> to restart the client, but abort the last" |
|
173 " job<br>" |
|
174 "<b>'Retry'</b> to restart the client and the last job<br>" |
|
175 "<b>'No'</b> to leave the client off.</p>" |
|
176 "<p>Note: The client can be restarted by opening and" |
|
177 " accepting the preferences dialog or reloading/ changing" |
|
178 " the project.</p>").format(lang), |
|
179 E5MessageBox.Yes | E5MessageBox.No | E5MessageBox.Retry, |
|
180 E5MessageBox.Yes) |
|
181 |
|
182 if res == E5MessageBox.Retry: |
|
183 self.enqueueRequest(*self.runningJob) |
|
184 else: |
|
185 fx, lng, fn, data = self.runningJob |
|
186 self.services[(fx, lng)][3](fx, lng, fn, self.tr( |
|
187 'An error in Erics background client stopped the service.') |
|
188 ) |
|
189 if res != E5MessageBox.No: |
|
190 self.isWorking = None |
|
191 self.restartService(lang, True) |
|
192 return |
153 elif data == 'Unknown service.': |
193 elif data == 'Unknown service.': |
154 callback = self.services.get((fx, lang)) |
194 callback = self.services.get((fx, lang)) |
155 if callback: |
195 if callback: |
156 callback[3](fx, lang, fn, data) |
196 callback[3](fx, lang, fn, data) |
157 else: |
197 else: |
159 if callback: |
199 if callback: |
160 callback[2](fn, *data) |
200 callback[2](fn, *data) |
161 |
201 |
162 self.isWorking = None |
202 self.isWorking = None |
163 self.__processQueue() |
203 self.__processQueue() |
|
204 |
|
205 def preferencesOrProjectChanged(self): |
|
206 """ |
|
207 Public slot to restart the built in languages. |
|
208 """ |
|
209 for pyName in ['Python', 'Python3']: |
|
210 interpreter = Preferences.getDebugger( |
|
211 pyName + "Interpreter") |
|
212 |
|
213 if pyName == 'Python': |
|
214 pyName = 'Python2' |
|
215 |
|
216 # Tweak the processes list to reflect the changed interpreter |
|
217 proc, inter = self.processes.pop(pyName, [None, None]) |
|
218 self.processes[pyName] = proc, interpreter |
|
219 |
|
220 self.restartService(pyName) |
|
221 |
|
222 def restartService(self, language, forceKill=False): |
|
223 """ |
|
224 Public method to restart a given lanuage. |
|
225 |
|
226 @param language to restart (str) |
|
227 @keyparam forceKill flag to kill a running task (bool) |
|
228 """ |
|
229 try: |
|
230 proc, interpreter = self.processes.pop(language) |
|
231 except KeyError: |
|
232 return |
|
233 |
|
234 # Don't kill a process if it's still working |
|
235 if not forceKill: |
|
236 while self.isWorking is not None: |
|
237 QApplication.processEvents() |
|
238 |
|
239 conn = self.connections.pop(language, None) |
|
240 if conn: |
|
241 conn.blockSignals(True) |
|
242 conn.close() |
|
243 if proc: |
|
244 proc.close() |
|
245 |
|
246 port = self.serverPort() |
|
247 process = self.__startExternalClient(interpreter, port) |
|
248 if process: |
|
249 self.processes[language] = process, interpreter |
164 |
250 |
165 def enqueueRequest(self, fx, lang, fn, data): |
251 def enqueueRequest(self, fx, lang, fn, data): |
166 """ |
252 """ |
167 Implement a queued processing of incomming events. |
253 Implement a queued processing of incomming events. |
168 |
254 |
240 |
326 |
241 for (fx, lng), args in self.services.items(): |
327 for (fx, lng), args in self.services.items(): |
242 if lng == lang: |
328 if lng == lang: |
243 # Register service with modulepath and module |
329 # Register service with modulepath and module |
244 self.enqueueRequest('INIT', lng, fx, args[:2]) |
330 self.enqueueRequest('INIT', lng, fx, args[:2]) |
|
331 |
|
332 # Syntax check the open editors again |
|
333 try: |
|
334 vm = e5App().getObject("ViewManager") |
|
335 except KeyError: |
|
336 return |
|
337 for editor in vm.getOpenEditors(): |
|
338 if editor.getLanguage() == lang: |
|
339 QTimer.singleShot(0, editor.checkSyntax) |
245 |
340 |
246 def on_disconnectSocket(self, lang): |
341 def on_disconnectSocket(self, lang): |
247 """ |
342 """ |
248 Slot when connection to a client is lost. |
343 Slot is called when connection to a client is lost. |
249 |
344 |
250 @param lang client language which connection is lost (str) |
345 @param lang client language which connection is lost (str) |
251 """ |
346 """ |
252 self.connections.pop(lang) |
347 conn = self.connections.pop(lang, None) |
253 # Maybe the task is killed while ideling |
348 if conn: |
254 if self.isWorking == lang: |
349 conn.close() |
|
350 fx, lng, fn, data = self.runningJob |
|
351 if fx != 'INIT' and lng == lang: |
|
352 self.services[(fx, lng)][3](fx, lng, fn, self.tr( |
|
353 'Erics background client disconnected because of an' |
|
354 ' unknown reason.') |
|
355 ) |
255 self.isWorking = None |
356 self.isWorking = None |
256 # Remove pending jobs and send warning to the waiting caller |
357 |
257 # Make a copy of the list because it's modified in the loop |
358 res = E5MessageBox.yesNo( |
258 for args in self.__queue[:]: |
359 None, |
259 fx, lng, fn, data = args |
360 self.tr('Background client disconnected.'), |
260 if lng == lang: |
361 self.tr( |
261 # Call onErrorCallback with error message |
362 'The background client for <b>{0}</b> disconnect because' |
262 self.__queue.remove(args) |
363 ' of an unknown reason.<br>Should it be restarted?' |
263 self.services[(fx, lng)][3](fx, fn, lng, self.tr( |
364 ).format(lang), |
264 'Error in Erics background service stopped service.')) |
365 yesDefault=True) |
265 |
366 if res: |
|
367 self.restartService(lang) |
|
368 |
266 def shutdown(self): |
369 def shutdown(self): |
267 """ |
370 """ |
268 Cleanup the connections and processes when Eric is shuting down. |
371 Cleanup the connections and processes when eric is shuting down. |
269 """ |
372 """ |
270 # Make copy of dictionary values because the list is changed by |
373 for connection in self.connections.values(): |
271 # on_disconnectSocket |
374 # Prevent calling of on_disconnectSocket |
272 for connection in list(self.connections.values()): |
375 connection.blockSignals(True) |
273 if connection: |
376 connection.close() |
274 connection.close() |
377 |
275 |
378 for process, interpreter in self.processes.values(): |
276 for process in self.processes: |
|
277 process.close() |
379 process.close() |
278 process = None |
380 process = None |