[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/python-emscripten/python/trunk/emscripten.pyx [Back]  [Original]

# Python wrapper for emscripten_* C functions

# Copyright (C) 2018, 2019, 2020  Sylvain Beucler

# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

# http://docs.cython.org/en/latest/src/tutorial/strings.html#auto-encoding-and-decoding
# Most of our strings are converted from/to JS through emscripten stringToUTF8/UTF8ToString
# cython: c_string_type=unicode, c_string_encoding=utf8
# Note: Py->C auto-UTF-8 (instead of .encode('UTF-8')) not supported for Py2
# Note: causes issues with Typed Memoryviews

from __future__ import print_function

cdef extern from "emscripten.h":
    ctypedef void (*em_callback_func)()
    ctypedef void (*em_arg_callback_func)(void*)
    ctypedef void (*em_async_wget_onload_func)(void*, void*, int)

    void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop)
    void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg, int fps, int simulate_infinite_loop)
    void emscripten_cancel_main_loop()
    void emscripten_exit_with_live_runtime()

    void emscripten_run_script(const char *script)
    int emscripten_run_script_int(const char *script)
    char *emscripten_run_script_string(const char *script)

    #void emscripten_async_wget(const char* url, const char* file, em_str_callback_func onload, em_str_callback_func onerror)
    void emscripten_async_wget_data(const char* url, void *arg, em_async_wget_onload_func onload, em_arg_callback_func onerror)
    void emscripten_async_call(em_arg_callback_func func, void *arg, int millis)

    void emscripten_sleep(unsigned int ms)
    void emscripten_wget(const char* url, const char* file)
    void emscripten_wget_data(const char* url, void** pbuffer, int* pnum, int *perror)

    # Emterpreter-only
    #void emscripten_sleep_with_yield(unsigned int ms)

    enum:
        EM_LOG_CONSOLE
        EM_LOG_WARN
        EM_LOG_ERROR
        EM_LOG_C_STACK
        EM_LOG_JS_STACK
        EM_LOG_DEMANGLE
        EM_LOG_NO_PATHS
        EM_LOG_FUNC_PARAMS

    int emscripten_get_compiler_setting(const char *name)
    void emscripten_debugger()
    void emscripten_log(int flags, const char* format, ...)
    int emscripten_get_callstack(int flags, char *out, int maxbytes)

LOG_CONSOLE = EM_LOG_CONSOLE
LOG_WARN = EM_LOG_WARN
LOG_ERROR = EM_LOG_ERROR
LOG_C_STACK = EM_LOG_C_STACK
LOG_JS_STACK = EM_LOG_JS_STACK
LOG_DEMANGLE = EM_LOG_DEMANGLE
LOG_NO_PATHS = EM_LOG_NO_PATHS
LOG_FUNC_PARAMS = EM_LOG_FUNC_PARAMS


# https://cython.readthedocs.io/en/latest/src/tutorial/memory_allocation.html
from libc.stdlib cimport malloc, free
from cpython.mem cimport PyMem_Malloc, PyMem_Free
# https://github.com/cython/cython/wiki/FAQ#what-is-the-difference-between-pyobject-and-object
from cpython.ref cimport PyObject, Py_XINCREF, Py_XDECREF

from cpython.buffer cimport PyBuffer_FillInfo

#cdef extern from "stdio.h":
#    int puts(const char *s);


# C callback - no memory management
# Take a single Python object and calls it
# Kept for documentation
cdef void callpyfunc(void *py_function):
    # not necessary as we're using a no-threading Python
    #PyEval_InitThreads()
    # Call Python function from C using ()()
    f = py_function
    f()


# C callbacks - memory management
cdef struct pycaller:
    PyObject* py_function
    PyObject* py_arg  # can be: set, None or NULL

cdef pycaller* pycaller_create(PyObject* py_function, PyObject* py_arg):
    cdef pycaller* c =  PyMem_Malloc(sizeof(pycaller))
    c.py_function = py_function
    c.py_arg = py_arg
    Py_XINCREF(c.py_function)
    if c.py_arg != NULL:
        Py_XINCREF(c.py_arg)
    return c

cdef void pycaller_free(pycaller *c):
    if c.py_arg != NULL:
        Py_XDECREF(c.py_arg)
    Py_XDECREF(c.py_function)
    PyMem_Free(c)

# Take a Python object and calls it ONCE on passed argument
# C callback for e.g. emscripten_async_call
cdef void pycaller_callback_once(void* p):
    pycaller_callback_recurring(p)
    pycaller_free(p)

# Take a Python object and calls it on passed argument
# C callback for e.g. emscripten_set_main_loop_arg
cdef void pycaller_callback_recurring(void* p):
    cdef pycaller* c = p
    py_function = (c.py_function)
    if c.py_arg != NULL:
        py_arg = (c.py_arg)
        py_function(py_arg)
    else:
        py_function()


