davemds pushed a commit to branch master. http://git.enlightenment.org/bindings/python/python-efl.git/commit/?id=eaf31100891e631e5d9dd0a713003b6d70178650
commit eaf31100891e631e5d9dd0a713003b6d70178650 Author: Dave Andreoli <d...@gurumeditation.it> Date: Mon Nov 16 20:45:07 2015 +0100 A new ecore module: ecore_con It's now super easy to perform various network task in a full efl fashion. Implemented "Lookup" for dns query and "Url" to perform http requests. "Server" will come soon. Comes with quite complete docs, examples and unit tests --- CODING | 1 + doc/ecore/class-lookup.rst | 6 + doc/ecore/class-url.rst | 9 + doc/ecore/ecore.rst | 24 +- doc/ecore/module-ecore_con.rst | 5 + efl/ecore/efl.ecore_events.pxi | 8 + efl/ecore_con/efl.ecore_con.pxd | 179 +++++++ efl/ecore_con/efl.ecore_con.pyx | 315 +++++++++++++ efl/ecore_con/efl.ecore_con_lookup.pxi | 88 ++++ efl/ecore_con/efl.ecore_con_url.pxi | 827 +++++++++++++++++++++++++++++++++ examples/ecore/con/lookup.py | 18 + examples/ecore/con/url.py | 31 ++ include/efl.ecore.pxd | 3 + setup.py | 18 +- tests/ecore/test_11_con.py | 156 +++++++ 15 files changed, 1680 insertions(+), 8 deletions(-) diff --git a/CODING b/CODING index f793701..ec9e28e 100644 --- a/CODING +++ b/CODING @@ -32,6 +32,7 @@ Documentation cheatsheet :func:`elm_list_go` (for functions) :attr:`homogeneous` (for properties) :ref:`Elm_List_Mode` (for enums) + :data:`ELM_LIST_LIMIT` (for enum values) :func:`efl.evas.Object.delete` (for items not in current scope) :func:`~efl.evas.Object.delete` (will show it short, just "delete") diff --git a/doc/ecore/class-lookup.rst b/doc/ecore/class-lookup.rst new file mode 100644 index 0000000..a98e4e9 --- /dev/null +++ b/doc/ecore/class-lookup.rst @@ -0,0 +1,6 @@ +.. currentmodule:: efl.ecore_con + +:class:`efl.ecore_con.Lookup` Class +=================================== + +.. autoclass:: efl.ecore_con.Lookup diff --git a/doc/ecore/class-url.rst b/doc/ecore/class-url.rst new file mode 100644 index 0000000..c3f735d --- /dev/null +++ b/doc/ecore/class-url.rst @@ -0,0 +1,9 @@ +.. currentmodule:: efl.ecore_con + +:class:`efl.ecore_con.Url` Class +=================================== + +.. autoclass:: efl.ecore_con.Url +.. autoclass:: efl.ecore_con.EventUrlComplete +.. autoclass:: efl.ecore_con.EventUrlProgress +.. autoclass:: efl.ecore_con.EventUrlData diff --git a/doc/ecore/ecore.rst b/doc/ecore/ecore.rst index bf261ce..cfb36c1 100644 --- a/doc/ecore/ecore.rst +++ b/doc/ecore/ecore.rst @@ -73,6 +73,16 @@ error. Any valid file descriptor can be used with this API, regardless of if was gotten with an OS specific API or from ecore. see :py:class:`FdHandler<efl.ecore.FdHandler>` + +File monitor +------------ + +Using the :py:class:`FileMonitor<efl.ecore.FileMonitor>` class you can monitor +a directory for changes, a single calback will be called when events occur. +Events will be generatd everytime a file or directory (that live in the +give path) is created/deleted/modified. + + File download ------------- @@ -82,13 +92,14 @@ used to inform the user while progress occurs and when the download has finished. -File monitor ------------- +Ecore Con +--------- -Using the :py:class:`FileMonitor<efl.ecore.FileMonitor>` class you can monitor -a directory for changes, a single calback will be called when events occur. -Events will be generatd everytime a file or directory (that live in the -give path) is created/deleted/modified. +The ecore_con module provide various utilities to perform different network +related tasks. Everything provided in a fully async way. Most notable are the +:class:`efl.ecore_con.Lookup` class to perform DNS requests, the +:class:`efl.ecore_con.Url` class to perform HTTP requests and the +:class:`efl.ecore_con.Server` class to implement your own server. @@ -99,6 +110,7 @@ API Reference :titlesonly: module-ecore + module-ecore_con Inheritance diagram diff --git a/doc/ecore/module-ecore_con.rst b/doc/ecore/module-ecore_con.rst new file mode 100644 index 0000000..c756d90 --- /dev/null +++ b/doc/ecore/module-ecore_con.rst @@ -0,0 +1,5 @@ + +.. automodule:: efl.ecore_con + :exclude-members: Url, EventUrlComplete, EventUrlProgress, EventUrlData, + Lookup, ConEventFilter + diff --git a/efl/ecore/efl.ecore_events.pxi b/efl/ecore/efl.ecore_events.pxi index 880cc7e..d3f739d 100644 --- a/efl/ecore/efl.ecore_events.pxi +++ b/efl/ecore/efl.ecore_events.pxi @@ -29,6 +29,11 @@ cdef object _event_mapping_register(int type, cls): cdef object _event_mapping_unregister(int type): _event_type_mapping.pop(type) +cdef object _event_mapping_get(int type): + if not type in _event_type_mapping: + raise ValueError("event type '%d' not registered." % type) + return _event_type_mapping.get(type) + cdef Eina_Bool event_handler_cb(void *data, int type, void *event) with gil: cdef EventHandler handler @@ -65,6 +70,9 @@ cdef class Event(object): cdef int _set_obj(self, void *obj) except 0: raise NotImplementedError("Event._set_obj() not implemented.") + cdef object _get_obj(self): + raise NotImplementedError("Event._get_obj() not implemented.") + cdef class EventHandler(object): def __init__(self, int type, func, *args, **kargs): diff --git a/efl/ecore_con/efl.ecore_con.pxd b/efl/ecore_con/efl.ecore_con.pxd new file mode 100644 index 0000000..04ec4a6 --- /dev/null +++ b/efl/ecore_con/efl.ecore_con.pxd @@ -0,0 +1,179 @@ +# Copyright (C) 2007-2015 various contributors (see AUTHORS) +# +# This file is part of Python-EFL. +# +# Python-EFL is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Python-EFL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. + + +from efl.eina cimport * +from efl.c_eo cimport Eo as cEo +from efl.eo cimport Eo, object_from_instance +from efl.ecore cimport Ecore_Event_Handler, Event +from efl.utils.conversions cimport _ctouni, eina_list_strings_to_python_list + + + +cdef extern from "Ecore_Con.h": + + # where is this defined in real ? + cdef struct sockaddr: + pass + + # defines + int ECORE_CON_EVENT_URL_COMPLETE + int ECORE_CON_EVENT_URL_PROGRESS + int ECORE_CON_EVENT_URL_DATA + + # typedefs + ctypedef cEo Ecore_Con_Url + + ctypedef struct Ecore_Con_Event_Url_Progress_SubParam: + double total + double now + + ctypedef struct Ecore_Con_Event_Url_Progress: + Ecore_Con_Url *url_con + Ecore_Con_Event_Url_Progress_SubParam down + Ecore_Con_Event_Url_Progress_SubParam up + + ctypedef struct Ecore_Con_Event_Url_Data: + Ecore_Con_Url *url_con + int size + unsigned char *data + + ctypedef struct Ecore_Con_Event_Url_Complete: + Ecore_Con_Url *url_con + int status + + ctypedef void (*Ecore_Con_Dns_Cb)(const char *canonname, const char *ip, + sockaddr *addr, int addrlen, void *data) + + # enums + cpdef enum Ecore_Con_Type: + ECORE_CON_LOCAL_USER + ECORE_CON_LOCAL_SYSTEM + ECORE_CON_LOCAL_ABSTRACT + ECORE_CON_REMOTE_TCP + ECORE_CON_REMOTE_MCAST + ECORE_CON_REMOTE_UDP + ECORE_CON_REMOTE_BROADCAST + ECORE_CON_REMOTE_NODELAY + ECORE_CON_REMOTE_CORK + ECORE_CON_USE_SSL2 + ECORE_CON_USE_SSL3 + ECORE_CON_USE_TLS + ECORE_CON_USE_MIXED + ECORE_CON_LOAD_CERT + ECORE_CON_NO_PROXY + ECORE_CON_SOCKET_ACTIVATE + ctypedef enum Ecore_Con_Type: + pass + + cpdef enum Ecore_Con_Url_Time: + ECORE_CON_URL_TIME_NONE + ECORE_CON_URL_TIME_IFMODSINCE + ECORE_CON_URL_TIME_IFUNMODSINCE + ctypedef enum Ecore_Con_Url_Time: + pass + + cpdef enum Ecore_Con_Url_Http_Version: + ECORE_CON_URL_HTTP_VERSION_1_0 + ECORE_CON_URL_HTTP_VERSION_1_1 + ctypedef enum Ecore_Con_Url_Http_Version: + pass + + # functions + int ecore_con_init() + int ecore_con_shutdown() +# Ecore_Con_Server *ecore_con_server_connect(Ecore_Con_Type type, const char *name, int port, const void *data) + + int ecore_con_url_init() + int ecore_con_url_shutdown() + void ecore_con_url_pipeline_set(Eina_Bool enable) + Eina_Bool ecore_con_url_pipeline_get() + Eina_Bool ecore_con_lookup(const char *name, Ecore_Con_Dns_Cb done_cb, const void *data) + + Ecore_Con_Url *ecore_con_url_new(const char *url) + void ecore_con_url_free(Ecore_Con_Url *url_obj) + Ecore_Con_Url * ecore_con_url_custom_new(const char *url, const char *custom_request) + void ecore_con_url_verbose_set(Ecore_Con_Url *url_con, Eina_Bool verbose) + Eina_Bool ecore_con_url_http_version_set(Ecore_Con_Url *url_con, Ecore_Con_Url_Http_Version version) + void ecore_con_url_timeout_set(Ecore_Con_Url *url_con, double timeout) + int ecore_con_url_status_code_get(Ecore_Con_Url *url_con) + Eina_Bool ecore_con_url_get(Ecore_Con_Url *url_con) + Eina_Bool ecore_con_url_head(Ecore_Con_Url *url_con) + Eina_Bool ecore_con_url_post(Ecore_Con_Url *url_con, const void *data, long length, const char *content_type) + Eina_Bool ecore_con_url_ftp_upload(Ecore_Con_Url *url_con, const char *filename, const char *user, const char *passwd, const char *upload_dir) + void ecore_con_url_ftp_use_epsv_set(Ecore_Con_Url *url_con, Eina_Bool use_epsv) + + Eina_Bool ecore_con_url_url_set(Ecore_Con_Url *obj, const char *url) + const char *ecore_con_url_url_get(const Ecore_Con_Url *obj) + void ecore_con_url_fd_set(Ecore_Con_Url *url_con, int fd) + + void ecore_con_url_additional_header_add(Ecore_Con_Url *url_con, const char *key, const char *value) + void ecore_con_url_additional_headers_clear(Ecore_Con_Url *url_con) + const Eina_List *ecore_con_url_response_headers_get(Ecore_Con_Url *url_con) + int ecore_con_url_received_bytes_get(Ecore_Con_Url *url_con) + Eina_Bool ecore_con_url_httpauth_set(Ecore_Con_Url *url_con, const char *username, const char *password, Eina_Bool safe) + void ecore_con_url_time(Ecore_Con_Url *url_con, Ecore_Con_Url_Time time_condition, double timestamp) + + void ecore_con_url_cookies_init(Ecore_Con_Url *url_con) + void ecore_con_url_cookies_clear(Ecore_Con_Url *url_con) + void ecore_con_url_cookies_session_clear(Ecore_Con_Url *url_con) + void ecore_con_url_cookies_ignore_old_session_set(Ecore_Con_Url *url_con, Eina_Bool ignore) + void ecore_con_url_cookies_file_add(Ecore_Con_Url *url_con, const char *file_name) + Eina_Bool ecore_con_url_cookies_jar_file_set(Ecore_Con_Url *url_con, const char *cookiejar_file) + void ecore_con_url_cookies_jar_write(Ecore_Con_Url *url_con) + + void ecore_con_url_ssl_verify_peer_set(Ecore_Con_Url *url_con, Eina_Bool verify) + int ecore_con_url_ssl_ca_set(Ecore_Con_Url *url_con, const char *ca_path) + + Eina_Bool ecore_con_url_proxy_set(Ecore_Con_Url *url_con, const char *proxy) + Eina_Bool ecore_con_url_proxy_username_set(Ecore_Con_Url *url_con, const char *username) + Eina_Bool ecore_con_url_proxy_password_set(Ecore_Con_Url *url_con, const char *password) + + +cdef class Url(Eo): + pass + +cdef class Lookup(object): + cdef object done_cb + cdef tuple args + cdef dict kargs + +cdef class EventUrlComplete(Event): + cdef readonly Url url + cdef readonly int status + + +cdef class EventUrlProgress(Event): + cdef readonly Url url + cdef readonly double down_total + cdef readonly double down_now + cdef readonly double up_total + cdef readonly double up_now + + +cdef class EventUrlData(Event): + cdef readonly Url url + cdef readonly int size + cdef readonly bytes data # TODO :/ + + +cdef class ConEventFilter(object): + cdef dict callbacks + cdef dict handlers + cdef callback_add(self, int ev_type, Eo obj, object func, tuple args, dict kargs) + cdef callback_del(self, int ev_type, Eo obj, object func, tuple args, dict kargs) + cdef callback_del_full(self, Eo obj) diff --git a/efl/ecore_con/efl.ecore_con.pyx b/efl/ecore_con/efl.ecore_con.pyx new file mode 100644 index 0000000..2d2f094 --- /dev/null +++ b/efl/ecore_con/efl.ecore_con.pyx @@ -0,0 +1,315 @@ +# Copyright (C) 2007-2015 various contributors (see AUTHORS) +# +# This file is part of Python-EFL. +# +# Python-EFL is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Python-EFL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. + +""" + +:mod:`efl.ecore_con` Module +########################### + +The ecore_con module provide various utilities to perform different network +related tasks. Everything in a full async ecore way. Most notable are the +:class:`Lookup` class to perform DNS requests, the :class:`Url` class to +perform HTTP requests and the :class:`Server` class to implement your own +server. + +Don't forget about the :class:`efl.ecore.FileDownload` class if what you need +is just to fetch some data to file. + + +Classes +======= + +.. toctree:: + + class-lookup.rst + class-url.rst + + + +Enumerations +============ + +.. _Ecore_Con_Type: + +Ecore Con Type +-------------- + +Types for an ecore_con client/server object. + +A correct way to set this type is with an ECORE_CON_$TYPE, optionally OR'ed +with an ECORE_CON_$USE if encryption is desired, and LOAD_CERT if the +previously loaded certificate should be used. + +.. data:: ECORE_CON_LOCAL_USER + + Socket in ~/.ecore. + +.. data:: ECORE_CON_LOCAL_SYSTEM + + Socket in /tmp. + +.. data:: ECORE_CON_LOCAL_ABSTRACT + + Abstract socket. + +.. data:: ECORE_CON_REMOTE_TCP + + Remote server using TCP. + + +.. data:: ECORE_CON_REMOTE_MCAST + + Remote multicast server. + + +.. data:: ECORE_CON_REMOTE_UDP + + Remote server using UDP. + +.. data:: ECORE_CON_REMOTE_BROADCAST + + Remote broadcast using UDP. + +.. data:: ECORE_CON_REMOTE_NODELAY + + Remote connection sending packets immediately. + +.. data:: ECORE_CON_REMOTE_CORK + + Remote connection sending data in large chunks. (only on linux) + +.. data:: ECORE_CON_USE_SSL2 + + Use SSL2: UNSUPPORTED. + +.. data:: ECORE_CON_USE_SSL3 + + Use SSL3: UNSUPPORTED. + +.. data:: ECORE_CON_USE_TLS + + Use TLS. + +.. data:: ECORE_CON_USE_MIXED + + Use both TLS and SSL3. + +.. data:: ECORE_CON_LOAD_CERT + + Attempt to use the loaded certificate. + +.. data:: ECORE_CON_NO_PROXY + + Disable all types of proxy on the server. + + +.. _Ecore_Con_Url_Time: + +Ecore Con Url Time +------------------ + +The type of condition to use when making an HTTP request dependent on time, so +that headers such as "If-Modified-Since" are used. + +.. data:: ECORE_CON_URL_TIME_NONE + + Do not place time restrictions on the HTTP requests. + +.. data:: ECORE_CON_URL_TIME_IFMODSINCE + + Add the "If-Modified-Since" HTTP header, so that the request is performed by + the server only if the target has been modified since the time value passed + to it in the request. + +.. data:: ECORE_CON_URL_TIME_IFUNMODSINCE + + Add the "If-Unmodified-Since" HTTP header, so that the request is performed + by the server only if the target has NOT been modified since the time value + passed to it in the request. + + +.. _Ecore_Con_Url_Http_Version: + +Ecore Con Url Http Version +-------------------------- + +The http version to use. + +.. data:: ECORE_CON_URL_HTTP_VERSION_1_0 + + HTTP version 1.0. + +.. data:: ECORE_CON_URL_HTTP_VERSION_1_1 + + HTTP version 1.1 (default) + + +Module level functions +====================== + +""" + +from libc.stdint cimport uintptr_t +from cpython cimport PyUnicode_AsUTF8String, Py_INCREF, Py_DECREF + +import traceback +import atexit + + +from efl.ecore cimport _event_mapping_register, _event_mapping_get, \ + ecore_event_handler_add, ecore_event_handler_del + + +cdef int _con_events_registered = 0 + + +def init(): + """Initialize the Ecore Con library + + .. note:: + You never need to call this function, it is automatically called when + the module is imported. + + .. versionadded:: 1.17 + + """ + + ecore_con_init() + ecore_con_url_init() + + global _con_events_registered + if _con_events_registered == 0: + _event_mapping_register(ECORE_CON_EVENT_URL_COMPLETE, EventUrlComplete) + _event_mapping_register(ECORE_CON_EVENT_URL_PROGRESS, EventUrlProgress) + _event_mapping_register(ECORE_CON_EVENT_URL_DATA, EventUrlData) + _con_events_registered = 1 + + +def shutdown(): + """Shuts down the Ecore Con library. + + .. note:: + You never need to call this function, it is automatically called atexit. + + .. versionadded:: 1.17 + + """ + ecore_con_url_shutdown() + ecore_con_shutdown() + + +cdef Eina_Bool _con_event_filter_cb(void *data, int ev_type, void *ev) with gil: + cdef: + ConEventFilter filter = <ConEventFilter>data + object event_cls + Event py_event + list cbs + Eo obj + object func + tuple args + dict kargs + + # create correct "EventAbc" python object, using the global mapping + event_cls = _event_mapping_get(ev_type) + if event_cls: + py_event = event_cls() + py_event._set_obj(ev) + + # do we have callbacks for this object/event ? + try: + obj = py_event._get_obj() + cbs = filter.callbacks.get(ev_type).get(obj) + except: + cbs = None + + if cbs: + cbs = cbs[:] # copy, so we can change filter.callbacks + for func, args, kargs in cbs: + try: + func(py_event, *args, **kargs) + except Exception: + traceback.print_exc() + + return 1 # always return true, no matter what + +cdef class ConEventFilter(object): + + """ + self.callbacks = { + EV_TYPE: { + objX: [(cb,args,kargs), ... ] + objY: [(cb,args,kargs), ... ] + }, + ... + } + """ + + def __cinit__(self): + self.callbacks = {} + self.handlers = {} + + cdef callback_add(self, int ev_type, Eo obj, object func, tuple args, dict kargs): + # store the function in the callbacks dict + if not ev_type in self.callbacks: + self.callbacks[ev_type] = {} + if not obj in self.callbacks[ev_type]: + self.callbacks[ev_type][obj] = [] + self.callbacks[ev_type][obj].append((func,args,kargs)) + + # connect a single ecore signal, one per event_type + cdef Ecore_Event_Handler* ee + if not ev_type in self.handlers: + ee = ecore_event_handler_add(ev_type, _con_event_filter_cb, + <void *>self) + self.handlers[ev_type] = <uintptr_t><void *>ee + + cdef callback_del(self, int ev_type, Eo obj, object func, tuple args, dict kargs): + try: + self.callbacks[ev_type][obj].remove((func, args, kargs)) + except ValueError: + raise ValueError( + "callback is not registered: %s, args=%s, kargs=%s" % + (func, args, kargs)) + + # can delete the ecore handler? + if self.callbacks.get(ev_type) and self.callbacks.get(ev_type).get(obj): + return + if ev_type in self.handlers: + handler = self.handlers.pop(ev_type) + ecore_event_handler_del(<Ecore_Event_Handler *><uintptr_t>handler) + + cdef callback_del_full(self, Eo obj): + for ev_type in self.callbacks: + if obj in self.callbacks[ev_type]: + # remove all the cbs for the obj + self.callbacks[ev_type].pop(obj, None) + + # can delete the ecore handler? + if len(self.callbacks[ev_type]) < 1 and ev_type in self.handlers: + handler = self.handlers.pop(ev_type) + ecore_event_handler_del(<Ecore_Event_Handler *><uintptr_t>handler) + +# name suggestions are welcome for this unusual "singleton" instance +cdef ConEventFilter GEF = ConEventFilter() + + +include "efl.ecore_con_lookup.pxi" +include "efl.ecore_con_url.pxi" + + +init() +atexit.register(shutdown) diff --git a/efl/ecore_con/efl.ecore_con_lookup.pxi b/efl/ecore_con/efl.ecore_con_lookup.pxi new file mode 100644 index 0000000..e002916 --- /dev/null +++ b/efl/ecore_con/efl.ecore_con_lookup.pxi @@ -0,0 +1,88 @@ +# Copyright (C) 2007-2015 various contributors (see AUTHORS) +# +# This file is part of Python-EFL. +# +# Python-EFL is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Python-EFL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. + + +cdef void _con_dns_lookup_cb(const char *canonname, const char *ip, sockaddr *sockaddr, int addrlen, void *data) with gil: + cdef Lookup self = <Lookup>data + + try: + # TODO read sockaddr and replace the placeholder None with something + # more usefull from the sockaddr struct. + self.done_cb(_ctouni(canonname), _ctouni(ip), None, *self.args, **self.kargs) + except Exception: + traceback.print_exc() + + Py_DECREF(self) + + +cdef class Lookup(object): + def __init__(self, name, done_cb, *args, **kargs): + """Lookup() + + A simple class to perform asynchronous DNS lookups. + + :param string name: The hostname to query + :param callable done_cb: The function to call when done + :param \*args: Any other arguments will be passed back in ``done_cb`` + :param \**kargs: Any other keywords arguments will be passed back in ``done_cb`` + + .. versionadded:: 1.17 + + Just create an instance and give a callback function to be called + when the operation is complete. + + This class performs a DNS lookup on the hostname specified by + `name`, then calls `done_cb` with the result and the data given as + parameter. The result will be given to the done_cb as follows: + + **expected `done_cb` signature**:: + + func(canonname, ip, sockaddr) + + where: + * **canonname** (string) is the canonical domain name + * **ip** (string) is the recolved ip address + * **sockaddr** (None) is a placeholder for future expansion + + + + **Usage example**:: + + import ecore_con + + def done_cb(canonname, ip, sockaddr): + print(canonname) + print(ip) + + ecore_con.Lookup('example.com', done_cb) + + + """ + + if not callable(done_cb): + raise TypeError("Parameter 'done_cb' must be callable") + + self.done_cb = done_cb + self.args = args + self.kargs = kargs + + if isinstance(name, unicode): name = PyUnicode_AsUTF8String(name) + ecore_con_lookup(<const char *>name if name is not None else NULL, + _con_dns_lookup_cb, <void*>self) + + Py_INCREF(self) + diff --git a/efl/ecore_con/efl.ecore_con_url.pxi b/efl/ecore_con/efl.ecore_con_url.pxi new file mode 100644 index 0000000..a48a6f8 --- /dev/null +++ b/efl/ecore_con/efl.ecore_con_url.pxi @@ -0,0 +1,827 @@ +# Copyright (C) 2007-2015 various contributors (see AUTHORS) +# +# This file is part of Python-EFL. +# +# Python-EFL is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Python-EFL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. + +# from efl.ecore cimport EventHandler +# from efl.utils.conversions cimport _ctouni as _charp_to_str + + + +cdef class EventUrlProgress(Event): + """Represents Ecore_Con_Event_Url_Progress event from C-api. + + This event notifies the progress of the current operation. + + attributes: + * url (:class:`Url`): the object that generate the event + * down_total(double): total size of the downloading data (in bytes) + * down_now(double): current size of the downloading data (in bytes) + * up_total(double): total size of the uploading data (in bytes) + * up_now(double): current size of the uploading data (in bytes) + + """ + cdef int _set_obj(self, void *ev) except 0: + cdef Ecore_Con_Event_Url_Progress *event + event = <Ecore_Con_Event_Url_Progress*>ev + + self.url = object_from_instance(event.url_con) + self.down_total = event.down.total + self.down_now = event.down.now + self.up_total = event.up.total + self.up_now = event.up.now + return 1 + + cdef object _get_obj(self): + return self.url + +cdef class EventUrlComplete(Event): + """Represents Ecore_Con_Event_Url_Complete event from C-api. + + This event notifies the operation is completed. + + attributes: + * url (:class:`Url`): the object that generate the event + * status(int): HTTP status code of the operation (200, 404, 401, etc.) + + """ + cdef int _set_obj(self, void *ev) except 0: + cdef Ecore_Con_Event_Url_Complete *event + event = <Ecore_Con_Event_Url_Complete*>ev + + self.url = object_from_instance(event.url_con) + self.status = event.status + return 1 + + cdef object _get_obj(self): + return self.url + +cdef class EventUrlData(Event): + """Represents Ecore_Con_Event_Url_Data event from C-api. + + This event hold the data while the are received. + + .. note:: + The data attribute is a raw series of bytes, map to ``str`` in python2 + and ``bytes`` in python3. + + attributes: + * url (:class:`Url`): the object that generate the event + * size(int): the size of the current received data (in bytes) + * data(bytes): the data received on this event + + """ + cdef int _set_obj(self, void *ev) except 0: + cdef Ecore_Con_Event_Url_Data *event + event = <Ecore_Con_Event_Url_Data*>ev + + self.url = object_from_instance(event.url_con) + self.size = event.size + self.data = event.data[:event.size] #raw string copy + return 1 + + cdef object _get_obj(self): + return self.url + + +cdef class Url(Eo): + """ + + Utility class to make it easy to perform http requests (POST, GET, etc). + + .. versionadded:: 1.17 + + Brief usage: + 1. Create an :class:`Url` object with ecore_con.Url('myurl') + 2. Register object callbacks using :func:`on_complete_event_add`, + :func:`on_progress_event_add` and :func:`on_data_event_add` to + receive the response, e.g. for HTTP/FTP downloads. + 3. Perform the operation with :func:`get`, :func:`head` and :func:`post` + + If it's necessary use the :attr:`url` property. to change the object url. + + .. note:: + It is good practice to reuse :class:`Url` objects wherever possible, + but bear in mind that each one can only perform one operation at a + time. You need to wait for the complete event before re-using or + destroying the object. + + .. warning:: + It is **really important** to call the :func:`delete()` method as soon + as you have finished with your object, as it automatically remove all + the registered events for you, that will otherwise continue to use + resources. + + Basic usage examples:: + + # HTTP GET + u = ecore.Url("http://www.google.com") + u.get() + + # HTTP POST + u = ecore.Url('https://httpbin.org/post') + u.post(b'my data to post', 'text/txt') + + # FTP download + u = ecore.Url("ftp://ftp.example.com/pub/myfile") + u.get() + + # FTP upload as ftp://ftp.example.com/file + u = ecore.Url("ftp://ftp.example.com") + u.ftp_upload("/tmp/file", "user", "pass", None) + + # FTP upload as ftp://ftp.example.com/dir/file + u = ecore.Url("ftp://ftp.example.com") + u.ftp_upload("/tmp/file", "user", "pass", "dir") + + To actually make something usefull with your request you will need to + connect the :class:`EventUrlComplete`, :class:`EventUrlProgress` and + :class:`EventUrlData` events using the :func:`on_complete_event_add` and + friends functions. + + A more complete example:: + + from efl import ecore + + def on_data(event): + print("data: " + str(event.data[:80])) + # do something here with the received data + + def on_progress(event): + # print(event) + print("received %d on a total of %d bytes" % ( + event.down_now, event.down_total)) + + def on_complete(event): + # print(event) + print("http result: %d" % event.status) + print("Total received bytes: %d" % event.url.received_bytes) + + u.delete() # don't forget to delete !! + + u = ecore.Url('http://www.google.com', verbose=False) + u.on_data_event_add(on_data) + u.on_progress_event_add(on_progress) + u.on_complete_event_add(on_complete) + u.get() + + ecore.main_loop_begin() + + If you need to save the received data to a file use the :attr:`fd` + property, as:: + + fd = open('/tmp/tmpMxBtta', 'w') + u = ecore.Url('http://example.com', fd=fd.fileno()) + u.get() + + .. seealso:: + If you just need to download a file please consider using the + simpler :class:`efl.ecore.FileDownload` class instead. + + .. seealso:: + The ecore module level functions :func:`url_pipeline_set` and + :func:`url_pipeline_get` to enable HTTP 1.1 pipelining. + + """ + def __init__(self, url, custom_request=None, **kargs): + """Url(...) + + :param url: URL that will receive requests. + :type url: string + :param custom_request: Custom request (e.g. GET, POST, HEAD, PUT, HEAD, + SUBSCRIBE and other obscure HTTP requests) + :type custom_request: string + :param \**kwargs: All the remaining keyword arguments are interpreted + as properties of the instance + + .. versionadded:: 1.17 + + """ + if isinstance(url, unicode): url = PyUnicode_AsUTF8String(url) + if custom_request is None: + self._set_obj(ecore_con_url_new( + <const char *>url if url is not None else NULL)) + else: + if isinstance(custom_request, unicode): + custom_request = PyUnicode_AsUTF8String(custom_request) + self._set_obj(ecore_con_url_custom_new( + <const char *>url if url is not None else NULL, + <const char *>custom_request if custom_request is not None else NULL)) + + self._set_properties_from_keyword_args(kargs) + + def __repr__(self): + return "%s(url=%s)" % (self.__class__.__name__, self.url) + + def delete(self): + """Delete the :class:`Url` object and free all used resources. + + .. note:: + This is **really important** to call as soon as you have finished + with your object, as it automatically remove all the registered + events. That will otherwise continue to use resources. + + """ + GEF.callback_del_full(self) + ecore_con_url_free(self.obj) + + property fd: + """Set up a file to have response data written into. + + This attr can be used to easily setup a file where the downloaded data + will be saved. + + Note that :class:`EventUrlData` events will not be emitted if a file + has been set to receive the response data. + + .. seealso:: + If you just need to download a file please consider using the + simpler :class:`efl.ecore.FileDownload` class instead. + + :type: int (**writeonly**) + + """ + def __set__(self, int fd): + ecore_con_url_fd_set(self.obj, fd) + + def get(self): + """Send a GET request. + + The request is performed immediately, but you need to setup event + handlers with :func:`on_complete_event_add` or + :func:`on_complete_event_add` to get more information about its result. + + :return: ``True`` on success, ``False`` on error. + + """ + return bool(ecore_con_url_get(self.obj)) + + def head(self): + """Send a HEAD request. + + The request is performed immediately, but you need to setup event + handlers with :func:`on_complete_event_add` or + :func:`on_complete_event_add` to get more information about its result. + + :return: ``True`` on success, ``False`` on error. + + """ + return bool(ecore_con_url_head(self.obj)) + + def post(self, bytes data, content_type): + """Send a post request. + + The request is performed immediately, but you need to setup event + handlers with :func:`on_complete_event_add` or + :func:`on_complete_event_add` to get more information about its result. + + :param data: Payload (data sent on the request). Can be ``None``. + :type data: bytes + :param content_type: Content type of the payload (e.g. `text/xml`). + Can be ``None``. + :type content_type: string + + :return: ``True`` on success, ``False`` on error. + + """ + if isinstance(content_type, unicode): + content_type = PyUnicode_AsUTF8String(content_type) + return bool(ecore_con_url_post(self.obj, + <const void*><const char *>data if data is not None else NULL, + len(data), + <const char *>content_type if content_type is not None else NULL)) + + def time(self, Ecore_Con_Url_Time time_condition, double timestamp): + """Whether HTTP requests should be conditional, dependent on + modification time. + + This function may set the header `If-Modified-Since` or + `If-Unmodified-Since`, depending on the value of time_condition, with + the value timestamp. + + :param time_condition: Condition to use for HTTP requests. + :type time_condition: :ref:`Ecore_Con_Url_Time` + :param timestamp: Time since 1 Jan 1970 to use in the condition. + :type timestamp: double + + """ + ecore_con_url_time(self.obj, time_condition, timestamp) + + def ftp_upload(self, filename, user, passwd, upload_dir): + """Upload a file to an ftp site. + + :param string filename: The path to the file to send + :param string user: The username to log in with + :param string passwd: The password to log in with + :param string upload_dir: The directory to which the file will upload + + :return: ``True`` on success, ``False`` otherwise. + :rtype: bool + + """ + if isinstance(filename, unicode): filename = PyUnicode_AsUTF8String(filename) + if isinstance(user, unicode): user = PyUnicode_AsUTF8String(user) + if isinstance(passwd, unicode): passwd = PyUnicode_AsUTF8String(passwd) + if isinstance(upload_dir, unicode): upload_dir = PyUnicode_AsUTF8String(upload_dir) + return bool(ecore_con_url_ftp_upload(self.obj, + <const char *>filename if filename is not None else NULL, + <const char *>user if user is not None else NULL, + <const char *>passwd if passwd is not None else NULL, + <const char *>upload_dir if upload_dir is not None else NULL)) + + property ftp_use_epsv: + """Enable or disable EPSV extension. + + :type: bool (**writeonly**) + + """ + def __set__(self, bint use_epsv): + ecore_con_url_ftp_use_epsv_set(self.obj, use_epsv) + + def cookies_init(self): + """Enable the cookie engine for subsequent HTTP requests. + + After this function is called, cookies set by the server in HTTP + responses will be parsed and stored, as well as sent back to the server + in new HTTP requests. + + """ + ecore_con_url_cookies_init(self.obj) + + def cookies_clear(self): + """Clear currently loaded cookies. + + The cleared cookies are removed and will not be sent in subsequent HTTP + requests, nor will they be written to the cookiejar file set via + :attr:`cookies_jar_file`. + + .. note:: + This function will initialize the cookie engine if it has not been + initialized yet. The cookie files set by + :func:`cookies_file_add` aren't loaded immediately, just + when the request is started. Thus, if you ask to clear the cookies, + but has a file already set by that function, the cookies will then + be loaded and you will have old cookies set. In order to don't have + any old cookie set, you need to don't call + :func:`cookies_file_add` ever on the :class:`Url` class, and + call this function to clear any cookie set by a previous request on + this handler. + + """ + ecore_con_url_cookies_clear(self.obj) + + def cookies_session_clear(self): + """Clear currently loaded session cookies. + + Session cookies are cookies with no expire date set, which usually + means they are removed after the current session is closed. + + The cleared cookies are removed and will not be sent in subsequent HTTP + requests, nor will they be written to the cookiejar file set via + :attr:`cookies_jar_file`. + + .. note:: + This function will initialize the cookie engine if it has not been + initialized yet. The cookie files set by + :func:`cookies_file_add` aren't loaded immediately, just + when the request is started. Thus, if you ask to clear the session + cookies, but has a file already set by that function, the session + cookies will then be loaded and you will have old cookies set. In + order to don't have any old session cookie set, you need to don't + call :func:`cookies_file_add` ever on the :class:`Url` class, and + call this function to clear any session cookie set by a previous + request on this handler. An easier way to don't use old session + cookies is by using the function + :attr:`cookies_ignore_old_session`. + + """ + ecore_con_url_cookies_session_clear(self.obj) + + def cookies_file_add(self, file_name): + """Add a file to the list of files from which to load cookies. + + Files must contain cookies defined according to two possible formats: + * HTTP-style header ("Set-Cookie: ..."). + * Netscape/Mozilla cookie data format. + + Cookies will only be read from this file. If you want to save cookies + to a file, use :attr:`cookies_jar_file`. Also notice that + this function supports the both types of cookie file cited above, while + :attr:`cookies_jar_file` will save only in the Netscape/Mozilla's + format. + + Please notice that the file will not be read immediately, but rather + added to a list of files that will be loaded and parsed at a later + time. + + .. note:: + This function will initialize the cookie engine if it has not been + initialized yet. + + :param string file_name: Name of the file that will be added to the list. + + """ + if isinstance(file_name, unicode): + file_name = PyUnicode_AsUTF8String(file_name) + ecore_con_url_cookies_file_add(self.obj, + <const char *>file_name if file_name is not None else NULL) + + property cookies_jar_file: + """The name of the file to which all current cookies will be written + when either cookies are flushed or Ecore_Con is shut down. + + Cookies are written following Netscape/Mozilla's data format, also + known as cookie-jar. + + Cookies will only be saved to this file. If you need to read cookies + from a file, use ecore_con_url_cookies_file_add() instead. + + .. note:: + This function will initialize the cookie engine if it has not been + initialized yet. + + .. seealso:: :func:`cookies_jar_write` + + :type: string (**writeonly**) + + """ + def __set__(self, cookiejar_file): + if isinstance(cookiejar_file, unicode): + cookiejar_file = PyUnicode_AsUTF8String(cookiejar_file) + ecore_con_url_cookies_jar_file_set(self.obj, + <const char *>cookiejar_file if cookiejar_file is not None else NULL) + if isinstance(cookiejar_file, unicode): + cookiejar_file = PyUnicode_AsUTF8String(cookiejar_file) + ecore_con_url_cookies_jar_file_set(self.obj, + <const char *>cookiejar_file if cookiejar_file is not None else NULL) + + def cookies_jar_write(self): + """Write all current cookies to the cookie jar immediately. + + A cookie-jar file must have been previously set by + :attr:`cookies_jar_file`, otherwise nothing will be done. + + .. note:: + This function will initialize the cookie engine if it has not been + initialized yet. + + .. seealso:: :attr:`cookies_jar_file` + + """ + ecore_con_url_cookies_jar_write(self.obj) + + property cookies_ignore_old_session: + """Control whether session cookies from previous sessions shall be loaded. + + Session cookies are cookies with no expire date set, which usually + means they are removed after the current session is closed. + + By default, when Ecore_Con_Url loads cookies from a file, all cookies + are loaded, including session cookies, which, most of the time, were + supposed to be loaded and valid only for that session. + + If ignore is set to ``True``, when Ecore_Con_Url loads cookies from + the files passed to :func:`cookies_file_add`, session cookies + will not be loaded. + + :type: bool (**writeonly**) + + """ + def __set__(self, bint ignore): + ecore_con_url_cookies_ignore_old_session_set(self.obj, ignore) + + property ssl_verify_peer: + """Toggle libcurl's verify peer's certificate option. + + If this is ``True``, libcurl will verify the authenticity of the + peer's certificate, otherwise it will not. Default behavior of libcurl + is to check peer's certificate. + + :type: bool (**writeonly**) + + """ + def __set__(self, bint verify): + ecore_con_url_ssl_verify_peer_set(self.obj, verify) + + property ssl_ca: + """Set a custom CA to trust for SSL/TLS connections. + + Specify the path of a file (in PEM format) containing one or more CA + certificate(s) to use for the validation of the server certificate. + + This can also disable CA validation if set to ``None``. + However, the server certificate still needs to be valid for the + connection to succeed (i.e., the certificate must concern the server + the connection is made to). + + :type: string (**writeonly**) + + """ + def __set__(self, ca_path): + if isinstance(ca_path, unicode): + ca_path = PyUnicode_AsUTF8String(ca_path) + ecore_con_url_ssl_ca_set(self.obj, + <const char *>ca_path if ca_path is not None else NULL) + + property proxy: + """Set the HTTP proxy to use. + + The parameter is the host name or dotted IP address. To specify port + number in this string, append :[port] to the end of the host name. The + proxy string may be prefixed with [protocol]:// since any such prefix + will be ignored. The proxy's port number may optionally be specified + with the separate option. If not specified, libcurl will default to + using port 1080 for proxies. + + Set this to ``None`` to disable the usage of proxy. + + :type: string (**writeonly**) + + """ + def __set__(self, proxy): + if isinstance(proxy, unicode): proxy = PyUnicode_AsUTF8String(proxy) + ecore_con_url_proxy_set(self.obj, + <const char *>proxy if proxy is not None else NULL) + + property proxy_username: + """Username to use for proxy. + + If socks protocol is used for proxy, protocol should be socks5 and + above. + + :type: string (**writeonly**) + + """ + def __set__(self, user): + if isinstance(user, unicode): user = PyUnicode_AsUTF8String(user) + ecore_con_url_proxy_username_set(self.obj, + <const char *>user if user is not None else NULL) + + property proxy_password: + """Password to use for proxy. + + If socks protocol is used for proxy, protocol should be socks5 and + above. + + :type: string (**writeonly**) + + """ + def __set__(self, passwd): + if isinstance(passwd, unicode): passwd = PyUnicode_AsUTF8String(passwd) + ecore_con_url_proxy_username_set(self.obj, + <const char *>passwd if passwd is not None else NULL) + + property timeout: + """transfer timeout in seconds. + + The maximum time in seconds that you allow the :class:`Url` class + transfer operation to take. Normally, name lookups can take a + considerable time and limiting operations to less than a few minutes + risk aborting perfectly normal operations. + + :type: double (**writeonly**) + + """ + def __set__(self, double timeout): + ecore_con_url_timeout_set(self.obj, timeout) + + property http_version: + """The HTTP version used for the request. + + Can be :data:`ECORE_CON_URL_HTTP_VERSION_1_0` or + :data:`ECORE_CON_URL_HTTP_VERSION_1_1` + + :type: :ref:`Ecore_Con_Url_Http_Version` (**writeonly**) + + """ + def __set__(self, Ecore_Con_Url_Http_Version version): + ecore_con_url_http_version_set(self.obj, version) + + property status_code: + """The returned HTTP STATUS code. + + This is used to, at any time, try to return the status code for a + transmission. + + :type: int (**readonly**) + + """ + def __get__(self): + return ecore_con_url_status_code_get(self.obj) + + property url: + """Controls the URL to send the request to. + + :type: string + + """ + def __get__(self): + return _ctouni(ecore_con_url_url_get(self.obj)) + + def __set__(self, url): + if isinstance(url, unicode): url = PyUnicode_AsUTF8String(url) + ecore_con_url_url_set(self.obj, <const char *>url if url is not None else NULL) + + property verbose: + """Toggle libcurl's verbose output. + + If set to ``True``, libcurl will output a lot of verbose + information about its operations, which is useful for debugging. The + verbose information will be sent to stderr. + + :type: bool (**writeonly**) + + """ + def __set__(self, bint verbose): + ecore_con_url_verbose_set(self.obj, verbose) + + def additional_header_add(self, key, value): + """Add an additional header to the request connection object. + + Add an additional header (User-Agent, Content-Type, etc.) to the + request connection object. This addition will be valid for only one + :func:`get` or :func:`post` call. + + :param string key: Header key + :param string value: Header value + + Some functions like :func:`time` also add headers to the request. + + """ + if isinstance(key, unicode): key = PyUnicode_AsUTF8String(key) + if isinstance(value, unicode): value = PyUnicode_AsUTF8String(value) + ecore_con_url_additional_header_add(self.obj, + <const char *>key if key is not None else NULL, + <const char *>value if value is not None else NULL) + + def additional_headers_clear(self): + """Clean additional headers. + + Clean additional headers associated with a connection object + (previously added with :func:additional_header_add`). + + """ + ecore_con_url_additional_headers_clear(self.obj) + + property response_headers: + """The headers from last request sent. + + Retrieve a list containing the response headers. This function should + be used after an :class:`EventUrlComplete` event (headers should + normally be ready at that time). + + :type: list of strings (**readonly**) + + """ + def __get__(self): + return eina_list_strings_to_python_list( + ecore_con_url_response_headers_get(self.obj)) + + property received_bytes: + """The number of bytes received. + + Retrieve the number of bytes received on the last request of the + :class:`Url` object. + + :type: int (**readonly**) + + """ + def __get__(self): + return ecore_con_url_received_bytes_get(self.obj) + + def httpauth_set(self, username, password, bint safe): + """Set to use http auth, with given username and password + + :param string username: Username to use in authentication + :param string password: Password to use in authentication + :param bool safe: Whether to use "safer" methods (eg, NOT http basic auth) + + :return: ``True`` on success, ``False`` on error. + :rtype: bool + + .. warning:: Require libcurl >= 7.19.1 to work, otherwise will + always return ``False``. + + """ + if isinstance(username, unicode): + username = PyUnicode_AsUTF8String(username) + if isinstance(password, unicode): + password = PyUnicode_AsUTF8String(password) + return bool(ecore_con_url_httpauth_set(self.obj, + <const char *>username if username is not None else NULL, + <const char *>password if password is not None else NULL, + safe)) + + def on_complete_event_add(self, func, *args, **kargs): + """Adds event listener to know when the Url operation is completed. + + The given function will be called with the following signature:: + + func(event, *args, **kargs) + + The ``event`` parameter is an :class:`EventUrlComplete` instance. + + :see: :func:`on_complete_event_del` + + """ + GEF.callback_add(ECORE_CON_EVENT_URL_COMPLETE, self, func, args, kargs) + + def on_complete_event_del(self, func, *args, **kargs): + """Removes an event listener previously registered + + Parameters must match exactly the ones given in the + :func:`on_complete_event_add` call + + :raise ValueError: if parameters don't match an already + registered callback. + """ + GEF.callback_del(ECORE_CON_EVENT_URL_COMPLETE, self, func, args, kargs) + + def on_progress_event_add(self, func, *args, **kargs): + """Adds event listener to know the operation status progress. + + The given function will be called with the following signature:: + + func(event, *args, **kargs) + + The ``event`` parameter is an :class:`EventUrlProgress` instance. + + :see: :func:`on_progress_event_del` + + """ + GEF.callback_add(ECORE_CON_EVENT_URL_PROGRESS, self, func, args, kargs) + + def on_progress_event_del(self, func, *args, **kargs): + """Removes an event listener previously registered + + Parameters must match exactly the ones given in the + :func:`on_progress_event_add` call + + :raise ValueError: if parameters don't match an already + registered callback. + """ + GEF.callback_del(ECORE_CON_EVENT_URL_PROGRESS, self, func, args, kargs) + + def on_data_event_add(self, func, *args, **kargs): + """Adds event listener to collect the data while they are received. + + The given function will be called with the following signature:: + + func(event, *args, **kargs) + + The ``event`` parameter is an :class:`EventUrlData` instance. + + :see: :func:`on_data_event_del` + + """ + GEF.callback_add(ECORE_CON_EVENT_URL_DATA, self, func, args, kargs) + + def on_data_event_del(self, func, *args, **kargs): + """Removes an event listener previously registered + + Parameters must match exactly the ones given in the + :func:`on_data_event_add` call + + :raise ValueError: if parameters don't match an already + registered callback. + """ + GEF.callback_del(ECORE_CON_EVENT_URL_DATA, self, func, args, kargs) + + +def url_pipeline_set(bint enable): + """Enable or disable HTTP 1.1 pipelining. + + Pipelining allows to send one request after another one, without having to + wait for the reply of the first request. The respective replies are + received in the order that the requests were sent. + + .. warning:: Enabling this feature will be valid for all requests done + using ecore_con_url. + + .. versionadded:: 1.17 + + """ + ecore_con_url_pipeline_set(enable) + +def url_pipeline_get(): + """Is HTTP 1.1 pipelining enable ? + + :return: ``True`` if enable + + .. versionadded:: 1.17 + + """ + return bool(ecore_con_url_pipeline_get()) + diff --git a/examples/ecore/con/lookup.py b/examples/ecore/con/lookup.py new file mode 100755 index 0000000..db71402 --- /dev/null +++ b/examples/ecore/con/lookup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +from efl import ecore +from efl import ecore_con + + +def on_complete(canonname, ip): + print('Lookup completed') + print('Canonname: %s' % canonname) + print('Ip: %s' % ip) + + ecore.main_loop_quit() + +ecore_con.Lookup('example.com', on_complete) + +ecore.main_loop_begin() diff --git a/examples/ecore/con/url.py b/examples/ecore/con/url.py new file mode 100755 index 0000000..2757e6d --- /dev/null +++ b/examples/ecore/con/url.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +from efl import ecore +from efl import ecore_con + +def on_data(event): + print("data: " + str(event.data)) + # do something here with the received data + +def on_progress(event): + # print(event) + print("received %d on a total of %d bytes" % ( + event.down_now, event.down_total)) + +def on_complete(event): + # print(event) + print("http result: %d" % event.status) + print("Total received bytes: %d" % event.url.received_bytes) + + u.delete() # don't forget to delete !! + ecore.main_loop_quit() + +u = ecore_con.Url('http://www.example.com', verbose=False) +u.on_data_event_add(on_data) +u.on_progress_event_add(on_progress) +u.on_complete_event_add(on_complete) +u.get() + +ecore.main_loop_begin() diff --git a/include/efl.ecore.pxd b/include/efl.ecore.pxd index 3ceece5..bc3d385 100644 --- a/include/efl.ecore.pxd +++ b/include/efl.ecore.pxd @@ -371,6 +371,7 @@ cdef class EventHandler(object): cdef class Event(object): cdef int _set_obj(self, void *obj) except 0 + cdef object _get_obj(self) cdef class EventSignalUser(Event): @@ -444,5 +445,7 @@ cdef class FileMonitor: cdef object _exec_monitor(self, Ecore_File_Event event, const char *path) + cdef object _event_mapping_register(int type, cls) cdef object _event_mapping_unregister(int type) +cdef object _event_mapping_get(int type) diff --git a/setup.py b/setup.py index 5a3c6d2..36a50ee 100755 --- a/setup.py +++ b/setup.py @@ -110,8 +110,9 @@ class CleanGenerated(Command): pass def run(self): - for lib in ("eo", "evas", "ecore", "ecore_x", "edje", "emotion", - "elementary", "ethumb", "dbus_mainloop", "utils"): + for lib in ("eo", "evas", "ecore", "ecore_x", "ecore_con", "edje", + "emotion", "elementary", "ethumb", "dbus_mainloop", + "utils"): lib_path = os.path.join(script_path, "efl", lib) for root, dirs, files in os.walk(lib_path): for f in files: @@ -307,6 +308,19 @@ if set(("build", "build_ext", "install", "bdist", "sdist")) & set(sys.argv): eina_libs + evas_libs) ext_modules.append(ecore_ext) + # === Ecore Con === + ecore_con_cflags, ecore_con_libs = pkg_config('EcoreCon', 'ecore-con', + EFL_MIN_VER) + ecore_con_ext = Extension("ecore_con", + ["efl/ecore_con/efl.ecore_con" + module_suffix], + include_dirs=['include/'], + extra_compile_args=list(set(ecore_cflags + + ecore_file_cflags + + ecore_con_cflags)), + extra_link_args=ecore_libs + ecore_file_libs + + ecore_con_libs + eina_libs) + ext_modules.append(ecore_con_ext) + # === Ecore X === try: ecore_input_cflags, ecore_input_libs = pkg_config('EcoreInput', diff --git a/tests/ecore/test_11_con.py b/tests/ecore/test_11_con.py new file mode 100644 index 0000000..51eb634 --- /dev/null +++ b/tests/ecore/test_11_con.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +import unittest +import os +import tempfile + +from efl import ecore, ecore_con + + +class TestCon(unittest.TestCase): + + def testLookup(self): + def _dns_complete(canonname, ip, sockaddr, arg1, my_karg): + self.assertEqual(canonname, 'google-public-dns-a.google.com.') + self.assertEqual(ip, '8.8.8.8') + self.assertEqual(arg1, "arg1") + self.assertEqual(my_karg, 1234) + ecore.main_loop_quit() + + ecore_con.Lookup('google-public-dns-a.google.com', + _dns_complete, "arg1", my_karg=1234) + ecore.main_loop_begin() + + def testUrl(self): + self.complete_counter = 0 + self.progress_counter = 0 + self.exit_counter = 3 # we expect 3 complete cb calls + self.test_url = 'http://www.example.com' + self.received_data = [] + + def _on_complete(event, add=1): + self.assertIsInstance(event, ecore_con.EventUrlComplete) + self.assertEqual(event.status, 200) # assume net is ok + self.assertEqual(event.url.url, self.test_url) + + self.complete_counter += add + self.exit_counter -= 1 + if self.exit_counter == 0: + ecore.main_loop_quit() + + def _on_progress(event, param1, two, one): + self.assertIsInstance(event, ecore_con.EventUrlProgress) + self.assertEqual(param1, 'param1') + self.assertEqual(one, 1) + self.assertEqual(two, 2) + self.progress_counter += 1 + + def _on_data(event): + self.assertIsInstance(event, ecore_con.EventUrlData) + self.received_data.append(event.data) + + u = ecore_con.Url(self.test_url) + self.assertIsInstance(u, ecore_con.Url) + + u.on_complete_event_add(_on_complete, add=100) + u.on_complete_event_add(_on_complete, add=10) + u.on_complete_event_del(_on_complete, add=100) #test event_del + u.on_complete_event_add(_on_complete, add=1) + u.on_complete_event_add(_on_complete, add=5) + + u.on_progress_event_add(_on_progress, 'param1', one=1, two=2) + u.on_data_event_add(_on_data) + + self.assertTrue(u.get()) #perform the GET request + + ecore.main_loop_begin() + + self.assertEqual(u.status_code, 200) # assume net is ok + self.assertEqual(self.complete_counter, 16) + self.assertTrue(self.progress_counter > 0) + + data = b''.join(self.received_data) + self.assertEqual(len(data), u.received_bytes) + + u.delete() + + def testUrlDelete(self): + + self.test_url1 = 'http://www.example.com' + self.test_url2 = 'http://www.google.com' + self.complete_counter = 0 + + def _on_complete1(event): + self.assertIsInstance(event, ecore_con.EventUrlComplete) + self.assertEqual(event.url.url, self.test_url1) + self.complete_counter += 1 + + def _on_complete2(event): + self.assertIsInstance(event, ecore_con.EventUrlComplete) + self.assertEqual(event.url.url, self.test_url2) + self.complete_counter += 10 + + u1 = ecore_con.Url(self.test_url1) + u1.on_complete_event_add(_on_complete1) + + u2 = ecore_con.Url(self.test_url2) + u2.on_complete_event_add(_on_complete2) + + u3 = ecore_con.Url(self.test_url1) + u3.on_complete_event_add(_on_complete1) + u3.get() + u3.delete() # the previous get will not run + + self.assertTrue(u1.get()) #perform the GET request + self.assertTrue(u2.get()) #perform the GET request + + ecore.Timer(2.5, lambda: ecore.main_loop_quit()) + ecore.main_loop_begin() + + self.assertEqual(u1.status_code, 200) # assume net is ok + self.assertEqual(u2.status_code, 200) # assume net is ok + self.assertEqual(self.complete_counter, 11) + + u1.delete() + u2.delete() + + def testUrlToFile(self): + self.test_url = 'http://www.example.com' + + def _on_complete(event): + ecore.main_loop_quit() + + fd, path = tempfile.mkstemp() + u = ecore_con.Url(self.test_url, fd=fd) + u.on_complete_event_add(_on_complete) + u.get() + + ecore.main_loop_begin() + self.assertEqual(u.status_code, 200) # assume net is ok + self.assertEqual(os.path.getsize(path), u.received_bytes) + os.unlink(path) + + u.delete() + + # def testFtp(self): + # pass #TODO + + def testPost(self): + self.test_url = 'https://httpbin.org/post' + self.data_to_post = b'my data to post' + + def _on_complete(event): + ecore.main_loop_quit() + + u = ecore_con.Url(self.test_url) + u.on_complete_event_add(_on_complete) + # u.post(self.data_to_post, "multipart/form-data") + u.post(self.data_to_post, "text/txt") + + ecore.main_loop_begin() + self.assertEqual(u.status_code, 200) # assume net is ok + + u.delete() + +if __name__ == '__main__': + unittest.main(verbosity=2) --