DebugClients/Python/AsyncFile.py

branch
debugger speed
changeset 5178
878ce843ca9f
parent 5161
f7b6ded9cc37
child 5179
5f56410e7624
equal deleted inserted replaced
5174:8c48f5e0cd92 5178:878ce843ca9f
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing an asynchronous file like socket interface for the
8 debugger.
9 """
10
11 import socket
12
13 from DebugUtilities import prepareJsonCommand
14
15
16 def AsyncPendingWrite(file):
17 """
18 Module function to check for data to be written.
19
20 @param file The file object to be checked (file)
21 @return Flag indicating if there is data wating (int)
22 """
23 try:
24 pending = file.pendingWrite()
25 except Exception:
26 pending = 0
27
28 return pending
29
30
31 class AsyncFile(object):
32 """
33 Class wrapping a socket object with a file interface.
34 """
35 maxtries = 10
36 maxbuffersize = 1024 * 1024 * 4
37
38 def __init__(self, sock, mode, name):
39 """
40 Constructor
41
42 @param sock the socket object being wrapped
43 @param mode mode of this file (string)
44 @param name name of this file (string)
45 """
46 # Initialise the attributes.
47 self.closed = False
48 self.sock = sock
49 self.mode = mode
50 self.name = name
51 self.nWriteErrors = 0
52 self.encoding = "utf-8"
53
54 self.wpending = ''
55
56 def __checkMode(self, mode):
57 """
58 Private method to check the mode.
59
60 This method checks, if an operation is permitted according to
61 the mode of the file. If it is not, an IOError is raised.
62
63 @param mode the mode to be checked (string)
64 @exception IOError raised to indicate a bad file descriptor
65 """
66 if mode != self.mode:
67 raise IOError((9, '[Errno 9] Bad file descriptor'))
68
69 def __nWrite(self, n):
70 """
71 Private method to write a specific number of pending bytes.
72
73 @param n the number of bytes to be written (int)
74 """
75 if n:
76 try:
77 buf = self.wpending[:n]
78 try:
79 buf = buf.encode('utf-8', 'backslashreplace')
80 except (UnicodeEncodeError, UnicodeDecodeError):
81 pass
82 self.sock.sendall(buf)
83 self.wpending = self.wpending[n:]
84 self.nWriteErrors = 0
85 except socket.error:
86 self.nWriteErrors += 1
87 if self.nWriteErrors > self.maxtries:
88 self.wpending = '' # delete all output
89
90 def pendingWrite(self):
91 """
92 Public method that returns the number of bytes waiting to be written.
93
94 @return the number of bytes to be written (int)
95 """
96 return self.wpending.rfind('\n') + 1
97
98 def close(self, closeit=False):
99 """
100 Public method to close the file.
101
102 @param closeit flag to indicate a close ordered by the debugger code
103 (boolean)
104 """
105 if closeit and not self.closed:
106 self.flush()
107 self.sock.close()
108 self.closed = True
109
110 def flush(self):
111 """
112 Public method to write all pending bytes.
113 """
114 self.__nWrite(len(self.wpending))
115
116 def isatty(self):
117 """
118 Public method to indicate whether a tty interface is supported.
119
120 @return always false
121 """
122 return False
123
124 def fileno(self):
125 """
126 Public method returning the file number.
127
128 @return file number (int)
129 """
130 try:
131 return self.sock.fileno()
132 except socket.error:
133 return -1
134
135 def readable(self):
136 """
137 Public method to check, if the stream is readable.
138
139 @return flag indicating a readable stream (boolean)
140 """
141 return self.mode == "r"
142
143 def read_p(self, size=-1):
144 """
145 Public method to read bytes from this file.
146
147 @param size maximum number of bytes to be read (int)
148 @return the bytes read (any)
149 """
150 self.__checkMode('r')
151
152 if size < 0:
153 size = 20000
154
155 return self.sock.recv(size).decode('utf8', 'backslashreplace')
156
157 def read(self, size=-1):
158 """
159 Public method to read bytes from this file.
160
161 @param size maximum number of bytes to be read (int)
162 @return the bytes read (any)
163 """
164 self.__checkMode('r')
165
166 buf = input()
167 if size >= 0:
168 buf = buf[:size]
169 return buf
170
171 def readline_p(self, size=-1):
172 """
173 Public method to read a line from this file.
174
175 <b>Note</b>: This method will not block and may return
176 only a part of a line if that is all that is available.
177
178 @param size maximum number of bytes to be read (int)
179 @return one line of text up to size bytes (string)
180 """
181 self.__checkMode('r')
182
183 if size < 0:
184 size = 20000
185
186 # The integration of the debugger client event loop and the connection
187 # to the debugger relies on the two lines of the debugger command being
188 # delivered as two separate events. Therefore we make sure we only
189 # read a line at a time.
190 line = self.sock.recv(size, socket.MSG_PEEK)
191
192 eol = line.find(b'\n')
193
194 if eol >= 0:
195 size = eol + 1
196 else:
197 size = len(line)
198
199 # Now we know how big the line is, read it for real.
200 return self.sock.recv(size).decode('utf8', 'backslashreplace')
201
202 def readlines(self, sizehint=-1):
203 """
204 Public method to read all lines from this file.
205
206 @param sizehint hint of the numbers of bytes to be read (int)
207 @return list of lines read (list of strings)
208 """
209 self.__checkMode('r')
210
211 lines = []
212 room = sizehint
213
214 line = self.readline_p(room)
215 linelen = len(line)
216
217 while linelen > 0:
218 lines.append(line)
219
220 if sizehint >= 0:
221 room = room - linelen
222
223 if room <= 0:
224 break
225
226 line = self.readline_p(room)
227 linelen = len(line)
228
229 return lines
230
231 def readline(self, sizehint=-1):
232 """
233 Public method to read one line from this file.
234
235 @param sizehint hint of the numbers of bytes to be read (int)
236 @return one line read (string)
237 """
238 self.__checkMode('r')
239
240 line = input() + '\n'
241 if sizehint >= 0:
242 line = line[:sizehint]
243 return line
244
245 def seekable(self):
246 """
247 Public method to check, if the stream is seekable.
248
249 @return flag indicating a seekable stream (boolean)
250 """
251 return False
252
253 def seek(self, offset, whence=0):
254 """
255 Public method to move the filepointer.
256
257 @param offset offset to move the filepointer to (integer)
258 @param whence position the offset relates to
259 @exception IOError This method is not supported and always raises an
260 IOError.
261 """
262 raise IOError((29, '[Errno 29] Illegal seek'))
263
264 def tell(self):
265 """
266 Public method to get the filepointer position.
267
268 @exception IOError This method is not supported and always raises an
269 IOError.
270 """
271 raise IOError((29, '[Errno 29] Illegal seek'))
272
273 def truncate(self, size=-1):
274 """
275 Public method to truncate the file.
276
277 @param size size to truncate to (integer)
278 @exception IOError This method is not supported and always raises an
279 IOError.
280 """
281 raise IOError((29, '[Errno 29] Illegal seek'))
282
283 def writable(self):
284 """
285 Public method to check, if a stream is writable.
286
287 @return flag indicating a writable stream (boolean)
288 """
289 return self.mode == "w"
290
291 def write(self, s):
292 """
293 Public method to write a string to the file.
294
295 @param s text to be written (string)
296 """
297 self.__checkMode('w')
298
299 cmd = prepareJsonCommand("ClientOutput", {
300 "text": s,
301 })
302 self.write_p(cmd)
303
304 def write_p(self, s):
305 """
306 Public method to write a string to the file.
307
308 @param s text to be written (string)
309 @exception socket.error raised to indicate too many send attempts
310 """
311 self.__checkMode('w')
312 tries = 0
313 if not self.wpending:
314 self.wpending = s
315 elif len(self.wpending) + len(s) > self.maxbuffersize:
316 # flush wpending if it is too big
317 while self.wpending:
318 # if we have a persistent error in sending the data, an
319 # exception will be raised in __nWrite
320 self.flush()
321 tries += 1
322 if tries > self.maxtries:
323 raise socket.error("Too many attempts to send data")
324 self.wpending = s
325 else:
326 self.wpending += s
327 self.__nWrite(self.pendingWrite())
328
329 def writelines(self, lines):
330 """
331 Public method to write a list of strings to the file.
332
333 @param lines list of texts to be written (list of string)
334 """
335 self.write("".join(lines))
336
337 #
338 # eflag: noqa = M702

eric ide

mercurial