[epiphany/in-tab-overview: 1/56] Add a service for snapshotting webpages



commit 9c6a4fd5c781ebccb8f44ba2690ec2d55deb00be
Author: Claudio Saavedra <csaavedra igalia com>
Date:   Tue Jan 24 14:25:29 2012 +0200

    Add a service for snapshotting webpages
    
    This service provides pixbufs for URLs while caching these locally as
    thumbnails. gnome-desktop-thumbnail is used to handle caching.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=668578

 configure.ac                |    4 +
 lib/Makefile.am             |    2 +
 lib/ephy-snapshot-service.c |  454 +++++++++++++++++++++++++++++++++++++++++++
 lib/ephy-snapshot-service.h |   94 +++++++++
 4 files changed, 554 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 01335ed..10946e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,6 +86,9 @@ LIBXML_REQUIRED=2.6.12
 LIBXSLT_REQUIRED=1.1.7
 WEBKIT_GTK_REQUIRED=1.9.6
 LIBSOUP_GNOME_REQUIRED=2.39.6
+WEBKIT_GTK_REQUIRED=1.9.5
+LIBSOUP_GNOME_REQUIRED=2.37.1
+GNOME_DESKTOP_REQUIRED=2.91.2
 GNOME_KEYRING_REQUIRED=2.26.0
 GSETTINGS_DESKTOP_SCHEMAS_REQUIRED=0.0.1
 LIBNOTIFY_REQUIRED=0.5.1
