[epiphany/overview: 2/26] Add a service for snapshotting webpages
- From: Claudio Saavedra <csaavedra src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/overview: 2/26] Add a service for snapshotting webpages
- Date: Wed, 27 Jun 2012 15:49:38 +0000 (UTC)
commit 0a8c9bc2ed551c647ce64b3d052bb67453f5088c
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 | 2 +
lib/Makefile.am | 2 +
lib/ephy-snapshot-service.c | 400 +++++++++++++++++++++++++++++++++++++++++++
lib/ephy-snapshot-service.h | 73 ++++++++
4 files changed, 477 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 5a587a8..965e5cc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,6 +86,7 @@ LIBXML_REQUIRED=2.6.12
LIBXSLT_REQUIRED=1.1.7
WEBKIT_GTK_REQUIRED=1.7.92
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 +127,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 7e5ba55..b2c14d5 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..a14bb75
--- /dev/null
+++ b/lib/ephy-snapshot-service.c
@@ -0,0 +1,400 @@
+/* -*- 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))
+
+/* values taken approx from the Web mockups. */
+#define EPHY_SNAPSHOT_SIZE 1000
+#define EPHY_THUMBNAIL_SIZE 175
+
+struct _EphySnapshotServicePrivate
+{
+ GThread *service_thread;
+ GAsyncQueue *queue;
+ gboolean active;
+ GnomeDesktopThumbnailFactory *factory;
+};
+
+G_DEFINE_TYPE (EphySnapshotService, ephy_snapshot_service, G_TYPE_OBJECT)
+
+typedef struct {
+ char *url;
+ time_t mtime;
+ GtkWidget *window;
+ GtkWidget *webview;
+ GdkPixbuf *snapshot;
+ GCancellable *cancellable;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+} SnapshotOp;
+
+static void
+snapshot_op_free (SnapshotOp *op)
+{
+ g_free (op->url);
+
+ if (op->window)
+ gtk_widget_destroy (op->window);
+ if (op->cancellable)
+ g_object_unref (op->cancellable);
+
+ g_slice_free (SnapshotOp, op);
+}
+
+static gboolean ephy_snapshot_service_complete_async (SnapshotOp *op);
+
+static gboolean process_snapshot_operation (SnapshotOp *op);
+
+/* Snapshot service thread. Jobs arrive here and are processed
+ one by one, not to choke the network. */
+
+static gpointer
+ephy_snapshot_service_thread (EphySnapshotService *self)
+{
+ EphySnapshotServicePrivate *priv = self->priv;
+ SnapshotOp *op;
+
+ g_assert (priv->service_thread == g_thread_self ());
+
+ do {
+ /* Block until there is a request. */
+ op = g_async_queue_pop (priv->queue);
+
+ /* This will be unlocked when the snapshot is finally
+ retrieved. We do this to have only one snapshotting job going on
+ in the mainloop and avoid choking the network while downloading
+ the page contents. */
+ g_async_queue_lock (priv->queue);
+
+ /* If this operation was cancelled, unlock the queue, free the
+ operation resources and continue. */
+ if (g_cancellable_is_cancelled (op->cancellable)) {
+ g_idle_add ((GSourceFunc)ephy_snapshot_service_complete_async, op);
+ g_async_queue_unlock (priv->queue);
+ continue;
+ }
+
+ /* Ask the mainloop to process the request and obtain the
+ * snapshot. */
+ g_idle_add ((GSourceFunc)process_snapshot_operation, op);
+
+ } while (TRUE);
+
+ return NULL;
+}
+
+/* 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);
+ self->priv->queue = g_async_queue_new ();
+ self->priv->service_thread = g_thread_new ("EphySnapshotService", (GThreadFunc) ephy_snapshot_service_thread, self);
+}
+
+/* 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 ();
+
+ op = (SnapshotOp*) user_data;
+
+ if (!g_cancellable_is_cancelled (cancellable))
+ op->snapshot = io_scheduler_get_cached_snapshot (ephy_snapshot_service_get_default (),
+ op->url, op->mtime);
+
+ if (op->snapshot || g_cancellable_is_cancelled (cancellable))
+ g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)ephy_snapshot_service_complete_async, op, NULL);
+ else
+ g_async_queue_push (service->priv->queue, op);
+
+ 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 (job, (GSourceFunc)ephy_snapshot_service_complete_async, op, NULL);
+ /* We can unlock the mutex, next job can be processed now. */
+ g_async_queue_unlock (service->priv->queue);
+
+ return FALSE;
+}
+
+/* Methods that run in the mainloop. */
+
+static gboolean
+webview_retrieve_snapshot (SnapshotOp *op)
+{
+ GdkPixbuf *snapshot, *scaled;
+ snapshot = gtk_offscreen_window_get_pixbuf (GTK_OFFSCREEN_WINDOW (op->window));
+ scaled = gnome_desktop_thumbnail_scale_down_pixbuf (snapshot, EPHY_THUMBNAIL_SIZE, EPHY_THUMBNAIL_SIZE);
+ g_object_unref (snapshot);
+
+ op->snapshot = scaled;
+
+ 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)
+{
+ if (load_event == 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_full (G_PRIORITY_LOW, (GSourceFunc) webview_retrieve_snapshot, op, NULL);
+ /* 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);
+ }
+}
+#else
+static void
+webview_load_status_changed_cb (WebKitWebView *webview,
+ GParamSpec *pspec,
+ SnapshotOp *op)
+{
+ WebKitLoadStatus status;
+
+ status = webkit_web_view_get_load_status (webview);
+
+ if (status == 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_full (G_PRIORITY_LOW, (GSourceFunc) webview_retrieve_snapshot, op, NULL);
+ /* 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_status_changed_cb, op);
+ }
+}
+#endif
+
+static gboolean
+process_snapshot_operation (SnapshotOp *op)
+{
+ EphySnapshotService *service;
+#ifndef HAVE_WEBKIT2
+ GtkWidget *sw;
+#endif
+
+ /* If the request was cancelled, complete it without retrieving the
+ snapshot and unlock the queue. */
+ if (g_cancellable_is_cancelled (op->cancellable)) {
+ service = ephy_snapshot_service_get_default ();
+ ephy_snapshot_service_complete_async (op);
+ g_async_queue_unlock (service->priv->queue);
+ return FALSE;
+ }
+
+ op->window = gtk_offscreen_window_new ();
+ op->webview = webkit_web_view_new ();
+
+#ifdef HAVE_WEBKIT2
+ g_signal_connect (op->webview, "load-changed",
+ G_CALLBACK (webview_load_changed_cb), op);
+ gtk_container_add (GTK_CONTAINER (op->window), op->webview);
+#else
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+
+ g_signal_connect (op->webview, "notify::load-status",
+ G_CALLBACK (webview_load_status_changed_cb),
+ op);
+ gtk_container_add (GTK_CONTAINER (op->window), sw);
+ gtk_container_add (GTK_CONTAINER (sw), op->webview);
+#endif
+ gtk_widget_set_size_request (op->window, EPHY_SNAPSHOT_SIZE, EPHY_SNAPSHOT_SIZE);
+ gtk_widget_show_all (op->window);
+ webkit_web_view_load_uri (WEBKIT_WEB_VIEW (op->webview), op->url);
+
+ return FALSE;
+}
+
+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;
+}
+
+/**
+ * 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,
+ 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->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, cancellable);
+}
+
+/**
+ * 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 g_object_ref (op->snapshot);
+}
diff --git a/lib/ephy-snapshot-service.h b/lib/ephy-snapshot-service.h
new file mode 100644
index 0000000..5ed1145
--- /dev/null
+++ b/lib/ephy-snapshot-service.h
@@ -0,0 +1,73 @@
+/* -*- 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>
+
+#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;
+};
+
+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,
+ 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);
+G_END_DECLS
+
+#endif /* _EPHY_SNAPSHOT_SERVICE_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]