lv_micropython sync file access test

This commit is contained in:
pmp-p 2019-07-04 11:58:49 +02:00
parent 935050905c
commit d86feed137
6 changed files with 501 additions and 4 deletions

View File

@ -20,13 +20,23 @@ BOTH_CFLAGS := -s WASM=0
CPP = clang -E
#for lvgl
CPPFLAGS += -I$(EM_CACHE)/asmjs/ports-builds/sdl2/include
ifdef WASM_FILE_API
CPPFLAGS += -DWASM_FILE_API=1
endif
ifdef EMSCRIPTEN
CPP += -isystem $(EMSCRIPTEN)/system/include/libc -isystem $(EMSCRIPTEN)/system/include/libcxx -isystem $(EM_CACHE)/asmjs/ports-builds/sdl2/include -isystem $(EMSCRIPTEN)/system/include
CPP += --sysroot $(EMSCRIPTEN)/system
CPP += -isystem $(EMSCRIPTEN)/system/include/libc
CPP += -isystem $(EMSCRIPTEN)/system/include/libcxx
CPP += $(CPPFLAGS)
# Act like 'emcc'
CPP += -U__i386 -U__i386 -Ui386 -U__SSE -U__SSE_MATH -U__SSE2 -U__SSE2_MATH -U__MMX__ -U__SSE__ -U__SSE_MATH__ -U__SSE2__ -U__SSE2_MATH__
endif
CFLAGS = -m32 -Wall $(INC) $(COPT)
CFLAGS = -m32 -Wall $(INC) $(COPT) $(CPPFLAGS)
LDFLAGS = -m32 -Wl,-Map=$@.map,--cref -Wl,--gc-sections
OPTIM := -O2
@ -35,6 +45,7 @@ CFLAGS += $(OPTIM) -DNDEBUG -D__EMSCRIPTEN__ -DLV_CONF_INCLUDE_SIMPLE
CFLAGS += -fdata-sections -ffunction-sections
CFLAGS += $(CFLAGS_MOD)
SRC_LIB = $(addprefix lib/,\
utils/interrupt_char.c \
utils/stdout_helpers.c \
@ -45,8 +56,13 @@ SRC_LIB = $(addprefix lib/,\
SRC_C = \
main.c \
mphalport.c \
modutime.c \
$(SRC_MOD)
modutime.c
ifdef WASM_FILE_API
SRC_C += file.c wasm_file_api.c
endif
SRC_C += $(SRC_MOD)
LIB_SRC_C = $(addprefix lib/,\
lv_bindings/driver/SDL/SDL_monitor.c \

View File

@ -0,0 +1,95 @@
Micropython on wasm platform
----------------------------
Some Python file operations are synchronous and won't fit in javascript async I/O system
without performance sacrifice ( using EMTERPRETER ) or changing python code ( using asyncio file operation ).
some of them include :
os.path.exists()
open()
file.read()
os.stat()
os.path.getsize()
It's still possible - if files are reasonably small and fast to access - to add synchronous operations to micropython
via a cache system to add script compatibility .
given some restrictions.
os.path.exists() will hammer http server via HEAD instead of GET, so results should be cached. Polling should not be used.
open() a file means downloading it entirely in a blocking manner.
open() must be the first operation called.
any other file operation will then success.
You will need to provide two functions in your js loader.
These are provided as examples and only cover some usefull cases.
window.urls = {}
window.urls.index = 0
function wasm_file_open(url, cachefile){
// will get a file and store it into cachefile filename
// or an autogenerated name.
// returns either file size >=0 or -1 for error.
try {
if (url[0]==":")
url = url.substr(1)
else {
// do some tests there for your CORS integration
// [TODO]
}
var ab = awfull_get(url)
var ret = ab.length
window.urls.index += 1
if (!cachefile){
cachefile = "cache_"+window.urls.index
ret = window.urls.index
}
FS.createDataFile("/", cachefile, ab, true, true);
return ret
} catch (x) {
console.log("wasm_file_open :"+x)
return -1
}
}
function wasm_file_exists(url, need_dot) {
// the need_dot parameter is for preventing package import lookup
// when hooking file support into import system.
// also some hosts could send html on directory hit.
if (need_dot) {
need_dot = url.split('.').pop()
if (need_dot==url) {
//console.log("wasm_file_exists not-a-file :"+url)
return -1
}
//console.log("wasm_file_exists ? :"+url)
}
var xhr = new XMLHttpRequest()
xhr.open('HEAD', url, false)
xhr.send()
var ret=-1
if (xhr.status == 200 )
ret=1
//console.log(ret)
return ret
}

40
ports/javascript/fdfile.h Normal file
View File

@ -0,0 +1,40 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
* Copyright (c) 2016 Paul Sokolovsky
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_UNIX_FDFILE_H
#define MICROPY_INCLUDED_UNIX_FDFILE_H
#include "py/obj.h"
typedef struct _mp_obj_fdfile_t {
mp_obj_base_t base;
int fd;
} mp_obj_fdfile_t;
extern const mp_obj_type_t mp_type_fileio;
extern const mp_obj_type_t mp_type_textio;
#endif // MICROPY_INCLUDED_UNIX_FDFILE_H

315
ports/javascript/file.c Normal file
View File

@ -0,0 +1,315 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "py/runtime.h"
#include "py/stream.h"
#include "py/builtin.h"
#include "py/mphal.h"
#include "fdfile.h"
#ifdef __EMSCRIPTEN__
#include "emscripten.h"
#else
#define EMSCRIPTEN_KEEPALIVE
#endif
#if MICROPY_PY_IO && !MICROPY_VFS
#ifdef MICROPY_CPYTHON_COMPAT
STATIC void check_fd_is_open(const mp_obj_fdfile_t *o) {
if (o->fd < 0) {
mp_raise_ValueError("I/O operation on closed file");
}
}
#else
#define check_fd_is_open(o)
#endif
//included from fdfile
//extern const mp_obj_type_t mp_type_fileio;
//extern const mp_obj_type_t mp_type_textio;
STATIC void fdfile_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in);
mp_printf(print, "<io.%s %d>", mp_obj_get_type_str(self_in), self->fd);
}
STATIC mp_uint_t fdfile_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) {
mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in);
check_fd_is_open(o);
mp_int_t r = read(o->fd, buf, size);
if (r == -1) {
*errcode = errno;
return MP_STREAM_ERROR;
}
return r;
}
STATIC mp_uint_t fdfile_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) {
mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in);
check_fd_is_open(o);
#if MICROPY_PY_OS_DUPTERM
if (o->fd <= STDERR_FILENO) {
mp_hal_stdout_tx_strn(buf, size);
return size;
}
#endif
mp_int_t r = write(o->fd, buf, size);
while (r == -1 && errno == EINTR) {
if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) {
mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
nlr_raise(obj);
}
r = write(o->fd, buf, size);
}
if (r == -1) {
*errcode = errno;
return MP_STREAM_ERROR;
}
return r;
}
STATIC mp_uint_t fdfile_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) {
mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in);
check_fd_is_open(o);
switch (request) {
case MP_STREAM_SEEK: {
struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)arg;
off_t off = lseek(o->fd, s->offset, s->whence);
if (off == (off_t)-1) {
*errcode = errno;
return MP_STREAM_ERROR;
}
s->offset = off;
return 0;
}
case MP_STREAM_FLUSH:
if (fsync(o->fd) < 0) {
*errcode = errno;
return MP_STREAM_ERROR;
}
return 0;
case MP_STREAM_CLOSE:
close(o->fd);
#ifdef MICROPY_CPYTHON_COMPAT
o->fd = -1;
#endif
return 0;
case MP_STREAM_GET_FILENO:
return o->fd;
default:
*errcode = EINVAL;
return MP_STREAM_ERROR;
}
}
STATIC mp_obj_t fdfile___exit__(size_t n_args, const mp_obj_t *args) {
(void)n_args;
return mp_stream_close(args[0]);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fdfile___exit___obj, 4, 4, fdfile___exit__);
STATIC mp_obj_t fdfile_fileno(mp_obj_t self_in) {
mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in);
check_fd_is_open(self);
return MP_OBJ_NEW_SMALL_INT(self->fd);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(fdfile_fileno_obj, fdfile_fileno);
// Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO,
// but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor
STATIC const mp_arg_t file_open_args[] = {
{ MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)} },
{ MP_QSTR_buffering, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
};
#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args)
// FIXME:
extern int wasm_file_open(const char *url);
STATIC mp_obj_t fdfile_open(const mp_obj_type_t *type, mp_arg_val_t *args) {
mp_obj_fdfile_t *o = m_new_obj(mp_obj_fdfile_t);
const char *mode_s = mp_obj_str_get_str(args[1].u_obj);
int can_online = 0;
int mode_rw = 0, mode_x = 0;
while (*mode_s) {
switch (*mode_s++) {
case 'r':
can_online = 1;
mode_rw = O_RDONLY;
break;
case 'w':
mode_rw = O_WRONLY;
mode_x = O_CREAT | O_TRUNC;
break;
case 'a':
mode_rw = O_WRONLY;
mode_x = O_CREAT | O_APPEND;
break;
case '+':
mode_rw = O_RDWR;
break;
#if MICROPY_PY_IO_FILEIO
// If we don't have io.FileIO, then files are in text mode implicitly
case 'b':
type = &mp_type_fileio;
break;
case 't':
type = &mp_type_textio;
break;
#endif
}
}
o->base.type = type;
mp_obj_t fid = args[0].u_obj;
if (MP_OBJ_IS_SMALL_INT(fid)) {
o->fd = MP_OBJ_SMALL_INT_VALUE(fid);
return MP_OBJ_FROM_PTR(o);
}
const char *fname = mp_obj_str_get_str(fid);
int fd=0;
if (can_online)
fd = wasm_file_open( fname );
if (!fd)
fd = open(fname, mode_x | mode_rw, 0644);
if (fd == -1) {
mp_raise_OSError(errno);
}
o->fd = fd;
return MP_OBJ_FROM_PTR(o);
}
STATIC mp_obj_t
fdfile_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
mp_arg_parse_all_kw_array(n_args, n_kw, args, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals);
return fdfile_open(type, arg_vals);
}
STATIC const mp_rom_map_elem_t
rawfile_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&fdfile_fileno_obj) },
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
{ MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) },
{ MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) },
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&fdfile___exit___obj) },
};
STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table);
#if MICROPY_PY_IO_FILEIO
STATIC const mp_stream_p_t
fileio_stream_p = {
.read = fdfile_read,
.write = fdfile_write,
.ioctl = fdfile_ioctl,
};
const mp_obj_type_t
mp_type_fileio = {
{ &mp_type_type },
.name = MP_QSTR_FileIO,
.print = fdfile_print,
.make_new = fdfile_make_new,
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &fileio_stream_p,
.locals_dict = (mp_obj_dict_t*)&rawfile_locals_dict,
};
#endif
STATIC const mp_stream_p_t
textio_stream_p = {
.read = fdfile_read,
.write = fdfile_write,
.ioctl = fdfile_ioctl,
.is_text = true,
};
const mp_obj_type_t
mp_type_textio = {
{ &mp_type_type },
.name = MP_QSTR_TextIOWrapper,
.print = fdfile_print,
.make_new = fdfile_make_new,
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &textio_stream_p,
.locals_dict = (mp_obj_dict_t*)&rawfile_locals_dict,
};
// Factory function for I/O stream classes
mp_obj_t
mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
// TODO: analyze buffering args and instantiate appropriate type
mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
mp_arg_parse_all(n_args, args, kwargs, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals);
return fdfile_open(&mp_type_textio, arg_vals);
}
MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
/*
EMSCRIPTEN_KEEPALIVE mp_obj_t
mp_builtin_open_obj(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
// TODO: analyze buffering args and instantiate appropriate type
#pragma message "finally there"
mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
mp_arg_parse_all(n_args, args, kwargs, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals);
return fdfile_open(&mp_type_textio, arg_vals);
}
*/
const mp_obj_fdfile_t mp_sys_stdin_obj = { .base = {&mp_type_textio}, .fd = STDIN_FILENO };
const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&mp_type_textio}, .fd = STDOUT_FILENO };
const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&mp_type_textio}, .fd = STDERR_FILENO };
#endif // MICROPY_PY_IO && !MICROPY_VFS

