Initial Import
[profile/ivi/clutter-toys.git] / attic / youhaa / src / glibcurl.c
1 /* $Id: glibcurl.c,v 1.14 2004/12/05 16:15:12 atterer Exp $ -*- C -*-
2   __   _
3   |_) /|  Copyright (C) 2004  |  richard@
4   | \/¯|  Richard Atterer     |  atterer.net
5   ¯ '` ¯
6   All rights reserved.
7
8   Permission to use, copy, modify, and distribute this software for any
9   purpose with or without fee is hereby granted, provided that the above
10   copyright notice and this permission notice appear in all copies.
11
12   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
15   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16   DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17   OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18   USE OR OTHER DEALINGS IN THE SOFTWARE.
19
20   Except as contained in this notice, the name of a copyright holder shall
21   not be used in advertising or otherwise to promote the sale, use or other
22   dealings in this Software without prior written authorization of the
23   copyright holder.
24
25 */
26
27 #include <glib.h>
28 #include <glibcurl.h>
29
30 #include <stdio.h>
31 #include <string.h>
32 #include <assert.h>
33
34 /* #define D(_args) fprintf _args; */
35 #define D(_args)
36
37 /* #if 1 */
38 #ifdef G_OS_WIN32
39 /*______________________________________________________________________*/
40
41 /* Timeout for the fds passed to glib's poll() call, in millisecs.
42    curl_multi_fdset(3) says we should call curl_multi_perform() at regular
43    intervals. */
44 #define GLIBCURL_TIMEOUT 500
45
46 /* A structure which "derives" (in glib speak) from GSource */
47 typedef struct CurlGSource_ {
48   GSource source; /* First: The type we're deriving from */
49
50   CURLM* multiHandle;
51   GThread* selectThread;
52   GCond* cond; /* To signal selectThread => main thread: call perform() */
53   GMutex* mutex; /* Not held by selectThread whenever it is waiting */
54
55   gboolean callPerform; /* TRUE => Call curl_multi_perform() Real Soon */
56   gint gtkBlockAndWait;
57   gboolean selectRunning; /* FALSE => selectThread terminates */
58
59   /* For data returned by curl_multi_fdset */
60   fd_set fdRead;
61   fd_set fdWrite;
62   fd_set fdExc;
63   int fdMax;
64
65 } CurlGSource;
66
67 /* Global state: Our CurlGSource object */
68 static CurlGSource* curlSrc = 0;
69
70 /* The "methods" of CurlGSource */
71 static gboolean prepare(GSource* source, gint* timeout);
72 static gboolean check(GSource* source);
73 static gboolean dispatch(GSource* source, GSourceFunc callback,
74                          gpointer user_data);
75 static void finalize(GSource* source);
76
77 static GSourceFuncs curlFuncs = {
78   &prepare, &check, &dispatch, &finalize, 0, 0
79 };
80 /*______________________________________________________________________*/
81
82 void glibcurl_init() {
83   /* Create source object for curl file descriptors, and hook it into the
84      default main context. */
85   GSource* src = g_source_new(&curlFuncs, sizeof(CurlGSource));
86   curlSrc = (CurlGSource*)src;
87   g_source_attach(&curlSrc->source, NULL);
88
89   if (!g_thread_supported()) g_thread_init(NULL);
90
91   /* Init rest of our data */
92   curlSrc->callPerform = 0;
93   curlSrc->selectThread = 0;
94   curlSrc->cond = g_cond_new();
95   curlSrc->mutex = g_mutex_new();
96   curlSrc->gtkBlockAndWait = 0;
97
98   /* Init libcurl */
99   curl_global_init(CURL_GLOBAL_ALL);
100   curlSrc->multiHandle = curl_multi_init();
101 }
102 /*______________________________________________________________________*/
103
104 void glibcurl_cleanup() {
105   D((stderr, "glibcurl_cleanup\n"));
106   /* You must call curl_multi_remove_handle() and curl_easy_cleanup() for all
107      requests before calling this. */
108 /*   assert(curlSrc->callPerform == 0); */
109
110   /* All easy handles must be finished */
111
112   /* Lock before accessing selectRunning/selectThread */
113   g_mutex_lock(curlSrc->mutex);
114   curlSrc->selectRunning = FALSE;
115   while (curlSrc->selectThread != NULL) {
116     g_mutex_unlock(curlSrc->mutex);
117     g_thread_yield();
118     g_cond_signal(curlSrc->cond); /* Make the select thread shut down */
119     g_thread_yield();
120     g_mutex_lock(curlSrc->mutex); /* Wait until it has shut down */
121   }
122   g_mutex_unlock(curlSrc->mutex);
123
124   assert(curlSrc->selectThread == NULL);
125
126   g_cond_free(curlSrc->cond);
127   g_mutex_free(curlSrc->mutex);
128
129   curl_multi_cleanup(curlSrc->multiHandle);
130   curlSrc->multiHandle = 0;
131   curl_global_cleanup();
132
133   g_source_unref(&curlSrc->source);
134   curlSrc = 0;
135 }
136 /*______________________________________________________________________*/
137
138 CURLM* glibcurl_handle() {
139   return curlSrc->multiHandle;
140 }
141 /*______________________________________________________________________*/
142
143 CURLMcode glibcurl_add(CURL *easy_handle) {
144   assert(curlSrc != 0);
145   assert(curlSrc->multiHandle != 0);
146   glibcurl_start();
147   return curl_multi_add_handle(curlSrc->multiHandle, easy_handle);
148 }
149 /*______________________________________________________________________*/
150
151 CURLMcode glibcurl_remove(CURL *easy_handle) {
152   D((stderr, "glibcurl_remove %p\n", easy_handle));
153   assert(curlSrc != 0);
154   assert(curlSrc->multiHandle != 0);
155   return curl_multi_remove_handle(curlSrc->multiHandle, easy_handle);
156 }
157 /*______________________________________________________________________*/
158
159 /* Call this whenever you have added a request using
160    curl_multi_add_handle(). */
161 void glibcurl_start() {
162   D((stderr, "glibcurl_start\n"));
163   curlSrc->callPerform = TRUE;
164 }
165 /*______________________________________________________________________*/
166
167 void glibcurl_set_callback(GlibcurlCallback function, void* data) {
168   g_source_set_callback(&curlSrc->source, (GSourceFunc)function, data,
169                         NULL);
170 }
171 /*______________________________________________________________________*/
172
173 static gpointer selectThread(gpointer data) {
174   int fdCount;
175   struct timeval timeout;
176   assert(data == 0); /* Just to get rid of unused param warning */
177
178   D((stderr, "selectThread\n"));
179   g_mutex_lock(curlSrc->mutex);
180   D((stderr, "selectThread: got lock\n"));
181
182   curlSrc->selectRunning = TRUE;
183   while (curlSrc->selectRunning) {
184
185     FD_ZERO(&curlSrc->fdRead);
186     FD_ZERO(&curlSrc->fdWrite);
187     FD_ZERO(&curlSrc->fdExc);
188     curlSrc->fdMax = -1;
189     /* What fds does libcurl want us to poll? */
190     curl_multi_fdset(curlSrc->multiHandle, &curlSrc->fdRead,
191                      &curlSrc->fdWrite, &curlSrc->fdExc, &curlSrc->fdMax);
192     timeout.tv_sec = GLIBCURL_TIMEOUT / 1000;
193     timeout.tv_usec = (GLIBCURL_TIMEOUT % 1000) * 1000;
194     fdCount = select(curlSrc->fdMax + 1, &curlSrc->fdRead, &curlSrc->fdWrite,
195                      &curlSrc->fdExc, &timeout);
196     D((stderr, "selectThread: select() fdCount=%d\n", fdCount));
197
198     g_atomic_int_inc(&curlSrc->gtkBlockAndWait); /* "GTK thread, block!" */
199     D((stderr, "selectThread: waking up GTK thread %d\n",
200        curlSrc->gtkBlockAndWait));
201     /* GTK thread will almost immediately block in prepare() */
202     g_main_context_wakeup(NULL);
203
204     /* Now unblock GTK thread, continue after it signals us */
205     D((stderr, "selectThread: pre-wait\n"));
206     g_cond_wait(curlSrc->cond, curlSrc->mutex);
207     D((stderr, "selectThread: post-wait\n"));
208
209   }
210
211   curlSrc->selectThread = NULL;
212   D((stderr, "selectThread: exit\n"));
213   g_mutex_unlock(curlSrc->mutex);
214   return NULL;
215 }
216 /*______________________________________________________________________*/
217
218 /* Returning FALSE may cause the main loop to block indefinitely, but that is
219    not a problem, we use g_main_context_wakeup to wake it up */
220 /* Returns TRUE iff it holds the mutex lock */
221 gboolean prepare(GSource* source, gint* timeout) {
222   assert(source == &curlSrc->source);
223   D((stderr, "prepare: callPerform=%d, thread=%p\n",
224      curlSrc->callPerform, curlSrc->selectThread));
225
226   *timeout = -1;
227
228   if (g_atomic_int_dec_and_test(&curlSrc->gtkBlockAndWait)) {
229     /* The select thread wants us to block */
230     D((stderr, "prepare: trying lock\n"));
231     g_mutex_lock(curlSrc->mutex);
232     D((stderr, "prepare: got lock\n"));
233     return TRUE;
234   } else {
235     g_atomic_int_inc(&curlSrc->gtkBlockAndWait);
236   }
237
238   /* Usual behaviour: Nothing happened, so don't dispatch. */
239   if (!curlSrc->callPerform) return FALSE;
240
241   /* Always dispatch if callPerform, i.e. 1st download just starting. */
242   D((stderr, "prepare: trying lock 2\n"));
243   /* Problem: We can block up to GLIBCURL_TIMEOUT msecs here, until the
244      select() call returns. However, under Win32 this does not appear to be a
245      problem (don't know why) - it _does_ tend to block the GTK thread under
246      Linux. */
247   g_mutex_lock(curlSrc->mutex);
248   D((stderr, "prepare: got lock 2\n"));
249   curlSrc->callPerform = FALSE;
250   if (curlSrc->selectThread == NULL) {
251     D((stderr, "prepare: starting select thread\n"));
252     /* Note that the thread will stop soon because we hold mutex */
253     curlSrc->selectThread = g_thread_create(&selectThread, 0, FALSE, NULL);
254     assert(curlSrc->selectThread != NULL);
255   }
256   return TRUE;
257 }
258 /*______________________________________________________________________*/
259
260 /* Called after all the file descriptors are polled by glib. */
261 gboolean check(GSource* source) {
262   assert(source == &curlSrc->source);
263   return FALSE;
264 }
265 /*______________________________________________________________________*/
266
267 gboolean dispatch(GSource* source, GSourceFunc callback,
268                   gpointer user_data) {
269   CURLMcode x;
270   int multiCount;
271
272   assert(source == &curlSrc->source);
273   do {
274     x = curl_multi_perform(curlSrc->multiHandle, &multiCount);
275     D((stderr, "dispatched: code=%d, reqs=%d\n", x, multiCount));
276   } while (x == CURLM_CALL_MULTI_PERFORM);
277
278   if (multiCount == 0)
279     curlSrc->selectRunning = FALSE;
280
281   if (callback != 0) (*callback)(user_data);
282
283   /* Let selectThread call select() again */
284   g_cond_signal(curlSrc->cond);
285   g_mutex_unlock(curlSrc->mutex);
286
287   return TRUE; /* "Do not destroy me" */
288 }
289 /*______________________________________________________________________*/
290
291 void finalize(GSource* source) {
292   assert(source == &curlSrc->source);
293 }
294 /*======================================================================*/
295
296 #else /* !G_OS_WIN32 */
297
298 /* Number of highest allowed fd */
299 #define GLIBCURL_FDMAX 127
300
301 /* Timeout for the fds passed to glib's poll() call, in millisecs.
302    curl_multi_fdset(3) says we should call curl_multi_perform() at regular
303    intervals. */
304 #define GLIBCURL_TIMEOUT 1000
305
306 /* GIOCondition event masks */
307 #define GLIBCURL_READ  (G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP)
308 #define GLIBCURL_WRITE (G_IO_OUT | G_IO_ERR | G_IO_HUP)
309 #define GLIBCURL_EXC   (G_IO_ERR | G_IO_HUP)
310
311 /** A structure which "derives" (in glib speak) from GSource */
312 typedef struct CurlGSource_ {
313   GSource source; /* First: The type we're deriving from */
314
315   CURLM* multiHandle;
316
317   /* Previously seen FDs, for comparing with libcurl's current fd_sets */
318   GPollFD lastPollFd[GLIBCURL_FDMAX + 1];
319   int lastPollFdMax; /* Index of highest non-empty entry in lastPollFd */
320
321   int callPerform; /* Non-zero => curl_multi_perform() gets called */
322
323   /* For data returned by curl_multi_fdset */
324   fd_set fdRead;
325   fd_set fdWrite;
326   fd_set fdExc;
327   int fdMax;
328
329 } CurlGSource;
330
331 /* Global state: Our CurlGSource object */
332 static CurlGSource* curlSrc = 0;
333
334 /* The "methods" of CurlGSource */
335 static gboolean prepare(GSource* source, gint* timeout);
336 static gboolean check(GSource* source);
337 static gboolean dispatch(GSource* source, GSourceFunc callback,
338                          gpointer user_data);
339 static void finalize(GSource* source);
340
341 static GSourceFuncs curlFuncs = {
342   &prepare, &check, &dispatch, &finalize, 0, 0
343 };
344 /*______________________________________________________________________*/
345
346 void glibcurl_init() {
347   int fd;
348   /* Create source object for curl file descriptors, and hook it into the
349      default main context. */
350   curlSrc = (CurlGSource*)g_source_new(&curlFuncs, sizeof(CurlGSource));
351   g_source_attach(&curlSrc->source, NULL);
352
353   /* Init rest of our data */
354   memset(&curlSrc->lastPollFd, 0, sizeof(curlSrc->lastPollFd));
355   for (fd = 1; fd <= GLIBCURL_FDMAX; ++fd)
356     curlSrc->lastPollFd[fd].fd = fd;
357   curlSrc->lastPollFdMax = 0;
358   curlSrc->callPerform = 0;
359
360   /* Init libcurl */
361   curl_global_init(CURL_GLOBAL_ALL);
362   curlSrc->multiHandle = curl_multi_init();
363
364   D((stderr, "events: R=%x W=%x X=%x\n", GLIBCURL_READ, GLIBCURL_WRITE,
365      GLIBCURL_EXC));
366 }
367 /*______________________________________________________________________*/
368
369 CURLM* glibcurl_handle() {
370   return curlSrc->multiHandle;
371 }
372 /*______________________________________________________________________*/
373
374 CURLMcode glibcurl_add(CURL *easy_handle) {
375   assert(curlSrc->multiHandle != 0);
376   curlSrc->callPerform = -1;
377   return curl_multi_add_handle(curlSrc->multiHandle, easy_handle);
378 }
379 /*______________________________________________________________________*/
380
381 CURLMcode glibcurl_remove(CURL *easy_handle) {
382   assert(curlSrc != 0);
383   assert(curlSrc->multiHandle != 0);
384   return curl_multi_remove_handle(curlSrc->multiHandle, easy_handle);
385 }
386 /*______________________________________________________________________*/
387
388 /* Call this whenever you have added a request using curl_multi_add_handle().
389    This is necessary to start new requests. It does so by triggering a call
390    to curl_multi_perform() even in the case where no open fds cause that
391    function to be called anyway. */
392 void glibcurl_start() {
393   curlSrc->callPerform = -1;
394 }
395 /*______________________________________________________________________*/
396
397 void glibcurl_set_callback(GlibcurlCallback function, void* data) {
398   g_source_set_callback(&curlSrc->source, (GSourceFunc)function, data,
399                         NULL);
400 }
401 /*______________________________________________________________________*/
402
403 void glibcurl_cleanup() {
404   /* You must call curl_multi_remove_handle() and curl_easy_cleanup() for all
405      requests before calling this. */
406 /*   assert(curlSrc->callPerform == 0); */
407
408   curl_multi_cleanup(curlSrc->multiHandle);
409   curlSrc->multiHandle = 0;
410   curl_global_cleanup();
411
412 /*   g_source_destroy(&curlSrc->source); */
413   g_source_unref(&curlSrc->source);
414   curlSrc = 0;
415 }
416 /*______________________________________________________________________*/
417
418 static void registerUnregisterFds() {
419   int fd, fdMax;
420
421   FD_ZERO(&curlSrc->fdRead);
422   FD_ZERO(&curlSrc->fdWrite);
423   FD_ZERO(&curlSrc->fdExc);
424   curlSrc->fdMax = -1;
425   /* What fds does libcurl want us to poll? */
426   curl_multi_fdset(curlSrc->multiHandle, &curlSrc->fdRead,
427                    &curlSrc->fdWrite, &curlSrc->fdExc, &curlSrc->fdMax);
428   /*fprintf(stderr, "registerUnregisterFds: fdMax=%d\n", curlSrc->fdMax);*/
429   assert(curlSrc->fdMax >= -1 && curlSrc->fdMax <= GLIBCURL_FDMAX);
430
431   fdMax = curlSrc->fdMax;
432   if (fdMax < curlSrc->lastPollFdMax) fdMax = curlSrc->lastPollFdMax;
433
434   /* Has the list of required events for any of the fds changed? */
435   for (fd = 0; fd <= fdMax; ++fd) {
436     gushort events = 0;
437     if (FD_ISSET(fd, &curlSrc->fdRead))  events |= GLIBCURL_READ;
438     if (FD_ISSET(fd, &curlSrc->fdWrite)) events |= GLIBCURL_WRITE;
439     if (FD_ISSET(fd, &curlSrc->fdExc))   events |= GLIBCURL_EXC;
440
441     /* List of events unchanged => no (de)registering */
442     if (events == curlSrc->lastPollFd[fd].events) continue;
443
444     D((stderr, "registerUnregisterFds: fd %d: old events %x, "
445        "new events %x\n", fd, curlSrc->lastPollFd[fd].events, events));
446
447     /* fd is already a lastPollFd, but event type has changed => do nothing.
448        Due to the implementation of g_main_context_query(), the new event
449        flags will be picked up automatically. */
450     if (events != 0 && curlSrc->lastPollFd[fd].events != 0) {
451       curlSrc->lastPollFd[fd].events = events;
452       continue;
453     }
454     curlSrc->lastPollFd[fd].events = events;
455
456     /* Otherwise, (de)register as appropriate */
457     if (events == 0) {
458       g_source_remove_poll(&curlSrc->source, &curlSrc->lastPollFd[fd]);
459       curlSrc->lastPollFd[fd].revents = 0;
460       D((stderr, "unregister fd %d\n", fd));
461     } else {
462       g_source_add_poll(&curlSrc->source, &curlSrc->lastPollFd[fd]);
463       D((stderr, "register fd %d\n", fd));
464     }
465   }
466
467   curlSrc->lastPollFdMax = curlSrc->fdMax;
468 }
469
470 /* Called before all the file descriptors are polled by the glib main loop.
471    We must have a look at all fds that libcurl wants polled. If any of them
472    are new/no longer needed, we have to (de)register them with glib. */
473 gboolean prepare(GSource* source, gint* timeout) {
474   D((stderr, "prepare\n"));
475   assert(source == &curlSrc->source);
476
477   if (curlSrc->multiHandle == 0) return FALSE;
478
479   registerUnregisterFds();
480
481   *timeout = GLIBCURL_TIMEOUT;
482 /*   return FALSE; */
483   return curlSrc->callPerform == -1 ? TRUE : FALSE;
484 }
485 /*______________________________________________________________________*/
486
487 /* Called after all the file descriptors are polled by glib.
488    g_main_context_check() has copied back the revents fields (set by glib's
489    poll() call) to our GPollFD objects. How inefficient all that copying
490    is... let's add some more and copy the results of these revents into
491    libcurl's fd_sets! */
492 gboolean check(GSource* source) {
493   int fd, somethingHappened = 0;
494
495   if (curlSrc->multiHandle == 0) return FALSE;
496
497   assert(source == &curlSrc->source);
498   FD_ZERO(&curlSrc->fdRead);
499   FD_ZERO(&curlSrc->fdWrite);
500   FD_ZERO(&curlSrc->fdExc);
501   for (fd = 0; fd <= curlSrc->fdMax; ++fd) {
502     gushort revents = curlSrc->lastPollFd[fd].revents;
503     if (revents == 0) continue;
504     somethingHappened = 1;
505 /*     D((stderr, "[fd%d] ", fd)); */
506     if (revents & (G_IO_IN | G_IO_PRI))
507       FD_SET((unsigned)fd, &curlSrc->fdRead);
508     if (revents & G_IO_OUT)
509       FD_SET((unsigned)fd, &curlSrc->fdWrite);
510     if (revents & (G_IO_ERR | G_IO_HUP))
511       FD_SET((unsigned)fd, &curlSrc->fdExc);
512   }
513 /*   D((stderr, "check: fdMax %d\n", curlSrc->fdMax)); */
514
515 /*   return TRUE; */
516 /*   return FALSE; */
517   return curlSrc->callPerform == -1 || somethingHappened != 0 ? TRUE : FALSE;
518 }
519 /*______________________________________________________________________*/
520
521 gboolean dispatch(GSource* source, GSourceFunc callback,
522                   gpointer user_data) {
523   CURLMcode x;
524
525   assert(source == &curlSrc->source);
526   assert(curlSrc->multiHandle != 0);
527   do {
528     x = curl_multi_perform(curlSrc->multiHandle, &curlSrc->callPerform);
529 /*     D((stderr, "dispatched %d\n", x)); */
530   } while (x == CURLM_CALL_MULTI_PERFORM);
531
532   /* If no more calls to curl_multi_perform(), unregister left-over fds */
533   if (curlSrc->callPerform == 0) registerUnregisterFds();
534
535   if (callback != 0) (*callback)(user_data);
536
537   return TRUE; /* "Do not destroy me" */
538 }
539 /*______________________________________________________________________*/
540
541 void finalize(GSource* source) {
542   assert(source == &curlSrc->source);
543   registerUnregisterFds();
544 }
545
546 #endif