cdef pycaller* main_loop = NULL

def set_main_loop_arg(py_function, py_arg, fps, simulate_infinite_loop):
    set_main_loop_arg_c(py_function, py_arg,
                        fps, simulate_infinite_loop)

def set_main_loop(py_function, fps, simulate_infinite_loop):
    set_main_loop_arg_c(py_function, NULL,
                        fps, simulate_infinite_loop)

# handle py_arg == NULL != None
cdef set_main_loop_arg_c(PyObject* py_function, PyObject* py_arg,
                         fps, simulate_infinite_loop):
    global main_loop
    if main_loop == NULL:
        main_loop = pycaller_create(py_function, py_arg)
    else:
        pass  # invalid, let emscripten_set_main_loop_arg() abort
    emscripten_set_main_loop_arg(pycaller_callback_recurring, main_loop,
                                 fps, simulate_infinite_loop)

def cancel_main_loop():
    global main_loop
    emscripten_cancel_main_loop()
    if main_loop != NULL:
       pycaller_free(main_loop)
    main_loop = NULL

# import emscripten,sys
# emscripten.set_main_loop(lambda: sys.stdout.write("main_loop\n"), 2, 0)
# emscripten.cancel_main_loop()
# emscripten.set_main_loop(lambda: sys.stdout.write("main_loop\n"), -1, 1)
# emscripten.set_main_loop_arg(lambda a: sys.stdout.write(a), "main_loop_arg\n", 2, 0)

def async_call(py_function, py_arg, millis):
    cdef pycaller* c = pycaller_create(py_function, py_arg)
    emscripten_async_call(pycaller_callback_once, c, millis)

# emscripten.async_call(lambda a: sys.stdout.write(a), "async_call_arg\n", 1000)


def exit_with_live_runtime():
    emscripten_exit_with_live_runtime();

def sleep(ms):
    emscripten_sleep(ms)

# Emterpreter-only
#def sleep_with_yield(ms):
#    emscripten_sleep_with_yield(ms)

def run_script(script):
    emscripten_run_script(script.encode('UTF-8'));

def run_script_int(script):
    return emscripten_run_script_int(script.encode('UTF-8'));

def run_script_string(script):
    return emscripten_run_script_string(script.encode('UTF-8'));


# async_wget
# Requires a C function without parameter, while we need to set
# callpyfunc_arg as callback (so we can call a Python function)
# Perhaps doable if we maintain a list of Python callbacks indexed by 'file' (and ignore potential conflict)
# Or dynamically generate C callbacks in WebAssembly but I doubt that's simple.
# Or implement it with async_wget_data + write output file manually (implies an additional copy)
#def async_wget(url, file, onload, onerror):
#    pass

cdef class pycaller_async_wget:
    cdef onload
    cdef onerror
    cdef arg
    def __cinit__(self, onload, onerror, arg):
        self.onload = onload
        self.onerror = onerror
        self.arg = arg
    #def __dealloc__(self):
    #    print("dealloc")

cdef void pycaller_callback_async_wget_onload(void* p, void* buf, int size):
    c = p
    # https://cython.readthedocs.io/en/latest/src/tutorial/strings.html#passing-byte-strings
    py_buf = (buf)[:size]  # copy
    c.onload(c.arg, py_buf)
    Py_XDECREF(p)
    # 'buf' freed right after by emscripten

cdef void pycaller_callback_async_wget_onerror(void* p):
    c = p
    if c.onerror is not None:
        c.onerror(c.arg)
    Py_XDECREF(p)

def async_wget_data(url, arg, onload, onerror=None):
    cdef pycaller_async_wget c = pycaller_async_wget(onload, onerror, arg)
    cdef PyObject* p = c
    Py_XINCREF(p)  # survive until callback
    emscripten_async_wget_data(url.encode('UTF-8'), p,
                               pycaller_callback_async_wget_onload,
                               pycaller_callback_async_wget_onerror)

# emscripten.async_wget_data('/', {'a':1}, lambda arg,buf: sys.stdout.write(repr(arg)+"\n"+repr(buf)+"\n"), lambda arg: sys.stdout.write(repr(arg)+"\nd/l error\n"))
# emscripten.async_wget_data('https://bank.confidential/', None, None, lambda arg: sys.stdout.write("d/l error\n"))
# emscripten.async_wget_data('https://bank.confidential/', None, None)


# requires -s RETAIN_COMPILER_SETTINGS=1 (otherwise Exception)
def get_compiler_setting(name):
    cdef void* amb = emscripten_get_compiler_setting(name.encode('UTF-8'))
    # can be int or char*, use heuristic
    # otherwise we could whitelist all known string parameters, if that's possible
    if amb < 1000:
        return amb
    else:
        return amb  # c_string_encoding
