src/eric7/DebugClients/Python/AsyncFile.py

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

eric ide

mercurial