/*****************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * $Id: 10-at-a-time.c,v 1.2 2006-10-13 14:01:19 bagder Exp $
 *
 * Example application source code using the multi interface to download many
 * files, but with a capped maximum amount of simultaneous transfers.
 *
 * Written by Michael Wallner
 */

#include <stdlib.h>
#include <event.h>
#include <curl/multi.h>

static const char *urls[] = {
  "http://www.microsoft.com",
  "http://www.opensource.org",
  "http://www.google.com",
  "http://www.yahoo.com",
  "http://www.ibm.com",
  "http://www.mysql.com",
  "http://www.oracle.com",
  "http://www.ripe.net",
  "http://www.iana.org",
  "http://www.amazon.com",
  "http://www.netcraft.com",
  "http://www.heise.de",
  "http://www.chip.de",
  "http://www.ca.com",
  "http://www.cnet.com",
  "http://www.news.com",
  "http://www.cnn.com",
  "http://www.wikipedia.org",
  "http://www.dell.com",
  "http://www.hp.com",
  "http://www.cert.org",
  "http://www.mit.edu",
  "http://www.nist.gov",
  "http://www.ebay.com",
  "http://www.playstation.com",
  "http://www.uefa.com",
  "http://www.ieee.org",
  "http://www.apple.com",
  "http://www.sony.com",
  "http://www.symantec.com",
  "http://www.zdnet.com",
  "http://www.fujitsu.com",
  "http://www.supermicro.com",
  "http://www.hotmail.com",
  "http://www.ecma.com",
  "http://www.bbc.co.uk",
  "http://news.google.com",
  "http://www.foxnews.com",
  "http://www.msn.com",
  "http://www.wired.com",
  "http://www.sky.com",
  "http://www.usatoday.com",
  "http://www.cbs.com",
  "http://www.nbc.com",
  "http://slashdot.org",
  "http://www.bloglines.com",
  "http://www.techweb.com",
  "http://www.newslink.org",
  "http://www.un.org",
};

static int Q=0;
static unsigned U=-1,C=0;
static struct event timeout;

#define MAX 10 /* number of simultaneous transfers */
#define CNT sizeof(urls)/sizeof(char*) /* total number of transfers to do */

static void update_timeout(CURLM *cm)
{
  long L;
  struct timeval T = {1, 0};
  
  if (evtimer_pending(&timeout, NULL))
    evtimer_del(&timeout);
  if (U) {
    if (curl_multi_timeout(cm, &L)) {
      fprintf(stderr, "E: curl_multi_timeout\n");
      exit(EXIT_FAILURE);
    }
    if (L > 0) {
      T.tv_sec = L/1000;
      T.tv_usec = (L%1000)*1000;
	}
    evtimer_add(&timeout, &T);
  }
}

static int write_cb(char *d, size_t n, size_t l, void *p)
{
  /* take care of the data here, ignored in this example */
  (void)d;
  (void)p;
  return n*l;
}

static void timer_cb(int socket, short action, void *event_data)
{
  CURLM *cm = event_data;
  
  fprintf(stderr, "~");
  while (CURLM_CALL_MULTI_PERFORM == curl_multi_socket(cm, CURL_SOCKET_TIMEOUT, &U));
  update_timeout(cm);
}

static void init(CURLM *cm, int i)
{
  CURL *eh = curl_easy_init();

  curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb);
  curl_easy_setopt(eh, CURLOPT_HEADER, 0);
  curl_easy_setopt(eh, CURLOPT_URL, urls[i]);
  curl_easy_setopt(eh, CURLOPT_PRIVATE, urls[i]);
  curl_easy_setopt(eh, CURLOPT_VERBOSE, 0);

  curl_multi_add_handle(cm, eh);
}

static void event_cb(int socket, short action, void *event_data)
{
  CURLMsg *msg;
  CURLM *cm = event_data;
  
  fprintf(stderr, "#");
  while (CURLM_CALL_MULTI_PERFORM == curl_multi_socket(cm, socket, &U));
  update_timeout(cm);

  while ((msg = curl_multi_info_read(cm, &Q))) {
    if (msg->msg == CURLMSG_DONE) {
      char *url;
      CURL *e = msg->easy_handle;
      curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &url);
      fprintf(stderr, "\nR: %d - %s <%s>",
          msg->data.result, curl_easy_strerror(msg->data.result), url);
      curl_multi_remove_handle(cm, e);
      curl_easy_cleanup(e);
    }
    else {
      fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg);
    }
    if (C < CNT) {
      init(cm, C++);
    }
  }
}

static int sock_cb(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
{
  int events = EV_PERSIST;
  struct event *ev = assign_data;
  CURLM *cm = socket_data;
  
  if (ev) {
    event_del(ev);
  } else {
    ev = calloc(1, sizeof(*ev));
    curl_multi_assign(cm, sock, ev);
  }
  switch (action) {
    case CURL_POLL_REMOVE:
      free(ev);
    case CURL_POLL_NONE:
      return 0;
    case CURL_POLL_IN:
      events |= EV_READ;
      break;
    case CURL_POLL_OUT:
      events |= EV_WRITE;
      break;
    case CURL_POLL_INOUT:
      events |= EV_READ|EV_WRITE;
      break;
    default:
      return action;
  }
  
  event_set(ev, sock, events, event_cb, cm);
  event_add(ev, NULL);
  return 0;
}

int main(void)
{
  CURLM *cm;
  long L;
  struct timeval T;

  curl_global_init(CURL_GLOBAL_ALL);
  event_init();

  cm = curl_multi_init();
  curl_multi_setopt(cm, CURLMOPT_SOCKETFUNCTION, sock_cb);
  curl_multi_setopt(cm, CURLMOPT_SOCKETDATA, cm);

  for (C = 0; C < MAX; ++C) {
    init(cm, C);
  }

  evtimer_set(&timeout, timer_cb, cm);
  while (CURLM_CALL_MULTI_PERFORM == curl_multi_socket_all(cm, &U));
  update_timeout(cm);
  event_dispatch();

  curl_multi_cleanup(cm);
  curl_global_cleanup();
  fprintf(stderr, "\n");

  return EXIT_SUCCESS;
}

