/* * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2 */ /* * For info on how to use libcurl, see: * http://curl.haxx.se/libcurl/c/libcurl-tutorial.html */ /* * Brief usage: * 1. Create an Ecore_Con_Url object * 2. Register to receive the ECORE_CON_EVENT_URL_COMPLETE event * (and optionally the ECORE_CON_EVENT_URL_DATA event to receive * the response, e.g. for HTTP/FTP downloads) * 3. Set the URL with ecore_con_url_url_set(...); * 4. Perform the operation with ecore_con_url_send(...); * * Note that it is good to reuse Ecore_Con_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 ECORE_CON_EVENT_URL_COMPLETE event before re-using * or destroying the object. * * Example Usage 1 (HTTP GET): * ecore_con_url_url_set(url_con, "http://www.google.com"); * ecore_con_url_send(url, NULL, 0, NULL); * * Example usage 2 (HTTP POST): * ecore_con_url_url_set(url_con, "http://www.example.com/post_handler.cgi"); * ecore_con_url_send(url, data, data_length, "multipart/form-data"); * * Example Usage 3 (FTP download): * ecore_con_url_url_set(url_con, "ftp://ftp.example.com/pub/myfile"); * ecore_con_url_send(url, NULL, 0, NULL); * * FIXME: Support more CURL features: Authentication, FTP upload, Progress callbacks and more... */ #include "Ecore.h" #include "ecore_private.h" #include "Ecore_Con.h" #include "ecore_con_private.h" #ifdef HAVE_CURL static int _ecore_con_url_fd_handler(void *data, Ecore_Fd_Handler *fd_handler); static int _ecore_con_url_perform(Ecore_Con_Url *url_con); static size_t _ecore_con_url_data_cb(void *buffer, size_t size, size_t nmemb, void *userp); static void _ecore_con_event_url_complete_free(void *data __UNUSED__, void *ev); static void _ecore_con_event_url_data_free(void *data __UNUSED__, void *ev); static int _ecore_con_url_process_completed_jobs(Ecore_Con_Url *url_con_to_match); int ECORE_CON_EVENT_URL_DATA = 0; int ECORE_CON_EVENT_URL_COMPLETE = 0; static CURLM *curlm = NULL; static Ecore_List *_url_con_list = NULL; static fd_set _current_fd_set; static int init_count = 0; #endif EAPI int ecore_con_url_init(void) { #ifdef HAVE_CURL if (!ECORE_CON_EVENT_URL_DATA) { ECORE_CON_EVENT_URL_DATA = ecore_event_type_new(); ECORE_CON_EVENT_URL_COMPLETE = ecore_event_type_new(); } if (!_url_con_list) { _url_con_list = ecore_list_new(); if (!_url_con_list) return 0; } if (!curlm) { FD_ZERO(&_current_fd_set); if (curl_global_init(CURL_GLOBAL_NOTHING)) { ecore_list_destroy(_url_con_list); _url_con_list = NULL; return 0; } curlm = curl_multi_init(); if (!curlm) { ecore_list_destroy(_url_con_list); _url_con_list = NULL; return 0; } } init_count++; return 1; #else return 0; #endif } EAPI int ecore_con_url_shutdown(void) { #ifdef HAVE_CURL if (!init_count) return 0; init_count--; if (_url_con_list) { if (!ecore_list_empty_is(_url_con_list)) { Ecore_Con_Url *url_con; while ((url_con = ecore_list_first_remove(_url_con_list))) { ecore_con_url_destroy(url_con); } } ecore_list_destroy(_url_con_list); _url_con_list = NULL; } if (curlm) { curl_multi_cleanup(curlm); curlm = NULL; } curl_global_cleanup(); #endif return 1; } EAPI Ecore_Con_Url * ecore_con_url_new(const char *url) { #ifdef HAVE_CURL Ecore_Con_Url *url_con; if (!init_count) return NULL; url_con = calloc(1, sizeof(Ecore_Con_Url)); if (!url_con) return NULL; url_con->curl_easy = curl_easy_init(); if (!url_con->curl_easy) { free(url_con); return NULL; } ecore_con_url_url_set(url_con, url); curl_easy_setopt(url_con->curl_easy, CURLOPT_WRITEFUNCTION, _ecore_con_url_data_cb); curl_easy_setopt(url_con->curl_easy, CURLOPT_WRITEDATA, url_con); /* * FIXME: Check that these timeouts are sensible defaults * FIXME: Provide a means to change these timeouts */ curl_easy_setopt(url_con->curl_easy, CURLOPT_CONNECTTIMEOUT, 30); curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEOUT, 300); curl_easy_setopt(url_con->curl_easy, CURLOPT_FOLLOWLOCATION, 1); return url_con; #else url = NULL; return NULL; #endif } EAPI void ecore_con_url_destroy(Ecore_Con_Url *url_con) { #ifdef HAVE_CURL if (!url_con) return; if (url_con->fd_handler) ecore_main_fd_handler_del(url_con->fd_handler); if (url_con->curl_easy) { if (url_con->active) curl_multi_remove_handle(curlm, url_con->curl_easy); curl_easy_cleanup(url_con->curl_easy); } curl_slist_free_all(url_con->headers); free(url_con->url); free(url_con); #else url_con = NULL; #endif } EAPI int ecore_con_url_url_set(Ecore_Con_Url *url_con, const char *url) { #ifdef HAVE_CURL if (url_con->active) return 0; free(url_con->url); url_con->url = NULL; if (url) url_con->url = strdup(url); curl_easy_setopt(url_con->curl_easy, CURLOPT_URL, url_con->url); #else url_con = NULL; url = NULL; #endif return 1; } EAPI int ecore_con_url_send(Ecore_Con_Url *url_con, void *data, size_t length, char *content_type) { #ifdef HAVE_CURL char tmp[256]; if (url_con->active) return 0; if (!url_con->url) return 0; curl_slist_free_all(url_con->headers); url_con->headers = NULL; if (data) { curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDS, data); curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDSIZE, length); if (content_type && (strlen(content_type) < 200)) { sprintf(tmp, "Content-type: %s", content_type); url_con->headers = curl_slist_append(url_con->headers, tmp); } sprintf(tmp, "Content-length: %d", length); url_con->headers = curl_slist_append(url_con->headers, tmp); } curl_easy_setopt(url_con->curl_easy, CURLOPT_HTTPHEADER, url_con->headers); return _ecore_con_url_perform(url_con); #else url_con = NULL; data = NULL; length = 0; content_type = NULL; return 0; #endif } #ifdef HAVE_CURL static size_t _ecore_con_url_data_cb(void *buffer, size_t size, size_t nmemb, void *userp) { Ecore_Con_Url *url_con; Ecore_Con_Event_Url_Data *e; size_t real_size = size * nmemb; url_con = (Ecore_Con_Url *)userp; e = calloc(1, sizeof(Ecore_Con_Event_Url_Data)); if (e) { e->url_con = url_con; e->data = buffer; e->size = real_size; ecore_event_add(ECORE_CON_EVENT_URL_DATA, e, _ecore_con_event_url_data_free, NULL); } return real_size; } /* * FIXME: Use * CURLOPT_PROGRESSFUNCTION and CURLOPT_PROGRESSDATA to * get reports on progress. * And maybe other nifty functions... */ static int _ecore_con_url_perform(Ecore_Con_Url *url_con) { fd_set read_set, write_set, exc_set; int fd_max; int fd; int flags; int still_running; int completed_immediately = 0; ecore_list_append(_url_con_list, url_con); url_con->active = 1; curl_multi_add_handle(curlm, url_con->curl_easy); while (curl_multi_perform(curlm, &still_running) == CURLM_CALL_MULTI_PERFORM); completed_immediately = _ecore_con_url_process_completed_jobs(url_con); if (!completed_immediately) { /* url_con still active -- set up an fd_handler */ FD_ZERO(&read_set); FD_ZERO(&write_set); FD_ZERO(&exc_set); /* Stupid curl, why can't I get the fd to the current added job? */ curl_multi_fdset(curlm, &read_set, &write_set, &exc_set, &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) { FD_SET(fd, &_current_fd_set); url_con->fd_handler = ecore_main_fd_handler_add(fd, flags, _ecore_con_url_fd_handler, NULL, NULL, NULL); } } } if (!url_con->fd_handler) { /* Failed to set up an fd_handler */ curl_multi_remove_handle(curlm, url_con->curl_easy); url_con->active = 0; return 0; } } return 1; } static int _ecore_con_url_fd_handler(void *data __UNUSED__, Ecore_Fd_Handler *fd_handler __UNUSED__) { int still_running; /* FIXME: Can this run for a long time? Maybe limit how long it can run */ while (curl_multi_perform(curlm, &still_running) == CURLM_CALL_MULTI_PERFORM); _ecore_con_url_process_completed_jobs(NULL); return 1; } static int _ecore_con_url_process_completed_jobs(Ecore_Con_Url *url_con_to_match) { Ecore_Con_Url *url_con; CURLMsg *curlmsg; int n_remaining; int job_matched = 0; /* Loop jobs and check if any are done */ while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != NULL) { if (curlmsg->msg != CURLMSG_DONE) continue; /* find the job which is done */ ecore_list_first_goto(_url_con_list); while ((url_con = ecore_list_current(_url_con_list))) { if (curlmsg->easy_handle == url_con->curl_easy) { /* We have found the completed job in our job list */ if (url_con_to_match && (url_con == url_con_to_match)) { job_matched = 1; } if (url_con->fd_handler) { FD_CLR(ecore_main_fd_handler_fd_get(url_con->fd_handler), &_current_fd_set); ecore_main_fd_handler_del(url_con->fd_handler); url_con->fd_handler = NULL; } ecore_list_remove(_url_con_list); curl_multi_remove_handle(curlm, url_con->curl_easy); url_con->active = 0; { Ecore_Con_Event_Url_Complete *e; e = calloc(1, sizeof(Ecore_Con_Event_Url_Complete)); if (e) { e->url_con = url_con; e->status = curlmsg->data.result; ecore_event_add(ECORE_CON_EVENT_URL_COMPLETE, e, _ecore_con_event_url_complete_free, NULL); } } break; } ecore_list_next(_url_con_list); } } return job_matched; } static void _ecore_con_event_url_data_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Url_Data *e; e = ev; free(e); } static void _ecore_con_event_url_complete_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Url_Complete *e; e = ev; free(e); } #endif