Logo Search packages:      
Sourcecode: uget version File versions  Download package

ug_plugin_curl.c

/*
 *
 *   Copyright (C) 2005-2009 by Raymond Huang
 *   plushuang at users.sourceforge.net
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU Lesser General Public License in all respects
 *  for all of the code used other than OpenSSL.  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so.  If you
 *  do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#include <string.h>

#include <ug_plugin_curl.h>
#include <ug_data_download.h>
#include <ug_stdio.h>
#include <ug_utils.h>
#include <ug_url.h>


/*    defined in curl.h
typedef int (*curl_progress_callback)(void *clientp,
                                      double dltotal,
                                      double dlnow,
                                      double ultotal,
                                      double ulnow);
typedef size_t (*curl_write_callback)(char *buffer,
                                      size_t size,
                                      size_t nitems,
                                      void *outstream);
typedef size_t (*curl_read_callback)(char *buffer,
                                     size_t size,
                                     size_t nitems,
                                     void *instream);
*/

// functions for UgPluginClass
static gboolean   ug_plugin_curl_global_init          (void);
static void       ug_plugin_curl_global_finalize      (void);

static void       ug_plugin_curl_init                 (UgPluginCurl* plugin);
static void       ug_plugin_curl_finalize       (UgPluginCurl* plugin);

static UgResult   ug_plugin_curl_set_state      (UgPluginCurl* plugin, UgState  state);
static UgResult   ug_plugin_curl_get_state      (UgPluginCurl* plugin, UgState* state);
static UgResult   ug_plugin_curl_get                  (UgPluginCurl* plugin, guint parameter, gpointer data);

// thread and setup functions
static gpointer   ug_plugin_curl_thread         (UgPluginCurl* plugin);
static void       ug_plugin_curl_set_proxy      (UgPluginCurl* plugin, CURL* curl);
static gboolean   ug_plugin_curl_open_file      (UgPluginCurl* plugin, UgDataCommon* common);

// libcurl callback functions
static int        ug_plugin_curl_progress       (UgPluginCurl* plugin, double  dltotal, double  dlnow, double  ultotal, double  ulnow);
static size_t     ug_plugin_curl_header_http    (char *buffer, size_t size, size_t nmemb, UgPluginCurl *plugin);

// static data for UgPluginClass
static const char *supported_schemes[] = {"http", "https", "ftp", "ftps", NULL};

static const      UgPluginClass     plugin_class_curl =
{
      "curl",                                                                                   // name
      NULL,                                                                               // reserve
      sizeof (UgPluginCurl),                                                        // instance_size
      supported_schemes,                                                                  // schemes
      NULL,                                                                               // file_types

      (UgGlobalInitFunc)            ug_plugin_curl_global_init,               // global_init
      (UgGlobalFinalizeFunc)  ug_plugin_curl_global_finalize,           // global_finalize

      (UgInitFunc)                  ug_plugin_curl_init,                      // init
      (UgFinalizeFunc)        ug_plugin_curl_finalize,                  // finalize

      (UgSetStateFunc)        ug_plugin_curl_set_state,                 // set_state
      (UgGetStateFunc)        ug_plugin_curl_get_state,                 // get_state
      (UgGetFunc)                   ug_plugin_curl_get,                             // get
};

// extern
const UgPluginClass*    UgPluginCurlClass = &plugin_class_curl;

// ----------------------------------------------------------------------------
// functions for UgPluginClass
static gboolean   ug_plugin_curl_global_init (void)
{
      curl_global_init (CURL_GLOBAL_ALL);
      return TRUE;
}

static void       ug_plugin_curl_global_finalize (void)
{
      curl_global_cleanup ();
}

static void       ug_plugin_curl_init (UgPluginCurl* plugin)
{
      UgDataset*        dataset;

      dataset = plugin->dataset;
      // initialize this struct/class
//    plugin->output_func = dataset->output_func;
//    plugin->output_data = dataset->output_data;

      // default values
      plugin->resumable = TRUE;
      plugin->retry_limit = 6;
      plugin->retry_count = 0;
      plugin->retry_delay = 3;
      plugin->redirection_limit = 30;
      plugin->redirection_count = 0;
      plugin->proxy_list_length = ug_dataset_list_length (dataset, UgDataProxyClass);
      plugin->proxy_list_index  = 0;
}

static void       ug_plugin_curl_finalize (UgPluginCurl* plugin)
{
      // add code here.
}

