I've done a cURL[1] wrapper for Ecore, which uses ecore's fd_handler support to perform asynchronous curl operations (in ecore's main_loop) with callbacks, making it trivial to use curl in ecore-based applications. For those who don't know, cURL is an excellent HTTP/FTP/whatever library [2].

I'd like to contribute it, but I need to know where to put it. My instinct is to make this a part of ecore (ecore_curl), which would only be included if you build with Ecore support. It's a pretty lightweight wrapper (single source file, 180 lines). Opinions?

I'm happy to discuss how it works and (try to) defend the way I've implemented it.

I've attached the source file directly from my app so you can get a sense. I'm happy to detach it from my app and integrate it into Ecore, including doing the autotools magic, once I know where to put it!

[1] http://curl.haxx.se/libcurl/
[2] "libcurl is a free and easy-to-use client-side URL transfer library, supporting FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE and LDAP. libcurl supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, Kerberos4), file transfer resume, http proxy tunneling and more!"

--
Simon Poole
www.appliancestudio.com
#include "common.h"

static CURLM *curlm;
static int _ecurl_init_count = 0;

typedef struct _Ecurl_Job {
        Ecore_Fd_Handler *fd_handler;
        CURL *curl;
        Ecurl_Callback completion_cb;
        void *user_data;
} Ecurl_Job;
static Ecore_List *_job_list;

static int _running_curl_handles = 0;
static fd_set _current_fd_set;

static void
_ecurl_fd_prep_handler(void *data, Ecore_Fd_Handler *fd_handler)
{
        fd_set read_set, write_set, exc_set;
        int fd_max;
        int fd;
        Ecore_Fd_Handler_Flags flags;

        signos_log_debug("_ecurl_fd_prep_handler\n");
        FD_ZERO(&read_set);
        FD_ZERO(&write_set);
        FD_ZERO(&exc_set);

        curl_multi_fdset(curlm, &read_set, &write_set, &exc_set, &fd_max);
        flags = 0;
        fd = ecore_main_fd_handler_fd_get(fd_handler);
        if (FD_ISSET(fd, &read_set)) flags |= ECORE_FD_READ;
        if (FD_ISSET(fd, &write_set)) flags |= ECORE_FD_WRITE;
        if (FD_ISSET(fd, &exc_set)) flags |= ECORE_FD_ERROR;
        ecore_main_fd_handler_active_set(fd_handler, flags);
}

static void
_ecurl_handle_completed_job(CURLMsg *curlmsg)
{
        Ecurl_Job *job;
        ecore_list_goto_first(_job_list);
        while (job = (Ecurl_Job *)ecore_list_current(_job_list)) {
                if (curlmsg->easy_handle == job->curl) {
                        // We have a match -- delete the job
                        signos_log_debug("CURL Job completed (fd %d)\n", 
ecore_main_fd_handler_fd_get(job->fd_handler));
                        ecore_main_fd_handler_del(job->fd_handler);
                        ecore_list_remove(_job_list);
                        FD_CLR(ecore_main_fd_handler_fd_get(job->fd_handler), 
&_current_fd_set);
                        if (job->completion_cb) 
job->completion_cb(curlmsg->data.result, job->user_data);
                        free(job);
                        // Goto next message
                        // TODO: curl_easy_cleanup()
                        break;
                }
                ecore_list_next(_job_list);
        }
}

static int
_ecurl_fd_handler(void *data, Ecore_Fd_Handler *fd_handler)
{
        Ecurl_Job *job;
        int n_handles = _running_curl_handles;

        signos_log_debug("_ecurl_fd_handler: (currently %d handles)\n", 
n_handles);
        while (curl_multi_perform(curlm, &_running_curl_handles) == 
CURLM_CALL_MULTI_PERFORM);
        signos_log_debug("_ecurl_fd_handler: (currently %d handles)\n", 
_running_curl_handles);
        if (_running_curl_handles < n_handles) {
                // A transfer must have completed
                CURLMsg *curlmsg;
                int n_remaining;

                // Handle job completion messages
                while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != 
NULL) {
                        signos_log_debug("received CURLMsg [%d], %d 
remaining\n", curlmsg->msg, n_remaining);
                        if (curlmsg->msg != CURLMSG_DONE) continue;
                        _ecurl_handle_completed_job(curlmsg);
                }
        }
        signos_log_debug("_ecurl_fd_handler: done\n");
}

int
ecurl_perform(CURL *curl, Ecurl_Callback completion_cb, void *user_data)
{
        CURLMcode ret;
        fd_set read_set, write_set, exc_set;
        int fd_max;
        int fd;
        int flags;
        Ecurl_Job *job;
        Ecore_Fd_Handler *fd_handler;
        int n_handles = _running_curl_handles;
        
        signos_log_debug("_ecurl_perform\n");
        job = malloc(sizeof(Ecurl_Job));
        job->curl = curl;
        job->completion_cb = completion_cb;
        job->user_data = user_data;

        curl_multi_add_handle(curlm, job->curl);
        _running_curl_handles++;
        while (curl_multi_perform(curlm, &_running_curl_handles) == 
CURLM_CALL_MULTI_PERFORM);
        if (_running_curl_handles < n_handles) {
                // A transfer must have completed.
                // Could be the new one, or could be an existing one.
                CURLMsg *curlmsg;
                int n_remaining;

                // Handle job completion messages
                while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != 
NULL) {
                        signos_log_debug("received CURLMsg [%d], %d 
remaining\n", curlmsg->msg, n_remaining);
                        if (curlmsg->msg != CURLMSG_DONE) continue;
                        // Find the matching job
                        if (curlmsg->easy_handle == curl) {
                                // The new job has already finished! That's a 
speedy server...
                                signos_log_debug("New CURL Job completed 
immediately!\n");
                                if (completion_cb) 
completion_cb(curlmsg->data.result, user_data);
                                free(job);
                                // Return immediately -- our work is done.
                                return 0;
                        }

                        // Must have been a pre-existing job
                        _ecurl_handle_completed_job(curlmsg);
                }
        }

        // Register an ecore fd_handler for the new job
        FD_ZERO(&read_set);
        FD_ZERO(&write_set);
        FD_ZERO(&exc_set);

        curl_multi_fdset(curlm, &read_set, &write_set, &exc_set, &fd_max);
        signos_log_debug("fdset returned fd_max=%d\n", fd_max);
        for (fd = 0; fd <= fd_max; fd++) {
                if (!FD_ISSET(fd, &_current_fd_set)) {
                        flags = 0;
                        if (FD_ISSET(fd, &read_set)) flags |= ECORE_FD_READ;
                        if (FD_ISSET(fd, &write_set)) flags |= ECORE_FD_WRITE;
                        if (FD_ISSET(fd, &exc_set)) flags |= ECORE_FD_ERROR;
                        if (flags) {
                                signos_log_debug("Found active fd [%d], 
registering handler\n", fd);
                                FD_SET(fd, &_current_fd_set);
                                job->fd_handler = ecore_main_fd_handler_add(fd, 
flags, _ecurl_fd_handler, NULL, NULL, NULL);
                                
ecore_main_fd_handler_prepare_callback_set(job->fd_handler, 
_ecurl_fd_prep_handler, job);
                                ecore_list_append(_job_list, job);
                        }
                }
        }
        return 0;
}

int
ecurl_init()
{
        if (++_ecurl_init_count == 1) {
                FD_ZERO(&_current_fd_set);
                _job_list = ecore_list_new();

                curl_global_init(CURL_GLOBAL_NOTHING);
                curlm = curl_multi_init();
                if (!curlm) --_ecurl_init_count;
        }
}

int
ecurl_shutdown()
{
        if (--_ecurl_init_count == 0) {
                Ecurl_Job *job;
                curl_multi_cleanup(curlm);
                if (!ecore_list_is_empty(_job_list)) {
                        ecore_list_goto_first(_job_list);
                        while (job = ecore_list_next(_job_list)) {
                                ecore_main_fd_handler_del(job->fd_handler);
                                curl_easy_cleanup(job->curl);
                                free(job);
                        }
                        ecore_list_destroy(_job_list);
                }
        }
}

Reply via email to