8000 py/modio: Implement BufferedReader · micropython/micropython@3ce4d08 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3ce4d08

Browse files
committed
py/modio: Implement BufferedReader
Some times there is a need for a BufferedReader in a way similar to how BufferedWritter works. A clear example is when using an underlying device requiring aligned reads, but a less clear example is when using deflate.DeflateIO which will do only 1-byte reads and can become crippling quickly when the underlying object is a python implemented stream instead of a native one. The BufferedReader will only attempt to do full-buffer reads and ensures word-alignment in a way similar to how the writer does. Similarly, it will also hide any errors when partial reads happen to ensure that any data copied so far can be returned first. Signed-off-by: Francisco Blas (klondike) Izquierdo Riera <klondike@klondike.es>
1 parent 321f045 commit 3ce4d08

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

ports/esp32/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1)
7878
#define MICROPY_PY_BUILTINS_HELP_TEXT esp32_help_text
7979
#define MICROPY_PY_IO_BUFFEREDWRITER (1)
80+
#define MICROPY_PY_IO_BUFFEREDREADER (1)
8081
#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1)
8182
#define MICROPY_PY_TIME_TIME_TIME_NS (1)
8283
#define MICROPY_PY_TIME_INCLUDEFILE "ports/esp32/modtime.c"

py/modio.c

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ STATIC MP_DEFINE_CONST_OBJ_TYPE(
108108

109109
#endif // MICROPY_PY_IO_IOBASE
110110

111-
#if MICROPY_PY_IO_BUFFEREDWRITER
111+
#if MICROPY_PY_IO_BUFFEREDWRITER || MICROPY_PY_IO_BUFFEREDREADER
112+
// The structure and the new method are shared by reader and writer
112113
typedef struct _mp_obj_bufwriter_t {
113114
mp_obj_base_t base;
114115
mp_obj_t stream;
@@ -129,6 +130,9 @@ STATIC mp_obj_t bufwriter_make_new(const mp_obj_type_t *type, size_t n_args, siz
129130
return o;
130131
}
131132

133+
#endif // MICROPY_PY_IO_BUFFEREDWRITER || MICROPY_PY_IO_BUFFEREDREADER
134+
135+
#if MICROPY_PY_IO_BUFFEREDWRITER
132136
// Writes out the data stored in the buffer so far
133137
STATIC int bufwriter_do_write(mp_obj_bufwriter_t *self) {
134138
int rv = 0;
@@ -240,6 +244,84 @@ STATIC MP_DEFINE_CONST_OBJ_TYPE(
240244
);
241245
#endif // MICROPY_PY_IO_BUFFEREDWRITER
242246

247+
#if MICROPY_PY_IO_BUFFEREDREADER
248+
STATIC mp_uint_t bufreader_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
249+
mp_obj_bufwriter_t *self = MP_OBJ_TO_PTR(self_in);
250+
251+
mp_uint_t org_size = size;
252+
// Cache this value since it should not change thus avoiding a dereference
253+
size_t alloc = self->alloc;
254+
255+
*errcode = self->error;
256+
self->error = 0;
257+
258+
while (*errcode == 0 && size > 0) {
259+
// Buffer filling policy here is to fill the entire buffer all the time.
260+
// This allows e.g. to have a block device as backing storage and read
261+
// entire blocks from it. memcpy below is not ideal and could be
262+
// optimized in some cases. But the way it is now it at least ensures
263+
// that buffer is word-aligned, to guard against obscure cases when it
264+
// matters, e.g.
265+
// https://github.com/micropython/micropython/issues/1863
266+
267+
// Buffer needs to have at least one byte
268+
if (self->len == 0) {
269+
self->len = mp_stream_read_exactly(self->stream, self->buf, alloc, errcode);
270+
if (self->len < alloc) {
271+
// If no data was read stop the loop as it is either an error or an EOF
272+
// This check is moved here because it speeds up the more common case
273+
// where the buffer was completely filled.
274+
if (self->len == 0) {
275+
break;
276+
}
277+
// Buffers may overlap, therefore we use memmove instead of memcpy
278+
memmove(self->buf + (alloc - self->len), self->buf, self->len);
279+
}
280+
}
281+
mp_uint_t rem = MIN(self->len, size);
282+
memcpy(buf, self->buf + (alloc - self->len), rem);
283+
self->len -= rem;
284+
buf = (byte *)buf + rem;
285+
size -= rem;
286+
}
287+
288+
// If there is a prior error
289+
if (*errcode != 0) {
290+
// If no data was read return it
291+
if (size == org_size) {
292+
return MP_STREAM_ERROR;
293+
}
294+
// Otherwise save it so we can fail on the next call
295+
self->error = *errcode;
296+
*errcode = 0;
297+
}
298+
299+
// Return the data that has been read so far
300+
return org_size - size;
301+
}
302+
303+
STATIC const mp_rom_map_elem_t bufreader_locals_dict_table[] = {
304+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
305+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
306+
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
307+
};
308+
STATIC MP_DEFINE_CONST_DICT(bufreader_locals_dict, bufreader_locals_dict_table);
309+
310+
STATIC const mp_stream_p_t bufreader_stream_p = {
311+
.read = bufreader_read,
312+
};
313+
314+
STATIC MP_DEFINE_CONST_OBJ_TYPE(
315+
mp_type_bufreader,
316+
MP_QSTR_BufferedReader,
317+
MP_TYPE_FLAG_NONE,
318+
make_new, bufwriter_make_new,
319+
protocol, &bufreader_stream_p,
320+
locals_dict, &bufreader_locals_dict
321+
);
322+
#endif // MICROPY_PY_IO_BUFFEREDREADER
323+
324+
243325
STATIC const mp_rom_map_elem_t mp_module_io_globals_table[] = {
244326
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_io) },
245327
// Note: mp_builtin_open_obj should be defined by port, it's not
@@ -255,6 +337,9 @@ STATIC const mp_rom_map_elem_t mp_module_io_globals_table[] = {
255337
#if MICROPY_PY_IO_BUFFEREDWRITER
256338
{ MP_ROM_QSTR(MP_QSTR_BufferedWriter), MP_ROM_PTR(&mp_type_bufwriter) },
257339
#endif
340+
#if MICROPY_PY_IO_BUFFEREDREADER
341+
{ MP_ROM_QSTR(MP_QSTR_BufferedReader), MP_ROM_PTR(&mp_type_bufreader) },
342+
#endif
258343
};
259344

260345
STATIC MP_DEFINE_CONST_DICT(mp_module_io_globals, mp_module_io_globals_table);

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,11 @@ typedef double mp_float_t;
13961396
#define MICROPY_PY_IO_BUFFEREDWRITER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING)
13971397
#endif
13981398

1399+
// Whether to provide "io.BufferedReader" class
1400+
#ifndef MICROPY_PY_IO_BUFFEREDREADER
1401+
#define MICROPY_PY_IO_BUFFEREDREADER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING)
1402+
#endif
1403+
13991404
// Whether to provide "struct" module
14001405
#ifndef MICROPY_PY_STRUCT
14011406
#define MICROPY_PY_STRUCT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)

0 commit comments

Comments
 (0)
0