static UgResult ug_plugin_curl_set_state (UgPluginCurl* plugin, UgState  state)
{
      UgState           old_state;

      old_state         = plugin->state;
      plugin->state     = state;

      if (state != old_state) {
            if (state == UG_STATE_RUNNING && old_state < UG_STATE_RUNNING) {
                  ug_plugin_ref ((UgPlugin*) plugin);       // call ug_plugin_unref () by ug_plugin_curl_thread ()
                  g_thread_create ((GThreadFunc) ug_plugin_curl_thread, plugin, FALSE, NULL);
            }

            ug_plugin_post ((UgPlugin*) plugin, ug_message_new_state (state));
      }

      return UG_RESULT_OK;
}

static UgResult   ug_plugin_curl_get_state      (UgPluginCurl* plugin, UgState* state)
{
      if (state) {
            *state = plugin->state;
            return UG_RESULT_OK;
      }

      return UG_RESULT_ERROR;
}

static UgResult   ug_plugin_curl_get  (UgPluginCurl* plugin, guint parameter, gpointer data)
{
      UgProgress* progress;
      gint64            complete_size, total_size;
      gdouble           consumed_time;

      if (parameter != UG_DATA_TYPE_INSTANCE)
            return UG_RESULT_UNSUPPORT;
      if (data == NULL || UG_DATA_CAST (data)->data_class != UgProgressClass)
            return UG_RESULT_UNSUPPORT;

      progress      = data;
      complete_size = plugin->complete_size;
      total_size    = plugin->total_size;
      consumed_time = plugin->consumed_time;

      progress->complete = complete_size;
      if (complete_size > total_size)
            progress->total = complete_size;
      else
            progress->total = total_size;

      // If total_size is unkonwn, don't calculate percent.
      if (total_size > 0)
            progress->percent = (gdouble)(complete_size*100) / total_size;
      else
            progress->percent = 0;

      progress->current_speed = plugin->current_speed;

      if (consumed_time > 0.0)
            progress->average_speed = (complete_size - plugin->file_offset_beg) / consumed_time;
      else
            progress->average_speed = 0;

      progress->consume_time = plugin->consumed_time;
      // If total size and average speed is unkonwn, don't calculate remain time.
      if (progress->average_speed > 0 && total_size > 0)
            progress->remain_time = (gdouble)(total_size - complete_size) / progress->average_speed;

//    progress->retry_count = plugin->retry_count;
      ug_str_set (&progress->file, NULL, 0);

      return UG_RESULT_OK;
}

