summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-thumbnails.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcaja-private/caja-thumbnails.c')
-rw-r--r--libcaja-private/caja-thumbnails.c1084
1 files changed, 1084 insertions, 0 deletions
diff --git a/libcaja-private/caja-thumbnails.c b/libcaja-private/caja-thumbnails.c
new file mode 100644
index 00000000..b656c550
--- /dev/null
+++ b/libcaja-private/caja-thumbnails.c
@@ -0,0 +1,1084 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-thumbnails.h: Thumbnail code for icon factory.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+ Copyright (C) 2002, 2003 Red Hat, Inc.
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Andy Hertzfeld <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-thumbnails.h"
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include "caja-directory-notify.h"
+#include "caja-global-preferences.h"
+#include "caja-file-utilities.h"
+#include <math.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-string.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gtk/gtk.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <libmateui/mate-desktop-thumbnail.h>
+
+#include "caja-file-private.h"
+
+/* turn this on to see messages about thumbnail creation */
+#if 0
+#define DEBUG_THUMBNAILS
+#endif
+
+/* Should never be a reasonable actual mtime */
+#define INVALID_MTIME 0
+
+/* Cool-off period between last file modification time and thumbnail creation */
+#define THUMBNAIL_CREATION_DELAY_SECS 3
+
+static gpointer thumbnail_thread_start (gpointer data);
+
+/* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */
+
+typedef struct
+{
+ char *image_uri;
+ char *mime_type;
+ time_t original_file_mtime;
+} CajaThumbnailInfo;
+
+struct CajaThumbnailAsyncLoadHandle
+{
+ GCancellable *cancellable;
+ char *file_path;
+ guint base_size;
+ guint nominal_size;
+ gboolean force_nominal;
+ CajaThumbnailAsyncLoadFunc load_func;
+ gpointer load_func_user_data;
+};
+
+
+/*
+ * Thumbnail thread state.
+ */
+
+/* The id of the idle handler used to start the thumbnail thread, or 0 if no
+ idle handler is currently registered. */
+static guint thumbnail_thread_starter_id = 0;
+
+/* Our mutex used when accessing data shared between the main thread and the
+ thumbnail thread, i.e. the thumbnail_thread_is_running flag and the
+ thumbnails_to_make list. */
+static pthread_mutex_t thumbnails_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* A flag to indicate whether a thumbnail thread is running, so we don't
+ start more than one. Lock thumbnails_mutex when accessing this. */
+static volatile gboolean thumbnail_thread_is_running = FALSE;
+
+/* Added in glib 2.14 */
+#ifndef G_QUEUE_INIT
+#define G_QUEUE_INIT { NULL, NULL, 0 }
+#endif
+
+/* The list of CajaThumbnailInfo structs containing information about the
+ thumbnails we are making. Lock thumbnails_mutex when accessing this. */
+static volatile GQueue thumbnails_to_make = G_QUEUE_INIT;
+
+/* Quickly check if uri is in thumbnails_to_make list */
+static GHashTable *thumbnails_to_make_hash = NULL;
+
+/* The currently thumbnailed icon. it also exists in the thumbnails_to_make list
+ * to avoid adding it again. Lock thumbnails_mutex when accessing this. */
+static CajaThumbnailInfo *currently_thumbnailing = NULL;
+
+static MateDesktopThumbnailFactory *thumbnail_factory = NULL;
+
+static int thumbnail_icon_size = 0;
+
+static gboolean
+get_file_mtime (const char *file_uri, time_t* mtime)
+{
+ GFile *file;
+ GFileInfo *info;
+ gboolean ret;
+
+ ret = FALSE;
+ *mtime = INVALID_MTIME;
+
+ file = g_file_new_for_uri (file_uri);
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+ if (info)
+ {
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ {
+ *mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ ret = TRUE;
+ }
+
+ g_object_unref (info);
+ }
+ g_object_unref (file);
+
+ return ret;
+}
+
+static void
+free_thumbnail_info (CajaThumbnailInfo *info)
+{
+ g_free (info->image_uri);
+ g_free (info->mime_type);
+ g_free (info);
+}
+
+static MateDesktopThumbnailFactory *
+get_thumbnail_factory (void)
+{
+ static MateDesktopThumbnailFactory *thumbnail_factory = NULL;
+
+ if (thumbnail_factory == NULL)
+ {
+ thumbnail_factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ }
+
+ return thumbnail_factory;
+}
+
+
+/* This function is added as a very low priority idle function to start the
+ thread to create any needed thumbnails. It is added with a very low priority
+ so that it doesn't delay showing the directory in the icon/list views.
+ We want to show the files in the directory as quickly as possible. */
+static gboolean
+thumbnail_thread_starter_cb (gpointer data)
+{
+ pthread_attr_t thread_attributes;
+ pthread_t thumbnail_thread;
+
+ /* Don't do this in thread, since g_object_ref is not threadsafe */
+ if (thumbnail_factory == NULL)
+ {
+ thumbnail_factory = get_thumbnail_factory ();
+ }
+
+ /* We create the thread in the detached state, as we don't need/want
+ to join with it at any point. */
+ pthread_attr_init (&thread_attributes);
+ pthread_attr_setdetachstate (&thread_attributes,
+ PTHREAD_CREATE_DETACHED);
+#ifdef _POSIX_THREAD_ATTR_STACKSIZE
+ pthread_attr_setstacksize (&thread_attributes, 128*1024);
+#endif
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Creating thumbnails thread\n");
+#endif
+ /* We set a flag to indicate the thread is running, so we don't create
+ a new one. We don't need to lock a mutex here, as the thumbnail
+ thread isn't running yet. And we know we won't create the thread
+ twice, as we also check thumbnail_thread_starter_id before
+ scheduling this idle function. */
+ thumbnail_thread_is_running = TRUE;
+ pthread_create (&thumbnail_thread, &thread_attributes,
+ thumbnail_thread_start, NULL);
+
+ thumbnail_thread_starter_id = 0;
+
+ return FALSE;
+}
+
+void
+caja_update_thumbnail_file_copied (const char *source_file_uri,
+ const char *destination_file_uri)
+{
+ char *old_thumbnail_path;
+ time_t mtime;
+ GdkPixbuf *pixbuf;
+ MateDesktopThumbnailFactory *factory;
+
+ old_thumbnail_path = mate_desktop_thumbnail_path_for_uri (source_file_uri, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ if (old_thumbnail_path != NULL &&
+ g_file_test (old_thumbnail_path, G_FILE_TEST_EXISTS))
+ {
+ if (get_file_mtime (destination_file_uri, &mtime))
+ {
+ pixbuf = gdk_pixbuf_new_from_file (old_thumbnail_path, NULL);
+
+ if (pixbuf && mate_desktop_thumbnail_has_uri (pixbuf, source_file_uri))
+ {
+ factory = get_thumbnail_factory ();
+ mate_desktop_thumbnail_factory_save_thumbnail (factory,
+ pixbuf,
+ destination_file_uri,
+ mtime);
+ }
+
+ if (pixbuf)
+ {
+ g_object_unref (pixbuf);
+ }
+ }
+ }
+
+ g_free (old_thumbnail_path);
+}
+
+void
+caja_update_thumbnail_file_renamed (const char *source_file_uri,
+ const char *destination_file_uri)
+{
+ caja_update_thumbnail_file_copied (source_file_uri, destination_file_uri);
+ caja_remove_thumbnail_for_file (source_file_uri);
+}
+
+void
+caja_remove_thumbnail_for_file (const char *file_uri)
+{
+ char *thumbnail_path;
+
+ thumbnail_path = mate_desktop_thumbnail_path_for_uri (file_uri, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ if (thumbnail_path != NULL)
+ {
+ unlink (thumbnail_path);
+ }
+ g_free (thumbnail_path);
+}
+
+static GdkPixbuf *
+caja_get_thumbnail_frame (void)
+{
+ char *image_path;
+ static GdkPixbuf *thumbnail_frame = NULL;
+
+ if (thumbnail_frame == NULL)
+ {
+ image_path = caja_pixmap_file ("thumbnail_frame.png");
+ if (image_path != NULL)
+ {
+ thumbnail_frame = gdk_pixbuf_new_from_file (image_path, NULL);
+ }
+ g_free (image_path);
+ }
+
+ return thumbnail_frame;
+}
+
+
+void
+caja_thumbnail_frame_image (GdkPixbuf **pixbuf)
+{
+ GdkPixbuf *pixbuf_with_frame, *frame;
+ int left_offset, top_offset, right_offset, bottom_offset;
+
+ /* The pixbuf isn't already framed (i.e., it was not made by
+ * an old Caja), so we must embed it in a frame.
+ */
+
+ frame = caja_get_thumbnail_frame ();
+ if (frame == NULL)
+ {
+ return;
+ }
+
+ left_offset = CAJA_THUMBNAIL_FRAME_LEFT;
+ top_offset = CAJA_THUMBNAIL_FRAME_TOP;
+ right_offset = CAJA_THUMBNAIL_FRAME_RIGHT;
+ bottom_offset = CAJA_THUMBNAIL_FRAME_BOTTOM;
+
+ pixbuf_with_frame = eel_embed_image_in_frame
+ (*pixbuf, frame,
+ left_offset, top_offset, right_offset, bottom_offset);
+ g_object_unref (*pixbuf);
+
+ *pixbuf = pixbuf_with_frame;
+}
+
+GdkPixbuf *
+caja_thumbnail_unframe_image (GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *pixbuf_without_frame, *frame;
+ int left_offset, top_offset, right_offset, bottom_offset;
+ int w, h;
+
+ /* The pixbuf isn't already framed (i.e., it was not made by
+ * an old Caja), so we must embed it in a frame.
+ */
+
+ frame = caja_get_thumbnail_frame ();
+ if (frame == NULL)
+ {
+ return NULL;
+ }
+
+ left_offset = CAJA_THUMBNAIL_FRAME_LEFT;
+ top_offset = CAJA_THUMBNAIL_FRAME_TOP;
+ right_offset = CAJA_THUMBNAIL_FRAME_RIGHT;
+ bottom_offset = CAJA_THUMBNAIL_FRAME_BOTTOM;
+
+ w = gdk_pixbuf_get_width (pixbuf) - left_offset - right_offset;
+ h = gdk_pixbuf_get_height (pixbuf) - top_offset - bottom_offset;
+ pixbuf_without_frame =
+ gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
+ gdk_pixbuf_get_has_alpha (pixbuf),
+ gdk_pixbuf_get_bits_per_sample (pixbuf),
+ w, h);
+
+ gdk_pixbuf_copy_area (pixbuf,
+ left_offset, top_offset,
+ w, h,
+ pixbuf_without_frame, 0, 0);
+
+ return pixbuf_without_frame;
+}
+
+typedef struct
+{
+ gboolean is_thumbnail;
+ guint base_size;
+ guint nominal_size;
+ gboolean force_nominal;
+ int original_height;
+ int original_width;
+ double *scale_x_out;
+ double *scale_y_out;
+} ThumbnailLoadArgs;
+
+static void
+thumbnail_loader_size_prepared (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ ThumbnailLoadArgs *args)
+{
+ int size = MAX (width, height);
+
+ args->original_width = width;
+ args->original_height = height;
+
+ if (args->force_nominal)
+ {
+ args->base_size = size;
+ }
+ else if (args->base_size == 0)
+ {
+ if (args->is_thumbnail)
+ {
+ args->base_size = 128 * CAJA_ICON_SIZE_STANDARD / thumbnail_icon_size;
+ }
+ else
+ {
+ if (size > args->nominal_size * thumbnail_icon_size / CAJA_ICON_SIZE_STANDARD)
+ {
+ args->base_size = size * CAJA_ICON_SIZE_STANDARD / thumbnail_icon_size;
+ }
+ else if (size > CAJA_ICON_SIZE_STANDARD)
+ {
+ args->base_size = args->nominal_size;
+ }
+ else
+ {
+ /* Don't scale up small icons */
+ args->base_size = CAJA_ICON_SIZE_STANDARD;
+ }
+ }
+ }
+
+ if (args->base_size != args->nominal_size)
+ {
+ double scale;
+
+ scale = (double) args->nominal_size / args->base_size;
+
+ if ((int) (width * scale) > CAJA_ICON_MAXIMUM_SIZE ||
+ (int) (height * scale) > CAJA_ICON_MAXIMUM_SIZE)
+ {
+ scale = MIN ((double) CAJA_ICON_MAXIMUM_SIZE / width,
+ (double) CAJA_ICON_MAXIMUM_SIZE / height);
+ }
+
+ width = MAX (1, floor (width * scale + 0.5));
+ height = MAX (1, floor (height * scale + 0.5));
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+ }
+
+}
+
+static void
+thumbnail_loader_area_prepared (GdkPixbufLoader *loader,
+ ThumbnailLoadArgs *args)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ *args->scale_x_out = (double) gdk_pixbuf_get_width (pixbuf) / args->original_width;
+ *args->scale_y_out = (double) gdk_pixbuf_get_height (pixbuf) / args->original_height;
+}
+
+static GdkPixbuf *
+get_pixbuf_from_data (const unsigned char *buffer,
+ gsize buflen,
+ const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ double *scale_x_out,
+ double *scale_y_out)
+{
+ GdkPixbufLoader *loader;
+ GdkPixbuf *pixbuf;
+ ThumbnailLoadArgs args;
+ GError *error;
+
+ if (thumbnail_icon_size == 0)
+ {
+ eel_preferences_add_auto_integer (CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE,
+ &thumbnail_icon_size);
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (thumbnail_loader_size_prepared),
+ &args);
+ g_signal_connect (loader, "area-prepared",
+ G_CALLBACK (thumbnail_loader_area_prepared),
+ &args);
+
+ args.is_thumbnail = strstr (path, "/.thumbnails/") != NULL;
+ args.base_size = base_size;
+ args.nominal_size = nominal_size;
+ args.force_nominal = force_nominal;
+ args.scale_x_out = scale_x_out;
+ args.scale_y_out = scale_y_out;
+
+ error = NULL;
+
+ if (!gdk_pixbuf_loader_write (loader, buffer, buflen, &error))
+ {
+ g_message ("Failed to write %s to thumbnail pixbuf loader: %s", path, error->message);
+
+ gdk_pixbuf_loader_close (loader, NULL);
+ g_object_unref (G_OBJECT (loader));
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ error = NULL;
+
+ if (!gdk_pixbuf_loader_close (loader, &error) ||
+ /* Seems we have to check this even if it returned TRUE (#403255) */
+ error != NULL)
+ {
+ /* In some cases, we don't get an error even with FALSE returns (#538888) */
+ if (error != NULL)
+ {
+ g_message ("Failed to close thumbnail pixbuf loader for %s: %s", path, error->message);
+ }
+ else
+ {
+ g_message ("Failed to close thumbnail pixbuf loader for %s", path);
+ }
+
+ g_object_unref (G_OBJECT (loader));
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
+
+ g_object_unref (G_OBJECT (loader));
+
+ return pixbuf;
+}
+
+
+/* routine to load an image from the passed-in path
+ */
+GdkPixbuf *
+caja_thumbnail_load_image (const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ double *scale_x_out,
+ double *scale_y_out)
+{
+ GdkPixbuf *pixbuf;
+ guchar *buffer;
+ gsize buflen;
+ GError *error;
+
+ error = NULL;
+
+ if (!g_file_get_contents (path, (gchar **) &buffer, &buflen, &error))
+ {
+ g_message ("Failed to load %s into memory: %s", path, error->message);
+
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ pixbuf = get_pixbuf_from_data (buffer, buflen, path,
+ base_size, nominal_size, force_nominal,
+ scale_x_out, scale_y_out);
+
+ g_free (buffer);
+
+ return pixbuf;
+}
+
+static void
+async_thumbnail_read_image (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ CajaThumbnailAsyncLoadHandle *handle = callback_data;
+ GdkPixbuf *pixbuf;
+ double scale_x, scale_y;
+ gsize file_size;
+ char *file_contents;
+
+ pixbuf = NULL;
+ scale_x = scale_y = 1.0;
+
+ if (g_file_load_contents_finish (G_FILE (source_object),
+ res,
+ &file_contents, &file_size,
+ NULL, NULL))
+ {
+ pixbuf = get_pixbuf_from_data (file_contents, file_size,
+ handle->file_path,
+ handle->base_size,
+ handle->nominal_size,
+ handle->force_nominal,
+ &scale_x, &scale_y);
+ g_free (file_contents);
+ }
+
+ handle->load_func (handle,
+ handle->file_path,
+ pixbuf, scale_x, scale_y,
+ handle->load_func_user_data);
+
+ g_object_unref (pixbuf);
+
+ g_object_unref (handle->cancellable);
+ g_free (handle->file_path);
+ g_free (handle);
+}
+
+CajaThumbnailAsyncLoadHandle *
+caja_thumbnail_load_image_async (const char *path,
+ guint base_size,
+ guint nominal_size,
+ gboolean force_nominal,
+ CajaThumbnailAsyncLoadFunc load_func,
+ gpointer load_func_user_data)
+{
+ CajaThumbnailAsyncLoadHandle *handle;
+ GFile *location;
+
+
+ handle = g_new (CajaThumbnailAsyncLoadHandle, 1);
+ handle->cancellable = g_cancellable_new ();
+ handle->file_path = g_strdup (path);
+ handle->base_size = base_size;
+ handle->nominal_size = nominal_size;
+ handle->force_nominal = force_nominal;
+ handle->load_func = load_func;
+ handle->load_func_user_data = load_func_user_data;
+
+
+ location = g_file_new_for_path (path);
+ g_file_load_contents_async (location, handle->cancellable,
+ async_thumbnail_read_image,
+ handle);
+ g_object_unref (location);
+
+ return handle;
+}
+
+void
+caja_thumbnail_load_image_cancel (CajaThumbnailAsyncLoadHandle *handle)
+{
+ g_assert (handle != NULL);
+
+ g_cancellable_cancel (handle->cancellable);
+}
+
+void
+caja_thumbnail_remove_from_queue (const char *file_uri)
+{
+ GList *node;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove from queue) Locking mutex\n");
+#endif
+ pthread_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ if (thumbnails_to_make_hash)
+ {
+ node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
+
+ if (node && node->data != currently_thumbnailing)
+ {
+ g_hash_table_remove (thumbnails_to_make_hash, file_uri);
+ free_thumbnail_info (node->data);
+ g_queue_delete_link ((GQueue *)&thumbnails_to_make, node);
+ }
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove from queue) Unlocking mutex\n");
+#endif
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+void
+caja_thumbnail_remove_all_from_queue (void)
+{
+ CajaThumbnailInfo *info;
+ GList *l, *next;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove all from queue) Locking mutex\n");
+#endif
+ pthread_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ l = thumbnails_to_make.head;
+ while (l != NULL)
+ {
+ info = l->data;
+ next = l->next;
+ if (info != currently_thumbnailing)
+ {
+ g_hash_table_remove (thumbnails_to_make_hash,
+ info->image_uri);
+ free_thumbnail_info (info);
+ g_queue_delete_link ((GQueue *)&thumbnails_to_make, l);
+ }
+
+ l = next;
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Remove all from queue) Unlocking mutex\n");
+#endif
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+void
+caja_thumbnail_prioritize (const char *file_uri)
+{
+ GList *node;
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Prioritize) Locking mutex\n");
+#endif
+ pthread_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ if (thumbnails_to_make_hash)
+ {
+ node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
+
+ if (node && node->data != currently_thumbnailing)
+ {
+ g_queue_unlink ((GQueue *)&thumbnails_to_make, node);
+ g_queue_push_head_link ((GQueue *)&thumbnails_to_make, node);
+ }
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Prioritize) Unlocking mutex\n");
+#endif
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+
+/***************************************************************************
+ * Thumbnail Thread Functions.
+ ***************************************************************************/
+
+
+/* This is a one-shot idle callback called from the main loop to call
+ notify_file_changed() for a thumbnail. It frees the uri afterwards.
+ We do this in an idle callback as I don't think caja_file_changed() is
+ thread-safe. */
+static gboolean
+thumbnail_thread_notify_file_changed (gpointer image_uri)
+{
+ CajaFile *file;
+
+ GDK_THREADS_ENTER ();
+
+ file = caja_file_get_by_uri ((char *) image_uri);
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Notifying file changed file:%p uri: %s\n", file, (char*) image_uri);
+#endif
+
+ if (file != NULL)
+ {
+ caja_file_set_is_thumbnailing (file, FALSE);
+ caja_file_invalidate_attributes (file,
+ CAJA_FILE_ATTRIBUTE_THUMBNAIL |
+ CAJA_FILE_ATTRIBUTE_INFO);
+ caja_file_unref (file);
+ }
+ g_free (image_uri);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static GHashTable *
+get_types_table (void)
+{
+ static GHashTable *image_mime_types = NULL;
+ GSList *format_list, *l;
+ char **types;
+ int i;
+
+ if (image_mime_types == NULL)
+ {
+ image_mime_types =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ eel_debug_call_at_shutdown_with_data ((GFreeFunc)g_hash_table_destroy,
+ image_mime_types);
+
+ format_list = gdk_pixbuf_get_formats ();
+ for (l = format_list; l; l = l->next)
+ {
+ types = gdk_pixbuf_format_get_mime_types (l->data);
+
+ for (i = 0; i < G_N_ELEMENTS (types); i++)
+ {
+ g_hash_table_insert (image_mime_types,
+ types [i],
+ GUINT_TO_POINTER (1));
+ }
+
+ g_free (types);
+ }
+
+ g_slist_free (format_list);
+ }
+
+ return image_mime_types;
+}
+
+static gboolean
+pixbuf_can_load_type (const char *mime_type)
+{
+ GHashTable *image_mime_types;
+
+ image_mime_types = get_types_table ();
+ if (g_hash_table_lookup (image_mime_types, mime_type))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+caja_can_thumbnail_internally (CajaFile *file)
+{
+ char *mime_type;
+ gboolean res;
+
+ mime_type = caja_file_get_mime_type (file);
+ res = pixbuf_can_load_type (mime_type);
+ g_free (mime_type);
+ return res;
+}
+
+gboolean
+caja_thumbnail_is_mimetype_limited_by_size (const char *mime_type)
+{
+ return pixbuf_can_load_type (mime_type);
+}
+
+gboolean
+caja_can_thumbnail (CajaFile *file)
+{
+ MateDesktopThumbnailFactory *factory;
+ gboolean res;
+ char *uri;
+ time_t mtime;
+ char *mime_type;
+
+ uri = caja_file_get_uri (file);
+ mime_type = caja_file_get_mime_type (file);
+ mtime = caja_file_get_mtime (file);
+
+ factory = get_thumbnail_factory ();
+ res = mate_desktop_thumbnail_factory_can_thumbnail (factory,
+ uri,
+ mime_type,
+ mtime);
+ g_free (mime_type);
+ g_free (uri);
+
+ return res;
+}
+
+void
+caja_create_thumbnail (CajaFile *file)
+{
+ time_t file_mtime = 0;
+ CajaThumbnailInfo *info;
+ CajaThumbnailInfo *existing_info;
+ GList *existing, *node;
+
+ caja_file_set_is_thumbnailing (file, TRUE);
+
+ info = g_new0 (CajaThumbnailInfo, 1);
+ info->image_uri = caja_file_get_uri (file);
+ info->mime_type = caja_file_get_mime_type (file);
+
+ /* Hopefully the CajaFile will already have the image file mtime,
+ so we can just use that. Otherwise we have to get it ourselves. */
+ if (file->details->got_file_info &&
+ file->details->file_info_is_up_to_date &&
+ file->details->mtime != 0)
+ {
+ file_mtime = file->details->mtime;
+ }
+ else
+ {
+ get_file_mtime (info->image_uri, &file_mtime);
+ }
+
+ info->original_file_mtime = file_mtime;
+
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Locking mutex\n");
+#endif
+ pthread_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ if (thumbnails_to_make_hash == NULL)
+ {
+ thumbnails_to_make_hash = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ }
+
+ /* Check if it is already in the list of thumbnails to make. */
+ existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
+ if (existing == NULL)
+ {
+ /* Add the thumbnail to the list. */
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Adding thumbnail: %s\n",
+ info->image_uri);
+#endif
+ g_queue_push_tail ((GQueue *)&thumbnails_to_make, info);
+ node = g_queue_peek_tail_link ((GQueue *)&thumbnails_to_make);
+ g_hash_table_insert (thumbnails_to_make_hash,
+ info->image_uri,
+ node);
+ /* If the thumbnail thread isn't running, and we haven't
+ scheduled an idle function to start it up, do that now.
+ We don't want to start it until all the other work is done,
+ so the GUI will be updated as quickly as possible.*/
+ if (thumbnail_thread_is_running == FALSE &&
+ thumbnail_thread_starter_id == 0)
+ {
+ thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_LOW, thumbnail_thread_starter_cb, NULL, NULL);
+ }
+ }
+ else
+ {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Updating non-current mtime: %s\n",
+ info->image_uri);
+#endif
+ /* The file in the queue might need a new original mtime */
+ existing_info = existing->data;
+ existing_info->original_file_mtime = info->original_file_mtime;
+ free_thumbnail_info (info);
+ }
+
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Main Thread) Unlocking mutex\n");
+#endif
+ pthread_mutex_unlock (&thumbnails_mutex);
+}
+
+/* thumbnail_thread is invoked as a separate thread to to make thumbnails. */
+static gpointer
+thumbnail_thread_start (gpointer data)
+{
+ CajaThumbnailInfo *info = NULL;
+ GdkPixbuf *pixbuf;
+ time_t current_orig_mtime = 0;
+ time_t current_time;
+ GList *node;
+
+ /* We loop until there are no more thumbails to make, at which point
+ we exit the thread. */
+ for (;;)
+ {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Locking mutex\n");
+#endif
+ pthread_mutex_lock (&thumbnails_mutex);
+
+ /*********************************
+ * MUTEX LOCKED
+ *********************************/
+
+ /* Pop the last thumbnail we just made off the head of the
+ list and free it. I did this here so we only have to lock
+ the mutex once per thumbnail, rather than once before
+ creating it and once after.
+ Don't pop the thumbnail off the queue if the original file
+ mtime of the request changed. Then we need to redo the thumbnail.
+ */
+ if (currently_thumbnailing &&
+ currently_thumbnailing->original_file_mtime == current_orig_mtime)
+ {
+ g_assert (info == currently_thumbnailing);
+ node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
+ g_assert (node != NULL);
+ g_hash_table_remove (thumbnails_to_make_hash, info->image_uri);
+ free_thumbnail_info (info);
+ g_queue_delete_link ((GQueue *)&thumbnails_to_make, node);
+ }
+ currently_thumbnailing = NULL;
+
+ /* If there are no more thumbnails to make, reset the
+ thumbnail_thread_is_running flag, unlock the mutex, and
+ exit the thread. */
+ if (g_queue_is_empty ((GQueue *)&thumbnails_to_make))
+ {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Exiting\n");
+#endif
+ thumbnail_thread_is_running = FALSE;
+ pthread_mutex_unlock (&thumbnails_mutex);
+ pthread_exit (NULL);
+ }
+
+ /* Get the next one to make. We leave it on the list until it
+ is created so the main thread doesn't add it again while we
+ are creating it. */
+ info = g_queue_peek_head ((GQueue *)&thumbnails_to_make);
+ currently_thumbnailing = info;
+ current_orig_mtime = info->original_file_mtime;
+ /*********************************
+ * MUTEX UNLOCKED
+ *********************************/
+
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Unlocking mutex\n");
+#endif
+ pthread_mutex_unlock (&thumbnails_mutex);
+
+ time (&current_time);
+
+ /* Don't try to create a thumbnail if the file was modified recently.
+ This prevents constant re-thumbnailing of changing files. */
+ if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS &&
+ current_time >= current_orig_mtime)
+ {
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Skipping: %s\n",
+ info->image_uri);
+#endif
+ /* Reschedule thumbnailing via a change notification */
+ g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed,
+ g_strdup (info->image_uri));
+ continue;
+ }
+
+ /* Create the thumbnail. */
+#ifdef DEBUG_THUMBNAILS
+ g_message ("(Thumbnail Thread) Creating thumbnail: %s\n",
+ info->image_uri);
+#endif
+
+ pixbuf = mate_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
+ info->image_uri,
+ info->mime_type);
+
+ if (pixbuf)
+ {
+ mate_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory,
+ pixbuf,
+ info->image_uri,
+ current_orig_mtime);
+ g_object_unref (pixbuf);
+ }
+ else
+ {
+ mate_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory,
+ info->image_uri,
+ current_orig_mtime);
+ }
+ /* We need to call caja_file_changed(), but I don't think that is
+ thread safe. So add an idle handler and do it from the main loop. */
+ g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ thumbnail_thread_notify_file_changed,
+ g_strdup (info->image_uri), NULL);
+ }
+}