7527f5808687ab49e4098a4a614bbd43779fb423
[framework/uifw/ecore.git] / src / lib / ecore_con / ecore_con_url.c
1 /*
2  * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
3  */
4
5 /*
6  * For info on how to use libcurl, see:
7  * http://curl.haxx.se/libcurl/c/libcurl-tutorial.html
8  */
9
10 /*
11  * Brief usage:
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(...);
18  *
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.
23  * 
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);
27  *
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");
31  *
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);
35  *
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);
39  *
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");
43  *
44  * FIXME: Support more CURL features: Authentication, Progress callbacks and more...
45  */
46 #include "Ecore.h"
47 #include "ecore_private.h"
48 #include "Ecore_Con.h"
49 #include "ecore_con_private.h"
50
51 #include <errno.h>
52 #include <sys/stat.h>
53 #include <sys/types.h>
54
55 /**
56  * @defgroup Ecore_Con_Url_Group Ecore URL Connection Functions
57  *
58  * Utility functions that set up, use and shut down the Ecore URL 
59  * Connection library.
60  * FIXME: write detailed description
61  */
62
63 int ECORE_CON_EVENT_URL_DATA = 0;
64 int ECORE_CON_EVENT_URL_COMPLETE = 0;
65 int ECORE_CON_EVENT_URL_PROGRESS = 0;
66
67 #ifdef HAVE_CURL
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);
75
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;
81
82 struct _Ecore_Con_Url_Event
83 {
84   int    type;
85   void  *ev;
86 };
87 typedef struct _Ecore_Con_Url_Event Ecore_Con_Url_Event;
88
89 static int
90 _url_complete_idler_cb(void *data)
91 {
92    Ecore_Con_Url_Event *lev;
93
94    lev = data;
95
96    ecore_event_add(lev->type, lev->ev, _ecore_con_event_url_free, NULL);
97    free(lev);
98
99    return 0;
100 }
101
102 static void
103 _url_complete_push_event(int type, void *ev)
104 {
105    Ecore_Con_Url_Event *lev;
106
107    lev = malloc(sizeof(Ecore_Con_Url_Event));
108    lev->type = type;
109    lev->ev = ev;
110
111    ecore_idler_add(_url_complete_idler_cb, lev);
112 }
113
114 #endif
115
116 /**
117  * Initialises the Ecore_Con_Url library.
118  * @return Number of times the library has been initialised without being
119  *          shut down.
120  * @ingroup Ecore_Con_Url_Group
121  */
122 EAPI int
123 ecore_con_url_init(void)
124 {
125 #ifdef HAVE_CURL
126    if (!ECORE_CON_EVENT_URL_DATA)
127      {
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();
131      }
132
133    if (!_url_con_list)
134      {
135         _url_con_list = ecore_list_new();
136         if (!_url_con_list) return 0;
137      }
138
139    if (!curlm)
140      {
141         FD_ZERO(&_current_fd_set);
142         if (curl_global_init(CURL_GLOBAL_NOTHING))
143           {
144              ecore_list_destroy(_url_con_list);
145              _url_con_list = NULL;
146              return 0;
147           }
148
149         curlm = curl_multi_init();
150         if (!curlm)
151           {
152              ecore_list_destroy(_url_con_list);
153              _url_con_list = NULL;
154              return 0;
155           }
156      }
157    init_count++;
158    return 1;
159 #else
160    return 0;
161 #endif
162 }
163
164 /**
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
168  */
169 EAPI int
170 ecore_con_url_shutdown(void)
171 {
172 #ifdef HAVE_CURL
173
174    if (!init_count)
175      return 0;
176
177    init_count--;
178    if (_url_con_list)
179      {
180         if (!ecore_list_empty_is(_url_con_list))
181           {
182              Ecore_Con_Url *url_con;
183              while ((url_con = ecore_list_first(_url_con_list)))
184                {
185                   ecore_con_url_destroy(url_con);
186                }
187           }
188         ecore_list_destroy(_url_con_list);
189         _url_con_list = NULL;
190      }
191
192    if (curlm)
193      {
194         curl_multi_cleanup(curlm);
195         curlm = NULL;
196      }
197
198    curl_global_cleanup();
199 #endif
200    return 1;
201 }
202
203 /**
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
207  */
208 EAPI Ecore_Con_Url *
209 ecore_con_url_new(const char *url)
210 {
211 #ifdef HAVE_CURL
212    Ecore_Con_Url *url_con;
213
214    if (!init_count) return NULL;
215
216    url_con = calloc(1, sizeof(Ecore_Con_Url));
217    if (!url_con) return NULL;
218
219    url_con->curl_easy = curl_easy_init();
220    if (!url_con->curl_easy)
221      {
222         free(url_con);
223         return NULL;
224      }
225
226    ECORE_MAGIC_SET(url_con, ECORE_MAGIC_CON_URL);
227
228    ecore_con_url_url_set(url_con, url);
229
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);
232
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);
236
237    /*
238     * FIXME: Check that these timeouts are sensible defaults
239     * FIXME: Provide a means to change these timeouts
240     */
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);
244
245    curl_easy_setopt(url_con->curl_easy, CURLOPT_ENCODING, "gzip,deflate");
246
247    url_con->fd = -1;
248    url_con->write_fd = -1;
249
250    return url_con;
251 #else
252    return NULL;
253    url = NULL;
254 #endif
255 }
256
257 /**
258  * Frees the Ecore_Con_Url.
259  * @return  FIXME: To be documented. 
260  * @ingroup Ecore_Con_Url_Group
261  */
262 EAPI void
263 ecore_con_url_destroy(Ecore_Con_Url *url_con)
264 {
265 #ifdef HAVE_CURL
266    if (!url_con) return;
267    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
268      {
269         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_destroy");
270         return;
271      }
272
273    ECORE_MAGIC_SET(url_con, ECORE_MAGIC_NONE);
274    if (url_con->fd_handler)
275      {
276         ecore_main_fd_handler_del(url_con->fd_handler);
277         url_con->fd = -1;
278      }
279    if (url_con->curl_easy)
280      {
281         if (url_con->active)
282           {
283              if (ecore_list_find(_url_con_list, ecore_direct_compare, url_con) == url_con)
284                ecore_list_remove(_url_con_list);
285              url_con->active = 0;
286
287              curl_multi_remove_handle(curlm, url_con->curl_easy);
288           }
289         curl_easy_cleanup(url_con->curl_easy);
290      }
291    curl_slist_free_all(url_con->headers);
292    free(url_con->url);
293    free(url_con);
294 #else
295    return;
296    url_con = NULL;
297 #endif
298 }
299
300 /**
301  * FIXME: To be documented.
302  * @return  FIXME: To be documented.
303  * @ingroup Ecore_Con_Url_Group
304  */
305 EAPI int
306 ecore_con_url_url_set(Ecore_Con_Url *url_con, const char *url)
307 {
308 #ifdef HAVE_CURL
309    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
310      {
311         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_url_set");
312         return 0;
313      }
314
315    if (url_con->active) return 0;
316
317    free(url_con->url);
318    url_con->url = NULL;
319    if (url)
320      url_con->url = strdup(url);
321    curl_easy_setopt(url_con->curl_easy, CURLOPT_URL, url_con->url);
322    return 1;
323 #else
324    return 0;
325    url_con = NULL;
326    url = NULL;
327 #endif
328 }
329
330 /**
331  * FIXME: To be documented.
332  * @return  FIXME: To be documented.
333  * @ingroup Ecore_Con_Url_Group
334  */
335 EAPI void
336 ecore_con_url_data_set(Ecore_Con_Url *url_con, void *data)
337 {
338 #ifdef HAVE_CURL
339    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
340      {
341         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_set");
342         return;
343      }
344
345    url_con->data = data;
346 #else
347    return;
348    url_con = NULL;
349    data = NULL;
350 #endif
351 }
352
353 /**
354  * FIXME: To be documented.
355  * @return  FIXME: To be documented.
356  * @ingroup Ecore_Con_Url_Group
357  */
358 EAPI void *
359 ecore_con_url_data_get(Ecore_Con_Url *url_con)
360 {
361 #ifdef HAVE_CURL
362    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
363      {
364         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_get");
365         return NULL;
366      }
367
368    return url_con->data;
369 #else
370    return NULL;
371    url_con = NULL;
372 #endif
373 }
374
375 /**
376  * FIXME: To be documented.
377  * @return  FIXME: To be documented.
378  * @ingroup Ecore_Con_Url_Group
379  */
380 EAPI void
381 ecore_con_url_time(Ecore_Con_Url *url_con, Ecore_Con_Url_Time condition, time_t tm)
382 {
383 #ifdef HAVE_CURL
384    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
385      {
386         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_time");
387         return;
388      }
389
390    url_con->condition = condition;
391    url_con->time = tm;
392 #else
393    return;
394    url_con = NULL;
395    condition = 0;
396    tm = 0;
397 #endif
398 }
399
400 /**
401  * FIXME: To be documented.
402  * @return  FIXME: To be documented.
403  * @ingroup Ecore_Con_Url_Group
404  */
405 EAPI void
406 ecore_con_url_fd_set(Ecore_Con_Url *url_con, int fd)
407 {
408 #ifdef HAVE_CURL
409    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
410      {
411         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_set");
412         return ;
413      }
414    url_con->write_fd = fd;
415 #endif   
416 }
417
418 /**
419  * FIXME: To be documented.
420  * @return  FIXME: To be documented.
421  * @ingroup Ecore_Con_Url_Group
422  */
423 EAPI int
424 ecore_con_url_received_bytes_get(Ecore_Con_Url *url_con)
425 {
426 #ifdef HAVE_CURL
427    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
428      {
429         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_received_bytes_get");
430         return -1;
431      }
432
433    return url_con->received;
434 #endif   
435    return 0;
436 }
437
438 /**
439  * FIXME: To be documented.
440  * @return  FIXME: To be documented.
441  * @ingroup Ecore_Con_Url_Group
442  */
443 EAPI int
444 ecore_con_url_send(Ecore_Con_Url *url_con, void *data, size_t length, char *content_type)
445 {
446 #ifdef HAVE_CURL
447    char tmp[256];
448
449    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
450      {
451         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_send");
452         return 0;
453      }
454
455    if (url_con->active) return 0;
456    if (!url_con->url) return 0;
457
458    curl_slist_free_all(url_con->headers);
459    url_con->headers = NULL;
460
461    if (data)
462      {
463         curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDS, data);
464         curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDSIZE, length);
465
466         if (content_type && (strlen(content_type) < 200))
467           {
468              sprintf(tmp, "Content-type: %s", content_type);
469              url_con->headers = curl_slist_append(url_con->headers, tmp);
470           }
471         sprintf(tmp, "Content-length: %d", length);
472         url_con->headers = curl_slist_append(url_con->headers, tmp);
473      }
474
475    switch (url_con->condition)
476      {
477       case ECORE_CON_URL_TIME_NONE:
478          curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
479          break;
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);
483          break;
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);
487          break;
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);
491          break;
492      }
493
494    curl_easy_setopt(url_con->curl_easy, CURLOPT_HTTPHEADER, url_con->headers);
495
496    return _ecore_con_url_perform(url_con);
497 #else
498    return 0;
499    url_con = NULL;
500    data = NULL;
501    length = 0;
502    content_type = NULL;
503 #endif
504 }
505
506 /**
507  * Makes a FTP upload
508  * @return  FIXME: To be more documented.
509  * @ingroup Ecore_Con_Url_Group
510  */
511 EAPI int 
512 ecore_con_url_ftp_upload(Ecore_Con_Url *url_con, char *filename, char *user, char *pass, char *upload_dir)
513 {
514 #ifdef HAVE_CURL
515    char url[4096];
516    char userpwd[4096];
517    FILE *fd;
518    struct stat file_info;
519         
520    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
521      {
522         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_ftp_upload");
523         return 0;
524      }
525      
526    if (url_con->active) return 0;
527    if (!url_con->url) return 0;
528    if (filename)
529      {
530         if (stat(filename, &file_info)) return 0;
531         fd = fopen(filename, "rb");
532         if (upload_dir)
533            snprintf(url, sizeof(url), "ftp://%s/%s/%s", url_con->url, upload_dir, basename(filename));
534         else
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);
543
544         return _ecore_con_url_perform(url_con);
545      }
546    else
547         return 0;
548 #else
549    return 0;
550    url_con = NULL;
551    filename = NULL;
552    user = NULL;
553    pass = NULL;
554    upload_dir = NULL;
555 #endif   
556 }
557
558 /**
559  * Enable or disable libcurl verbose output, useful for debug
560  * @return  FIXME: To be more documented.
561  * @ingroup Ecore_Con_Url_Group
562  */
563 EAPI void
564 ecore_con_url_verbose_set(Ecore_Con_Url *url_con, int verbose)
565 {
566 #ifdef HAVE_CURL
567    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
568      {
569         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_verbose_set");
570         return;
571      }
572      
573    if (url_con->active) return;
574    if (!url_con->url) return;
575    if (verbose == TRUE) 
576         curl_easy_setopt(url_con->curl_easy, CURLOPT_VERBOSE, 1);
577    else 
578         curl_easy_setopt(url_con->curl_easy, CURLOPT_VERBOSE, 0);
579 #endif
580 }
581
582 /**
583  * Enable or disable EPSV extension
584  * @return  FIXME: To be more documented.
585  * @ingroup Ecore_Con_Url_Group
586  */
587 EAPI void
588 ecore_con_url_ftp_use_epsv_set(Ecore_Con_Url *url_con, int use_epsv)
589 {
590 #ifdef HAVE_CURL
591    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
592      {
593         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_ftp_use_epsv_set");
594         return;
595      }
596      
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);
601    else 
602         curl_easy_setopt(url_con->curl_easy, CURLOPT_FTP_USE_EPSV, 0);
603 #endif
604 }
605
606 #ifdef HAVE_CURL
607 static int
608 _ecore_con_url_suspend_fd_handler(void)
609 {
610    Ecore_Con_Url        *url_con;
611    int                   deleted = 0;
612
613    if (!_url_con_list)
614      return 0;
615
616    ecore_list_first_goto(_url_con_list);
617    while ((url_con = ecore_list_current(_url_con_list)))
618      {
619         if (url_con->active && url_con->fd_handler)
620           {
621              ecore_main_fd_handler_del(url_con->fd_handler);
622              url_con->fd_handler = NULL;
623              deleted++;
624           }
625         ecore_list_next(_url_con_list);
626      }
627
628    return deleted;
629 }
630
631 static int
632 _ecore_con_url_restart_fd_handler(void)
633 {
634    Ecore_Con_Url        *url_con;
635    int                   activated = 0;
636
637    if (!_url_con_list)
638      return 0;
639
640    ecore_list_first_goto(_url_con_list);
641    while ((url_con = ecore_list_current(_url_con_list)))
642      {
643         if (url_con->fd_handler == NULL
644             && url_con->fd != -1)
645           {
646              url_con->fd_handler = ecore_main_fd_handler_add(url_con->fd,
647                                                              url_con->flags,
648                                                              _ecore_con_url_fd_handler,
649                                                              NULL, NULL, NULL);
650              activated++;
651           }
652         ecore_list_next(_url_con_list);
653      }
654
655    return activated;
656 }
657
658 static size_t
659 _ecore_con_url_data_cb(void *buffer, size_t size, size_t nitems, void *userp)
660 {
661    Ecore_Con_Url *url_con;
662    Ecore_Con_Event_Url_Data *e;
663    size_t real_size = size * nitems;
664
665    url_con = (Ecore_Con_Url *)userp;
666
667    if (!url_con) return -1;
668    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
669      {
670         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_cb");
671         return -1;
672      }
673
674    url_con->received += real_size;
675
676    if (url_con->write_fd < 0)
677      {
678         e = malloc(sizeof(Ecore_Con_Event_Url_Data) + sizeof(unsigned char) * (real_size - 1));
679         if (e)
680           {
681              e->url_con = url_con;
682              e->size = real_size;
683              memcpy(e->data, buffer, real_size);
684              ecore_event_add(ECORE_CON_EVENT_URL_DATA, e,
685                              _ecore_con_event_url_free, NULL);
686           }
687      }
688    else
689      {
690         ssize_t count = 0;
691         size_t  total_size = real_size;
692         size_t  offset = 0;
693
694         while (total_size > 0)
695           {
696              count = write(url_con->write_fd, (char*) buffer + offset, total_size);
697              if (count < 0)
698                {
699                   if (errno != EAGAIN && errno != EINTR)
700                     return -1;
701                }
702              else
703                {
704                   total_size -= count;
705                   offset += count;
706                }
707           }
708      }
709
710    return real_size;
711 }
712
713 #define ECORE_CON_URL_TRANSMISSION(Transmit, Event, Url_con, Total, Now) \
714 { \
715    Ecore_Con_Event_Url_Progress *e; \
716    if ((Total != 0) || (Now != 0)) \
717      { \
718         e = calloc(1, sizeof(Ecore_Con_Event_Url_Progress)); \
719         if (e) \
720           { \
721              e->url_con = url_con; \
722              e->total = Total; \
723              e->now = Now; \
724              ecore_event_add(Event, e, _ecore_con_event_url_free, NULL); \
725           } \
726      } \
727 }
728
729 static int
730 _ecore_con_url_progress_cb(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
731 {
732    Ecore_Con_Event_Url_Progress *e;
733    Ecore_Con_Url                *url_con;
734
735    url_con = clientp;
736
737    e = calloc(1, sizeof(Ecore_Con_Event_Url_Progress));
738    if (e)
739      {
740         e->url_con = url_con;
741         e->down.total = dltotal;
742         e->down.now = dlnow;
743         e->up.total = ultotal;
744         e->up.now = ulnow;
745         ecore_event_add(ECORE_CON_EVENT_URL_PROGRESS, e, _ecore_con_event_url_free, NULL);
746      }
747
748    return 0;
749 }
750
751 static size_t 
752 _ecore_con_url_read_cb(void *ptr, size_t size, size_t nitems, void *stream)
753 {
754    size_t retcode = fread(ptr, size, nitems, stream);
755    if (ferror((FILE*)stream)) {
756         fclose(stream);
757         return CURL_READFUNC_ABORT;
758    } else if ((retcode == 0) || (retcode < nitems)) {
759         fclose((FILE*)stream);
760         return 0;
761    }
762    fprintf(stderr, "*** We read %d bytes from file\n", retcode);
763    return retcode;
764 }
765
766 static int
767 _ecore_con_url_perform(Ecore_Con_Url *url_con)
768 {
769    fd_set read_set, write_set, exc_set;
770    double start;
771    int fd_max;
772    int fd;
773    int flags;
774    int still_running;
775    int completed_immediately = 0;
776
777    ecore_list_append(_url_con_list, url_con);
778
779    start = ecore_time_get();
780    url_con->active = 1;
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);
784
785    completed_immediately =  _ecore_con_url_process_completed_jobs(url_con);
786
787    if (!completed_immediately)
788      {
789         /* url_con still active -- set up an fd_handler */
790         FD_ZERO(&read_set);
791         FD_ZERO(&write_set);
792         FD_ZERO(&exc_set);
793
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++)
797           {
798              if (!FD_ISSET(fd, &_current_fd_set))
799                {
800                   flags = 0;
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;
804                   if (flags)
805                     {
806                        FD_SET(fd, &_current_fd_set);
807                        url_con->fd = fd;
808                        url_con->flags = flags;
809                        url_con->fd_handler = ecore_main_fd_handler_add(fd, flags,
810                                                                        _ecore_con_url_fd_handler,
811                                                                        NULL, NULL, NULL);
812                        break;
813                     }
814                }
815           }
816         if (!url_con->fd_handler)
817           {
818              /* Failed to set up an fd_handler */
819              curl_multi_remove_handle(curlm, url_con->curl_easy);
820              url_con->active = 0;
821              url_con->fd = -1;
822              return 0;
823           }
824      }
825
826    return 1;
827 }
828
829 static int
830 _ecore_con_url_idler_handler(void *data)
831 {
832    double       start;
833    int          done = 1;
834    int          still_running;
835
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)
840        {
841           done = 0;
842           break;
843        }
844
845    _ecore_con_url_process_completed_jobs(NULL);
846
847    if (done)
848      {
849         _ecore_con_url_restart_fd_handler();
850         _fd_idler_handler = NULL;
851         return 0;
852      }
853
854    return 1;
855 }
856
857 static int
858 _ecore_con_url_fd_handler(void *data __UNUSED__, Ecore_Fd_Handler *fd_handler __UNUSED__)
859 {
860    _ecore_con_url_suspend_fd_handler();
861
862    if (_fd_idler_handler == NULL)
863      _fd_idler_handler = ecore_idler_add(_ecore_con_url_idler_handler, NULL);
864
865    return 1;
866 }
867
868 static int
869 _ecore_con_url_process_completed_jobs(Ecore_Con_Url *url_con_to_match)
870 {
871    Ecore_Con_Url *url_con;
872    CURLMsg *curlmsg;
873    int n_remaining;
874    int job_matched = 0;
875
876    /* Loop jobs and check if any are done */
877    while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != NULL)
878      {
879         if (curlmsg->msg != CURLMSG_DONE) continue;
880
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)))
884           {
885              if (curlmsg->easy_handle == url_con->curl_easy)
886                {
887                   /* We have found the completed job in our job list */
888                   if (url_con_to_match && (url_con == url_con_to_match)) {
889                        job_matched = 1;
890                   }
891                   if (url_con->fd != -1)
892                     {
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);
896                        url_con->fd = -1;
897                        url_con->fd_handler = NULL;
898                     }
899                   ecore_list_remove(_url_con_list);
900                   url_con->active = 0;
901                     {
902                        Ecore_Con_Event_Url_Complete *e;
903                        e = calloc(1, sizeof(Ecore_Con_Event_Url_Complete));
904                        if (e)
905                          {
906                             e->url_con = url_con;
907
908                             e->status = 0;
909                             curl_easy_getinfo(curlmsg->easy_handle, CURLINFO_RESPONSE_CODE, &e->status);
910
911                             _url_complete_push_event(ECORE_CON_EVENT_URL_COMPLETE, e);
912                          }
913                     }
914                   curl_multi_remove_handle(curlm, url_con->curl_easy);
915                   break;
916                }
917              ecore_list_next(_url_con_list);
918           }
919      }
920    return job_matched;
921 }
922
923 static void
924 _ecore_con_event_url_free(void *data __UNUSED__, void *ev)
925 {
926    free(ev);
927 }
928
929 #endif