// ----------------------------------------------------------------------------
// thread and setup functions
static gpointer  ug_plugin_curl_thread (UgPluginCurl* plugin)
{
      CURL*             curl;
      CURLcode          curl_code;
      UgUrlPart*        urlpart;
      // UgData
      UgDataCommon*     common;
      UgDataHttp*       http;
      UgDataFtp*        ftp;
      UgMessage*        message;


      plugin->curl      = curl_easy_init ();
      urlpart                 = &plugin->urlpart;
      curl        = plugin->curl;
      curl_code   = CURLE_FAILED_INIT;


      // common option ----------------------------------------------------------
      common = ug_dataset_get (plugin->dataset, UgDataCommonClass, 0);
      if (common == NULL) {
            message = ug_message_new_error (UG_MESSAGE_ERROR_UNSUPPORTED_SCHEME, NULL);
            ug_plugin_post ((UgPlugin*) plugin, message);
            goto exit;
      }
//    if (common->retry_limit)
      plugin->retry_limit = common->retry_limit;
//    if (common->retry_delay)
      plugin->retry_delay = common->retry_delay;


      // HTTP option ------------------------------------------------------------
      http = ug_dataset_get (plugin->dataset, UgDataHttpClass, 0);
      if (http) {
//          if (http->redirection_limit)
            plugin->redirection_limit = http->redirection_limit;

            if (http->user_agent)
                  curl_easy_setopt (curl, CURLOPT_USERAGENT, http->user_agent);
            else
                  curl_easy_setopt (curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");
//                curl_easy_setopt (curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");

            // cookie
            if (http->cookie_data)
                  curl_easy_setopt (curl, CURLOPT_COOKIE, http->cookie_data);
            else if (http->cookie_file)
                  curl_easy_setopt (curl, CURLOPT_COOKIEFILE, http->cookie_file);
            else
                  curl_easy_setopt (curl, CURLOPT_COOKIEFILE, "");

            if (http->post_data) {
                  curl_easy_setopt (curl, CURLOPT_POST, TRUE);
                  curl_easy_setopt (curl, CURLOPT_POSTFIELDS, http->post_data);
                  curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, strlen (http->post_data));
            }
      }


      // FTP option -------------------------------------------------------------
      ftp = ug_dataset_get (plugin->dataset, UgDataFtpClass, 0);
      if (ftp && ftp->extended_passive_mode) {
            // use EPSV command (Extended Passive Mode).
            curl_easy_setopt (curl, CURLOPT_FTP_USE_EPSV, TRUE);
            // don't use PORT, EPRT, and LPRT command.
            curl_easy_setopt (curl, CURLOPT_FTPPORT, NULL);
            // '-' symbol to let the library use your system's default IP address.
//          curl_easy_setopt (curl, CURLOPT_FTPPORT, "-");
      }
      else {
            // use PASV command (Passive Mode).
            // don't use EPSV command.
            curl_easy_setopt (curl, CURLOPT_FTP_USE_EPSV, FALSE);
            // don't use EPRT and LPRT command.
            curl_easy_setopt (curl, CURLOPT_FTP_USE_EPRT, FALSE);
            // don't use PORT command.
            curl_easy_setopt (curl, CURLOPT_FTPPORT, NULL);
      }

      // Others -----------------------------------------------------------------
      // progress
      curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback) ug_plugin_curl_progress);
      curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, plugin);
      curl_easy_setopt (curl, CURLOPT_NOPROGRESS, FALSE);
      // others
      curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 0);    // Don't use libcurl's redirection.
      curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, plugin->curl_error_string);
      ug_plugin_curl_set_proxy (plugin, plugin->curl);

      plugin->timer = g_timer_new ();

      while (plugin->state == UG_STATE_RUNNING) {
            // Redirection loop --- Start -----------------------------------------
            // check & set URL
            if (common->url == NULL) {
                  message = ug_message_new_error (UG_MESSAGE_ERROR_UNSUPPORTED_SCHEME, NULL);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  goto exit;
            }
            curl_easy_setopt (curl, CURLOPT_URL, common->url);

            // ----------------------------------------------------
            // Parse current URL and set related data --- Start ---
            ug_url_part (urlpart, common->url, -1);
            if (urlpart->url_scheme_len == 0) {
                  message = ug_message_new_error (UG_MESSAGE_ERROR_UNSUPPORTED_SCHEME, NULL);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  goto exit;
            }
            if (common->file == NULL) {
                  if (urlpart->file_len)
                        common->file = ug_url_unescape_to_utf8 (urlpart->file, urlpart->file_len);
                  else
                        common->file = g_strdup ("index.htm");
                  message = ug_message_new_data (UG_MESSAGE_DATA_FILE_CHANGED, common->file);
                  ug_plugin_post ((UgPlugin*) plugin, message);
            }

            // clear header callback
            curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, NULL);
            curl_easy_setopt (curl, CURLOPT_HEADERDATA, NULL);
