Re: [Cache V2 1/1] grl-net: Use libsoup cache



Comments below

On Fri, Mar 11, 2011 at 10:55:59AM +0100, Juan A. Suarez Romero wrote:
> If libsoup version is newer enough, use the cache feature it provides.
> 
> Thus, it will cache the content to speed up network access.
> 
> Signed-off-by: Juan A. Suarez Romero <jasuarez igalia com>
> ---
>  configure.ac          |    4 +
>  libs/net/Makefile.am  |    6 +
>  libs/net/grl-net-wc.c |  384 ++++++++++++++++++++++++++++++++++++++++++++++++-
>  libs/net/grl-net-wc.h |   11 ++-
>  libs/net/wc-test.c    |    2 +-
>  5 files changed, 399 insertions(+), 8 deletions(-)
> 
> diff --git a/configure.ac b/configure.ac
> index c307620..8a91bd2 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -117,6 +117,9 @@ PKG_CHECK_MODULES(NET, libsoup-2.4,
>  			HAVE_LIBSOUP=yes,
>  			HAVE_LIBSOUP=no)
>  
> +PKG_CHECK_MODULES(NETCACHE, libsoup-2.4 >= 2.33.4,
> +                            HAVE_CACHED_LIBSOUP=yes,
> +                            HAVE_CACHED_LIBSOUP=no)
>  AC_ARG_ENABLE([grl_net],
>          AS_HELP_STRING([--enable-grl-net],
>                  [Enable Grilo Net library (default: auto)]),
> @@ -134,6 +137,7 @@ AC_ARG_ENABLE([grl_net],
>          ])
>  
>  AM_CONDITIONAL(BUILD_GRILO_NET, test "x$HAVE_LIBSOUP" = "xyes")
> +AM_CONDITIONAL(BUILD_GRILO_NET_WITH_CACHE, test "x$HAVE_CACHED_LIBSOUP" = "xyes")
>  
>  # ----------------------------------------------------------
>  # DEBUG SUPPORT
> diff --git a/libs/net/Makefile.am b/libs/net/Makefile.am
> index d7542fa..b43cc9a 100644
> --- a/libs/net/Makefile.am
> +++ b/libs/net/Makefile.am
> @@ -5,6 +5,7 @@
>  #
>  # Copyright (C) 2010 Igalia S.L. All rights reserved.
>  
> +
>  lib_LTLIBRARIES = libgrlnet-@GRL_MAJORMINOR@.la
>  noinst_PROGRAMS = wc-test
>  
> @@ -20,6 +21,11 @@ libgrlnet_@GRL_MAJORMINOR@_la_CFLAGS =	\
>  	$(DEPS_CFLAGS)			\
>  	$(NET_CFLAGS)
>  
> +if BUILD_GRILO_NET_WITH_CACHE
> +libgrlnet_@GRL_MAJORMINOR@_la_CFLAGS += \
> +	-DLIBSOUP_WITH_CACHE
> +endif
> +
>  libgrlnet_@GRL_MAJORMINOR@_la_LIBADD =	\
>  	$(top_builddir)/src/lib@GRL_NAME@.la	\
>  	$(DEPS_LIBS)				\
> diff --git a/libs/net/grl-net-wc.c b/libs/net/grl-net-wc.c
> index 0b6253f..4effa78 100644
> --- a/libs/net/grl-net-wc.c
> +++ b/libs/net/grl-net-wc.c
> @@ -4,6 +4,7 @@
>   * Contact: Iago Toral Quiroga <itoral igalia com>
>   *
>   * Authors: Víctor M. Jáquez L. <vjaquez igalia com>
> + *          Juan A. Suarez Romero <jasuarez igalia com>
>   *
>   * This library is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU Lesser General Public License
> @@ -35,8 +36,18 @@
>  #include "config.h"
>  #endif
>  
> +#include <string.h>
>  #include <libsoup/soup.h>
>  
> +#ifdef LIBSOUP_WITH_CACHE
> +/* Using the cache feature requires to use the unstable API */
> +#define LIBSOUP_USE_UNSTABLE_REQUEST_API
> +#define BUFFER_SIZE (50*1024)
> +#include <libsoup/soup-cache.h>
> +#include <libsoup/soup-requester.h>
> +#include <libsoup/soup-request-http.h>
> +#endif
> +
>  #include <grilo.h>
>  #include "grl-net-wc.h"
>  
> @@ -47,6 +58,9 @@ enum {
>    PROP_0,
>    PROP_LOG_LEVEL,
>    PROP_THROTTLING,
> +  PROP_IDENTIFIER,
> +  PROP_CACHE,
> +  PROP_CACHE_SIZE,
>  };
>  
>  #define GRL_NET_WC_GET_PRIVATE(object)			\
> @@ -57,8 +71,16 @@ enum {
>  typedef struct _RequestClosure RequestClosure;
>  
>  struct _GrlNetWcPrivate {
> +  gchar *identifier;
>    SoupSession *session;
>    SoupLoggerLogLevel log_level;
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  SoupRequester *requester;
> +  SoupCache *cache;
> +  gboolean use_cache;
> +  guint cache_size;
> +  gchar *previous_data;
> +#endif
>    guint throttling;
>    GTimeVal last_request;
>    GQueue *pending; /* closure queue for delayed requests */
> @@ -72,6 +94,15 @@ struct _RequestClosure {
>    guint source_id;
>  };
>  
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +typedef struct {
> +  SoupRequest *request;
> +  gchar *buffer;
> +  gsize length;
> +  gsize offset;
> +} RequestResult;
> +#endif
> +
>  GQuark
>  grl_net_wc_error_quark (void)
>  {
> @@ -90,6 +121,10 @@ static void grl_net_wc_get_property (GObject *object,
>                                       GValue *value,
>                                       GParamSpec *pspec);
>  
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +static void cache_down (GrlNetWc *self);
> +#endif
> +
>  static void
>  grl_net_wc_class_init (GrlNetWcClass *klass)
>  {
> @@ -130,6 +165,49 @@ grl_net_wc_class_init (GrlNetWcClass *klass)
>                                                        0, G_MAXUINT, 0,
>                                                        G_PARAM_READWRITE |
>                                                        G_PARAM_STATIC_STRINGS));
> +  /**
> +   * GrlNetWc::identifier
> +   *
> +   * An unique identifier for GrlNet. This identifier is used when cache is
> +   * supported. Cached content in a subdirectory named "identifier".
> +   */
> +  g_object_class_install_property (g_klass,
> +                                   PROP_IDENTIFIER,
> +                                   g_param_spec_string ("identifier",
> +                                                        "GrlNet identifier",
> +                                                        "Unique identifier for GrlNet",
> +                                                        NULL,
> +                                                        G_PARAM_READWRITE |
> +                                                        G_PARAM_CONSTRUCT_ONLY |
> +
>                                                          G_PARAM_STATIC_STRINGS));

Why should we demand to the API user a cache identifier? I guess we could
figure out one with out messing the user.

> +  /**
> +   * 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.
> +   */

Why 10Mb? IMO that's too much for xml documents or so. We are not downloading
the media itself, but the metadata. Especially if we are on embedded devices.

> +  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));
>  }
>  
>  static void
> @@ -141,6 +219,11 @@ grl_net_wc_init (GrlNetWc *wc)
>  
>    wc->priv->session = soup_session_async_new ();
>    wc->priv->pending = g_queue_new ();
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  wc->priv->requester = soup_requester_new();
> +  soup_session_add_feature (wc->priv->session,
> +                            SOUP_SESSION_FEATURE (wc->priv->requester));
> +#endif
>  }
>  
>  static void
> @@ -150,6 +233,12 @@ grl_net_wc_finalize (GObject *object)
>  
>    wc = GRL_NET_WC (object);
>    grl_net_wc_flush_delayed_requests (wc);
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  cache_down (wc);
> +  g_free (wc->priv->identifier);
> +  g_free (wc->priv->previous_data);
> +  g_object_unref (wc->priv->requester);
> +#endif
>    g_queue_free (wc->priv->pending);
>    g_object_unref (wc->priv->session);
>  
> @@ -173,6 +262,15 @@ grl_net_wc_set_property (GObject *object,
>    case PROP_THROTTLING:
>      grl_net_wc_set_throttling (wc, g_value_get_uint (value));
>      break;
> +  case PROP_IDENTIFIER:
> +    grl_net_wc_set_identifier (wc, g_value_get_string (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;
>    default:
>      G_OBJECT_WARN_INVALID_PROPERTY_ID (wc, propid, pspec);
>    }
> @@ -195,6 +293,15 @@ grl_net_wc_get_property (GObject *object,
>    case PROP_THROTTLING:
>      g_value_set_uint (value, wc->priv->throttling);
>      break;
> +  case PROP_IDENTIFIER:
> +    g_value_set_string (value, wc->priv->identifier);
> +    break;
> +  case PROP_CACHE:
> +    g_value_set_boolean (value, wc->priv->use_cache);
> +    break;
> +  case PROP_CACHE_SIZE:
> +    g_value_set_uint (value, wc->priv->cache_size);
> +    break;
>    default:
>      G_OBJECT_WARN_INVALID_PROPERTY_ID (wc, propid, pspec);
>    }
> @@ -261,6 +368,88 @@ parse_error (guint status,
>    }
>  }
>  
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +static void
> +read_async_cb (GObject *source, GAsyncResult *res, gpointer user_data)
> +{
> +  GSimpleAsyncResult *result;
> +  RequestResult *rr;
> +  SoupMessage *msg;
> +  GError *error = NULL;
> +  gsize to_read;
> +  gssize s;
> +
> +  result = G_SIMPLE_ASYNC_RESULT (user_data);
> +  rr = g_simple_async_result_get_op_res_gpointer (result);
> +
> +  s = g_input_stream_read_finish (G_INPUT_STREAM (source), res, &error);
> +  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);
> +
> +  if (error) {
> +    if (error->code == G_IO_ERROR_CANCELLED) {
> +      g_simple_async_result_set_error (result, GRL_NET_WC_ERROR,
> +                                       GRL_NET_WC_ERROR_CANCELLED,
> +                                       "Operation was cancelled");
> +    } else {
> +      g_simple_async_result_set_error (result, GRL_NET_WC_ERROR,
> +                                       GRL_NET_WC_ERROR_UNAVAILABLE,
> +                                       "Data not available");
> +    }
> +    g_error_free (error);
> +
> +    g_simple_async_result_complete (result);
> +    return;
> +  }
> +
> +  msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (rr->request));
> +
> +  if (msg) {
> +    if (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);
> +}
> +
> +static void
> +reply_cb (GObject *source, GAsyncResult *res, gpointer user_data)
> +{
> +  GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
> +  RequestResult *rr = g_simple_async_result_get_op_res_gpointer (result);
> +
> +  GInputStream *in = soup_request_send_finish (rr->request, res, NULL);
> +  rr->length = soup_request_get_content_length (rr->request) + 1;
> +  if (rr->length == 1) {
> +    rr->length = BUFFER_SIZE;

Why 50K as a fallback? 

> +  }
> +  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);
> +}
> +
> +#else
> +
>  static void
>  reply_cb (SoupSession *session,
>            SoupMessage *msg,
> @@ -299,6 +488,25 @@ message_cancel_cb (GCancellable *cancellable,
>                                   msg, SOUP_STATUS_CANCELLED);
>  
>  }
> +#endif
> +
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +static void
> +get_url_now (GrlNetWc *self,
> +             const char *url,
> +             GAsyncResult *result,
> +             GCancellable *cancellable)
> +{
> +  RequestResult *rr = g_slice_new0 (RequestResult);
> +
> +  rr->request = soup_requester_request (self->priv->requester, url, NULL);
> +  g_simple_async_result_set_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result),
> +                                             rr,
> +                                             NULL);
> +  soup_request_send_async (rr->request, cancellable, reply_cb, result);
> +}
> +
> +#else
>  
>  static void
>  get_url_now (GrlNetWc *self,
> @@ -348,6 +556,7 @@ get_url_now (GrlNetWc *self,
>                                reply_cb,
>                                result);
>  }
> +#endif
>  
>  static gboolean
>  get_url_delayed (gpointer user_data)
> @@ -405,16 +614,49 @@ get_url (GrlNetWc *self,
>    }
>  }
>  
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +static void
> +cache_down (GrlNetWc *self)
> +{
> +  soup_session_remove_feature (self->priv->session,
> +                               SOUP_SESSION_FEATURE (self->priv->cache));
> +  soup_cache_clear (self->priv->cache);
> +  g_object_unref (self->priv->cache);
> +  self->priv->cache = NULL;
> +}
> +
> +static void
> +cache_up (GrlNetWc *self)
> +{
> +  gchar *cache_dir;
> +
> +  cache_dir = g_build_filename (g_get_user_cache_dir (),
> +                                g_get_prgname (),
> +                                self->priv->identifier,
> +                                NULL);
> +  self->priv->cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
> +  grl_net_wc_set_cache_size (self, self->priv->cache_size);
> +  soup_session_add_feature (self->priv->session,
> +                            SOUP_SESSION_FEATURE (self->priv->cache));
> +  g_free (cache_dir);
> +}
> +#endif
> +
>  /**
>   * grl_net_wc_new:
> + * @identifier: an identifier

Can it be NULL?

> + *
> + * Creates a new #GrlNetWc with unique @identifier.
>   *
>   * Returns: a new allocated instance of #GrlNetWc. Do g_object_unref() after
>   * use it.
>   */
>  GrlNetWc *
> -grl_net_wc_new (void)
> +grl_net_wc_new (const gchar *identifier)
>  {
> -  return g_object_new (GRL_TYPE_NET_WC, NULL);
> +  return g_object_new (GRL_TYPE_NET_WC,
> +                       "identifier", identifier,
> +                       NULL);
>  }
>  
>  /**
> @@ -471,7 +713,6 @@ grl_net_wc_request_finish (GrlNetWc *self,
>                             GError **error)
>  {
>    GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (result);
> -  SoupMessage *msg;
>    gboolean ret = TRUE;
>  
>    g_warn_if_fail (g_simple_async_result_get_source_tag (res) ==
> @@ -482,15 +723,40 @@ grl_net_wc_request_finish (GrlNetWc *self,
>      goto end_func;
>    }
>  
> -  msg = (SoupMessage *) g_simple_async_result_get_op_res_gpointer (res);
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  RequestResult *rr = g_simple_async_result_get_op_res_gpointer (res);
>  
> -  if (content != NULL)
> -    *content = (gchar *) msg->response_body->data;
> +  if (self->priv->previous_data) {
> +    g_free (self->priv->previous_data);
> +  }
>  
> +  self->priv->previous_data = rr->buffer;
> +
> +  if (content) {
> +    *content = self->priv->previous_data;
> +  } else {
> +    g_free (rr->buffer);
> +  }
> +
> +  if (length) {
> +    *length = rr->offset;
> +  }
> +
> +#else
> +  SoupMessage *msg = (SoupMessage *) g_simple_async_result_get_op_res_gpointer (res);
> +
> +  if (content != NULL)
> +    *content = g_strdup ((gchar *) msg->response_body->data);

mmmhh... I wanted to avoid a mem copy, delegating to the user that option if
she wants to maintain the data after a new request is done or unref the wc.

Now that you are forcing the memcopy, we should change all the plugins which
use this.

>    if (length != NULL)
>      *length = (gsize) msg->response_body->length;
> +#endif
>  
>  end_func:
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  g_object_unref (rr->request);
> +  g_slice_free (RequestResult, rr);
> +#endif
> +
>    g_object_unref (res);
>    return ret;
>  }
> @@ -552,6 +818,112 @@ grl_net_wc_set_throttling (GrlNetWc *self,
>  }
>  
>  /**
> + * grl_net_wc_set_identifier:
> + * @self: a #GrlNetWc instance
> + * @identifier: new identifier
> + *
> + * Changes the identifier for this instance.
> + *
> + * If cache is supported, gets rid of previous cache and create a new one.
> + **/
> +void
> +grl_net_wc_set_identifier (GrlNetWc *self,
> +                           const gchar *identifier)
> +{
> +  g_return_if_fail (GRL_IS_NET_WC (self));
> +
> +  if (g_strcmp0 (self->priv->identifier, identifier) == 0) {
> +    return;
> +  }
> +
> +  if (self->priv->identifier) {
> +    g_free (self->priv->identifier);
> +  }
> +
> +  self->priv->identifier = g_strdup (identifier);
> +
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  if (!self->priv->use_cache) {
> +    return;
> +  }
> +
> +  /* Get rid of old cache */
> +  if (self->priv->cache) {
> +    cache_down (self);
> +  }
> +
> +  /* Create a new cache */
> +  if (self->priv->identifier) {
> +    cache_up (self);
> +  }
> +#endif
> +}
> +
> +/**
> + * 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.
> + **/
> +void
> +grl_net_wc_set_cache (GrlNetWc *self,
> +                      gboolean use_cache)
> +{
> +  g_return_if_fail (GRL_IS_NET_WC (self));
> +
> +  if (self->priv->use_cache == use_cache) {
> +    return;
> +  }
> +
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  if (self->priv->cache) {
> +    cache_down (self);
> +  }
> +
> +  self->priv->use_cache = use_cache;
> +  if (use_cache && self->priv->identifier) {
> +    cache_up (self);
> +  }
> +
> +#else
> +
> +  GRL_WARNING ("Cache not supported");
> +  wc->priv->cache = FALSE;
> +#endif
> +}
> +
> +/**
> + * 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.
> + **/
> +void
> +grl_net_wc_set_cache_size (GrlNetWc *self,
> +                           guint cache_size)
> +{
> +  g_return_if_fail (GRL_IS_NET_WC (self));
> +
> +  if (self->priv->cache_size == cache_size) {
> +    return;
> +  }
> +
> +  self->priv->cache_size = cache_size;
> +
> +#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
> +  if (self->priv->cache) {
> +    guint size_in_bytes = cache_size * 1024 * 1024;
> +    soup_cache_set_max_size (self->priv->cache, size_in_bytes);
> +  }
> +#endif
> +}
> +
> +/**
>   * grl_net_wc_flush_delayed_requests:
>   * @self: a #GrlNetWc instance
>   *
> diff --git a/libs/net/grl-net-wc.h b/libs/net/grl-net-wc.h
> index 71dfd72..5d1ad85 100644
> --- a/libs/net/grl-net-wc.h
> +++ b/libs/net/grl-net-wc.h
> @@ -109,7 +109,7 @@ GType grl_net_wc_get_type (void) G_GNUC_CONST;
>  
>  GQuark grl_net_wc_error_quark (void) G_GNUC_CONST;
>  
> -GrlNetWc *grl_net_wc_new (void);
> +GrlNetWc *grl_net_wc_new (const gchar *identifier);
>  
>  void grl_net_wc_request_async (GrlNetWc *self,
>  			       const char *uri,
> @@ -129,6 +129,15 @@ void grl_net_wc_set_log_level (GrlNetWc *self,
>  void grl_net_wc_set_throttling (GrlNetWc *self,
>  				guint throttling);
>  
> +void grl_net_wc_set_identifier (GrlNetWc *self,
> +                                const gchar *identifier);
> +
> +void grl_net_wc_set_cache (GrlNetWc *self,
> +                           gboolean use_cache);
> +
> +void grl_net_wc_set_cache_size (GrlNetWc *self,
> +                                guint cache_size);
> +
>  void grl_net_wc_flush_delayed_requests (GrlNetWc *self);
>  
>  G_END_DECLS
> diff --git a/libs/net/wc-test.c b/libs/net/wc-test.c
> index 6313c67..8edbdba 100644
> --- a/libs/net/wc-test.c
> +++ b/libs/net/wc-test.c
> @@ -76,7 +76,7 @@ main (int argc, const char **argv)
>    if (argc == 2)
>      uri = argv[1];
>  
> -  wc = grl_net_wc_new ();
> +  wc = grl_net_wc_new (NULL);
>  
>    g_object_set (wc, "loglevel", 1, NULL);
>  

We shall verify how the apple trailer plugin goes with this type of cache.

vmjl


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]