# emscripten.get_compiler_setting('EMULATE_FUNCTION_POINTER_CASTS')
#   1
# emscripten.get_compiler_setting('OPT_LEVEL')
#   3
# emscripten.get_compiler_setting('EMSCRIPTEN_VERSION')
#   u'1.39.0'
# emscripten.get_compiler_setting('non-existent')
#   u'invalid compiler setting: non-existent'
# emscripten.get_compiler_setting('EXPORTED_FUNCTIONS')
#   u'invalid compiler setting: EXPORTED_FUNCTIONS'  # :(

def debugger():
    emscripten_debugger()
# open the JavaScript console
# emscripten.debugger()

def log(flags, fmt, *args):
    # No variadic function support in Cython?
    # No va_arg variant for emscripten_log either.
    # Let's offer limited support
    cdef char* cstr
    cdef char* cformat
    pystrfmt = fmt.encode('UTF-8')
    cformat = pystrfmt
    if len(args) == 0:
        emscripten_log(flags, cformat)
    elif len(args) > 0:
        if len(args) == 1:
            arg = args[0]
            if type(arg) == int:
                emscripten_log(flags, cformat, arg)
            elif type(arg) == float:
                emscripten_log(flags, cformat, arg)
            elif type(arg) in (str, unicode):
                pystr = arg.encode('UTF-8')
                cstr = pystr
                emscripten_log(flags, cformat, cstr)
            else:
                pystr = ("emscripten.log: unsupported argument " + str(type(arg))).encode('UTF-8')
                cstr = pystr
                emscripten_log(flags, cstr)
        else:
            emscripten_log(flags, "emscripten.log: only up to 2 arguments are supported")
# import emscripten; emscripten.log(0, "hello %02d", 1)
# import emscripten; emscripten.log(emscripten.LOG_WARN|emscripten.LOG_CONSOLE|emscripten.LOG_C_STACK, "warning!")
# emscripten_log doesn't to properly support UTF-8
# import emscripten; emscripten.log(0, u"")
# import emscripten; emscripten.log(0, "%s", u"")

def get_callstack(flags):
    cdef int size = emscripten_get_callstack(flags, NULL, 0)
    # "subsequent calls will carry different line numbers, so it is
    # best to allocate a few bytes extra to be safe"
    size += 1024
    cdef char* buf = PyMem_Malloc(size)
    emscripten_get_callstack(flags, buf, size)
    cdef object ret = buf  # c_string_encoding
    PyMem_Free(buf)
    return ret
# from emscripten import *
# print(get_callstack(0))
# print(get_callstack(LOG_C_STACK|LOG_JS_STACK|LOG_DEMANGLE|LOG_NO_PATHS|LOG_FUNC_PARAMS))


# Pseudo-synchronous, requires ASYNCIFY
def wget(url, file):
    return emscripten_wget(url.encode('UTF-8'), file.encode('UTF-8'))
# emscripten.wget('/hello', '/hello'); open('/hello','rb').read()
# Notes:
# - FS error if file already exists
# - Download indicator showing up not going away
# - Download progress bar showing up not going away on error

# Wrap a malloc'd buffer with buffer interface and automatic free()
cdef class MallocBuffer:
    cdef char *buf
    cdef int size
    def __init__(self):
        raise Exception("MallocBuffer: constructor not available from Python")
    # constructor from non-Python parameters (__cinit__ don't accept them)
    @staticmethod
    cdef MallocBuffer from_string_and_size(char* buf, int size):
        cdef MallocBuffer ret = MallocBuffer.__new__(MallocBuffer)
        ret.buf = buf
        ret.size = size
        return ret
    def __dealloc__(self):
        free(self.buf)
    def __getbuffer__(self, Py_buffer *view, int flags):
        is_readonly = 0
        PyBuffer_FillInfo(view, self, self.buf, self.size, is_readonly, flags)
    def __releasebuffer__(self, Py_buffer *view):
        pass

# Pseudo-synchronous, requires ASYNCIFY
def wget_data(url):
    cdef char* buf
    cdef int num, error
    emscripten_wget_data(url.encode('UTF-8'), &buf, &num, &error)
    if error != 0:
        return None
    pybuf = MallocBuffer.from_string_and_size(buf, num)
    return pybuf
# import emscripten,cStringIO; r = emscripten.wget_data('/hello'); cStringIO.StringIO(r).read(); memoryview(r).tobytes()


# Non-API utility

def syncfs():
    emscripten_run_script(r"""
        FS.syncfs(false, function(err) {
            if (err) {
                console.trace(); console.log(err, err.message);
                Module.print("Warning: write error: " + err.message + "\n");
            }
        });
    """);

Web Proxy Viewer  |  New URL  |  Original Page