//          curl_easy_setopt (curl, CURLOPT_WRITEHEADER, NULL);         // CURLOPT_WRITEHEADER == CURLOPT_HEADERDATA

            if (common->user || common->password) {
                  // set user & password by common data
                  curl_easy_setopt (curl, CURLOPT_USERNAME, (common->user)     ? common->user     : "");
                  curl_easy_setopt (curl, CURLOPT_PASSWORD, (common->password) ? common->password : "");
                  curl_easy_setopt (curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
            }
            else {
                  // clear user & password
                  curl_easy_setopt (curl, CURLOPT_USERNAME, NULL);
                  curl_easy_setopt (curl, CURLOPT_PASSWORD, NULL);
                  curl_easy_setopt (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
            }

            if (urlpart->url_scheme_len >= 4 && g_ascii_strncasecmp (urlpart->url, "http", 4) == 0) {
                  // set HTTP user & password
                  if (http && (http->user || http->password) ) {
                        curl_easy_setopt (curl, CURLOPT_USERNAME, (http->user)     ? http->user     : "");
                        curl_easy_setopt (curl, CURLOPT_PASSWORD, (http->password) ? http->password : "");
                        curl_easy_setopt (curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
                  }
                  // set HTTP referer
                  if (http && http->referer)
                        curl_easy_setopt (curl, CURLOPT_REFERER, http->referer);
                  else {
                        g_free (plugin->http_referer);
                        plugin->http_referer = g_strndup (urlpart->url, urlpart->url_location_len);
                        curl_easy_setopt (curl, CURLOPT_REFERER, plugin->http_referer);
                  }
                  // set header callback for HTTP
                  curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, (curl_write_callback) ug_plugin_curl_header_http);
                  curl_easy_setopt (curl, CURLOPT_HEADERDATA, plugin);
//                curl_easy_setopt (curl, CURLOPT_WRITEHEADER, plugin); // CURLOPT_WRITEHEADER == CURLOPT_HEADERDATA
            }
            else if (urlpart->url_scheme_len >= 3 && g_ascii_strncasecmp (urlpart->url, "ftp", 3) == 0) {
                  // set FTP user & password
                  if (ftp && (ftp->user || ftp->password)) {
                        curl_easy_setopt (curl, CURLOPT_USERNAME, (ftp->user)     ? ftp->user     : "");
                        curl_easy_setopt (curl, CURLOPT_PASSWORD, (ftp->password) ? ftp->password : "");
                  }
            }
            // Parse current URL and set related data --- End ---
            // --------------------------------------------------

            // output
            if (ug_plugin_curl_open_file (plugin, common) == FALSE)
                  goto exit;

            // set/reset progress & others
            plugin->progress_callback_counts = 0;
            plugin->complete_size     = plugin->file_offset_beg;
            plugin->consumed_time     = g_timer_elapsed (plugin->timer, NULL);

            message = ug_message_new_info (UG_MESSAGE_INFO_TRANSMIT, NULL);
            ug_plugin_post ((UgPlugin*) plugin, message);
            // perform
            curl_code = curl_easy_perform (curl);

            // If an error occurred.
            if (plugin->error_occurred) {
                  plugin->error_occurred = FALSE;
                  goto exit;
            }

            // ug_plugin_curl_header_http() handle redirection
            if (plugin->redirection) {
                  plugin->redirection = FALSE;
                  plugin->redirection_count++;

                  if (plugin->redirection_count > plugin->redirection_limit) {
                        message = ug_message_new_error (UG_MESSAGE_ERROR_HTTP_TOO_MANY_REDIRECTIONS, NULL);
                        ug_plugin_post ((UgPlugin*) plugin, message);
                        goto exit;
                  }
                  continue;
            }
            // Redirection loop --- End -------------------------------------------------

            switch (curl_code) {
            case CURLE_OK:
                  goto exit;

            // can resume (retry)
            case CURLE_PARTIAL_FILE:
                  // update data
                  plugin->resumable = TRUE;
                  ug_fseek (plugin->file_stream, 0, SEEK_END);
                  plugin->file_offset_beg = ug_ftell (plugin->file_stream);
                  // send message
                  message = ug_message_new_info (UG_MESSAGE_INFO_RESUMABLE, NULL);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  break;

            // can't resume (retry)
            case CURLE_RANGE_ERROR:
            case CURLE_BAD_DOWNLOAD_RESUME:
                  // update data
                  plugin->resumable = FALSE;
                  plugin->file_offset_beg = 0;
                  // send message
                  message = ug_message_new_info (UG_MESSAGE_INFO_NOT_RESUMABLE, NULL);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  break;

            // retry
            case CURLE_GOT_NOTHING:
            case CURLE_RECV_ERROR:
            case CURLE_OPERATION_TIMEDOUT:
            case CURLE_BAD_CONTENT_ENCODING:
                  message = ug_message_new_error (UG_MESSAGE_ERROR_CUSTOM, plugin->curl_error_string);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  break;

            // can't connect (retry)
            case CURLE_COULDNT_CONNECT:
                  message = ug_message_new_error (UG_MESSAGE_ERROR_CONNECT_FAILED, plugin->curl_error_string);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  break;

            // abort by user (exit)
            case CURLE_ABORTED_BY_CALLBACK:
                  goto exit;

            // out of resource (exit)
            case CURLE_OUT_OF_MEMORY:
            case CURLE_WRITE_ERROR:
                  message = ug_message_new_error (UG_MESSAGE_ERROR_OUT_OF_RESOURCE, plugin->curl_error_string);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  goto exit;

            // exit
            case CURLE_UNSUPPORTED_PROTOCOL:
                  message = ug_message_new_error (UG_MESSAGE_ERROR_UNSUPPORTED_SCHEME, plugin->curl_error_string);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  goto exit;

            // other error (exit)
            case CURLE_COULDNT_RESOLVE_HOST:
            case CURLE_COULDNT_RESOLVE_PROXY:
            case CURLE_FAILED_INIT:
            case CURLE_URL_MALFORMAT:
            case CURLE_FTP_WEIRD_SERVER_REPLY:
            case CURLE_REMOTE_ACCESS_DENIED:
            default:
                  message = ug_message_new_error (UG_MESSAGE_ERROR_CUSTOM, plugin->curl_error_string);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  goto exit;
            }

            ug_fseek (plugin->file_stream, plugin->file_offset_beg, SEEK_SET);
            ug_fs_truncate (plugin->file_stream, plugin->file_offset_beg);

            ug_plugin_curl_set_proxy (plugin, curl);

            // retry
            if (plugin->retry_count >= plugin->retry_limit) {
                  message = ug_message_new_error (UG_MESSAGE_ERROR_TOO_MANY_RETRIES, NULL);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  break;
            }
            message = ug_message_new_info (UG_MESSAGE_INFO_RETRY, NULL);
            ug_plugin_post ((UgPlugin*) plugin, message);
            ug_plugin_delay ((UgPlugin*) plugin, plugin->retry_delay * 1000);
            plugin->retry_count++;
      }

exit:
      curl_easy_cleanup (curl);

      if (plugin->file_stream) {
            // get size of downloaded file
            ug_fseek (plugin->file_stream, 0, SEEK_END);
            plugin->file_offset_beg = ug_ftell (plugin->file_stream);
            // close downloaded file
            fclose (plugin->file_stream);
            plugin->file_stream = NULL;
      }

      if (curl_code == CURLE_OK) {
            ug_plugin_rename_file ((UgPlugin*) plugin, plugin->path_tmp, plugin->path);
            message = ug_message_new_info (UG_MESSAGE_INFO_FINISH, NULL);
            ug_plugin_post ((UgPlugin*) plugin, message);
      }
      else {
            // delete empty downloaded file.
            if (plugin->file_offset_beg == 0)
                  ug_unlink (plugin->path_tmp);
            ug_unlink (plugin->path);
      }
      g_free (plugin->path_tmp);
      g_free (plugin->path);
      plugin->path_tmp = NULL;
      plugin->path     = NULL;

      g_free (plugin->http_referer);
      plugin->http_referer = NULL;

      if (plugin->timer) {
            g_timer_destroy (plugin->timer);
            plugin->timer = NULL;
      }

      if (plugin->state == UG_STATE_RUNNING)
            ug_plugin_curl_set_state (plugin, UG_STATE_STOP);
      ug_plugin_unref ((UgPlugin*) plugin);     // call ug_plugin_ref () by ug_plugin_curl_set_state ()

      return NULL;
}

