[Xfce4-commits] <tumbler:master> Group scheduler for background thumbnailing
Jannis Pohlmann
noreply at xfce.org
Tue Oct 6 20:54:02 CEST 2009
Updating branch refs/heads/master
to bbb9f66d47f0fbb2028439a8eba32ffd794a9dd4 (commit)
from b2aa6175031905006c86e3ff749728ac811e1553 (commit)
commit bbb9f66d47f0fbb2028439a8eba32ffd794a9dd4
Author: Philip Van Hoof <philip at codeminded.be>
Date: Tue Oct 6 16:34:14 2009 +0200
Group scheduler for background thumbnailing
Signed-off-by: Jannis Pohlmann <jannis at xfce.org>
tumblerd/Makefile.am | 2 +
tumblerd/tumbler-group-scheduler.c | 571 ++++++++++++++++++++++++++++++++++++
tumblerd/tumbler-group-scheduler.h | 46 +++
3 files changed, 619 insertions(+), 0 deletions(-)
diff --git a/tumblerd/Makefile.am b/tumblerd/Makefile.am
index 1813994..2991e54 100644
--- a/tumblerd/Makefile.am
+++ b/tumblerd/Makefile.am
@@ -43,6 +43,8 @@ tumblerd_SOURCES = \
tumbler-specialized-thumbnailer.h \
tumbler-lifo-scheduler.c \
tumbler-lifo-scheduler.h \
+ tumbler-group-scheduler.c \
+ tumbler-group-scheduler.h \
tumbler-utils.h \
$(tumblerd_built_sources)
diff --git a/tumblerd/tumbler-group-scheduler.c b/tumblerd/tumbler-group-scheduler.c
new file mode 100644
index 0000000..4d0406e
--- /dev/null
+++ b/tumblerd/tumbler-group-scheduler.c
@@ -0,0 +1,571 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (C) 2009, Nokia
+ *
+ * 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 of
+ * the License, 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.
+ *
+ * Author: Philip Van Hoof <philip at codeminded.be>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <float.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#include <tumbler/tumbler.h>
+
+#include <tumblerd/tumbler-group-scheduler.h>
+#include <tumblerd/tumbler-scheduler.h>
+
+
+
+/* Property identifiers */
+enum
+{
+ PROP_0,
+ PROP_KIND,
+};
+
+static void tumbler_group_scheduler_iface_init (TumblerSchedulerIface *iface);
+static void tumbler_group_scheduler_finalize (GObject *object);
+static void tumbler_group_scheduler_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void tumbler_group_scheduler_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void tumbler_group_scheduler_push (TumblerScheduler *scheduler,
+ TumblerSchedulerRequest *request);
+static void tumbler_group_scheduler_unqueue (TumblerScheduler *scheduler,
+ guint handle);
+static void tumbler_group_scheduler_finish_request (TumblerGroupScheduler *scheduler,
+ TumblerSchedulerRequest *request);
+static void tumbler_group_scheduler_unqueue_request (TumblerSchedulerRequest *request,
+ gpointer user_data);
+static void tumbler_group_scheduler_thread (gpointer data,
+ gpointer user_data);
+static void tumbler_group_scheduler_thumbnailer_error (TumblerThumbnailer *thumbnailer,
+ const gchar *failed_uri,
+ gint error_code,
+ const gchar *message,
+ GPtrArray *errors);
+static void tumbler_group_scheduler_thumbnailer_ready (TumblerThumbnailer *thumbnailer,
+ const gchar *uri,
+ GPtrArray *readies);
+
+
+
+struct _TumblerGroupSchedulerClass
+{
+ GObjectClass __parent__;
+};
+
+struct _TumblerGroupScheduler
+{
+ GObject __parent__;
+
+ GThreadPool *pool;
+ GMutex *mutex;
+ GList *requests;
+ guint group;
+ gchar *kind;
+};
+
+
+
+G_LOCK_DEFINE (group_access_lock);
+
+
+
+G_DEFINE_TYPE_WITH_CODE (TumblerGroupScheduler,
+ tumbler_group_scheduler,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TUMBLER_TYPE_SCHEDULER,
+ tumbler_group_scheduler_iface_init));
+
+
+
+static void
+tumbler_group_scheduler_class_init (TumblerGroupSchedulerClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = tumbler_group_scheduler_finalize;
+ gobject_class->get_property = tumbler_group_scheduler_get_property;
+ gobject_class->set_property = tumbler_group_scheduler_set_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_KIND,
+ g_param_spec_string ("kind",
+ "kind",
+ "kind",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE));
+}
+
+static const gchar*
+tumbler_group_scheduler_get_kind (TumblerScheduler *scheduler)
+{
+ TumblerGroupScheduler *s = TUMBLER_GROUP_SCHEDULER (scheduler);
+ return s->kind;
+}
+
+static void
+tumbler_group_scheduler_iface_init (TumblerSchedulerIface *iface)
+{
+ iface->push = tumbler_group_scheduler_push;
+ iface->unqueue = tumbler_group_scheduler_unqueue;
+ iface->get_kind = tumbler_group_scheduler_get_kind;
+}
+
+
+
+static void
+tumbler_group_scheduler_init (TumblerGroupScheduler *scheduler)
+{
+ scheduler->mutex = g_mutex_new ();
+ scheduler->requests = NULL;
+
+ /* allocate a pool with max. 1 threads for requests */
+ scheduler->pool = g_thread_pool_new (tumbler_group_scheduler_thread,
+ scheduler, 1, TRUE, NULL);
+
+}
+
+
+
+static void
+tumbler_group_scheduler_finalize (GObject *object)
+{
+ TumblerGroupScheduler *scheduler = TUMBLER_GROUP_SCHEDULER (object);
+
+ /* destroy both thread pools */
+ g_thread_pool_free (scheduler->pool, TRUE, TRUE);
+
+ /* release all pending requests */
+ g_list_foreach (scheduler->requests, (GFunc) tumbler_scheduler_request_free, NULL);
+
+ /* destroy the request list */
+ g_list_free (scheduler->requests);
+
+ /* destroy the mutex */
+ g_mutex_free (scheduler->mutex);
+
+ (*G_OBJECT_CLASS (tumbler_group_scheduler_parent_class)->finalize) (object);
+}
+
+
+
+static void
+tumbler_group_scheduler_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TumblerGroupScheduler *scheduler = TUMBLER_GROUP_SCHEDULER (object);
+
+ switch (prop_id)
+ {
+ case PROP_KIND:
+ g_value_set_string (value, scheduler->kind);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+
+static void
+tumbler_group_scheduler_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TumblerGroupScheduler *scheduler = TUMBLER_GROUP_SCHEDULER (object);
+
+ switch (prop_id)
+ {
+ case PROP_KIND:
+ scheduler->kind = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+
+static void
+tumbler_group_scheduler_push (TumblerScheduler *scheduler,
+ TumblerSchedulerRequest *request)
+{
+ TumblerGroupScheduler *group_scheduler =
+ TUMBLER_GROUP_SCHEDULER (scheduler);
+
+ g_return_if_fail (TUMBLER_IS_GROUP_SCHEDULER (scheduler));
+ g_return_if_fail (request != NULL);
+
+ g_mutex_lock (group_scheduler->mutex);
+
+ /* gain ownership over the requests (sets request->scheduler) */
+ tumbler_scheduler_take_request (scheduler, request);
+
+ /* prepend the request to the request list */
+ group_scheduler->requests =
+ g_list_prepend (group_scheduler->requests, request);
+
+ /* enqueue the request in the pool */
+ g_thread_pool_push (group_scheduler->pool, request, NULL);
+
+ g_mutex_unlock (group_scheduler->mutex);
+}
+
+
+
+static void
+tumbler_group_scheduler_unqueue (TumblerScheduler *scheduler,
+ guint handle)
+{
+ TumblerGroupScheduler *group_scheduler =
+ TUMBLER_GROUP_SCHEDULER (scheduler);
+
+ g_return_if_fail (TUMBLER_IS_GROUP_SCHEDULER (scheduler));
+ g_return_if_fail (handle != 0);
+
+ g_mutex_lock (group_scheduler->mutex);
+
+ g_list_foreach (group_scheduler->requests,
+ (GFunc) tumbler_group_scheduler_unqueue_request,
+ GUINT_TO_POINTER (handle));
+
+ g_mutex_unlock (group_scheduler->mutex);
+}
+
+
+
+static void
+tumbler_group_scheduler_finish_request (TumblerGroupScheduler *scheduler,
+ TumblerSchedulerRequest *request)
+{
+ g_return_if_fail (TUMBLER_IS_GROUP_SCHEDULER (scheduler));
+ g_return_if_fail (request != NULL);
+
+ g_signal_emit_by_name (scheduler, "finished", request->handle);
+
+ scheduler->requests = g_list_remove (scheduler->requests,
+ request);
+
+ tumbler_scheduler_request_free (request);
+}
+
+
+
+static void
+tumbler_group_scheduler_unqueue_request (TumblerSchedulerRequest *request,
+ gpointer user_data)
+{
+ guint handle = GPOINTER_TO_UINT (user_data);
+
+ g_return_if_fail (request != NULL);
+ g_return_if_fail (handle != 0);
+
+ if (request->handle == handle)
+ request->unqueued = TRUE;
+}
+
+typedef struct {
+ guint error_code;
+ gchar *message, *failed_uri;
+} GroupError;
+
+static void
+group_error_free (gpointer data, gpointer user_data)
+{
+ GroupError *grp_error = data;
+
+ g_free (grp_error->message);
+ g_free (grp_error->failed_uri);
+ g_free (grp_error);
+}
+
+static void
+tumbler_group_scheduler_thread (gpointer data,
+ gpointer user_data)
+{
+ TumblerGroupScheduler *scheduler = user_data;
+ TumblerSchedulerRequest *request = data;
+ TumblerFileInfo *info;
+ const gchar **uris;
+ gboolean outdated;
+ gboolean uri_needs_update;
+ guint64 mtime;
+ GError *error = NULL;
+ GList *cached_uris = NULL;
+ GList *missing_uris = NULL;
+ GList *thumbnails;
+ GList *lp;
+ gint n;
+ GPtrArray *errors, *readies;
+
+ g_return_if_fail (TUMBLER_IS_GROUP_SCHEDULER (scheduler));
+ g_return_if_fail (request != NULL);
+
+ /* notify others that we're starting to process this request */
+ g_signal_emit_by_name (request->scheduler, "started", request->handle);
+
+
+ /* finish the request if it was unqueued */
+ if (request->unqueued)
+ {
+ g_mutex_lock (scheduler->mutex);
+ tumbler_group_scheduler_finish_request (scheduler, request);
+ g_mutex_unlock (scheduler->mutex);
+ return;
+ }
+
+ /* process URI by URI */
+ for (n = 0; request->uris[n] != NULL; ++n)
+ {
+ /* finish the request if it was unqueued */
+ if (request->unqueued)
+ {
+ g_mutex_lock (scheduler->mutex);
+ tumbler_group_scheduler_finish_request (scheduler, request);
+ g_mutex_unlock (scheduler->mutex);
+ return;
+ }
+
+ info = tumbler_file_info_new (request->uris[n]);
+ uri_needs_update = FALSE;
+
+ G_LOCK (group_access_lock);
+
+ if (tumbler_file_info_load (info, NULL, &error))
+ {
+ if (request->thumbnailers[n] != NULL)
+ {
+ mtime = tumbler_file_info_get_mtime (info);
+
+ thumbnails = tumbler_file_info_get_thumbnails (info);
+
+ for (lp = thumbnails;
+ error == NULL && lp != NULL;
+ lp = lp->next)
+ {
+ if (tumbler_thumbnail_load (lp->data, NULL, &error))
+ {
+ outdated = tumbler_thumbnail_needs_update (lp->data,
+ request->uris[n],
+ mtime);
+
+ uri_needs_update = uri_needs_update || outdated;
+ }
+ }
+ }
+ else
+ {
+ g_set_error (&error, TUMBLER_ERROR, TUMBLER_ERROR_NO_THUMBNAILER,
+ _("No thumbnailer available for \"%s\""),
+ request->uris[n]);
+ }
+ }
+
+ g_object_unref (info);
+
+ G_UNLOCK (group_access_lock);
+
+ if (error == NULL)
+ {
+ if (uri_needs_update)
+ missing_uris = g_list_prepend (missing_uris, GINT_TO_POINTER (n));
+ else
+ cached_uris = g_list_prepend (cached_uris, request->uris[n]);
+ }
+ else
+ {
+ tumbler_scheduler_emit_uri_error (TUMBLER_SCHEDULER (scheduler), request,
+ request->uris[n], error);
+
+ g_clear_error (&error);
+ }
+ }
+
+ /* check if we have any cached files */
+ if (cached_uris != NULL)
+ {
+ uris = g_new0 (const gchar *, g_list_length (cached_uris) + 1);
+ for (n = 0, lp = g_list_last (cached_uris); lp != NULL; lp = lp->prev, ++n)
+ uris[n] = lp->data;
+ uris[n] = NULL;
+
+ /* notify others that the cached thumbnails are ready */
+ g_signal_emit_by_name (scheduler, "ready", uris);
+
+ /* free string array and cached list */
+ g_list_free (cached_uris);
+ g_free (uris);
+ }
+
+ errors = g_ptr_array_new ();
+ readies = g_ptr_array_new ();
+
+ /* iterate over invalid/missing URI list */
+ for (lp = g_list_last (missing_uris); lp != NULL; lp = lp->prev)
+ {
+ n = GPOINTER_TO_INT (lp->data);
+
+ /* finish the request if it was unqueued */
+ if (request->unqueued)
+ {
+ g_mutex_lock (scheduler->mutex);
+ tumbler_group_scheduler_finish_request (scheduler, request);
+ g_mutex_unlock (scheduler->mutex);
+ return;
+ }
+
+ /* connect to the error signal of the thumbnailer */
+ g_signal_connect (request->thumbnailers[n], "error",
+ G_CALLBACK (tumbler_group_scheduler_thumbnailer_error),
+ errors);
+
+ /* connect to the ready signal of the thumbnailer */
+ g_signal_connect (request->thumbnailers[n], "ready",
+ G_CALLBACK (tumbler_group_scheduler_thumbnailer_ready),
+ readies);
+
+ /* tell the thumbnailer to generate the thumbnail */
+ tumbler_thumbnailer_create (request->thumbnailers[n], request->uris[n],
+ request->mime_hints[n]);
+
+ /* disconnect from all signals when we're finished */
+ g_signal_handlers_disconnect_matched (request->thumbnailers[n],
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, request);
+ }
+
+ g_mutex_lock (scheduler->mutex);
+
+ /* We put all the errors and readies together, to avoid DBus traffic */
+
+ if (errors->len > 0) {
+ guint i, error_code;
+ GString *message = g_string_new ("");
+ GStrv failed_uris = (GStrv) g_malloc0 (sizeof (gchar *) * errors->len);
+
+ for (i = 0; i < errors->len; i++) {
+ GroupError *grp_error = g_ptr_array_index (errors, i);
+
+ if (i != 0) {
+ if (grp_error->message)
+ g_string_append_c (message, ' ');
+ } else
+ error_code = grp_error->error_code;
+
+ if (grp_error->message)
+ g_string_append (message, grp_error->message);
+
+ failed_uris[i] = g_ptr_array_index (errors, i);
+ }
+
+ /* forward the error signal */
+ g_signal_emit_by_name (request->scheduler, "error", request->handle,
+ failed_uris, error_code, message->str);
+
+ g_free (failed_uris);
+ g_string_free (message, TRUE);
+ }
+
+ g_ptr_array_foreach (errors, group_error_free, NULL);
+ g_ptr_array_free (errors, TRUE);
+
+ if (readies->len > 0) {
+ GStrv ready_uris = (GStrv) g_new0 (gchar*, readies->len);
+ guint i;
+
+ for (i = 0; i < readies->len; i++)
+ ready_uris[i] = g_ptr_array_index (readies, i);
+
+ g_signal_emit_by_name (request->scheduler, "ready", ready_uris);
+
+ g_free (ready_uris);
+ }
+
+ g_ptr_array_foreach (readies, (GFunc) g_free, NULL);
+ g_ptr_array_free (readies, TRUE);
+
+ /* notify others that we're finished processing the request */
+ tumbler_group_scheduler_finish_request (scheduler, request);
+
+ g_mutex_unlock (scheduler->mutex);
+}
+
+
+
+static void
+tumbler_group_scheduler_thumbnailer_error (TumblerThumbnailer *thumbnailer,
+ const gchar *failed_uri,
+ gint error_code,
+ const gchar *message,
+ GPtrArray *errors)
+{
+ GroupError *grp_error;
+
+ g_return_if_fail (TUMBLER_IS_THUMBNAILER (thumbnailer));
+ g_return_if_fail (failed_uri != NULL);
+
+ grp_error = g_new0 (GroupError, 1);
+
+ grp_error->error_code = error_code;
+ if (message) {
+ grp_error->message = g_strdup (message);
+ }
+ grp_error->failed_uri = g_strdup (failed_uri);
+
+ g_ptr_array_add (errors, grp_error);
+}
+
+
+
+static void
+tumbler_group_scheduler_thumbnailer_ready (TumblerThumbnailer *thumbnailer,
+ const gchar *uri,
+ GPtrArray *readies)
+{
+ g_return_if_fail (TUMBLER_IS_THUMBNAILER (thumbnailer));
+ g_return_if_fail (uri != NULL);
+
+ g_ptr_array_add (readies, g_strdup (uri));
+}
+
+
+
+TumblerScheduler *
+tumbler_group_scheduler_new (const gchar *kind)
+{
+ return g_object_new (TUMBLER_TYPE_GROUP_SCHEDULER, "kind", kind, NULL);
+}
diff --git a/tumblerd/tumbler-group-scheduler.h b/tumblerd/tumbler-group-scheduler.h
new file mode 100644
index 0000000..e45efab
--- /dev/null
+++ b/tumblerd/tumbler-group-scheduler.h
@@ -0,0 +1,46 @@
+/* vi:set et ai sw=2 sts=2 ts=2: */
+/*-
+ * Copyright (C) 2009, Nokia
+ *
+ * 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 of
+ * the License, 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.
+ *
+ * Author: Philip Van Hoof <philip at codeminded.be>
+ */
+
+#ifndef __TUMBLER_GROUP_SCHEDULER_H__
+#define __TUMBLER_GROUP_SCHEDULER_H__
+
+#include <tumblerd/tumbler-scheduler.h>
+
+G_BEGIN_DECLS;
+
+#define TUMBLER_TYPE_GROUP_SCHEDULER (tumbler_group_scheduler_get_type ())
+#define TUMBLER_GROUP_SCHEDULER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TUMBLER_TYPE_GROUP_SCHEDULER, TumblerGroupScheduler))
+#define TUMBLER_GROUP_SCHEDULER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TUMBLER_TYPE_GROUP_SCHEDULER, TumblerGroupSchedulerClass))
+#define TUMBLER_IS_GROUP_SCHEDULER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TUMBLER_TYPE_GROUP_SCHEDULER))
+#define TUMBLER_IS_GROUP_SCHEDULER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TUMBLER_TYPE_GROUP_SCHEDULER)
+#define TUMBLER_GROUP_SCHEDULER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TUMBLER_TYPE_GROUP_SCHEDULER, TumblerGroupSchedulerClass))
+
+typedef struct _TumblerGroupSchedulerClass TumblerGroupSchedulerClass;
+typedef struct _TumblerGroupScheduler TumblerGroupScheduler;
+
+GType tumbler_group_scheduler_get_type (void) G_GNUC_CONST;
+
+TumblerScheduler *tumbler_group_scheduler_new (const gchar *kind) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS;
+
+#endif /* !__TUMBLER_GROUP_SCHEDULER_H__ */
More information about the Xfce4-commits
mailing list