View File

@ -249,6 +249,9 @@ mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs)
}
MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
#endif
#else
#pragma message "file.c will require external WASM_FILE_API functions"
#pragma message "see wasm_file_api.txt"
#endif
void nlr_jump_fail(void *val) {

View File

@ -0,0 +1,28 @@
#include <stdio.h>
#include <string.h>
#ifdef __EMSCRIPTEN__
#include "emscripten.h"
#endif
// VERY BAD
int wasm_file_open(const char *url) {
fprintf(stderr,"204:wasm_file_open[%s]\n", url);
if (strlen(url)>1 && url[0]==':') {
fprintf(stderr," -> same host[%s]\n", url);
int fidx = EM_ASM_INT({return wasm_file_open(UTF8ToString($0)); }, url );
char fname[256];
snprintf(fname, sizeof(fname), "cache_%d", fidx);
return fileno( fopen(fname,"r") );
}
if ( (strlen(url)>6) && (url[4]==':' || url[5]==':') ) {
fprintf(stderr," -> remote host[%s]\n", url);
int fidx = EM_ASM_INT({return wasm_file_open(UTF8ToString($0)); }, url );
char fname[256];
snprintf(fname, sizeof(fname), "cache_%d", fidx);
return fileno( fopen(fname,"r") );
}
return 0;
}