static void ug_plugin_curl_set_proxy (UgPluginCurl* plugin, CURL* curl)
{
      UgDataProxy*      proxy;
      int                     curl_proxy_type;

      // If no proxy setting, return
      if (plugin->proxy_list_length == 0)
            return;
      if (plugin->proxy_list_index >= plugin->proxy_list_length)
            plugin->proxy_list_index = 0;
      proxy = ug_dataset_get (plugin->dataset, UgDataProxyClass, plugin->proxy_list_index);
      plugin->proxy_list_index++;

      // proxy type
      switch (proxy->type) {
      case UG_DATA_PROXY_NONE:
            curl_easy_setopt (curl, CURLOPT_PROXY, NULL);
            return;
      case UG_DATA_PROXY_SOCKS4:
            curl_proxy_type = CURLPROXY_SOCKS4;
            break;
      case UG_DATA_PROXY_SOCKS5:
            curl_proxy_type = CURLPROXY_SOCKS5;
            break;
      case UG_DATA_PROXY_HTTP:
      default:
            curl_proxy_type = CURLPROXY_HTTP;
            break;
      }
      curl_easy_setopt (curl, CURLOPT_PROXYTYPE, curl_proxy_type);

      // proxy host
      curl_easy_setopt (curl, CURLOPT_PROXY, proxy->host);

      // proxy port
      if (proxy->port)
            curl_easy_setopt (curl, CURLOPT_PROXYPORT, proxy->port);
      else
            curl_easy_setopt (curl, CURLOPT_PROXYPORT, 80);

      // proxy user and password
      if (proxy->user     || proxy->password  ||
            curl_proxy_type == CURLPROXY_SOCKS4 ||
            curl_proxy_type == CURLPROXY_SOCKS5)
      {
            curl_easy_setopt (curl, CURLOPT_PROXYUSERNAME, (proxy->user)     ? proxy->user     : "");
            curl_easy_setopt (curl, CURLOPT_PROXYPASSWORD, (proxy->password) ? proxy->password : "");
      }
      else {
            curl_easy_setopt (curl, CURLOPT_PROXYUSERNAME, NULL);
            curl_easy_setopt (curl, CURLOPT_PROXYPASSWORD, NULL);
      }
}

