1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2016, 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 https://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
23/* <DESC>
24 * multi_socket API using libuv
25 * </DESC>
26 */
27/* Example application code using the multi socket interface to download
28   multiple files at once, but instead of using curl_multi_perform and
29   curl_multi_wait, which uses select(), we use libuv.
30   It supports epoll, kqueue, etc. on unixes and fast IO completion ports on
31   Windows, which means, it should be very fast on all platforms..
32
33   Written by Clemens Gruber, based on an outdated example from uvbook and
34   some tests from libuv.
35
36   Requires libuv and (of course) libcurl.
37
38   See http://nikhilm.github.com/uvbook/ for more information on libuv.
39*/
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <uv.h>
44#include <curl/curl.h>
45
46uv_loop_t *loop;
47CURLM *curl_handle;
48uv_timer_t timeout;
49
50typedef struct curl_context_s {
51  uv_poll_t poll_handle;
52  curl_socket_t sockfd;
53} curl_context_t;
54
55curl_context_t* create_curl_context(curl_socket_t sockfd)
56{
57  curl_context_t *context;
58
59  context = (curl_context_t *) malloc(sizeof *context);
60
61  context->sockfd = sockfd;
62
63  uv_poll_init_socket(loop, &context->poll_handle, sockfd);
64  context->poll_handle.data = context;
65
66  return context;
67}
68
69void curl_close_cb(uv_handle_t *handle)
70{
71  curl_context_t *context = (curl_context_t *) handle->data;
72  free(context);
73}
74
75void destroy_curl_context(curl_context_t *context)
76{
77  uv_close((uv_handle_t *) &context->poll_handle, curl_close_cb);
78}
79
80
81void add_download(const char *url, int num)
82{
83  char filename[50];
84  FILE *file;
85  CURL *handle;
86
87  snprintf(filename, 50, "%d.download", num);
88
89  file = fopen(filename, "wb");
90  if(!file) {
91    fprintf(stderr, "Error opening %s\n", filename);
92    return;
93  }
94
95  handle = curl_easy_init();
96  curl_easy_setopt(handle, CURLOPT_WRITEDATA, file);
97  curl_easy_setopt(handle, CURLOPT_PRIVATE, file);
98  curl_easy_setopt(handle, CURLOPT_URL, url);
99  curl_multi_add_handle(curl_handle, handle);
100  fprintf(stderr, "Added download %s -> %s\n", url, filename);
101}
102
103static void check_multi_info(void)
104{
105  int running_handles;
106  char *done_url;
107  CURLMsg *message;
108  int pending;
109  FILE *file;
110
111  while((message = curl_multi_info_read(curl_handle, &pending))) {
112    switch(message->msg) {
113    case CURLMSG_DONE:
114      curl_easy_getinfo(message->easy_handle, CURLINFO_EFFECTIVE_URL,
115                        &done_url);
116      curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, &file);
117      printf("%s DONE\n", done_url);
118
119      curl_multi_remove_handle(curl_handle, message->easy_handle);
120      curl_easy_cleanup(message->easy_handle);
121      if(file) {
122        fclose(file);
123      }
124      break;
125
126    default:
127      fprintf(stderr, "CURLMSG default\n");
128      break;
129    }
130  }
131}
132
133void curl_perform(uv_poll_t *req, int status, int events)
134{
135  int running_handles;
136  int flags = 0;
137  curl_context_t *context;
138  char *done_url;
139  CURLMsg *message;
140  int pending;
141
142  uv_timer_stop(&timeout);
143
144  if(events & UV_READABLE)
145    flags |= CURL_CSELECT_IN;
146  if(events & UV_WRITABLE)
147    flags |= CURL_CSELECT_OUT;
148
149  context = (curl_context_t *) req;
150
151  curl_multi_socket_action(curl_handle, context->sockfd, flags,
152                           &running_handles);
153
154  check_multi_info();
155}
156
157void on_timeout(uv_timer_t *req, int status)
158{
159  int running_handles;
160  curl_multi_socket_action(curl_handle, CURL_SOCKET_TIMEOUT, 0,
161                           &running_handles);
162  check_multi_info();
163}
164
165void start_timeout(CURLM *multi, long timeout_ms, void *userp)
166{
167  if(timeout_ms <= 0)
168    timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in
169                       a bit */
170  uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
171}
172
173int handle_socket(CURL *easy, curl_socket_t s, int action, void *userp,
174                  void *socketp)
175{
176  curl_context_t *curl_context;
177  if(action == CURL_POLL_IN || action == CURL_POLL_OUT) {
178    if(socketp) {
179      curl_context = (curl_context_t *) socketp;
180    }
181    else {
182      curl_context = create_curl_context(s);
183    }
184    curl_multi_assign(curl_handle, s, (void *) curl_context);
185  }
186
187  switch(action) {
188  case CURL_POLL_IN:
189    uv_poll_start(&curl_context->poll_handle, UV_READABLE, curl_perform);
190    break;
191  case CURL_POLL_OUT:
192    uv_poll_start(&curl_context->poll_handle, UV_WRITABLE, curl_perform);
193    break;
194  case CURL_POLL_REMOVE:
195    if(socketp) {
196      uv_poll_stop(&((curl_context_t*)socketp)->poll_handle);
197      destroy_curl_context((curl_context_t*) socketp);
198      curl_multi_assign(curl_handle, s, NULL);
199    }
200    break;
201  default:
202    abort();
203  }
204
205  return 0;
206}
207
208int main(int argc, char **argv)
209{
210  loop = uv_default_loop();
211
212  if(argc <= 1)
213    return 0;
214
215  if(curl_global_init(CURL_GLOBAL_ALL)) {
216    fprintf(stderr, "Could not init cURL\n");
217    return 1;
218  }
219
220  uv_timer_init(loop, &timeout);
221
222  curl_handle = curl_multi_init();
223  curl_multi_setopt(curl_handle, CURLMOPT_SOCKETFUNCTION, handle_socket);
224  curl_multi_setopt(curl_handle, CURLMOPT_TIMERFUNCTION, start_timeout);
225
226  while(argc-- > 1) {
227    add_download(argv[argc], argc);
228  }
229
230  uv_run(loop, UV_RUN_DEFAULT);
231  curl_multi_cleanup(curl_handle);
232
233  return 0;
234}
235