summaryrefslogtreecommitdiffstats
path: root/libgimpthumb/gimpthumbnail.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libgimpthumb/gimpthumbnail.c1400
1 files changed, 1400 insertions, 0 deletions
diff --git a/libgimpthumb/gimpthumbnail.c b/libgimpthumb/gimpthumbnail.c
new file mode 100644
index 0000000..e7f9d3d
--- /dev/null
+++ b/libgimpthumb/gimpthumbnail.c
@@ -0,0 +1,1400 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * Thumbnail handling according to the Thumbnail Managing Standard.
+ * https://specifications.freedesktop.org/thumbnail-spec/
+ *
+ * Copyright (C) 2001-2004 Sven Neumann <sven@gimp.org>
+ * Michael Natterer <mitch@gimp.org>
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib/gstdio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpparam.h"
+
+#ifdef G_OS_WIN32
+#include "libgimpbase/gimpwin32-io.h"
+#include <process.h>
+#define _getpid getpid
+#endif
+
+#include "gimpthumb-types.h"
+#include "gimpthumb-error.h"
+#include "gimpthumb-utils.h"
+#include "gimpthumbnail.h"
+
+#include "libgimp/libgimp-intl.h"
+
+
+/**
+ * SECTION: gimpthumbnail
+ * @title: GimpThumbnail
+ * @short_description: The GimpThumbnail object
+ *
+ * The GimpThumbnail object
+ **/
+
+
+/* #define GIMP_THUMB_DEBUG */
+
+
+#if defined (GIMP_THUMB_DEBUG) && defined (__GNUC__)
+#define GIMP_THUMB_DEBUG_CALL(t) \
+ g_printerr ("%s: %s\n", \
+ __FUNCTION__, t->image_uri ? t->image_uri : "(null)")
+#else
+#define GIMP_THUMB_DEBUG_CALL(t) ((void)(0))
+#endif
+
+
+#define TAG_DESCRIPTION "tEXt::Description"
+#define TAG_SOFTWARE "tEXt::Software"
+#define TAG_THUMB_URI "tEXt::Thumb::URI"
+#define TAG_THUMB_MTIME "tEXt::Thumb::MTime"
+#define TAG_THUMB_FILESIZE "tEXt::Thumb::Size"
+#define TAG_THUMB_MIMETYPE "tEXt::Thumb::Mimetype"
+#define TAG_THUMB_IMAGE_WIDTH "tEXt::Thumb::Image::Width"
+#define TAG_THUMB_IMAGE_HEIGHT "tEXt::Thumb::Image::Height"
+#define TAG_THUMB_GIMP_TYPE "tEXt::Thumb::X-GIMP::Type"
+#define TAG_THUMB_GIMP_LAYERS "tEXt::Thumb::X-GIMP::Layers"
+
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE_STATE,
+ PROP_IMAGE_URI,
+ PROP_IMAGE_MTIME,
+ PROP_IMAGE_FILESIZE,
+ PROP_IMAGE_MIMETYPE,
+ PROP_IMAGE_WIDTH,
+ PROP_IMAGE_HEIGHT,
+ PROP_IMAGE_TYPE,
+ PROP_IMAGE_NUM_LAYERS,
+ PROP_THUMB_STATE
+};
+
+
+static void gimp_thumbnail_finalize (GObject *object);
+static void gimp_thumbnail_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_thumbnail_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_thumbnail_reset_info (GimpThumbnail *thumbnail);
+
+static void gimp_thumbnail_update_image (GimpThumbnail *thumbnail);
+static void gimp_thumbnail_update_thumb (GimpThumbnail *thumbnail,
+ GimpThumbSize size);
+
+static gboolean gimp_thumbnail_save (GimpThumbnail *thumbnail,
+ GimpThumbSize size,
+ const gchar *filename,
+ GdkPixbuf *pixbuf,
+ const gchar *software,
+ GError **error);
+#ifdef GIMP_THUMB_DEBUG
+static void gimp_thumbnail_debug_notify (GObject *object,
+ GParamSpec *pspec);
+#endif
+
+
+G_DEFINE_TYPE (GimpThumbnail, gimp_thumbnail, G_TYPE_OBJECT)
+
+#define parent_class gimp_thumbnail_parent_class
+
+
+static void
+gimp_thumbnail_class_init (GimpThumbnailClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_thumbnail_finalize;
+ object_class->set_property = gimp_thumbnail_set_property;
+ object_class->get_property = gimp_thumbnail_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_STATE,
+ g_param_spec_enum ("image-state", NULL,
+ "State of the image associated to the thumbnail object",
+ GIMP_TYPE_THUMB_STATE,
+ GIMP_THUMB_STATE_UNKNOWN,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_URI,
+ g_param_spec_string ("image-uri", NULL,
+ "URI of the image file",
+ NULL,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_MTIME,
+ g_param_spec_int64 ("image-mtime", NULL,
+ "Modification time of the image file in seconds since the Epoch",
+ G_MININT64, G_MAXINT64, 0,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_FILESIZE,
+ g_param_spec_int64 ("image-filesize", NULL,
+ "Size of the image file in bytes",
+ 0, G_MAXINT64, 0,
+ GIMP_PARAM_READWRITE));
+ /**
+ * GimpThumbnail::image-mimetype:
+ *
+ * Image mimetype
+ *
+ * Since: 2.2
+ **/
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_MIMETYPE,
+ g_param_spec_string ("image-mimetype", NULL,
+ "Image mimetype",
+ NULL,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_WIDTH,
+ g_param_spec_int ("image-width", NULL,
+ "Width of the image in pixels",
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_HEIGHT,
+ g_param_spec_int ("image-height", NULL,
+ "Height of the image in pixels",
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_TYPE,
+ g_param_spec_string ("image-type", NULL,
+ "String describing the type of the image format",
+ NULL,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IMAGE_NUM_LAYERS,
+ g_param_spec_int ("image-num-layers", NULL,
+ "The number of layers in the image",
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_THUMB_STATE,
+ g_param_spec_enum ("thumb-state", NULL,
+ "State of the thumbnail file",
+ GIMP_TYPE_THUMB_STATE,
+ GIMP_THUMB_STATE_UNKNOWN,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_thumbnail_init (GimpThumbnail *thumbnail)
+{
+ thumbnail->image_state = GIMP_THUMB_STATE_UNKNOWN;
+ thumbnail->image_uri = NULL;
+ thumbnail->image_filename = NULL;
+ thumbnail->image_mtime = 0;
+ thumbnail->image_filesize = 0;
+ thumbnail->image_mimetype = NULL;
+ thumbnail->image_width = 0;
+ thumbnail->image_height = 0;
+ thumbnail->image_type = NULL;
+ thumbnail->image_num_layers = 0;
+
+ thumbnail->thumb_state = GIMP_THUMB_STATE_UNKNOWN;
+ thumbnail->thumb_size = -1;
+ thumbnail->thumb_filename = NULL;
+ thumbnail->thumb_mtime = 0;
+ thumbnail->thumb_filesize = 0;
+
+#ifdef GIMP_THUMB_DEBUG
+ g_signal_connect (thumbnail, "notify",
+ G_CALLBACK (gimp_thumbnail_debug_notify),
+ NULL);
+#endif
+}
+
+static void
+gimp_thumbnail_finalize (GObject *object)
+{
+ GimpThumbnail *thumbnail = GIMP_THUMBNAIL (object);
+
+ g_clear_pointer (&thumbnail->image_uri, g_free);
+ g_clear_pointer (&thumbnail->image_filename, g_free);
+ g_clear_pointer (&thumbnail->image_mimetype, g_free);
+ g_clear_pointer (&thumbnail->image_type, g_free);
+ g_clear_pointer (&thumbnail->thumb_filename, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_thumbnail_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpThumbnail *thumbnail = GIMP_THUMBNAIL (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE_STATE:
+ thumbnail->image_state = g_value_get_enum (value);
+ break;
+ case PROP_IMAGE_URI:
+ gimp_thumbnail_set_uri (GIMP_THUMBNAIL (object),
+ g_value_get_string (value));
+ break;
+ case PROP_IMAGE_MTIME:
+ thumbnail->image_mtime = g_value_get_int64 (value);
+ break;
+ case PROP_IMAGE_FILESIZE:
+ thumbnail->image_filesize = g_value_get_int64 (value);
+ break;
+ case PROP_IMAGE_MIMETYPE:
+ g_free (thumbnail->image_mimetype);
+ thumbnail->image_mimetype = g_value_dup_string (value);
+ break;
+ case PROP_IMAGE_WIDTH:
+ thumbnail->image_width = g_value_get_int (value);
+ break;
+ case PROP_IMAGE_HEIGHT:
+ thumbnail->image_height = g_value_get_int (value);
+ break;
+ case PROP_IMAGE_TYPE:
+ g_free (thumbnail->image_type);
+ thumbnail->image_type = g_value_dup_string (value);
+ break;
+ case PROP_IMAGE_NUM_LAYERS:
+ thumbnail->image_num_layers = g_value_get_int (value);
+ break;
+ case PROP_THUMB_STATE:
+ thumbnail->thumb_state = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_thumbnail_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpThumbnail *thumbnail = GIMP_THUMBNAIL (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE_STATE:
+ g_value_set_enum (value, thumbnail->image_state);
+ break;
+ case PROP_IMAGE_URI:
+ g_value_set_string (value, thumbnail->image_uri);
+ break;
+ case PROP_IMAGE_MTIME:
+ g_value_set_int64 (value, thumbnail->image_mtime);
+ break;
+ case PROP_IMAGE_FILESIZE:
+ g_value_set_int64 (value, thumbnail->image_filesize);
+ break;
+ case PROP_IMAGE_MIMETYPE:
+ g_value_set_string (value, thumbnail->image_mimetype);
+ break;
+ case PROP_IMAGE_WIDTH:
+ g_value_set_int (value, thumbnail->image_width);
+ break;
+ case PROP_IMAGE_HEIGHT:
+ g_value_set_int (value, thumbnail->image_height);
+ break;
+ case PROP_IMAGE_TYPE:
+ g_value_set_string (value, thumbnail->image_type);
+ break;
+ case PROP_IMAGE_NUM_LAYERS:
+ g_value_set_int (value, thumbnail->image_num_layers);
+ break;
+ case PROP_THUMB_STATE:
+ g_value_set_enum (value, thumbnail->thumb_state);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_thumbnail_new:
+ *
+ * Creates a new #GimpThumbnail object.
+ *
+ * Return value: a newly allocated GimpThumbnail object
+ **/
+GimpThumbnail *
+gimp_thumbnail_new (void)
+{
+ return g_object_new (GIMP_TYPE_THUMBNAIL, NULL);
+}
+
+/**
+ * gimp_thumbnail_set_uri:
+ * @thumbnail: a #GimpThumbnail object
+ * @uri: an escaped URI
+ *
+ * Sets the location of the image file associated with the #thumbnail.
+ *
+ * All information stored in the #GimpThumbnail is reset.
+ **/
+void
+gimp_thumbnail_set_uri (GimpThumbnail *thumbnail,
+ const gchar *uri)
+{
+ g_return_if_fail (GIMP_IS_THUMBNAIL (thumbnail));
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ if (thumbnail->image_uri)
+ g_free (thumbnail->image_uri);
+
+ thumbnail->image_uri = g_strdup (uri);
+
+ g_clear_pointer (&thumbnail->image_filename, g_free);
+ g_clear_pointer (&thumbnail->thumb_filename, g_free);
+
+ thumbnail->thumb_size = -1;
+ thumbnail->thumb_filesize = 0;
+ thumbnail->thumb_mtime = 0;
+
+ g_object_set (thumbnail,
+ "image-state", GIMP_THUMB_STATE_UNKNOWN,
+ "image-filesize", (gint64) 0,
+ "image-mtime", (gint64) 0,
+ "image-mimetype", NULL,
+ "image-width", 0,
+ "image-height", 0,
+ "image-type", NULL,
+ "image-num-layers", 0,
+ "thumb-state", GIMP_THUMB_STATE_UNKNOWN,
+ NULL);
+}
+
+/**
+ * gimp_thumbnail_set_filename:
+ * @thumbnail: a #GimpThumbnail object
+ * @filename: a local filename in the encoding of the filesystem
+ * @error: return location for possible errors
+ *
+ * Sets the location of the image file associated with the #thumbnail.
+ *
+ * Return value: %TRUE if the filename was successfully set,
+ * %FALSE otherwise
+ **/
+gboolean
+gimp_thumbnail_set_filename (GimpThumbnail *thumbnail,
+ const gchar *filename,
+ GError **error)
+{
+ gchar *uri = NULL;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ if (filename)
+ uri = g_filename_to_uri (filename, NULL, error);
+
+ gimp_thumbnail_set_uri (thumbnail, uri);
+
+ g_free (uri);
+
+ return (!filename || uri);
+}
+
+/**
+ * gimp_thumbnail_set_from_thumb:
+ * @thumbnail: a #GimpThumbnail object
+ * @filename: filename of a local thumbnail file
+ * @error: return location for possible errors
+ *
+ * This function tries to load the thumbnail file pointed to by
+ * @filename and retrieves the URI of the original image file from
+ * it. This allows you to find the image file associated with a
+ * thumbnail file.
+ *
+ * This will only work with thumbnails from the global thumbnail
+ * directory that contain a valid Thumb::URI tag.
+ *
+ * Return value: %TRUE if the pixbuf could be loaded, %FALSE otherwise
+ **/
+gboolean
+gimp_thumbnail_set_from_thumb (GimpThumbnail *thumbnail,
+ const gchar *filename,
+ GError **error)
+{
+ GdkPixbuf *pixbuf;
+ const gchar *uri;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ pixbuf = gdk_pixbuf_new_from_file (filename, error);
+ if (! pixbuf)
+ return FALSE;
+
+ uri = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_URI);
+ if (! uri)
+ {
+ g_set_error (error, GIMP_THUMB_ERROR, 0,
+ _("Thumbnail contains no Thumb::URI tag"));
+ g_object_unref (pixbuf);
+ return FALSE;
+ }
+
+ gimp_thumbnail_set_uri (thumbnail, uri);
+ g_object_unref (pixbuf);
+
+ return TRUE;
+}
+
+/**
+ * gimp_thumbnail_peek_image:
+ * @thumbnail: a #GimpThumbnail object
+ *
+ * Checks the image file associated with the @thumbnail and updates
+ * information such as state, filesize and modification time.
+ *
+ * Return value: the image's #GimpThumbState after the update
+ **/
+GimpThumbState
+gimp_thumbnail_peek_image (GimpThumbnail *thumbnail)
+{
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail),
+ GIMP_THUMB_STATE_UNKNOWN);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ g_object_freeze_notify (G_OBJECT (thumbnail));
+
+ gimp_thumbnail_update_image (thumbnail);
+
+ g_object_thaw_notify (G_OBJECT (thumbnail));
+
+ return thumbnail->image_state;
+}
+
+/**
+ * gimp_thumbnail_peek_thumb:
+ * @thumbnail: a #GimpThumbnail object
+ * @size: the preferred size of the thumbnail image
+ *
+ * Checks if a thumbnail file for the @thumbnail exists. It doesn't
+ * load the thumbnail image and thus cannot check if the thumbnail is
+ * valid and uptodate for the image file asosciated with the
+ * @thumbnail.
+ *
+ * If you want to check the thumbnail, either attempt to load it using
+ * gimp_thumbnail_load_thumb(), or, if you don't need the resulting
+ * thumbnail pixbuf, use gimp_thumbnail_check_thumb().
+ *
+ * Return value: the thumbnail's #GimpThumbState after the update
+ **/
+GimpThumbState
+gimp_thumbnail_peek_thumb (GimpThumbnail *thumbnail,
+ GimpThumbSize size)
+{
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail),
+ GIMP_THUMB_STATE_UNKNOWN);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ g_object_freeze_notify (G_OBJECT (thumbnail));
+
+ gimp_thumbnail_update_image (thumbnail);
+ gimp_thumbnail_update_thumb (thumbnail, size);
+
+ g_object_thaw_notify (G_OBJECT (thumbnail));
+
+ return thumbnail->thumb_state;
+}
+
+/**
+ * gimp_thumbnail_check_thumb:
+ * @thumbnail: a #GimpThumbnail object
+ * @size: the preferred size of the thumbnail image
+ *
+ * Checks if a thumbnail file for the @thumbnail exists, loads it and
+ * verifies it is valid and uptodate for the image file asosciated
+ * with the @thumbnail.
+ *
+ * Return value: the thumbnail's #GimpThumbState after the update
+ *
+ * Since: 2.2
+ **/
+GimpThumbState
+gimp_thumbnail_check_thumb (GimpThumbnail *thumbnail,
+ GimpThumbSize size)
+{
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ if (gimp_thumbnail_peek_thumb (thumbnail, size) == GIMP_THUMB_STATE_OK)
+ return GIMP_THUMB_STATE_OK;
+
+ pixbuf = gimp_thumbnail_load_thumb (thumbnail, size, NULL);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ return thumbnail->thumb_state;
+}
+
+static void
+gimp_thumbnail_update_image (GimpThumbnail *thumbnail)
+{
+ GimpThumbState state;
+ gint64 mtime = 0;
+ gint64 filesize = 0;
+
+ if (! thumbnail->image_uri)
+ return;
+
+ state = thumbnail->image_state;
+
+ switch (state)
+ {
+ case GIMP_THUMB_STATE_UNKNOWN:
+ g_return_if_fail (thumbnail->image_filename == NULL);
+
+ thumbnail->image_filename =
+ _gimp_thumb_filename_from_uri (thumbnail->image_uri);
+
+ if (! thumbnail->image_filename)
+ state = GIMP_THUMB_STATE_REMOTE;
+
+ break;
+
+ case GIMP_THUMB_STATE_REMOTE:
+ break;
+
+ default:
+ g_return_if_fail (thumbnail->image_filename != NULL);
+ break;
+ }
+
+ switch (state)
+ {
+ case GIMP_THUMB_STATE_REMOTE:
+ break;
+
+ default:
+ switch (gimp_thumb_file_test (thumbnail->image_filename,
+ &mtime, &filesize,
+ &thumbnail->image_not_found_errno))
+ {
+ case GIMP_THUMB_FILE_TYPE_REGULAR:
+ state = GIMP_THUMB_STATE_EXISTS;
+ break;
+
+ case GIMP_THUMB_FILE_TYPE_FOLDER:
+ state = GIMP_THUMB_STATE_FOLDER;
+ break;
+
+ case GIMP_THUMB_FILE_TYPE_SPECIAL:
+ state = GIMP_THUMB_STATE_SPECIAL;
+ break;
+
+ default:
+ state = GIMP_THUMB_STATE_NOT_FOUND;
+ break;
+ }
+ break;
+ }
+
+ if (state != thumbnail->image_state)
+ {
+ g_object_set (thumbnail,
+ "image-state", state,
+ NULL);
+ }
+
+ if (mtime != thumbnail->image_mtime || filesize != thumbnail->image_filesize)
+ {
+ g_object_set (thumbnail,
+ "image-mtime", mtime,
+ "image-filesize", filesize,
+ NULL);
+
+ if (thumbnail->thumb_state == GIMP_THUMB_STATE_OK)
+ g_object_set (thumbnail,
+ "thumb-state", GIMP_THUMB_STATE_OLD,
+ NULL);
+ }
+}
+
+static void
+gimp_thumbnail_update_thumb (GimpThumbnail *thumbnail,
+ GimpThumbSize size)
+{
+ gchar *filename;
+ GimpThumbState state;
+ gint64 filesize = 0;
+ gint64 mtime = 0;
+
+ if (! thumbnail->image_uri)
+ return;
+
+ state = thumbnail->thumb_state;
+
+ filename = gimp_thumb_find_thumb (thumbnail->image_uri, &size);
+
+ /* We don't want to clear the GIMP_THUMB_STATE_FAILED state, because
+ * it is normal to have no filename if thumbnail creation failed. */
+ if (state != GIMP_THUMB_STATE_FAILED && ! filename)
+ state = GIMP_THUMB_STATE_NOT_FOUND;
+
+ switch (state)
+ {
+ case GIMP_THUMB_STATE_EXISTS:
+ case GIMP_THUMB_STATE_OLD:
+ case GIMP_THUMB_STATE_OK:
+ g_return_if_fail (thumbnail->thumb_filename != NULL);
+
+ if (thumbnail->thumb_size == size &&
+ thumbnail->thumb_filesize == filesize &&
+ thumbnail->thumb_mtime == mtime)
+ {
+ g_free (filename);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (thumbnail->thumb_filename)
+ g_free (thumbnail->thumb_filename);
+
+ thumbnail->thumb_filename = filename;
+
+ if (filename)
+ state = (size > GIMP_THUMB_SIZE_FAIL ?
+ GIMP_THUMB_STATE_EXISTS : GIMP_THUMB_STATE_FAILED);
+
+ thumbnail->thumb_size = size;
+ thumbnail->thumb_filesize = filesize;
+ thumbnail->thumb_mtime = mtime;
+
+ if (state != thumbnail->thumb_state)
+ {
+ g_object_freeze_notify (G_OBJECT (thumbnail));
+
+ g_object_set (thumbnail, "thumb-state", state, NULL);
+ gimp_thumbnail_reset_info (thumbnail);
+
+ g_object_thaw_notify (G_OBJECT (thumbnail));
+ }
+}
+
+static void
+gimp_thumbnail_reset_info (GimpThumbnail *thumbnail)
+{
+ g_object_set (thumbnail,
+ "image-width", 0,
+ "image-height", 0,
+ "image-type", NULL,
+ "image-num-layers", 0,
+ NULL);
+}
+
+static void
+gimp_thumbnail_set_info_from_pixbuf (GimpThumbnail *thumbnail,
+ GdkPixbuf *pixbuf)
+{
+ const gchar *option;
+ gint num;
+
+ g_object_freeze_notify (G_OBJECT (thumbnail));
+
+ gimp_thumbnail_reset_info (thumbnail);
+
+ g_free (thumbnail->image_mimetype);
+ thumbnail->image_mimetype =
+ g_strdup (gdk_pixbuf_get_option (pixbuf, TAG_THUMB_MIMETYPE));
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_IMAGE_WIDTH);
+ if (option && sscanf (option, "%d", &num) == 1)
+ thumbnail->image_width = num;
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_IMAGE_HEIGHT);
+ if (option && sscanf (option, "%d", &num) == 1)
+ thumbnail->image_height = num;
+
+ thumbnail->image_type =
+ g_strdup (gdk_pixbuf_get_option (pixbuf, TAG_THUMB_GIMP_TYPE));
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_GIMP_LAYERS);
+ if (option && sscanf (option, "%d", &num) == 1)
+ thumbnail->image_num_layers = num;
+
+ g_object_thaw_notify (G_OBJECT (thumbnail));
+}
+
+static gboolean
+gimp_thumbnail_save (GimpThumbnail *thumbnail,
+ GimpThumbSize size,
+ const gchar *filename,
+ GdkPixbuf *pixbuf,
+ const gchar *software,
+ GError **error)
+{
+ const gchar *keys[12];
+ gchar *values[12];
+ gchar *basename;
+ gchar *dirname;
+ gchar *tmpname;
+ gboolean success;
+ gint i = 0;
+
+ keys[i] = TAG_DESCRIPTION;
+ values[i] = g_strdup_printf ("Thumbnail of %s", thumbnail->image_uri);
+ i++;
+
+ keys[i] = TAG_SOFTWARE;
+ values[i] = g_strdup (software);
+ i++;
+
+ keys[i] = TAG_THUMB_URI;
+ values[i] = g_strdup (thumbnail->image_uri);
+ i++;
+
+ keys[i] = TAG_THUMB_MTIME;
+ values[i] = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_mtime);
+ i++;
+
+ keys[i] = TAG_THUMB_FILESIZE;
+ values[i] = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_filesize);
+ i++;
+
+ if (thumbnail->image_mimetype)
+ {
+ keys[i] = TAG_THUMB_MIMETYPE;
+ values[i] = g_strdup (thumbnail->image_mimetype);
+ i++;
+ }
+
+ if (thumbnail->image_width > 0)
+ {
+ keys[i] = TAG_THUMB_IMAGE_WIDTH;
+ values[i] = g_strdup_printf ("%d", thumbnail->image_width);
+ i++;
+ }
+
+ if (thumbnail->image_height > 0)
+ {
+ keys[i] = TAG_THUMB_IMAGE_HEIGHT;
+ values[i] = g_strdup_printf ("%d", thumbnail->image_height);
+ i++;
+ }
+
+ if (thumbnail->image_type)
+ {
+ keys[i] = TAG_THUMB_GIMP_TYPE;
+ values[i] = g_strdup (thumbnail->image_type);
+ i++;
+ }
+
+ if (thumbnail->image_num_layers > 0)
+ {
+ keys[i] = TAG_THUMB_GIMP_LAYERS;
+ values[i] = g_strdup_printf ("%d", thumbnail->image_num_layers);
+ i++;
+ }
+
+ keys[i] = NULL;
+ values[i] = NULL;
+
+ basename = g_path_get_basename (filename);
+ dirname = g_path_get_dirname (filename);
+
+ tmpname = g_strdup_printf ("%s%cgimp-thumb-%d-%.8s",
+ dirname, G_DIR_SEPARATOR, getpid (), basename);
+
+ g_free (dirname);
+ g_free (basename);
+
+ success = gdk_pixbuf_savev (pixbuf, tmpname, "png",
+ (gchar **) keys, values,
+ error);
+
+ for (i = 0; keys[i]; i++)
+ g_free (values[i]);
+
+ if (success)
+ {
+#ifdef GIMP_THUMB_DEBUG
+ g_printerr ("thumbnail saved to temporary file %s\n", tmpname);
+#endif
+
+ success = (g_rename (tmpname, filename) == 0);
+
+ if (! success)
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Could not create thumbnail for %s: %s"),
+ thumbnail->image_uri, g_strerror (errno));
+ }
+
+ if (success)
+ {
+#ifdef GIMP_THUMB_DEBUG
+ g_printerr ("temporary thumbnail file renamed to %s\n", filename);
+#endif
+
+ success = (g_chmod (filename, 0600) == 0);
+
+ if (! success)
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ "Could not set permissions of thumbnail for %s: %s",
+ thumbnail->image_uri, g_strerror (errno));
+
+ g_object_freeze_notify (G_OBJECT (thumbnail));
+
+ gimp_thumbnail_update_thumb (thumbnail, size);
+
+ if (success &&
+ thumbnail->thumb_state == GIMP_THUMB_STATE_EXISTS &&
+ strcmp (filename, thumbnail->thumb_filename) == 0)
+ {
+ thumbnail->thumb_state = GIMP_THUMB_STATE_OK;
+ }
+
+ g_object_thaw_notify (G_OBJECT (thumbnail));
+ }
+
+ g_unlink (tmpname);
+ g_free (tmpname);
+
+ return success;
+}
+
+#ifdef GIMP_THUMB_DEBUG
+static void
+gimp_thumbnail_debug_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ GValue value = G_VALUE_INIT;
+ gchar *str = NULL;
+ const gchar *name;
+
+ g_value_init (&value, pspec->value_type);
+ g_object_get_property (object, pspec->name, &value);
+
+ if (G_VALUE_HOLDS_STRING (&value))
+ {
+ str = g_value_dup_string (&value);
+ }
+ else if (g_value_type_transformable (pspec->value_type, G_TYPE_STRING))
+ {
+ GValue tmp = G_VALUE_INIT;
+
+ g_value_init (&tmp, G_TYPE_STRING);
+ g_value_transform (&value, &tmp);
+
+ str = g_value_dup_string (&tmp);
+
+ g_value_unset (&tmp);
+ }
+
+ g_value_unset (&value);
+
+ name = GIMP_THUMBNAIL (object)->image_uri;
+
+ g_printerr (" GimpThumb (%s) %s: %s\n",
+ name ? name : "(null)", pspec->name, str);
+
+ g_free (str);
+}
+#endif
+
+
+/**
+ * gimp_thumbnail_load_thumb:
+ * @thumbnail: a #GimpThumbnail object
+ * @size: the preferred #GimpThumbSize for the preview
+ * @error: return location for possible errors
+ *
+ * Attempts to load a thumbnail preview for the image associated with
+ * @thumbnail. Before you use this function you need need to set an
+ * image location using gimp_thumbnail_set_uri() or
+ * gimp_thumbnail_set_filename(). You can also peek at the thumb
+ * before loading it using gimp_thumbnail_peek_thumb.
+ *
+ * This function will return the best matching pixbuf for the
+ * specified @size. It returns the pixbuf as loaded from disk. It is
+ * left to the caller to scale it to the desired size. The returned
+ * pixbuf may also represent an outdated preview of the image file.
+ * In order to verify if the preview is uptodate, you should check the
+ * "thumb_state" property after calling this function.
+ *
+ * Return value: a preview pixbuf or %NULL if no thumbnail was found
+ **/
+GdkPixbuf *
+gimp_thumbnail_load_thumb (GimpThumbnail *thumbnail,
+ GimpThumbSize size,
+ GError **error)
+{
+ GimpThumbState state;
+ GdkPixbuf *pixbuf;
+ const gchar *option;
+ gint64 image_mtime;
+ gint64 image_size;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), NULL);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ if (! thumbnail->image_uri)
+ return NULL;
+
+ state = gimp_thumbnail_peek_thumb (thumbnail, size);
+
+ if (state < GIMP_THUMB_STATE_EXISTS || state == GIMP_THUMB_STATE_FAILED)
+ return NULL;
+
+ pixbuf = gdk_pixbuf_new_from_file (thumbnail->thumb_filename, NULL);
+ if (! pixbuf)
+ return NULL;
+
+#ifdef GIMP_THUMB_DEBUG
+ g_printerr ("thumbnail loaded from %s\n", thumbnail->thumb_filename);
+#endif
+
+ g_object_freeze_notify (G_OBJECT (thumbnail));
+
+ /* URI and mtime from the thumbnail need to match our file */
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_URI);
+ if (!option)
+ goto finish;
+
+ if (strcmp (option, thumbnail->image_uri))
+ {
+ /* might be a local thumbnail, try if the local part matches */
+ const gchar *baseuri = strrchr (thumbnail->image_uri, '/');
+
+ if (!baseuri || strcmp (option, baseuri))
+ goto finish;
+ }
+
+ state = GIMP_THUMB_STATE_OLD;
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_MTIME);
+ if (!option || sscanf (option, "%" G_GINT64_FORMAT, &image_mtime) != 1)
+ goto finish;
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_FILESIZE);
+ if (option && sscanf (option, "%" G_GINT64_FORMAT, &image_size) != 1)
+ goto finish;
+
+ /* TAG_THUMB_FILESIZE is optional but must match if present */
+ if (image_mtime == thumbnail->image_mtime &&
+ (option == NULL || image_size == thumbnail->image_filesize))
+ {
+ if (thumbnail->thumb_size == GIMP_THUMB_SIZE_FAIL)
+ state = GIMP_THUMB_STATE_FAILED;
+ else
+ state = GIMP_THUMB_STATE_OK;
+ }
+
+ if (state == GIMP_THUMB_STATE_FAILED)
+ gimp_thumbnail_reset_info (thumbnail);
+ else
+ gimp_thumbnail_set_info_from_pixbuf (thumbnail, pixbuf);
+
+ finish:
+ if (thumbnail->thumb_size == GIMP_THUMB_SIZE_FAIL ||
+ (state != GIMP_THUMB_STATE_OLD && state != GIMP_THUMB_STATE_OK))
+ {
+ g_object_unref (pixbuf);
+ pixbuf = NULL;
+ }
+
+ g_object_set (thumbnail,
+ "thumb-state", state,
+ NULL);
+
+ g_object_thaw_notify (G_OBJECT (thumbnail));
+
+ return pixbuf;
+}
+
+/**
+ * gimp_thumbnail_save_thumb:
+ * @thumbnail: a #GimpThumbnail object
+ * @pixbuf: a #GdkPixbuf representing the preview thumbnail
+ * @software: a string describing the software saving the thumbnail
+ * @error: return location for possible errors
+ *
+ * Saves a preview thumbnail for the image associated with @thumbnail.
+ * to the global thumbnail repository.
+ *
+ * The caller is responsible for setting the image file location, it's
+ * filesize, modification time. One way to set this info is to is to
+ * call gimp_thumbnail_set_uri() followed by gimp_thumbnail_peek_image().
+ * Since this won't work for remote images, it is left to the user of
+ * gimp_thumbnail_save_thumb() to do this or to set the information
+ * using the @thumbnail object properties.
+ *
+ * The image format type and the number of layers can optionally be
+ * set in order to be stored with the preview image.
+ *
+ * Return value: %TRUE if a thumbnail was successfully written,
+ * %FALSE otherwise
+ **/
+gboolean
+gimp_thumbnail_save_thumb (GimpThumbnail *thumbnail,
+ GdkPixbuf *pixbuf,
+ const gchar *software,
+ GError **error)
+{
+ GimpThumbSize size;
+ gchar *name;
+ gboolean success;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+ g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);
+ g_return_val_if_fail (software != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ size = MAX (gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
+ if (size < 1)
+ return TRUE;
+
+ name = gimp_thumb_name_from_uri (thumbnail->image_uri, size);
+ if (! name)
+ return TRUE;
+
+ if (! gimp_thumb_ensure_thumb_dir (size, error))
+ {
+ g_free (name);
+ return FALSE;
+ }
+
+ success = gimp_thumbnail_save (thumbnail,
+ size, name, pixbuf, software,
+ error);
+ g_free (name);
+
+ return success;
+}
+
+/**
+ * gimp_thumbnail_save_thumb_local:
+ * @thumbnail: a #GimpThumbnail object
+ * @pixbuf: a #GdkPixbuf representing the preview thumbnail
+ * @software: a string describing the software saving the thumbnail
+ * @error: return location for possible errors
+ *
+ * Saves a preview thumbnail for the image associated with @thumbnail
+ * to the local thumbnail repository. Local thumbnails have been added
+ * with version 0.7 of the spec.
+ *
+ * Please see also gimp_thumbnail_save_thumb(). The notes made there
+ * apply here as well.
+ *
+ * Return value: %TRUE if a thumbnail was successfully written,
+ * %FALSE otherwise
+ *
+ * Since: 2.2
+ **/
+gboolean
+gimp_thumbnail_save_thumb_local (GimpThumbnail *thumbnail,
+ GdkPixbuf *pixbuf,
+ const gchar *software,
+ GError **error)
+{
+ GimpThumbSize size;
+ gchar *name;
+ gchar *filename;
+ gchar *dirname;
+ gboolean success;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+ g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);
+ g_return_val_if_fail (software != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ size = MAX (gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
+ if (size < 1)
+ return TRUE;
+
+ filename = _gimp_thumb_filename_from_uri (thumbnail->image_uri);
+ if (! filename)
+ return TRUE;
+
+ dirname = g_path_get_dirname (filename);
+ g_free (filename);
+
+ name = gimp_thumb_name_from_uri_local (thumbnail->image_uri, size);
+ if (! name)
+ {
+ g_free (dirname);
+ return TRUE;
+ }
+
+ if (! gimp_thumb_ensure_thumb_dir_local (dirname, size, error))
+ {
+ g_free (name);
+ g_free (dirname);
+ return FALSE;
+ }
+
+ g_free (dirname);
+
+ success = gimp_thumbnail_save (thumbnail,
+ size, name, pixbuf, software,
+ error);
+ g_free (name);
+
+ return success;
+}
+
+/**
+ * gimp_thumbnail_save_failure:
+ * @thumbnail: a #GimpThumbnail object
+ * @software: a string describing the software saving the thumbnail
+ * @error: return location for possible errors
+ *
+ * Saves a failure thumbnail for the image associated with
+ * @thumbnail. This is an empty pixbuf that indicates that an attempt
+ * to create a preview for the image file failed. It should be used to
+ * prevent the software from further attempts to create this thumbnail.
+ *
+ * Return value: %TRUE if a failure thumbnail was successfully written,
+ * %FALSE otherwise
+ **/
+gboolean
+gimp_thumbnail_save_failure (GimpThumbnail *thumbnail,
+ const gchar *software,
+ GError **error)
+{
+ GdkPixbuf *pixbuf;
+ gchar *name;
+ gchar *desc;
+ gchar *time_str;
+ gchar *size_str;
+ gboolean success;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+ g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
+ g_return_val_if_fail (software != NULL, FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ name = gimp_thumb_name_from_uri (thumbnail->image_uri, GIMP_THUMB_SIZE_FAIL);
+ if (! name)
+ return TRUE;
+
+ if (! gimp_thumb_ensure_thumb_dir (GIMP_THUMB_SIZE_FAIL, error))
+ {
+ g_free (name);
+ return FALSE;
+ }
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
+
+ desc = g_strdup_printf ("Thumbnail failure for %s", thumbnail->image_uri);
+ time_str = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_mtime);
+ size_str = g_strdup_printf ("%" G_GINT64_FORMAT, thumbnail->image_filesize);
+
+ success = gdk_pixbuf_save (pixbuf, name, "png", error,
+ TAG_DESCRIPTION, desc,
+ TAG_SOFTWARE, software,
+ TAG_THUMB_URI, thumbnail->image_uri,
+ TAG_THUMB_MTIME, time_str,
+ TAG_THUMB_FILESIZE, size_str,
+ NULL);
+ if (success)
+ {
+ success = (g_chmod (name, 0600) == 0);
+
+ if (success)
+ gimp_thumbnail_update_thumb (thumbnail, GIMP_THUMB_SIZE_NORMAL);
+ else
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ "Could not set permissions of thumbnail '%s': %s",
+ name, g_strerror (errno));
+ }
+
+ g_object_unref (pixbuf);
+
+ g_free (size_str);
+ g_free (time_str);
+ g_free (desc);
+ g_free (name);
+
+ return success;
+}
+
+/**
+ * gimp_thumbnail_delete_failure:
+ * @thumbnail: a #GimpThumbnail object
+ *
+ * Removes a failure thumbnail if one exists. This function should be
+ * used after a thumbnail has been successfully created.
+ *
+ * Since: 2.2
+ **/
+void
+gimp_thumbnail_delete_failure (GimpThumbnail *thumbnail)
+{
+ gchar *filename;
+
+ g_return_if_fail (GIMP_IS_THUMBNAIL (thumbnail));
+ g_return_if_fail (thumbnail->image_uri != NULL);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ filename = gimp_thumb_name_from_uri (thumbnail->image_uri,
+ GIMP_THUMB_SIZE_FAIL);
+ if (filename)
+ {
+ g_unlink (filename);
+ g_free (filename);
+ }
+}
+
+/**
+ * gimp_thumbnail_delete_others:
+ * @thumbnail: a #GimpThumbnail object
+ * @size: the thumbnail size which should not be deleted
+ *
+ * Removes all other thumbnails from the global thumbnail
+ * repository. Only the thumbnail for @size is not deleted. This
+ * function should be used after a thumbnail has been successfully
+ * updated. See the spec for a more detailed description on when to
+ * delete thumbnails.
+ *
+ * Since: 2.2
+ **/
+void
+gimp_thumbnail_delete_others (GimpThumbnail *thumbnail,
+ GimpThumbSize size)
+{
+ g_return_if_fail (GIMP_IS_THUMBNAIL (thumbnail));
+ g_return_if_fail (thumbnail->image_uri != NULL);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ _gimp_thumbs_delete_others (thumbnail->image_uri, size);
+}
+
+/**
+ * gimp_thumbnail_has_failed:
+ * @thumbnail: a #GimpThumbnail object
+ *
+ * Checks if a valid failure thumbnail for the given thumbnail exists
+ * in the global thumbnail repository. This may be the case even if
+ * gimp_thumbnail_peek_thumb() doesn't return %GIMP_THUMB_STATE_FAILED
+ * since there might be a real thumbnail and a failure thumbnail for
+ * the same image file.
+ *
+ * The application should not attempt to create the thumbnail if a
+ * valid failure thumbnail exists.
+ *
+ * Return value: %TRUE if a failure thumbnail exists or
+ *
+ * Since: 2.2
+ **/
+gboolean
+gimp_thumbnail_has_failed (GimpThumbnail *thumbnail)
+{
+ GdkPixbuf *pixbuf;
+ const gchar *option;
+ gchar *filename;
+ gint64 image_mtime;
+ gint64 image_size;
+ gboolean failed = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_THUMBNAIL (thumbnail), FALSE);
+ g_return_val_if_fail (thumbnail->image_uri != NULL, FALSE);
+
+ GIMP_THUMB_DEBUG_CALL (thumbnail);
+
+ filename = gimp_thumb_name_from_uri (thumbnail->image_uri,
+ GIMP_THUMB_SIZE_FAIL);
+ if (! filename)
+ return FALSE;
+
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+ g_free (filename);
+
+ if (! pixbuf)
+ return FALSE;
+
+ if (gimp_thumbnail_peek_image (thumbnail) < GIMP_THUMB_STATE_EXISTS)
+ goto finish;
+
+ /* URI and mtime from the thumbnail need to match our file */
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_URI);
+ if (! option || strcmp (option, thumbnail->image_uri))
+ goto finish;
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_MTIME);
+ if (!option || sscanf (option, "%" G_GINT64_FORMAT, &image_mtime) != 1)
+ goto finish;
+
+ option = gdk_pixbuf_get_option (pixbuf, TAG_THUMB_FILESIZE);
+ if (option && sscanf (option, "%" G_GINT64_FORMAT, &image_size) != 1)
+ goto finish;
+
+ /* TAG_THUMB_FILESIZE is optional but must match if present */
+ if (image_mtime == thumbnail->image_mtime &&
+ (option == NULL || image_size == thumbnail->image_filesize))
+ {
+ failed = TRUE;
+ }
+
+ finish:
+ g_object_unref (pixbuf);
+
+ return failed;
+}