static gboolean   ug_plugin_curl_open_file (UgPluginCurl* plugin, UgDataCommon* common)
{
      if (plugin->file_stream)
            return TRUE;

      g_free (plugin->path);
      plugin->path = ug_plugin_create_file ((UgPlugin*) plugin, common->folder, common->file, &plugin->path_folder_len);
      if (plugin->path == NULL)
            return FALSE;

      g_free (plugin->path_tmp);
      plugin->path_tmp = g_strconcat (plugin->path, ".ug_", NULL);

//    if (plugin->resumable)
            plugin->file_stream = ug_fopen (plugin->path_tmp, "ab+");
//    else
//          plugin->file_stream = ug_fopen (plugin->path_tmp, "wb");

      if (plugin->file_stream == NULL) {
            ug_plugin_post ((UgPlugin*) plugin, ug_message_new_error (UG_MESSAGE_ERROR_FILE_OPEN_FAILED, NULL));
            ug_unlink (plugin->path);
            return FALSE;
      }

//    if (plugin->resumable) {
            ug_fseek (plugin->file_stream, 0, SEEK_END);
            plugin->file_offset_beg = ug_ftell (plugin->file_stream);
//    } else
//          plugin->file_offset_beg = 0;

      curl_easy_setopt (plugin->curl, CURLOPT_WRITEDATA , plugin->file_stream);
#if defined(_WIN32) && !defined(__MINGW32__)    // for MS VC only
      curl_easy_setopt (plugin->curl, CURLOPT_WRITEFUNCTION , fwrite);
#endif
      curl_easy_setopt (plugin->curl, CURLOPT_RESUME_FROM_LARGE, plugin->file_offset_beg);
      return TRUE;
}

// ----------------------------------------------------------------------------
// libcurl callback functions
static int ug_plugin_curl_progress (UgPluginCurl* plugin,
                                    double  dltotal, double  dlnow,
                                    double  ultotal, double  ulnow)
{
      gdouble           consumed_time;
      gdouble           time_diff;
      gint64            complete_size;

      plugin->progress_callback_counts++;
      if (plugin->progress_callback_counts == 3) {
            plugin->progress_callback_counts = 0;

            consumed_time = g_timer_elapsed (plugin->timer, NULL);
            complete_size = (gint64)dlnow + plugin->file_offset_beg;

            time_diff = consumed_time - plugin->consumed_time;
            if (time_diff > 0)
                  plugin->current_speed = (complete_size - plugin->complete_size) / time_diff;
            else
                  plugin->current_speed = (gdouble)(complete_size - plugin->complete_size);

            plugin->complete_size = complete_size;
            plugin->consumed_time = consumed_time;

            plugin->total_size    = (gint64)dltotal + plugin->file_offset_beg;

            ug_plugin_post ((UgPlugin*) plugin, ug_message_new_progress ());
      }

      if (plugin->state < UG_STATE_RUNNING)
            return TRUE;    // Return TRUE if abort
      return 0;
}

