0621d3eefe5fc48df44ce221d67322e6865227b5
[platform/upstream/curl.git] / docs / examples / ghiper.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at http://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22 /* <DESC>
23  * multi socket API usage together with with glib2
24  * </DESC>
25  */
26 /* Example application source code using the multi socket interface to
27  * download many files at once.
28  *
29  * Written by Jeff Pohlmeyer
30
31 Requires glib-2.x and a (POSIX?) system that has mkfifo().
32
33 This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
34 sample programs, adapted to use glib's g_io_channel in place of libevent.
35
36 When running, the program creates the named pipe "hiper.fifo"
37
38 Whenever there is input into the fifo, the program reads the input as a list
39 of URL's and creates some new easy handles to fetch each URL via the
40 curl_multi "hiper" API.
41
42
43 Thus, you can try a single URL:
44   % echo http://www.yahoo.com > hiper.fifo
45
46 Or a whole bunch of them:
47   % cat my-url-list > hiper.fifo
48
49 The fifo buffer is handled almost instantly, so you can even add more URL's
50 while the previous requests are still being downloaded.
51
52 This is purely a demo app, all retrieved data is simply discarded by the write
53 callback.
54
55 */
56
57
58 #include <glib.h>
59 #include <sys/stat.h>
60 #include <unistd.h>
61 #include <fcntl.h>
62 #include <stdlib.h>
63 #include <stdio.h>
64 #include <errno.h>
65 #include <curl/curl.h>
66
67
68 #define MSG_OUT g_print   /* Change to "g_error" to write to stderr */
69 #define SHOW_VERBOSE 0    /* Set to non-zero for libcurl messages */
70 #define SHOW_PROGRESS 0   /* Set to non-zero to enable progress callback */
71
72
73
74 /* Global information, common to all connections */
75 typedef struct _GlobalInfo {
76   CURLM *multi;
77   guint timer_event;
78   int still_running;
79 } GlobalInfo;
80
81
82
83 /* Information associated with a specific easy handle */
84 typedef struct _ConnInfo {
85   CURL *easy;
86   char *url;
87   GlobalInfo *global;
88   char error[CURL_ERROR_SIZE];
89 } ConnInfo;
90
91
92 /* Information associated with a specific socket */
93 typedef struct _SockInfo {
94   curl_socket_t sockfd;
95   CURL *easy;
96   int action;
97   long timeout;
98   GIOChannel *ch;
99   guint ev;
100   GlobalInfo *global;
101 } SockInfo;
102
103
104
105
106 /* Die if we get a bad CURLMcode somewhere */
107 static void mcode_or_die(const char *where, CURLMcode code) {
108   if ( CURLM_OK != code ) {
109     const char *s;
110     switch (code) {
111       case     CURLM_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
112       case     CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
113       case     CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
114       case     CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
115       case     CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";         break;
116       case     CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
117       case     CURLM_LAST:               s="CURLM_LAST";               break;
118       default: s="CURLM_unknown";
119     }
120     MSG_OUT("ERROR: %s returns %s\n", where, s);
121     exit(code);
122   }
123 }
124
125
126
127 /* Check for completed transfers, and remove their easy handles */
128 static void check_multi_info(GlobalInfo *g)
129 {
130   char *eff_url;
131   CURLMsg *msg;
132   int msgs_left;
133   ConnInfo *conn;
134   CURL *easy;
135   CURLcode res;
136
137   MSG_OUT("REMAINING: %d\n", g->still_running);
138   while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
139     if (msg->msg == CURLMSG_DONE) {
140       easy = msg->easy_handle;
141       res = msg->data.result;
142       curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
143       curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
144       MSG_OUT("DONE: %s => (%d) %s\n", eff_url, res, conn->error);
145       curl_multi_remove_handle(g->multi, easy);
146       free(conn->url);
147       curl_easy_cleanup(easy);
148       free(conn);
149     }
150   }
151 }
152
153
154
155 /* Called by glib when our timeout expires */
156 static gboolean timer_cb(gpointer data)
157 {
158   GlobalInfo *g = (GlobalInfo *)data;
159   CURLMcode rc;
160
161   rc = curl_multi_socket_action(g->multi,
162                                   CURL_SOCKET_TIMEOUT, 0, &g->still_running);
163   mcode_or_die("timer_cb: curl_multi_socket_action", rc);
164   check_multi_info(g);
165   return FALSE;
166 }
167
168
169
170 /* Update the event timer after curl_multi library calls */
171 static int update_timeout_cb(CURLM *multi, long timeout_ms, void *userp)
172 {
173   struct timeval timeout;
174   GlobalInfo *g=(GlobalInfo *)userp;
175   timeout.tv_sec = timeout_ms/1000;
176   timeout.tv_usec = (timeout_ms%1000)*1000;
177
178   MSG_OUT("*** update_timeout_cb %ld => %ld:%ld ***\n",
179               timeout_ms, timeout.tv_sec, timeout.tv_usec);
180
181   g->timer_event = g_timeout_add(timeout_ms, timer_cb, g);
182   return 0;
183 }
184
185
186
187
188 /* Called by glib when we get action on a multi socket */
189 static gboolean event_cb(GIOChannel *ch, GIOCondition condition, gpointer data)
190 {
191   GlobalInfo *g = (GlobalInfo*) data;
192   CURLMcode rc;
193   int fd=g_io_channel_unix_get_fd(ch);
194
195   int action =
196     (condition & G_IO_IN ? CURL_CSELECT_IN : 0) |
197     (condition & G_IO_OUT ? CURL_CSELECT_OUT : 0);
198
199   rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
200   mcode_or_die("event_cb: curl_multi_socket_action", rc);
201
202   check_multi_info(g);
203   if(g->still_running) {
204     return TRUE;
205   } else {
206     MSG_OUT("last transfer done, kill timeout\n");
207     if (g->timer_event) { g_source_remove(g->timer_event); }
208     return FALSE;
209   }
210 }
211
212
213
214 /* Clean up the SockInfo structure */
215 static void remsock(SockInfo *f)
216 {
217   if (!f) { return; }
218   if (f->ev) { g_source_remove(f->ev); }
219   g_free(f);
220 }
221
222
223
224 /* Assign information to a SockInfo structure */
225 static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
226 {
227   GIOCondition kind =
228      (act&CURL_POLL_IN?G_IO_IN:0)|(act&CURL_POLL_OUT?G_IO_OUT:0);
229
230   f->sockfd = s;
231   f->action = act;
232   f->easy = e;
233   if (f->ev) { g_source_remove(f->ev); }
234   f->ev=g_io_add_watch(f->ch, kind, event_cb,g);
235
236 }
237
238
239
240 /* Initialize a new SockInfo structure */
241 static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
242 {
243   SockInfo *fdp = g_malloc0(sizeof(SockInfo));
244
245   fdp->global = g;
246   fdp->ch=g_io_channel_unix_new(s);
247   setsock(fdp, s, easy, action, g);
248   curl_multi_assign(g->multi, s, fdp);
249 }
250
251
252
253 /* CURLMOPT_SOCKETFUNCTION */
254 static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
255 {
256   GlobalInfo *g = (GlobalInfo*) cbp;
257   SockInfo *fdp = (SockInfo*) sockp;
258   static const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
259
260   MSG_OUT("socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
261   if (what == CURL_POLL_REMOVE) {
262     MSG_OUT("\n");
263     remsock(fdp);
264   } else {
265     if (!fdp) {
266       MSG_OUT("Adding data: %s%s\n",
267              what&CURL_POLL_IN?"READ":"",
268              what&CURL_POLL_OUT?"WRITE":"" );
269       addsock(s, e, what, g);
270     }
271     else {
272       MSG_OUT(
273         "Changing action from %d to %d\n", fdp->action, what);
274       setsock(fdp, s, e, what, g);
275     }
276   }
277   return 0;
278 }
279
280
281
282 /* CURLOPT_WRITEFUNCTION */
283 static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
284 {
285   size_t realsize = size * nmemb;
286   ConnInfo *conn = (ConnInfo*) data;
287   (void)ptr;
288   (void)conn;
289   return realsize;
290 }
291
292
293
294 /* CURLOPT_PROGRESSFUNCTION */
295 static int prog_cb (void *p, double dltotal, double dlnow, double ult, double uln)
296 {
297   ConnInfo *conn = (ConnInfo *)p;
298   MSG_OUT("Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
299   return 0;
300 }
301
302
303
304 /* Create a new easy handle, and add it to the global curl_multi */
305 static void new_conn(char *url, GlobalInfo *g )
306 {
307   ConnInfo *conn;
308   CURLMcode rc;
309
310   conn = g_malloc0(sizeof(ConnInfo));
311
312   conn->error[0]='\0';
313
314   conn->easy = curl_easy_init();
315   if (!conn->easy) {
316     MSG_OUT("curl_easy_init() failed, exiting!\n");
317     exit(2);
318   }
319   conn->global = g;
320   conn->url = g_strdup(url);
321   curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
322   curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
323   curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn);
324   curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, (long)SHOW_VERBOSE);
325   curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
326   curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
327   curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, SHOW_PROGRESS?0L:1L);
328   curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
329   curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
330   curl_easy_setopt(conn->easy, CURLOPT_FOLLOWLOCATION, 1L);
331   curl_easy_setopt(conn->easy, CURLOPT_CONNECTTIMEOUT, 30L);
332   curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1L);
333   curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 30L);
334
335   MSG_OUT("Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
336   rc =curl_multi_add_handle(g->multi, conn->easy);
337   mcode_or_die("new_conn: curl_multi_add_handle", rc);
338
339   /* note that the add_handle() will set a time-out to trigger very soon so
340      that the necessary socket_action() call will be called by this app */
341 }
342
343
344 /* This gets called by glib whenever data is received from the fifo */
345 static gboolean fifo_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
346 {
347   #define BUF_SIZE 1024
348   gsize len, tp;
349   gchar *buf, *tmp, *all=NULL;
350   GIOStatus rv;
351
352   do {
353     GError *err=NULL;
354     rv = g_io_channel_read_line (ch,&buf,&len,&tp,&err);
355     if ( buf ) {
356       if (tp) { buf[tp]='\0'; }
357       new_conn(buf,(GlobalInfo*)data);
358       g_free(buf);
359     } else {
360       buf = g_malloc(BUF_SIZE+1);
361       while (TRUE) {
362         buf[BUF_SIZE]='\0';
363         g_io_channel_read_chars(ch,buf,BUF_SIZE,&len,&err);
364         if (len) {
365           buf[len]='\0';
366           if (all) {
367             tmp=all;
368             all=g_strdup_printf("%s%s", tmp, buf);
369             g_free(tmp);
370           } else {
371             all = g_strdup(buf);
372           }
373         } else {
374            break;
375         }
376       }
377       if (all) {
378         new_conn(all,(GlobalInfo*)data);
379         g_free(all);
380       }
381       g_free(buf);
382     }
383     if ( err ) {
384       g_error("fifo_cb: %s", err->message);
385       g_free(err);
386       break;
387     }
388   } while ( (len) && (rv == G_IO_STATUS_NORMAL) );
389   return TRUE;
390 }
391
392
393
394
395 int init_fifo(void)
396 {
397  struct stat st;
398  const char *fifo = "hiper.fifo";
399  int socket;
400
401  if (lstat (fifo, &st) == 0) {
402   if ((st.st_mode & S_IFMT) == S_IFREG) {
403    errno = EEXIST;
404    perror("lstat");
405    exit (1);
406   }
407  }
408
409  unlink (fifo);
410  if (mkfifo (fifo, 0600) == -1) {
411   perror("mkfifo");
412   exit (1);
413  }
414
415  socket = open (fifo, O_RDWR | O_NONBLOCK, 0);
416
417  if (socket == -1) {
418   perror("open");
419   exit (1);
420  }
421  MSG_OUT("Now, pipe some URL's into > %s\n", fifo);
422
423  return socket;
424
425 }
426
427
428
429
430 int main(int argc, char **argv)
431 {
432   GlobalInfo *g;
433   CURLMcode rc;
434   GMainLoop*gmain;
435   int fd;
436   GIOChannel* ch;
437   g=g_malloc0(sizeof(GlobalInfo));
438
439   fd=init_fifo();
440   ch=g_io_channel_unix_new(fd);
441   g_io_add_watch(ch,G_IO_IN,fifo_cb,g);
442   gmain=g_main_loop_new(NULL,FALSE);
443   g->multi = curl_multi_init();
444   curl_multi_setopt(g->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
445   curl_multi_setopt(g->multi, CURLMOPT_SOCKETDATA, g);
446   curl_multi_setopt(g->multi, CURLMOPT_TIMERFUNCTION, update_timeout_cb);
447   curl_multi_setopt(g->multi, CURLMOPT_TIMERDATA, g);
448
449   /* we don't call any curl_multi_socket*() function yet as we have no handles
450      added! */
451
452   g_main_loop_run(gmain);
453   curl_multi_cleanup(g->multi);
454   return 0;
455 }