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 |