2 * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
6 * For info on how to use libcurl, see:
7 * http://curl.haxx.se/libcurl/c/libcurl-tutorial.html
12 * 1. Create an Ecore_Con_Url object
13 * 2. Register to receive the ECORE_CON_EVENT_URL_COMPLETE event
14 * (and optionally the ECORE_CON_EVENT_URL_DATA event to receive
15 * the response, e.g. for HTTP/FTP downloads)
16 * 3. Set the URL with ecore_con_url_url_set(...);
17 * 4. Perform the operation with ecore_con_url_send(...);
19 * Note that it is good to reuse Ecore_Con_Url objects wherever possible, but
20 * bear in mind that each one can only perform one operation at a time.
21 * You need to wait for the ECORE_CON_EVENT_URL_COMPLETE event before re-using
22 * or destroying the object.
24 * Example Usage 1 (HTTP GET):
25 * ecore_con_url_url_set(url_con, "http://www.google.com");
26 * ecore_con_url_send(url_con, NULL, 0, NULL);
28 * Example usage 2 (HTTP POST):
29 * ecore_con_url_url_set(url_con, "http://www.example.com/post_handler.cgi");
30 * ecore_con_url_send(url_con, data, data_length, "multipart/form-data");
32 * Example Usage 3 (FTP download):
33 * ecore_con_url_url_set(url_con, "ftp://ftp.example.com/pub/myfile");
34 * ecore_con_url_send(url_con, NULL, 0, NULL);
36 * Example Usage 4 (FTP upload as ftp://ftp.example.com/file):
37 * ecore_con_url_url_set(url_con, "ftp://ftp.example.com");
38 * ecore_con_url_ftp_upload(url_con, "/tmp/file", "user", "pass", NULL);
40 * Example Usage 5 (FTP upload as ftp://ftp.example.com/dir/file):
41 * ecore_con_url_url_set(url_con, "ftp://ftp.example.com");
42 * ecore_con_url_ftp_upload(url_con, "/tmp/file", "user", "pass","dir");
44 * FIXME: Support more CURL features: Authentication, Progress callbacks and more...
47 #include "ecore_private.h"
48 #include "Ecore_Con.h"
49 #include "ecore_con_private.h"
53 #include <sys/types.h>
56 * @defgroup Ecore_Con_Url_Group Ecore URL Connection Functions
58 * Utility functions that set up, use and shut down the Ecore URL
60 * FIXME: write detailed description
63 int ECORE_CON_EVENT_URL_DATA = 0;
64 int ECORE_CON_EVENT_URL_COMPLETE = 0;
65 int ECORE_CON_EVENT_URL_PROGRESS = 0;
68 static int _ecore_con_url_fd_handler(void *data, Ecore_Fd_Handler *fd_handler);
69 static int _ecore_con_url_perform(Ecore_Con_Url *url_con);
70 static size_t _ecore_con_url_data_cb(void *buffer, size_t size, size_t nitems, void *userp);
71 static int _ecore_con_url_progress_cb(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
72 static size_t _ecore_con_url_read_cb(void *ptr, size_t size, size_t nitems, void *stream);
73 static void _ecore_con_event_url_free(void *data __UNUSED__, void *ev);
74 static int _ecore_con_url_process_completed_jobs(Ecore_Con_Url *url_con_to_match);
76 static Ecore_Idler *_fd_idler_handler = NULL;
77 static Ecore_List *_url_con_list = NULL;
78 static CURLM *curlm = NULL;
79 static fd_set _current_fd_set;
80 static int init_count = 0;
82 struct _Ecore_Con_Url_Event
87 typedef struct _Ecore_Con_Url_Event Ecore_Con_Url_Event;
90 _url_complete_idler_cb(void *data)
92 Ecore_Con_Url_Event *lev;
96 ecore_event_add(lev->type, lev->ev, _ecore_con_event_url_free, NULL);
103 _url_complete_push_event(int type, void *ev)
105 Ecore_Con_Url_Event *lev;
107 lev = malloc(sizeof(Ecore_Con_Url_Event));
111 ecore_idler_add(_url_complete_idler_cb, lev);
117 * Initialises the Ecore_Con_Url library.
118 * @return Number of times the library has been initialised without being
120 * @ingroup Ecore_Con_Url_Group
123 ecore_con_url_init(void)
126 if (!ECORE_CON_EVENT_URL_DATA)
128 ECORE_CON_EVENT_URL_DATA = ecore_event_type_new();
129 ECORE_CON_EVENT_URL_COMPLETE = ecore_event_type_new();
130 ECORE_CON_EVENT_URL_PROGRESS = ecore_event_type_new();
135 _url_con_list = ecore_list_new();
136 if (!_url_con_list) return 0;
141 FD_ZERO(&_current_fd_set);
142 if (curl_global_init(CURL_GLOBAL_NOTHING))
144 ecore_list_destroy(_url_con_list);
145 _url_con_list = NULL;
149 curlm = curl_multi_init();
152 ecore_list_destroy(_url_con_list);
153 _url_con_list = NULL;
165 * Shuts down the Ecore_Con_Url library.
166 * @return Number of calls that still uses Ecore_Con_Url
167 * @ingroup Ecore_Con_Url_Group
170 ecore_con_url_shutdown(void)
180 if (!ecore_list_empty_is(_url_con_list))
182 Ecore_Con_Url *url_con;
183 while ((url_con = ecore_list_first(_url_con_list)))
185 ecore_con_url_destroy(url_con);
188 ecore_list_destroy(_url_con_list);
189 _url_con_list = NULL;
194 curl_multi_cleanup(curlm);
198 curl_global_cleanup();
204 * Creates and initializes a new Ecore_Con_Url.
205 * @return NULL on error, a new Ecore_Con_Url on success.
206 * @ingroup Ecore_Con_Url_Group
209 ecore_con_url_new(const char *url)
212 Ecore_Con_Url *url_con;
214 if (!init_count) return NULL;
216 url_con = calloc(1, sizeof(Ecore_Con_Url));
217 if (!url_con) return NULL;
219 url_con->curl_easy = curl_easy_init();
220 if (!url_con->curl_easy)
226 ECORE_MAGIC_SET(url_con, ECORE_MAGIC_CON_URL);
228 ecore_con_url_url_set(url_con, url);
230 curl_easy_setopt(url_con->curl_easy, CURLOPT_WRITEFUNCTION, _ecore_con_url_data_cb);
231 curl_easy_setopt(url_con->curl_easy, CURLOPT_WRITEDATA, url_con);
233 curl_easy_setopt(url_con->curl_easy, CURLOPT_PROGRESSFUNCTION, _ecore_con_url_progress_cb);
234 curl_easy_setopt(url_con->curl_easy, CURLOPT_PROGRESSDATA, url_con);
235 curl_easy_setopt(url_con->curl_easy, CURLOPT_NOPROGRESS, FALSE);
238 * FIXME: Check that these timeouts are sensible defaults
239 * FIXME: Provide a means to change these timeouts
241 curl_easy_setopt(url_con->curl_easy, CURLOPT_CONNECTTIMEOUT, 30);
242 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEOUT, 300);
243 curl_easy_setopt(url_con->curl_easy, CURLOPT_FOLLOWLOCATION, 1);
245 curl_easy_setopt(url_con->curl_easy, CURLOPT_ENCODING, "gzip,deflate");
248 url_con->write_fd = -1;
258 * Frees the Ecore_Con_Url.
259 * @return FIXME: To be documented.
260 * @ingroup Ecore_Con_Url_Group
263 ecore_con_url_destroy(Ecore_Con_Url *url_con)
266 if (!url_con) return;
267 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
269 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_destroy");
273 ECORE_MAGIC_SET(url_con, ECORE_MAGIC_NONE);
274 if (url_con->fd_handler)
276 ecore_main_fd_handler_del(url_con->fd_handler);
279 if (url_con->curl_easy)
283 if (ecore_list_find(_url_con_list, ecore_direct_compare, url_con) == url_con)
284 ecore_list_remove(_url_con_list);
287 curl_multi_remove_handle(curlm, url_con->curl_easy);
289 curl_easy_cleanup(url_con->curl_easy);
291 curl_slist_free_all(url_con->headers);
301 * FIXME: To be documented.
302 * @return FIXME: To be documented.
303 * @ingroup Ecore_Con_Url_Group
306 ecore_con_url_url_set(Ecore_Con_Url *url_con, const char *url)
309 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
311 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_url_set");
315 if (url_con->active) return 0;
320 url_con->url = strdup(url);
321 curl_easy_setopt(url_con->curl_easy, CURLOPT_URL, url_con->url);
331 * FIXME: To be documented.
332 * @return FIXME: To be documented.
333 * @ingroup Ecore_Con_Url_Group
336 ecore_con_url_data_set(Ecore_Con_Url *url_con, void *data)
339 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
341 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_set");
345 url_con->data = data;
354 * FIXME: To be documented.
355 * @return FIXME: To be documented.
356 * @ingroup Ecore_Con_Url_Group
359 ecore_con_url_data_get(Ecore_Con_Url *url_con)
362 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
364 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_get");
368 return url_con->data;
376 * FIXME: To be documented.
377 * @return FIXME: To be documented.
378 * @ingroup Ecore_Con_Url_Group
381 ecore_con_url_time(Ecore_Con_Url *url_con, Ecore_Con_Url_Time condition, time_t tm)
384 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
386 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_time");
390 url_con->condition = condition;
401 * FIXME: To be documented.
402 * @return FIXME: To be documented.
403 * @ingroup Ecore_Con_Url_Group
406 ecore_con_url_fd_set(Ecore_Con_Url *url_con, int fd)
409 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
411 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_set");
414 url_con->write_fd = fd;
419 * FIXME: To be documented.
420 * @return FIXME: To be documented.
421 * @ingroup Ecore_Con_Url_Group
424 ecore_con_url_received_bytes_get(Ecore_Con_Url *url_con)
427 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
429 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_received_bytes_get");
433 return url_con->received;
439 * FIXME: To be documented.
440 * @return FIXME: To be documented.
441 * @ingroup Ecore_Con_Url_Group
444 ecore_con_url_send(Ecore_Con_Url *url_con, void *data, size_t length, char *content_type)
449 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
451 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_send");
455 if (url_con->active) return 0;
456 if (!url_con->url) return 0;
458 curl_slist_free_all(url_con->headers);
459 url_con->headers = NULL;
463 curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDS, data);
464 curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDSIZE, length);
466 if (content_type && (strlen(content_type) < 200))
468 sprintf(tmp, "Content-type: %s", content_type);
469 url_con->headers = curl_slist_append(url_con->headers, tmp);
471 sprintf(tmp, "Content-length: %d", length);
472 url_con->headers = curl_slist_append(url_con->headers, tmp);
475 switch (url_con->condition)
477 case ECORE_CON_URL_TIME_NONE:
478 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
480 case ECORE_CON_URL_TIME_IFMODSINCE:
481 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
482 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEVALUE, url_con->time);
484 case ECORE_CON_URL_TIME_IFUNMODSINCE:
485 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFUNMODSINCE);
486 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEVALUE, url_con->time);
488 case ECORE_CON_URL_TIME_LASTMOD:
489 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_LASTMOD);
490 curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEVALUE, url_con->time);
494 curl_easy_setopt(url_con->curl_easy, CURLOPT_HTTPHEADER, url_con->headers);
496 return _ecore_con_url_perform(url_con);
508 * @return FIXME: To be more documented.
509 * @ingroup Ecore_Con_Url_Group
512 ecore_con_url_ftp_upload(Ecore_Con_Url *url_con, char *filename, char *user, char *pass, char *upload_dir)
518 struct stat file_info;
520 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
522 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_ftp_upload");
526 if (url_con->active) return 0;
527 if (!url_con->url) return 0;
530 if (stat(filename, &file_info)) return 0;
531 fd = fopen(filename, "rb");
533 snprintf(url, sizeof(url), "ftp://%s/%s/%s", url_con->url, upload_dir, basename(filename));
535 snprintf(url, sizeof(url), "ftp://%s/%s", url_con->url, basename(filename));
536 snprintf(userpwd, sizeof(userpwd), "%s:%s", user, pass);
537 curl_easy_setopt(url_con->curl_easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
538 curl_easy_setopt(url_con->curl_easy, CURLOPT_USERPWD, userpwd);
539 curl_easy_setopt(url_con->curl_easy, CURLOPT_UPLOAD, 1);
540 curl_easy_setopt(url_con->curl_easy, CURLOPT_READFUNCTION, _ecore_con_url_read_cb);
541 curl_easy_setopt(url_con->curl_easy, CURLOPT_READDATA, fd);
542 ecore_con_url_url_set(url_con, url);
544 return _ecore_con_url_perform(url_con);
559 * Enable or disable libcurl verbose output, useful for debug
560 * @return FIXME: To be more documented.
561 * @ingroup Ecore_Con_Url_Group
564 ecore_con_url_verbose_set(Ecore_Con_Url *url_con, int verbose)
567 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
569 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_verbose_set");
573 if (url_con->active) return;
574 if (!url_con->url) return;
576 curl_easy_setopt(url_con->curl_easy, CURLOPT_VERBOSE, 1);
578 curl_easy_setopt(url_con->curl_easy, CURLOPT_VERBOSE, 0);
583 * Enable or disable EPSV extension
584 * @return FIXME: To be more documented.
585 * @ingroup Ecore_Con_Url_Group
588 ecore_con_url_ftp_use_epsv_set(Ecore_Con_Url *url_con, int use_epsv)
591 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
593 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_ftp_use_epsv_set");
597 if (url_con->active) return;
598 if (!url_con->url) return;
599 if (use_epsv == TRUE)
600 curl_easy_setopt(url_con->curl_easy, CURLOPT_FTP_USE_EPSV, 1);
602 curl_easy_setopt(url_con->curl_easy, CURLOPT_FTP_USE_EPSV, 0);
608 _ecore_con_url_suspend_fd_handler(void)
610 Ecore_Con_Url *url_con;
616 ecore_list_first_goto(_url_con_list);
617 while ((url_con = ecore_list_current(_url_con_list)))
619 if (url_con->active && url_con->fd_handler)
621 ecore_main_fd_handler_del(url_con->fd_handler);
622 url_con->fd_handler = NULL;
625 ecore_list_next(_url_con_list);
632 _ecore_con_url_restart_fd_handler(void)
634 Ecore_Con_Url *url_con;
640 ecore_list_first_goto(_url_con_list);
641 while ((url_con = ecore_list_current(_url_con_list)))
643 if (url_con->fd_handler == NULL
644 && url_con->fd != -1)
646 url_con->fd_handler = ecore_main_fd_handler_add(url_con->fd,
648 _ecore_con_url_fd_handler,
652 ecore_list_next(_url_con_list);
659 _ecore_con_url_data_cb(void *buffer, size_t size, size_t nitems, void *userp)
661 Ecore_Con_Url *url_con;
662 Ecore_Con_Event_Url_Data *e;
663 size_t real_size = size * nitems;
665 url_con = (Ecore_Con_Url *)userp;
667 if (!url_con) return -1;
668 if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
670 ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_cb");
674 url_con->received += real_size;
676 if (url_con->write_fd < 0)
678 e = malloc(sizeof(Ecore_Con_Event_Url_Data) + sizeof(unsigned char) * (real_size - 1));
681 e->url_con = url_con;
683 memcpy(e->data, buffer, real_size);
684 ecore_event_add(ECORE_CON_EVENT_URL_DATA, e,
685 _ecore_con_event_url_free, NULL);
691 size_t total_size = real_size;
694 while (total_size > 0)
696 count = write(url_con->write_fd, (char*) buffer + offset, total_size);
699 if (errno != EAGAIN && errno != EINTR)
713 #define ECORE_CON_URL_TRANSMISSION(Transmit, Event, Url_con, Total, Now) \
715 Ecore_Con_Event_Url_Progress *e; \
716 if ((Total != 0) || (Now != 0)) \
718 e = calloc(1, sizeof(Ecore_Con_Event_Url_Progress)); \
721 e->url_con = url_con; \
724 ecore_event_add(Event, e, _ecore_con_event_url_free, NULL); \
730 _ecore_con_url_progress_cb(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
732 Ecore_Con_Event_Url_Progress *e;
733 Ecore_Con_Url *url_con;
737 e = calloc(1, sizeof(Ecore_Con_Event_Url_Progress));
740 e->url_con = url_con;
741 e->down.total = dltotal;
743 e->up.total = ultotal;
745 ecore_event_add(ECORE_CON_EVENT_URL_PROGRESS, e, _ecore_con_event_url_free, NULL);
752 _ecore_con_url_read_cb(void *ptr, size_t size, size_t nitems, void *stream)
754 size_t retcode = fread(ptr, size, nitems, stream);
755 if (ferror((FILE*)stream)) {
757 return CURL_READFUNC_ABORT;
758 } else if ((retcode == 0) || (retcode < nitems)) {
759 fclose((FILE*)stream);
762 fprintf(stderr, "*** We read %d bytes from file\n", retcode);
767 _ecore_con_url_perform(Ecore_Con_Url *url_con)
769 fd_set read_set, write_set, exc_set;
775 int completed_immediately = 0;
777 ecore_list_append(_url_con_list, url_con);
779 start = ecore_time_get();
781 curl_multi_add_handle(curlm, url_con->curl_easy);
782 /* This one can't be stopped, or the download never start. */
783 while (curl_multi_perform(curlm, &still_running) == CURLM_CALL_MULTI_PERFORM);
785 completed_immediately = _ecore_con_url_process_completed_jobs(url_con);
787 if (!completed_immediately)
789 /* url_con still active -- set up an fd_handler */
794 /* Stupid curl, why can't I get the fd to the current added job? */
795 curl_multi_fdset(curlm, &read_set, &write_set, &exc_set, &fd_max);
796 for (fd = 0; fd <= fd_max; fd++)
798 if (!FD_ISSET(fd, &_current_fd_set))
801 if (FD_ISSET(fd, &read_set)) flags |= ECORE_FD_READ;
802 if (FD_ISSET(fd, &write_set)) flags |= ECORE_FD_WRITE;
803 if (FD_ISSET(fd, &exc_set)) flags |= ECORE_FD_ERROR;
806 FD_SET(fd, &_current_fd_set);
808 url_con->flags = flags;
809 url_con->fd_handler = ecore_main_fd_handler_add(fd, flags,
810 _ecore_con_url_fd_handler,
816 if (!url_con->fd_handler)
818 /* Failed to set up an fd_handler */
819 curl_multi_remove_handle(curlm, url_con->curl_easy);
830 _ecore_con_url_idler_handler(void *data)
836 start = ecore_time_get();
837 while (curl_multi_perform(curlm, &still_running) == CURLM_CALL_MULTI_PERFORM)
838 /* make this 1/20th of a second to keep interactivity high */
839 if ((ecore_time_get() - start) > 0.2)
845 _ecore_con_url_process_completed_jobs(NULL);
849 _ecore_con_url_restart_fd_handler();
850 _fd_idler_handler = NULL;
858 _ecore_con_url_fd_handler(void *data __UNUSED__, Ecore_Fd_Handler *fd_handler __UNUSED__)
860 _ecore_con_url_suspend_fd_handler();
862 if (_fd_idler_handler == NULL)
863 _fd_idler_handler = ecore_idler_add(_ecore_con_url_idler_handler, NULL);
869 _ecore_con_url_process_completed_jobs(Ecore_Con_Url *url_con_to_match)
871 Ecore_Con_Url *url_con;
876 /* Loop jobs and check if any are done */
877 while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != NULL)
879 if (curlmsg->msg != CURLMSG_DONE) continue;
881 /* find the job which is done */
882 ecore_list_first_goto(_url_con_list);
883 while ((url_con = ecore_list_current(_url_con_list)))
885 if (curlmsg->easy_handle == url_con->curl_easy)
887 /* We have found the completed job in our job list */
888 if (url_con_to_match && (url_con == url_con_to_match)) {
891 if (url_con->fd != -1)
893 FD_CLR(url_con->fd, &_current_fd_set);
894 if (url_con->fd_handler)
895 ecore_main_fd_handler_del(url_con->fd_handler);
897 url_con->fd_handler = NULL;
899 ecore_list_remove(_url_con_list);
902 Ecore_Con_Event_Url_Complete *e;
903 e = calloc(1, sizeof(Ecore_Con_Event_Url_Complete));
906 e->url_con = url_con;
909 curl_easy_getinfo(curlmsg->easy_handle, CURLINFO_RESPONSE_CODE, &e->status);
911 _url_complete_push_event(ECORE_CON_EVENT_URL_COMPLETE, e);
914 curl_multi_remove_handle(curlm, url_con->curl_easy);
917 ecore_list_next(_url_con_list);
924 _ecore_con_event_url_free(void *data __UNUSED__, void *ev)