@@ -126,6 +129,7 @@ PKG_CHECK_MODULES([DEPENDENCIES], [
 		  libxslt >= $LIBXSLT_REQUIRED
 		  $WEBKIT_GTK_PC_NAME >= $WEBKIT_GTK_REQUIRED
 		  libsoup-gnome-2.4 >= $LIBSOUP_GNOME_REQUIRED
+		  gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED
 		  gnome-keyring-1 >= $GNOME_KEYRING_REQUIRED
 		  gsettings-desktop-schemas >= $GSETTINGS_DESKTOP_SCHEMAS_REQUIRED
 		  libnotify >= $LIBNOTIFY_REQUIRED
diff --git a/lib/Makefile.am b/lib/Makefile.am
index ba15f27..7c9401d 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -28,6 +28,7 @@ NOINST_H_FILES = \
 	ephy-sqlite-connection.h		\
 	ephy-sqlite-statement.h			\
 	ephy-string.h				\
+	ephy-snapshot-service.h			\
 	ephy-time-helpers.h			\
 	ephy-web-app-utils.h			\
 	ephy-zoom.h
@@ -68,6 +69,7 @@ libephymisc_la_SOURCES = \
 	ephy-shlib-loader.c			\
 	ephy-signal-accumulator.c		\
 	ephy-smaps.c				\
+	ephy-snapshot-service.c			\
 	ephy-sqlite-connection.c		\
 	ephy-sqlite-statement.c			\
 	ephy-state.c				\
diff --git a/lib/ephy-snapshot-service.c b/lib/ephy-snapshot-service.c
new file mode 100644
index 0000000..4908c6a
--- /dev/null
+++ b/lib/ephy-snapshot-service.c
@@ -0,0 +1,454 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright  2012 Igalia S.L.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "ephy-snapshot-service.h"
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#endif
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#ifdef HAVE_WEBKIT2
+#include <webkit2/webkit2.h>
+#else
+#include <webkit/webkit.h>
+#endif
+
+#define EPHY_SNAPSHOT_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EPHY_TYPE_SNAPSHOT_SERVICE, EphySnapshotServicePrivate))
+
+struct _EphySnapshotServicePrivate
+{
+  GnomeDesktopThumbnailFactory *factory;
+};
+
+G_DEFINE_TYPE (EphySnapshotService, ephy_snapshot_service, G_TYPE_OBJECT)
+
+typedef struct {
+  WebKitWebView *webview;
+  char *url;
+  time_t mtime;
+  GdkPixbuf *snapshot;
+  GCancellable *cancellable;
+  GAsyncReadyCallback callback;
+  gpointer user_data;
+} SnapshotOp;
+
+static gboolean ephy_snapshot_service_complete_async (SnapshotOp *op);
+static gboolean ephy_snapshot_service_take_from_webview (SnapshotOp *op);
+
+static void
+snapshot_op_free (SnapshotOp *op)
+{
+  g_free (op->url);
+
+  if (op->cancellable)
+    g_object_unref (op->cancellable);
+  if (op->webview)
+    g_object_unref (op->webview);
+
+  g_slice_free (SnapshotOp, op);
+}
+
+/* GObject boilerplate methods. */
+
+static void
+ephy_snapshot_service_class_init (EphySnapshotServiceClass *klass)
+{
+  g_type_class_add_private (klass, sizeof (EphySnapshotServicePrivate));
+}
+
+
+static void
+ephy_snapshot_service_init (EphySnapshotService *self)
+{
+
+  self->priv = EPHY_SNAPSHOT_SERVICE_GET_PRIVATE (self);
+  self->priv->factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+}
+
+/* IO scheduler methods, used for IO. */
+
+static GdkPixbuf *
+io_scheduler_get_cached_snapshot (EphySnapshotService *service,
+                                  char *url, time_t mtime)
+{
+  GdkPixbuf *snapshot;
+  char *uri;
+
+  uri =  gnome_desktop_thumbnail_factory_lookup (service->priv->factory,
+                                                 url, mtime);
+  if (uri == NULL)
+    return NULL;
+
+  snapshot = gdk_pixbuf_new_from_file (uri, NULL);
+  g_free (uri);
+
+  return snapshot;
+}
+
+static gboolean
+io_scheduler_try_cache_query (GIOSchedulerJob *job,
+                              GCancellable *cancellable,
+                              gpointer user_data)
+{
+  SnapshotOp *op;
+  EphySnapshotService *service = ephy_snapshot_service_get_default ();
+  GSourceFunc func;
+
+  op = (SnapshotOp*) user_data;
+
+  if (g_cancellable_is_cancelled (op->cancellable)) {
+    func = (GSourceFunc)ephy_snapshot_service_complete_async;
+    goto out;
+  }
+
+  op->snapshot = io_scheduler_get_cached_snapshot (service, op->url, op->mtime);
+
+  /* If we do have a cached snapshot or we don't have a webview
+     already, just complete early. */
+  if (op->snapshot || !op->webview)
+    func = (GSourceFunc)ephy_snapshot_service_complete_async;
+  else
+    func = (GSourceFunc)ephy_snapshot_service_take_from_webview;
+
+out:
+  g_io_scheduler_job_send_to_mainloop (job, func, op, NULL);
+  return FALSE;
+}
+
+static gboolean
+io_scheduler_save_thumbnail (GIOSchedulerJob *job,
+                             GCancellable *cancellable,
+                             gpointer user_data)
+{
+  SnapshotOp *op;
+  EphySnapshotService *service;
+
+  op = (SnapshotOp*) user_data;
+  service = ephy_snapshot_service_get_default ();
+  gnome_desktop_thumbnail_factory_save_thumbnail (service->priv->factory,
+                                                  op->snapshot,
+                                                  op->url,
+                                                  op->mtime);
+
+  g_io_scheduler_job_send_to_mainloop_async (job,
+                                             (GSourceFunc)ephy_snapshot_service_complete_async,
+                                             op, NULL);
+
+  return FALSE;
+}
+
+/* Methods that run in the mainloop. */
+
+static gboolean
+ephy_snapshot_service_complete_async (SnapshotOp *op)
+{
+  GSimpleAsyncResult *res;
+
+  res = g_simple_async_result_new (G_OBJECT (ephy_snapshot_service_get_default ()),
+                                   op->callback,
+                                   op->user_data,
+                                   ephy_snapshot_service_complete_async);
+  g_simple_async_result_set_check_cancellable (res, op->cancellable);
+  g_simple_async_result_set_op_res_gpointer (res, op, (GDestroyNotify)snapshot_op_free);
+  g_simple_async_result_complete (res);
+  g_object_unref (res);
+
+  return FALSE;
+}
+
+static gboolean
+webview_retrieve_snapshot (SnapshotOp *op)
+{
+  cairo_surface_t *surface;
+
+#ifdef HAVE_WEBKIT2
+  /* FIXME: We need to add this API to WebKit2. */
+  surface = NULL;
+#else
+  surface = webkit_web_view_get_snapshot (WEBKIT_WEB_VIEW (op->webview));
+#endif
+
+  if (surface == NULL) {
+    ephy_snapshot_service_complete_async (op);
+    return FALSE;
+  }
+
+  op->snapshot = ephy_snapshot_service_crop_snapshot (surface);
+
+  g_io_scheduler_push_job ((GIOSchedulerJobFunc)io_scheduler_save_thumbnail,
+                           op, NULL, G_PRIORITY_LOW, NULL);
+  return FALSE;
+}
+
+#ifdef HAVE_WEBKIT2
+static void
+webview_load_changed_cb (WebKitWebView *webview,
+                         WebKitLoadEvent load_event,
+                         SnapshotOp *op)
+{
+  switch (load_event) {
+  case WEBKIT_LOAD_FINISHED:
+    /* Load finished doesn't ensure that we actually have visible content yet,
+       so hold a bit before retrieving the snapshot. */
+    g_idle_add ((GSourceFunc) webview_retrieve_snapshot, op);
+    /* Some pages might end up causing this condition to happen twice, so remove
+       the handler in order to avoid calling the above idle function twice. */
+    g_signal_handlers_disconnect_by_func (webview, webview_load_changed_cb, op);
+    break;
+  }
+}
+
+static gboolean
+webview_load_failed_cb (WebKitWebView *webview,
+                        WebKitLoadEvent load_event,
+                        const char failing_uri,
+                        GError *error,
+                        SnapshotOp *op)
+{
+  ephy_snapshot_service_complete_async (op);
+
+  return FALSE;
+}
+#else
+static void
+webview_load_status_changed_cb (WebKitWebView *webview,
+                                GParamSpec *pspec,
+                                SnapshotOp *op)
+{
+  WebKitLoadStatus status;
+
+  status = webkit_web_view_get_load_status (webview);
+
+  switch (status) {
+  case WEBKIT_LOAD_FINISHED:
+    /* Load finished doesn't ensure that we actually have visible
+       content yet, so hold a bit before retrieving the snapshot. */
+    g_idle_add ((GSourceFunc) webview_retrieve_snapshot, op);
+    g_signal_handlers_disconnect_by_func (webview, webview_load_status_changed_cb, op);
+    break;
+  case WEBKIT_LOAD_FAILED:
+    g_signal_handlers_disconnect_by_func (webview, webview_load_status_changed_cb, op);
+    ephy_snapshot_service_complete_async (op);
+    break;
+  default:
+    break;
+  }
+}
+#endif
+
+static gboolean
+ephy_snapshot_service_take_from_webview (SnapshotOp *op)
+{
+  WebKitWebView *webview = op->webview;
+
+  if (g_cancellable_is_cancelled (op->cancellable)) {
+    ephy_snapshot_service_complete_async (op);
+    return FALSE;
+  }
+
+#ifdef HAVE_WEBKIT2
+  if (webkit_web_view_get_estimated_load_progress (WEBKIT_WEB_VIEW (webview))== 1.0)
+    webview_retrieve_snapshot (op);
+  else {
+    g_signal_connect (webview, "load-changed",
+                      G_CALLBACK (webview_load_changed_cb), op);
+    g_signal_connect (webview, "load-failed",
+                      G_CALLBACK (webview_load_failed_cb), op);
+  }
+#else
+  if (webkit_web_view_get_load_status (webview) == WEBKIT_LOAD_FINISHED)
+    webview_retrieve_snapshot (op);
+  else
+    g_signal_connect (webview, "notify::load-status",
+                      G_CALLBACK (webview_load_status_changed_cb),
+                      op);
+#endif
+
+  return FALSE;
+}
+
+/**
+ * ephy_snapshot_service_get_default:
+ *
+ * Gets the default instance of #EphySnapshotService.
+ *
+ * Returns: a #EphySnapshotService
+ **/
+EphySnapshotService *
+ephy_snapshot_service_get_default (void)
+{
+  static EphySnapshotService *service = NULL;
+
+  if (service == NULL)
+    service = g_object_new (EPHY_TYPE_SNAPSHOT_SERVICE, NULL);
+
+  return service;
+}
+
+/**
+ * ephy_snapshot_service_get_snapshot:
+ * @service: a #EphySnapshotService
+ * @url: the URL for which a snapshot is needed
+ * @mtime: @the last
+ * @callback: a #EphySnapshotServiceCallback
+ * @userdata: user data to pass to @callback
+ *
+ * Schedules a query for a snapshot of @url. If there is an up-to-date
+ * snapshot in the cache, this will be retrieved. Otherwise, this
+ * the snapshot will be taken, cached, and retrieved.
+ *
+ **/
+void
+ephy_snapshot_service_get_snapshot_async (EphySnapshotService *service,
+                                          WebKitWebView *webview,
+                                          const char *url,
+                                          const time_t mtime,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data)
+{
+  SnapshotOp *op;
+
+  g_return_if_fail (EPHY_IS_SNAPSHOT_SERVICE (service));
+  g_return_if_fail (url != NULL);
+
+  op = g_slice_alloc0 (sizeof(SnapshotOp));
+  op->url = g_strdup (url);
+  op->mtime = mtime;
+  op->webview = webview ? g_object_ref (webview) : NULL;
+  op->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+  op->callback = callback;
+  op->user_data = user_data;
+
+  /* Query for the snapshot from the cache using the IO scheduler, so
+     that there is no UI blocking during the cache query. */
+  g_io_scheduler_push_job (io_scheduler_try_cache_query,
+                           op, NULL, G_PRIORITY_LOW, NULL);
+}
+
+/**
+ * ephy_snapshot_service_get_snapshot_finish:
+ * @service: a #EphySnapshotService
+ * @result: a #GAsyncResult
+ * @error: a location to store a #GError or %NULL
+ *
+ * Finishes the retrieval of a snapshot. Call from the
+ * #GAsyncReadyCallback passed to
+ * ephy_snapshot_service_get_snapshot_async().
+ *
+ * Returns: (transfer full): the snapshot.
+ **/
+GdkPixbuf *
+ephy_snapshot_service_get_snapshot_finish (EphySnapshotService *service,
+                                           GAsyncResult *result,
+                                           GError **error)
+{
+  GSimpleAsyncResult *simple;
+  SnapshotOp *op;
+
+  g_return_val_if_fail (g_simple_async_result_is_valid (result,
+                                                        G_OBJECT (service),
+                                                        ephy_snapshot_service_complete_async),
+                        NULL);
+
+  simple  = (GSimpleAsyncResult *) result;
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    return NULL;
+
+  op = g_simple_async_result_get_op_res_gpointer (simple);
+
+  return op->snapshot ? g_object_ref (op->snapshot) : NULL;
+}
+
+void
+ephy_snapshot_service_save_snapshot_async (EphySnapshotService *service,
+                                           GdkPixbuf *snapshot,
+                                           const char *url,
+                                           time_t mtime,
+                                           GCancellable *cancellable,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data)
+{
+  SnapshotOp *op;
+
+  g_return_if_fail (EPHY_IS_SNAPSHOT_SERVICE (service));
+  g_return_if_fail (GDK_IS_PIXBUF (snapshot));
+  g_return_if_fail (url != NULL);
+
+  op = g_slice_alloc0 (sizeof(SnapshotOp));
+  op->snapshot = g_object_ref (snapshot);
+  op->url = g_strdup (url);
+  op->mtime = mtime;
+  op->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+  op->callback = callback;
+  op->user_data = user_data;
+
+  g_io_scheduler_push_job (io_scheduler_save_thumbnail,
+                           op, NULL, G_PRIORITY_LOW, NULL);
+}
+
+GdkPixbuf *
+ephy_snapshot_service_crop_snapshot (cairo_surface_t *surface)
+{
+  GdkPixbuf *snapshot, *scaled;
+  int orig_width, orig_height;
+  float orig_aspect_ratio, dest_aspect_ratio;
+  int x_offset, new_width = 0, new_height;
+
+  orig_width = cairo_image_surface_get_width (surface);
+  orig_height = cairo_image_surface_get_height (surface);
+
+  if (orig_width < EPHY_THUMBNAIL_WIDTH ||
+      orig_height < EPHY_THUMBNAIL_HEIGHT) {
+    snapshot = gdk_pixbuf_get_from_surface (surface,
+                                            0, 0,
+                                            orig_width, orig_height);
+    scaled = gdk_pixbuf_scale_simple (snapshot,
+                                      EPHY_THUMBNAIL_WIDTH,
+                                      EPHY_THUMBNAIL_HEIGHT,
+                                      GDK_INTERP_TILES);
+  } else {
+    orig_aspect_ratio = orig_width / (float)orig_height;
+    dest_aspect_ratio = EPHY_THUMBNAIL_WIDTH / (float)EPHY_THUMBNAIL_HEIGHT;
+
+    if (orig_aspect_ratio > dest_aspect_ratio) {
+      /* Wider than taller, crop the sides. */
+      new_width = orig_height * dest_aspect_ratio;
+      new_height = orig_height;
+      x_offset = (orig_width - new_width) / 2;
+    } else {
+      /* Crop the bottom otherwise. */
+      new_width = orig_width;
+      new_height = orig_width / (float)dest_aspect_ratio;
+      x_offset = 0;
+    }
+
+    snapshot = gdk_pixbuf_get_from_surface (surface, x_offset, 0, new_width, new_height);
+    scaled = gnome_desktop_thumbnail_scale_down_pixbuf (snapshot,
+                                                        EPHY_THUMBNAIL_WIDTH,
+                                                        EPHY_THUMBNAIL_HEIGHT);
+  }
+
+  g_object_unref (snapshot);
+
+  return scaled;
+}
diff --git a/lib/ephy-snapshot-service.h b/lib/ephy-snapshot-service.h
new file mode 100644
index 0000000..4de9745
--- /dev/null
+++ b/lib/ephy-snapshot-service.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright  2012 Igalia S.L.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef _EPHY_SNAPSHOT_SERVICE_H
+#define _EPHY_SNAPSHOT_SERVICE_H
+
+#include <gtk/gtk.h>
+#ifdef HAVE_WEBKIT2
+#include <webkit2/webkit2.h>
+#else
+#include <webkit/webkit.h>
+#endif
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SNAPSHOT_SERVICE            (ephy_snapshot_service_get_type())
+#define EPHY_SNAPSHOT_SERVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EPHY_TYPE_SNAPSHOT_SERVICE, EphySnapshotService))
+#define EPHY_SNAPSHOT_SERVICE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EPHY_TYPE_SNAPSHOT_SERVICE, EphySnapshotServiceClass))
+#define EPHY_IS_SNAPSHOT_SERVICE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EPHY_TYPE_SNAPSHOT_SERVICE))
+#define EPHY_IS_SNAPSHOT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EPHY_TYPE_SNAPSHOT_SERVICE))
+#define EPHY_SNAPSHOT_SERVICE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EPHY_TYPE_SNAPSHOT_SERVICE, EphySnapshotServiceClass))
+
+typedef struct _EphySnapshotService        EphySnapshotService;
+typedef struct _EphySnapshotServiceClass   EphySnapshotServiceClass;
+typedef struct _EphySnapshotServicePrivate EphySnapshotServicePrivate;
+
+struct _EphySnapshotService
+{
+  GObject parent;
+
+  /*< private >*/
+  EphySnapshotServicePrivate *priv;
+};
+
+struct _EphySnapshotServiceClass
+{
+  GObjectClass parent_class;
+};
+
+/* Values taken from the Web mockups. */
+#define EPHY_THUMBNAIL_WIDTH 172
+#define EPHY_THUMBNAIL_HEIGHT 172
+
+typedef void (* EphySnapshotServiceCallback) (GdkPixbuf *snapshot,
+                                              gpointer user_data);
+
+GType                ephy_snapshot_service_get_type (void) G_GNUC_CONST;
+
+EphySnapshotService *ephy_snapshot_service_get_default (void);
+
+void ephy_snapshot_service_get_snapshot_async (EphySnapshotService *service,
+                                               WebKitWebView *webview,
+                                               const char *url,
+                                               const time_t mtime,
+                                               GCancellable *cancellable,
+                                               GAsyncReadyCallback callback,
+                                               gpointer user_data);
+
+GdkPixbuf * ephy_snapshot_service_get_snapshot_finish (EphySnapshotService *service,
+                                                       GAsyncResult *result,
+                                                       GError **error);
+
+void ephy_snapshot_service_save_snapshot_async (EphySnapshotService *service,
+                                                GdkPixbuf *snapshot,
+                                                const char *url,
+                                                time_t mtime,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+
+GdkPixbuf*  ephy_snapshot_service_crop_snapshot (cairo_surface_t *surface);
+
+G_END_DECLS
+
+#endif /* _EPHY_SNAPSHOT_SERVICE_H */



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