static size_t ug_plugin_curl_header_http (char *buffer, size_t size, size_t nmemb, UgPluginCurl *plugin)
{
      UgDataCommon*     common;
      UgMessage*        message;
      UgUrlPart*        urlpart;
      gchar*                  url;
      gchar*                  file;
      guint             buffer_len;

#ifdef NDEBUG
      buffer_len = ug_str_line_len (buffer, size*nmemb, 0); // exclude character '\n'
#else
      buffer_len = size*nmemb;
      g_print ("%.*s", buffer_len, buffer);
      buffer_len = ug_str_line_len (buffer, buffer_len, 0); // exclude character '\n'
#endif
      buffer [buffer_len] = 0;      // NULL-terminal, remove "\r\n"
      url  = NULL;
      file = NULL;
      // The HTTP method is supposed to be case-sensitive, but
      // the HTTP headers are case-insensitive supposed to be according to RFC 2616.

      // handle HTTP header "Location:"
      if (buffer_len > 10 && g_ascii_strncasecmp (buffer, "Location: ", 10) == 0) {
            buffer += 10;
            buffer_len -= 10;
            plugin->redirection = TRUE;
            // set new URL
            common = ug_dataset_get (plugin->dataset, UgDataCommonClass, 0);
            ug_str_set (&common->url, buffer, buffer_len);
            // post message
//          message = ug_message_new_data (UG_MESSAGE_DATA_URL_CHANGED, common->url);
//          ug_plugin_post ((UgPlugin*) plugin, message);
            message = ug_message_new_data (UG_MESSAGE_DATA_HTTP_LOCATION, common->url);
            ug_plugin_post ((UgPlugin*) plugin, message);
            url = buffer;
      }
      // handle HTTP header "Content-Location:"
      else if (buffer_len > 18 && g_ascii_strncasecmp (buffer, "Content-Location: ", 18) == 0) {
            buffer += 18;
            buffer_len -= 18;
            url = g_strndup (buffer, buffer_len);
            message = ug_message_new_data (UG_MESSAGE_DATA_HTTP_CONTENT_LOCATION, url);
            ug_plugin_post ((UgPlugin*) plugin, message);
            g_free (url);
            url = buffer;
      }
      // handle HTTP header "Content-Disposition:"
      else if (buffer_len > 21 && g_ascii_strncasecmp (buffer, "Content-Disposition: ", 21) == 0) {
            buffer += 21;
            buffer_len -= 21;
            // message
            file = g_strndup (buffer, buffer_len);
            message = ug_message_new_data (UG_MESSAGE_DATA_HTTP_CONTENT_DISPOSITION, file);
            ug_plugin_post ((UgPlugin*) plugin, message);
            g_free (file);
            // grab filename
            file = strstr (buffer, "filename=");
            if (file) {
                  buffer_len -= (file - buffer) + 9;
                  buffer = file + 9;
                  if (buffer[0] == '\"')
                        buffer_len = ug_str_find_charset (++buffer, --buffer_len, 0, "\"");
                  else
                        buffer_len = ug_str_find_charset (buffer, buffer_len, 0, ";");
                  file = buffer;
            }
      }

      // Try to get filename from header "Content-Location:" or "Location:"
      if (url) {
            urlpart = &plugin->urlpart;
            ug_url_part (urlpart, buffer, buffer_len);
            if (urlpart->file_len > 0) {
                  buffer     = (gchar*) urlpart->file;
                  buffer_len = urlpart->file_len;
                  file       = buffer;
            }
      }

      // Try to get filename from header "Content-Location:", "Location:", or "Content-disposition:"
      if (file) {
            // unescape filename
            file = ug_url_unescape_to_utf8 (buffer, buffer_len);

            if (file) {
                  // change filename
                  message = ug_message_new_data (UG_MESSAGE_DATA_FILE_CHANGED, file);
                  ug_plugin_post ((UgPlugin*) plugin, message);
                  common = ug_dataset_get (plugin->dataset, UgDataCommonClass, 0);
                  ug_str_set (&common->folder, plugin->path, plugin->path_folder_len);
                  ug_str_set (&common->file, file, -1);
                  g_free (file);
                  // delete old file
                  fclose (plugin->file_stream);
                  plugin->file_stream = NULL;
                  ug_unlink (plugin->path_tmp);
                  ug_unlink (plugin->path);
                  // re-open file
                  if (ug_plugin_curl_open_file (plugin, common) == FALSE) {
                        plugin->error_occurred = TRUE;
                        return 0;   // curl_easy_perform() will return CURLE_ABORTED_BY_CALLBACK
                  }
            }
            else {
                  // filename is not usable.
                  message = ug_message_new_warning (UG_MESSAGE_WARNING_FILE_RENAME_FAILED, NULL);
                  ug_plugin_post ((UgPlugin*) plugin, message);
            }
      }

      if (plugin->redirection)
            return 0;   // curl_easy_perform() will return CURLE_ABORTED_BY_CALLBACK

      return nmemb;
}


Generated by  Doxygen 1.6.0   Back to index