eric7/DebugClients/Python/AsyncFile.py

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

eric ide

mercurial