/*****************************************************************************
 *                                  _   _ ____  _     
 *  Project                     ___| | | |  _ \| |    
 *                             / __| | | | |_) | |    
 *                            | (__| |_| |  _ <| |___ 
 *                             \___|\___/|_| \_\_____|
 *
 * $Id: fopen.c,v 1.1 2002/05/13 07:28:10 bagder Exp $
 *
 * This example source code introduces an fopen()/fread()/fclose() emulation
 * for URL reads. Using an approach similar to this, you could replace your
 * program's fopen() with this url_fopen() and fread() with url_fread() and
 * it should be possible to read remote streams instead of (only) local files.
 *
 * See the main() function at the bottom that shows a tiny app in action.
 *
 * This source code is a proof of concept. It will need further attention to
 * become production-use useful and solid.
 *
 * This example requires libcurl 7.9.7 or later.
 */
#include <stdio.h>
#include <string.h>
#include <sys/time.h>

#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>

struct data {
  int type;
  union {
    CURL *curl;
    FILE *file;
  } handle;

  /* TODO: We should perhaps document the biggest possible buffer chunk we can
     get from libcurl in one single callback... */
  char buffer[CURL_MAX_WRITE_SIZE];

  char *readptr; /* read from here */
  int bytes;     /* bytes available from read pointer */

  CURLMcode m;   /* stored from a previous url_fread() */
};

typedef struct data URL_FILE;

/* we use a global one for convenience */
CURLM *multi_handle;

static
size_t write_callback(char *buffer,
                      size_t size,
                      size_t nitems,
                      void *userp)
{
  URL_FILE *url = (URL_FILE *)userp;
  size *= nitems;

  memcpy(url->readptr, buffer, size);
  url->readptr += size;
  url->bytes += size;

  return size;
}

URL_FILE *url_fopen(char *url, char *operation)
{
  /* this code could check for URLs or types in the 'url' and
     basicly use the real fopen() for standard files */

  URL_FILE *file;
  int still_running;

  file = (URL_FILE *)malloc(sizeof(URL_FILE));
  if(!file)
    return NULL;

  memset(file, 0, sizeof(URL_FILE));

  file->type = 1; /* marked as URL, use 0 for plain file */
  file->handle.curl = curl_easy_init();

  curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
  curl_easy_setopt(file->handle.curl, CURLOPT_FILE, file);
  curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, FALSE);
  curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);

  if(!multi_handle)
    multi_handle = curl_multi_init();

  curl_multi_add_handle(multi_handle, file->handle.curl);

  while(CURLM_CALL_MULTI_PERFORM ==
        curl_multi_perform(multi_handle, &still_running));

  /* if still_running would be 0 now, we should return NULL */

  return file;
}

void url_fclose(URL_FILE *file)
{
  /* make sure the easy handle is not in the multi handle anymore */
  curl_multi_remove_handle(multi_handle, file->handle.curl);

  /* cleanup */
  curl_easy_cleanup(file->handle.curl);
}



size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
{
  fd_set fdread;
  fd_set fdwrite;
  fd_set fdexcep;
  int maxfd;
  struct timeval timeout;
  int rc;
  int still_running = 0;

  if(!file->bytes) { /* no data available at this point */

    file->readptr = file->buffer; /* reset read pointer */

    if(CURLM_CALL_MULTI_PERFORM == file->m) {
      while(CURLM_CALL_MULTI_PERFORM ==
            curl_multi_perform(multi_handle, &still_running)) {
        if(file->bytes) {
          printf("(fread) WOAH! THis happened!\n");
          break;        
        }
      }
      if(!still_running) {
        printf("NO MORE RUNNING AROUND!\n");
        return 0;
      }
    }

    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);
  
    /* set a suitable timeout to fail on */
    timeout.tv_sec = 500; /* 5 minutes */
    timeout.tv_usec = 0;

    /* get file descriptors from the transfers */
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

    switch(rc) {
    case -1:
      /* select error */
      break;
    case 0:
      break;
    default:
      /* timeout or readable/writable sockets */
      do {
        file->m = curl_multi_perform(multi_handle, &still_running);

        if(file->bytes)
          /* we have received data, return that now */
          break;

      } while(CURLM_CALL_MULTI_PERFORM == file->m);


      if(!still_running)
        printf("NO MORE RUNNING AROUND!\n");

      break;
    }
  }
  else
    printf("(fread) Skip network read\n");

  if(file->bytes) {
    /* data already available, return that */
    int want = size * nmemb;

    if(file->bytes < want)
      want = file->bytes;

    memcpy(ptr, file->readptr, want);
    file->readptr += want;
    file->bytes -= want;

    printf("(fread) return %d bytes\n", want);

    return want;
  }
  return 0; /* no data available to return */
}


int main(int argc, char *argv[])
{
  URL_FILE *handle;
  int nread;
  char buffer[256];

  handle = url_fopen("http://www.haxx.se", "r");

  if(!handle) {
    printf("couldn't url_fopen()\n");
  }

  do {
    nread = url_fread(buffer, sizeof(buffer), 1, handle);

    printf("We got: %d bytes\n", nread);
  } while(nread);

  url_fclose(handle);

  return 0;
}
