src/eric7/DebugClients/Python/AsyncFile.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
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
40 MAX_TRIES = 10
41
42 BUFSIZE = 2**14 # 16 kBytes
43 CMD_BUFSIZE = 2**12 # 4 kBytes
44
45 def __init__(self, sock, mode, name):
46 """
47 Constructor
48
49 @param sock the socket object being wrapped
50 @type socket
51 @param mode mode of this file
52 @type str
53 @param name name of this file
54 @type str
55 """
56 # Initialise the attributes.
57 self.closed = False
58 self.sock = sock
59 self.mode = mode
60 self.name = name
61 self.nWriteErrors = 0
62 self.encoding = "utf-8"
63 self.errors = None
64 self.newlines = None
65 self.line_buffering = False
66
67 self.writeLock = threading.RLock()
68 self.wpending = []
69
70 def __checkMode(self, mode):
71 """
72 Private method to check the mode.
73
74 This method checks, if an operation is permitted according to
75 the mode of the file. If it is not, an OSError is raised.
76
77 @param mode the mode to be checked
78 @type string
79 @exception OSError raised to indicate a bad file descriptor
80 """
81 if mode != self.mode:
82 raise OSError((9, "[Errno 9] Bad file descriptor"))
83
84 def pendingWrite(self):
85 """
86 Public method that returns the number of strings waiting to be written.
87
88 @return the number of strings to be written
89 @rtype int
90 """
91 return len(self.wpending)
92
93 def close(self, closeit=False):
94 """
95 Public method to close the file.
96
97 @param closeit flag to indicate a close ordered by the debugger code
98 @type bool
99 """
100 if closeit and not self.closed:
101 self.flush()
102 self.sock.close()
103 self.closed = True
104
105 def flush(self):
106 """
107 Public method to write all pending entries.
108 """
109 self.writeLock.acquire()
110 while self.wpending:
111 try:
112 buf = self.wpending.pop(0)
113 except IndexError:
114 break
115
116 try:
117 with contextlib.suppress(UnicodeEncodeError, 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(
352 "ClientOutput",
353 {
354 "text": s,
355 "debuggerId": "",
356 },
357 )
358 self.wpending.append(cmd)
359 self.flush()
360 self.writeLock.release()
361
362 def write_p(self, s):
363 """
364 Public method to write a json-rpc 2.0 coded string to the file.
365
366 @param s text to be written
367 @type str
368 """
369 self.__checkMode("w")
370
371 self.wpending.append(s)
372 self.flush()
373
374 def writelines(self, lines):
375 """
376 Public method to write a list of strings to the file.
377
378 @param lines list of texts to be written
379 @type list of str
380 """
381 self.write("".join(lines))

eric ide

mercurial