/* * Copyright (C) 2010, 2011 Igalia S.L. * Copyright (C) 2012 Canonical Ltd. * * Contact: Iago Toral Quiroga * * Authors: Víctor M. Jáquez L. * Juan A. Suarez Romero * Jens Georg * Mathias Hasselmann * * 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; 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 * */ /** * SECTION:grl-net-wc * @short_description: small and simple HTTP client * * Most of the Grilo's sources need to access to web resources. The purpose of * this utility class is to provide a thin and lean mechanism for those plugins * to interact with those resources. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define LIBSOUP_USE_UNSTABLE_REQUEST_API #include #include #include #include #include #include #include #include #include "grl-net-wc.h" #include "grl-net-mock-private.h" #define GRL_LOG_DOMAIN_DEFAULT wc_log_domain GRL_LOG_DOMAIN_STATIC(wc_log_domain); #define GRL_NET_CAPTURE_DIR_VAR "GRL_NET_CAPTURE_DIR" enum { PROP_0, PROP_LOG_LEVEL, PROP_THROTTLING, PROP_CACHE, PROP_CACHE_SIZE, PROP_USER_AGENT }; struct request_res { SoupRequest *request; gchar *buffer; gsize length; gsize offset; }; struct _GrlNetWcPrivate { SoupSession *session; SoupLoggerLogLevel log_level; /* throttling in secs */ guint throttling; /* last request time, timestamp in seconds */ gint64 last_request; /* closure queue for delayed requests */ GQueue *pending; /* cache size in Mb */ guint cache_size; gchar *previous_data; }; static const char *capture_dir = NULL; GQuark grl_net_wc_error_quark (void) { return g_quark_from_static_string ("grl-wc-error-quark"); } G_DEFINE_TYPE_WITH_PRIVATE (GrlNetWc, grl_net_wc, G_TYPE_OBJECT); static void grl_net_wc_finalize (GObject *object); static void grl_net_wc_set_property (GObject *object, guint propid, const GValue *value, GParamSpec *pspec); static void grl_net_wc_get_property (GObject *object, guint propid, GValue *value, GParamSpec *pspec); static void grl_net_wc_class_init (GrlNetWcClass *klass) { GObjectClass *g_klass; g_klass = G_OBJECT_CLASS (klass); g_klass->finalize = grl_net_wc_finalize; g_klass->set_property = grl_net_wc_set_property; g_klass->get_property = grl_net_wc_get_property; /** * GrlNetWc::loglevel: * * The log level for HTTP connections. This value is used by libsoup. */ g_object_class_install_property (g_klass, PROP_LOG_LEVEL, g_param_spec_uint ("loglevel", "Log level", "Log level for HTTP connections", 0, 3, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GrlNetWc::throttling: * * The timeout in seconds between connections. All the connections will be * queued and each one will be dispatched after waiting this value. */ g_object_class_install_property (g_klass, PROP_THROTTLING, g_param_spec_uint ("throttling", "throttle timeout", "Time to throttle connections", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GrlNetWc::cache: * * %TRUE if cache must be used. %FALSE otherwise. */ g_object_class_install_property (g_klass, PROP_CACHE, g_param_spec_boolean ("cache", "Use cache", "Use cache", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); /** * GrlNetWc::cache-size: * * Maximum size of cache, in Mb. Default value is 10Mb. */ g_object_class_install_property (g_klass, PROP_CACHE_SIZE, g_param_spec_uint ("cache-size", "Cache size", "Size of cache in Mb", 0, G_MAXUINT, 10, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); /** * GrlNetWc::user-agent: * * User agent identifier. */ g_object_class_install_property (g_klass, PROP_USER_AGENT, g_param_spec_string ("user-agent", "User Agent", "User agent identifier", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); } static void free_op_res (void *op) { struct request_res *rr = op; g_object_unref (rr->request); g_slice_free (struct request_res, rr); } /* * use-thread-context is available for libsoup-2.4 >= 2.39.0 * We check in run-time if it's available */ static void set_thread_context (GrlNetWc *self) { GrlNetWcPrivate *priv = self->priv; GObjectClass *klass = G_OBJECT_GET_CLASS (priv->session); GParamSpec *spec = g_object_class_find_property (klass, "use-thread-context"); if (spec) g_object_set (priv->session, "use-thread-context", TRUE, NULL); } static void init_dump_directory (void) { capture_dir = g_getenv (GRL_NET_CAPTURE_DIR_VAR); if (capture_dir && is_mocked ()) { GRL_WARNING ("Cannot capture while mocking is enabled."); capture_dir = NULL; return; } if (capture_dir && g_mkdir_with_parents (capture_dir, 0700) != 0) { GRL_WARNING ("Could not create capture directory \"%s\": %s", capture_dir, g_strerror (errno)); capture_dir = NULL; return; } } static void cache_down (GrlNetWc *self) { GFile *cache_dir_file; GrlNetWcPrivate *priv = self->priv; SoupSessionFeature *cache = soup_session_get_feature (priv->session, SOUP_TYPE_CACHE); gchar *cache_dir; GRL_DEBUG ("cache down"); if (!cache) { return; } soup_cache_clear (SOUP_CACHE (cache)); g_object_get (cache, "cache-dir", &cache_dir, NULL); cache_dir_file = g_file_new_for_path (cache_dir); g_free (cache_dir); g_file_delete (cache_dir_file, NULL, NULL); g_object_unref (G_OBJECT (cache_dir_file)); soup_session_remove_feature (priv->session, cache); } static void cache_up (GrlNetWc *self) { SoupCache *cache; GrlNetWcPrivate *priv = self->priv; gchar *dir; GRL_DEBUG ("cache up"); dir = g_dir_make_tmp ("grilo-plugin-cache-XXXXXX", NULL); if (!dir) return; cache = soup_cache_new (dir, SOUP_CACHE_SINGLE_USER); g_free (dir); soup_session_add_feature (priv->session, SOUP_SESSION_FEATURE (cache)); if (priv->cache_size) { soup_cache_set_max_size (cache, priv->cache_size * 1024 * 1024); } g_object_unref (cache); } static gboolean cache_is_available (GrlNetWc *self) { return soup_session_get_feature (self->priv->session, SOUP_TYPE_CACHE) != NULL; } static void init_requester (GrlNetWc *self) { init_dump_directory (); } static void finalize_requester (GrlNetWc *self) { GrlNetWcPrivate *priv = self->priv; cache_down (self); g_free (priv->previous_data); } static void grl_net_wc_init (GrlNetWc *wc) { GRL_LOG_DOMAIN_INIT (wc_log_domain, "wc"); wc->priv = grl_net_wc_get_instance_private (wc); wc->priv->session = soup_session_async_new (); g_object_set (G_OBJECT (wc->priv->session), "ssl-use-system-ca-file", TRUE, NULL); wc->priv->pending = g_queue_new (); set_thread_context (wc); init_mock_requester (wc); init_requester (wc); } static void grl_net_wc_finalize (GObject *object) { GrlNetWc *wc; wc = GRL_NET_WC (object); grl_net_wc_flush_delayed_requests (wc); cache_down (wc); finalize_requester (wc); finalize_mock_requester (wc); g_queue_free (wc->priv->pending); g_object_unref (wc->priv->session); G_OBJECT_CLASS (grl_net_wc_parent_class)->finalize (object); } static void grl_net_wc_set_property (GObject *object, guint propid, const GValue *value, GParamSpec *pspec) { GrlNetWc *wc; wc = GRL_NET_WC (object); switch (propid) { case PROP_LOG_LEVEL: grl_net_wc_set_log_level (wc, g_value_get_uint (value)); break; case PROP_THROTTLING: grl_net_wc_set_throttling (wc, g_value_get_uint (value)); break; case PROP_CACHE: grl_net_wc_set_cache (wc, g_value_get_boolean (value)); break; case PROP_CACHE_SIZE: grl_net_wc_set_cache_size (wc, g_value_get_uint (value)); break; case PROP_USER_AGENT: g_object_set (G_OBJECT (wc->priv->session), "user-agent", g_value_get_string (value), NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (wc, propid, pspec); } } static void grl_net_wc_get_property (GObject *object, guint propid, GValue *value, GParamSpec *pspec) { GrlNetWc *wc; wc = GRL_NET_WC (object); switch (propid) { case PROP_LOG_LEVEL: g_value_set_uint (value, wc->priv->log_level); break; case PROP_THROTTLING: g_value_set_uint (value, wc->priv->throttling); break; case PROP_CACHE: g_value_set_boolean(value, cache_is_available (wc)); break; case PROP_CACHE_SIZE: g_value_set_uint (value, wc->priv->cache_size); break; case PROP_USER_AGENT: g_object_get_property (G_OBJECT (wc->priv->session), "user_agent", value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (wc, propid, pspec); } } struct request_clos { GrlNetWc *self; char *url; GAsyncResult *result; GCancellable *cancellable; GHashTable *headers; guint source_id; }; static void request_clos_destroy (gpointer data) { struct request_clos *c = (struct request_clos *) data; g_free (c->url); g_clear_object (&c->cancellable); g_clear_pointer (&c->headers, g_hash_table_unref); g_free (c); } static void parse_error (guint status, const gchar *reason, const gchar *response, GSimpleAsyncResult *result) { if (!response || *response == '\0') response = reason; switch (status) { case SOUP_STATUS_CANT_RESOLVE: case SOUP_STATUS_CANT_CONNECT: case SOUP_STATUS_SSL_FAILED: case SOUP_STATUS_IO_ERROR: g_simple_async_result_set_error (result, GRL_NET_WC_ERROR, GRL_NET_WC_ERROR_NETWORK_ERROR, _("Cannot connect to the server")); return; case SOUP_STATUS_CANT_RESOLVE_PROXY: case SOUP_STATUS_CANT_CONNECT_PROXY: g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, _("Cannot connect to the proxy server")); return; case SOUP_STATUS_INTERNAL_SERVER_ERROR: /* 500 */ case SOUP_STATUS_MALFORMED: case SOUP_STATUS_BAD_REQUEST: /* 400 */ g_simple_async_result_set_error (result, GRL_NET_WC_ERROR, GRL_NET_WC_ERROR_PROTOCOL_ERROR, _("Invalid request URI or header: %s"), response); return; case SOUP_STATUS_UNAUTHORIZED: /* 401 */ case SOUP_STATUS_FORBIDDEN: /* 403 */ g_simple_async_result_set_error (result, GRL_NET_WC_ERROR, GRL_NET_WC_ERROR_AUTHENTICATION_REQUIRED, _("Authentication required: %s"), response); return; case SOUP_STATUS_NOT_FOUND: /* 404 */ g_simple_async_result_set_error (result, GRL_NET_WC_ERROR, GRL_NET_WC_ERROR_NOT_FOUND, _("The requested resource was not found: %s"), response); return; case SOUP_STATUS_CONFLICT: /* 409 */ case SOUP_STATUS_PRECONDITION_FAILED: /* 412 */ g_simple_async_result_set_error (result, GRL_NET_WC_ERROR, GRL_NET_WC_ERROR_CONFLICT, _("The entry has been modified since it was downloaded: %s"), response); return; case SOUP_STATUS_CANCELLED: g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")); return; default: GRL_DEBUG ("Unhandled status: %s", soup_status_get_phrase (status)); g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", soup_status_get_phrase (status)); } } static char * build_request_filename (const char *uri) { char *hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1); char *filename = g_strdup_printf ("%"G_GINT64_FORMAT "-%s.data", g_get_monotonic_time (), hash); g_free (hash); return filename; } static void dump_data (SoupURI *uri, const char *buffer, const gsize length) { if (!capture_dir) return; char *uri_string = soup_uri_to_string (uri, FALSE); /* Write request content to file in capture directory. */ char *request_filename = build_request_filename (uri_string); char *path = g_build_filename (capture_dir, request_filename, NULL); GError *error = NULL; if (!g_file_set_contents (path, buffer, length, &error)) { GRL_WARNING ("Could not write contents to disk: %s", error->message); g_error_free (error); } g_free (path); /* Append record about the just written file to "grl-net-mock-data-%PID.ini" * in the capture directory. */ char *filename = g_strdup_printf ("grl-net-mock-data-%u.ini", getpid()); path = g_build_filename (capture_dir, filename, NULL); g_free (filename); FILE *stream = g_fopen (path, "at"); g_free (path); if (!stream) { GRL_WARNING ("Could not write contents to disk: %s", g_strerror (errno)); } else { if (ftell (stream) == 0) fprintf (stream, "[default]\nversion=%d\n\n", GRL_NET_MOCK_VERSION); fprintf (stream, "[%s]\ndata=%s\n\n", uri_string, request_filename); fclose (stream); } g_free (request_filename); g_free (uri_string); } static void read_async_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data); struct request_res *rr = g_simple_async_result_get_op_res_gpointer (result);; GError *error = NULL; gssize s = g_input_stream_read_finish (G_INPUT_STREAM (source), res, &error); gsize to_read; if (s > 0) { /* Continue reading */ rr->offset += s; to_read = rr->length - rr->offset; if (!to_read) { /* Buffer is not enough; we need to assign more space */ rr->length *= 2; rr->buffer = g_renew (gchar, rr->buffer, rr->length); to_read = rr->length - rr->offset; } g_input_stream_read_async (G_INPUT_STREAM (source), rr->buffer + rr->offset, to_read, G_PRIORITY_DEFAULT, NULL, read_async_cb, user_data); return; } /* Put the end of string */ rr->buffer[rr->offset] = '\0'; g_input_stream_close (G_INPUT_STREAM (source), NULL, NULL); g_object_unref (source); if (error) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")); } else { g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED, _("Data not available")); } g_error_free (error); g_simple_async_result_complete (result); g_object_unref (result); return; } { SoupMessage *msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (rr->request)); if (msg && msg->status_code != SOUP_STATUS_OK) { parse_error (msg->status_code, msg->reason_phrase, msg->response_body->data, G_SIMPLE_ASYNC_RESULT (user_data)); g_object_unref (msg); } } g_simple_async_result_complete (result); g_object_unref (result); } static void reply_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data); struct request_res *rr = g_simple_async_result_get_op_res_gpointer (result); GError *error = NULL; GInputStream *in = soup_request_send_finish (rr->request, res, &error); if (error) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_simple_async_result_set_from_error (result, error); } else { g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED, _("Data not available")); } g_error_free (error); g_simple_async_result_complete (result); g_object_unref (result); return; } rr->length = soup_request_get_content_length (rr->request) + 1; if (rr->length == 1) rr->length = 50 * 1024; rr->buffer = g_new (gchar, rr->length); g_input_stream_read_async (in, rr->buffer, rr->length, G_PRIORITY_DEFAULT, NULL, read_async_cb, user_data); } static void get_url_now (GrlNetWc *self, const char *url, GHashTable *headers, GAsyncResult *result, GCancellable *cancellable) { GrlNetWcPrivate *priv = self->priv; SoupURI *uri; struct request_res *rr = g_slice_new0 (struct request_res); g_simple_async_result_set_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result), rr, NULL); uri = soup_uri_new (url); if (uri) { rr->request = soup_session_request_uri (priv->session, uri, NULL); soup_uri_free (uri); } else { rr->request = NULL; } if (!rr->request) { g_simple_async_result_set_error (G_SIMPLE_ASYNC_RESULT (result), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid URL %s"), url); g_simple_async_result_complete (G_SIMPLE_ASYNC_RESULT (result)); g_object_unref (result); return; } if (headers != NULL) { SoupMessage *message; GHashTableIter iter; const char *key, *value; message = soup_request_http_get_message (SOUP_REQUEST_HTTP (rr->request)); if (message) { g_hash_table_iter_init (&iter, headers); while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *)&value)) { soup_message_headers_append (message->request_headers, key, value); } g_object_unref (message); } } soup_request_send_async (rr->request, cancellable, reply_cb, result); } static gboolean get_url_cb (gpointer user_data) { struct request_clos *c = (struct request_clos *) user_data; /* validation */ { GrlNetWcPrivate *priv = c->self->priv; struct request_clos *d = g_queue_pop_tail (priv->pending); g_assert (c == d); } if (is_mocked ()) get_url_mocked (c->self, c->url, c->headers, c->result, c->cancellable); else get_url_now (c->self, c->url, c->headers, c->result, c->cancellable); return FALSE; } static void get_url (GrlNetWc *self, const char *url, GHashTable *headers, GAsyncResult *result, GCancellable *cancellable) { guint id; gint64 now; struct request_clos *c; GrlNetWcPrivate *priv = self->priv; /* closure */ c = g_new (struct request_clos, 1); c->self = self; c->url = g_strdup (url); c->headers = headers? g_hash_table_ref (headers): NULL; c->result = result; c->cancellable = cancellable ? g_object_ref (cancellable) : NULL; now = g_get_real_time () / G_USEC_PER_SEC; /* If grl-net-wc is not mocked, we need to check if throttling is set * otherwise the throttling delay check would always be true */ if (is_mocked () || priv->throttling == 0 || (now - priv->last_request) > priv->throttling) { priv->last_request = now; id = g_idle_add_full (G_PRIORITY_HIGH_IDLE, get_url_cb, c, request_clos_destroy); } else { priv->last_request += priv->throttling; GRL_DEBUG ("delaying web request by %" G_GINT64_FORMAT " seconds", priv->last_request - now); id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, priv->last_request - now, get_url_cb, c, request_clos_destroy); } g_source_set_name_by_id (id, "[grl-net] get_url_cb"); c->source_id = id; g_queue_push_head (self->priv->pending, c); } static void get_content (GrlNetWc *self, void *op, gchar **content, gsize *length) { GrlNetWcPrivate *priv = self->priv; struct request_res *rr = op; g_clear_pointer (&priv->previous_data, g_free); if (is_mocked ()) { get_content_mocked (self, op, &(priv->previous_data), length); } else { dump_data (soup_request_get_uri (rr->request), rr->buffer, rr->offset); priv->previous_data = rr->buffer; if (length) { *length = rr->offset; } } if (content) *content = self->priv->previous_data; else { if (length) { *length = 0; } } } /** * grl_net_wc_new: * * Creates a new #GrlNetWc. * * Returns: a new allocated instance of #GrlNetWc. Do g_object_unref() after * use it. */ GrlNetWc * grl_net_wc_new () { return g_object_new (GRL_TYPE_NET_WC, NULL); } /** * grl_net_wc_request_async: * @self: a #GrlNetWc instance * @uri: The URI of the resource to request * @cancellable: (allow-none): a #GCancellable instance or %NULL to ignore * @callback: The callback when the result is ready * @user_data: User data set for the @callback * * Request the fetching of a web resource given the @uri. This request is * asynchronous, thus the result will be returned within the @callback. */ void grl_net_wc_request_async (GrlNetWc *self, const char *uri, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { grl_net_wc_request_with_headers_hash_async (self, uri, NULL, cancellable, callback, user_data); } /** * grl_net_wc_request_with_headers_async: * @self: a #GrlNetWc instance * @uri: The URI of the resource to request * @cancellable: (allow-none): a #GCancellable instance or %NULL to ignore * @callback: The callback when the result is ready * @user_data: User data set for the @callback * @...: List of tuples of header name and header value, terminated by * %NULL. * * Request the fetching of a web resource given the @uri. This request is * asynchronous, thus the result will be returned within the @callback. * * Since: 0.2.2 */ void grl_net_wc_request_with_headers_async (GrlNetWc *self, const char *uri, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, ...) { va_list va_args; const gchar *header_name = NULL, *header_value = NULL; GHashTable *headers = NULL; va_start (va_args, user_data); header_name = va_arg (va_args, const gchar *); while (header_name) { header_value = va_arg (va_args, const gchar *); if (header_value) { if (headers == NULL) { headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } g_hash_table_insert (headers, g_strdup (header_name), g_strdup (header_value)); } header_name = va_arg (va_args, const gchar *); } va_end (va_args); grl_net_wc_request_with_headers_hash_async (self, uri, headers, cancellable, callback, user_data); g_clear_pointer (&headers, g_hash_table_unref); } /** * grl_net_wc_request_with_headers_hash_async: (rename-to grl_net_wc_request_with_headers_async) * @self: a #GrlNetWc instance * @uri: The URI of the resource to request * @headers: (allow-none) (element-type utf8 utf8): a set of additional HTTP * headers for this request or %NULL to ignore * @cancellable: (allow-none): a #GCancellable instance or %NULL to ignore * @callback: The callback when the result is ready * @user_data: User data set for the @callback * * Request the fetching of a web resource given the @uri. This request is * asynchronous, thus the result will be returned within the @callback. * * Since: 0.2.2 */ void grl_net_wc_request_with_headers_hash_async (GrlNetWc *self, const char *uri, GHashTable *headers, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, grl_net_wc_request_async); get_url (self, uri, headers, G_ASYNC_RESULT (result), cancellable); } /** * grl_net_wc_request_finish: * @self: a #GrlNetWc instance * @result: The result of the request * @content: (out) (array length=length) (element-type guint8) (allow-none) * (transfer full): The contents of the resource * @length: (out) (allow-none): The length of the contents or %NULL if it is not * needed * @error: return location for a #GError, or %NULL * * Finishes an asynchronous load of the file's contents. * The contents are placed in contents, and length is set to the size of the * contents string. * * The content address will be invalidated at the next request. So if you * want to keep it, please copy it into another address. * * Returns: %TRUE if the request was successfull. If %FALSE an error occurred. */ gboolean grl_net_wc_request_finish (GrlNetWc *self, GAsyncResult *result, gchar **content, gsize *length, GError **error) { GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (result); gboolean ret = TRUE; g_warn_if_fail (g_simple_async_result_get_source_tag (res) == grl_net_wc_request_async); void *op = g_simple_async_result_get_op_res_gpointer (res); if (g_simple_async_result_propagate_error (res, error) == TRUE) { ret = FALSE; goto end_func; } get_content(self, op, content, length); end_func: if (is_mocked ()) free_mock_op_res (op); else free_op_res (op); return ret; } /** * grl_net_wc_set_log_level: * @self: a #GrlNetWc instance * @log_level: the libsoup log level to set [0,3] * * Setting the log level the logger feature is added into * the libsoup session. */ void grl_net_wc_set_log_level (GrlNetWc *self, guint log_level) { SoupLogger *logger; g_return_if_fail (log_level <= 3); g_return_if_fail (GRL_IS_NET_WC (self)); if (self->priv->log_level == log_level) return; soup_session_remove_feature_by_type (self->priv->session, SOUP_TYPE_LOGGER); logger = soup_logger_new ((SoupLoggerLogLevel) log_level, -1); soup_session_add_feature (self->priv->session, SOUP_SESSION_FEATURE (logger)); g_object_unref (logger); self->priv->log_level = (SoupLoggerLogLevel) log_level; } /** * grl_net_wc_set_throttling: * @self: a #GrlNetWc instance * @throttling: the number of seconds to wait between requests * * Setting this property, the #GrlNetWc will queue all the requests and * will dispatch them with a pause between them of this value. */ void grl_net_wc_set_throttling (GrlNetWc *self, guint throttling) { g_return_if_fail (GRL_IS_NET_WC (self)); if (throttling > 0) { /* max conns per host = 1 */ g_object_set (self->priv->session, SOUP_SESSION_MAX_CONNS_PER_HOST, 1, NULL); } else { /* default value */ g_object_set (self->priv->session, SOUP_SESSION_MAX_CONNS_PER_HOST, 2, NULL); } self->priv->throttling = throttling; } /** * grl_net_wc_set_cache: * @self: a #GrlNetWc instance * @use_cache: if cache must be used or not * * Sets if cache must be used. Note that this will only work if caching is * supporting. If sets %TRUE, a new cache will be created. If sets to %FALSE, * current cache is clean and removed. * * Since: 0.1.12 **/ void grl_net_wc_set_cache (GrlNetWc *self, gboolean use_cache) { g_return_if_fail (GRL_IS_NET_WC (self)); if (use_cache && !cache_is_available (self)) cache_up (self); else if (!use_cache && cache_is_available (self)) cache_down (self); } /** * grl_net_wc_set_cache_size: * @self: a #GrlNetWc instance * @cache_size: size of cache (in Mb) * * Sets the new maximum size of cache, in Megabytes. Default value is 10. Using * 0 means no cache will be done. * * Since: 0.1.12 **/ void grl_net_wc_set_cache_size (GrlNetWc *self, guint size) { g_return_if_fail (GRL_IS_NET_WC (self)); if (self->priv->cache_size == size) return; self->priv->cache_size = size; SoupSessionFeature *cache = soup_session_get_feature (self->priv->session, SOUP_TYPE_CACHE); if (!cache) return; soup_cache_set_max_size (SOUP_CACHE (cache), size * 1024 * 1024); } /** * grl_net_wc_flush_delayed_requests: * @self: a #GrlNetWc instance * * This method will flush all the pending request in the queue. */ void grl_net_wc_flush_delayed_requests (GrlNetWc *self) { GrlNetWcPrivate *priv = self->priv; struct request_clos *c; g_return_if_fail (GRL_IS_NET_WC (self)); while ((c = g_queue_pop_head (priv->pending))) { if (c->cancellable) g_cancellable_cancel (c->cancellable); /* This will call the destroy notify, request_clos_destroy() */ g_source_remove (c->source_id); } priv->last_request = g_get_real_time() / G_USEC_PER_SEC; }