/* Egg Libraries: egg-pixbuf-thumbnail.c
 * 
 * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
 * Copyright (c) 2002 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

#include <stdlib.h>
#include <string.h>

/* #include <glib/gi18n.h> */

#define GDK_PIXBUF_ENABLE_BACKEND
#include <gdk-pixbuf/gdk-pixbuf-io.h>

#include "eggmd5.h"
#include "egg-pixbuf-thumbnail.h"
#include "intl.h"

/* FIXME: #include "gdk-pixbuf-private.h" */

/* 30 days in seconds */
#define EXPIRATION_TIME		2592000

/* Metadata Keys */
/* Standard */
#define THUMB_URI_KEY		"tEXt::Thumb::URI"
#define THUMB_MTIME_KEY		"tEXt::Thumb::MTime"
#define THUMB_MIME_TYPE_KEY	"tEXt::Thumb::Mimetype"
#define THUMB_FILESIZE_KEY	"tEXt::Thumb::Size"
#define THUMB_WIDTH_KEY		"tEXt::Thumb::Image::Width"
#define THUMB_HEIGHT_KEY	"tEXt::Thumb::Image::Height"
#define THUMB_PAGES_KEY		"tEXt::Thumb::Document::Pages"
#define THUMB_LENGTH_KEY	"tEXt::Thumb::Movie::Length"
#define THUMB_DESCRIPTION_KEY	"tEXt::Description"
#define THUMB_SOFTWARE_KEY	"tEXt::Software"

/* Custom */
#define THUMB_SIZE_KEY		"tEXt::X-GdkPixbuf::Size"
#define NORMAL_SIZE_NAME	"normal"
#define LARGE_SIZE_NAME		"large"

/* Used by failed creation attempts */
#define THUMB_ERROR_DOMAIN_KEY	"tEXt::X-GdkPixbuf::FailDomain"
#define FILE_ERROR_NAME		"file"
#define PIXBUF_ERROR_NAME	"pixbuf"
#define THUMB_ERROR_CODE_KEY	"tEXt::X-GdkPixbuf::FailCode"
#define THUMB_ERROR_MESSAGE_KEY	"tEXt::X-GdkPixbuf::FailMessage"

/* Misc Strings */
#define NORMAL_DIR_NAME		NORMAL_SIZE_NAME
#define LARGE_DIR_NAME		LARGE_SIZE_NAME
#define FAIL_DIR_NAME		"fail"
#define APP_DIR_NAME		"gdk-pixbuf-2"
#define THUMB_SOFTWARE_VALUE	"GdkPixbuf"


#define SIZE_TO_DIR(size) ({\
  const gchar *__r; \
  if (size == EGG_PIXBUF_THUMB_NORMAL) \
    __r = NORMAL_DIR_NAME; \
  else if (size == EGG_PIXBUF_THUMB_LARGE) \
    __r = LARGE_DIR_NAME; \
  else \
    __r = NULL; \
  __r; \
})


static gboolean ensure_thumbnail_dirs (GError **error);


typedef struct
{
  gint orig_width;
  gint orig_height;
  gint size;
}
ImageInfo;

static void
loader_size_prepared_cb (GdkPixbufLoader *loader,
			 gint             width,
			 gint             height,
			 ImageInfo       *info)
{
  info->orig_width = width;
  info->orig_height = height;

  if (info->size > 0 && (width > info->size || height > info->size))
    {
      gdouble scale;

      if (width > height)
	scale = (gdouble) info->size / width;
      else
	scale = (gdouble) info->size / height;

      gdk_pixbuf_loader_set_size (loader, width * scale, height * scale);
    }
}


static GdkPixbuf *
load_image_at_max_size (const gchar  *filename,
			ImageInfo    *info,
			gchar       **mime_type,
			GError      **error)
{
  GdkPixbuf *retval;
  GdkPixbufLoader *loader;
  gint fd;
  gssize result;
  guchar buffer[8192];

  fd = open (filename, O_RDONLY);
  if (fd < 0)
    {
      gchar *utf8_filename;

      utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
		   _("Error opening `%s': %s"), utf8_filename,
		   g_strerror (errno));
      g_free (utf8_filename);

      return NULL;
    }

  loader = gdk_pixbuf_loader_new ();
  g_signal_connect (loader, "size-prepared", G_CALLBACK (loader_size_prepared_cb), info);

  do
    {
      result = read (fd, buffer, sizeof (buffer));

      /* Die on read() error. */
      if (result < 0)
	{
	  gchar *utf8_filename;

	  gdk_pixbuf_loader_close (loader, NULL);
	  close (fd);
	  g_object_unref (loader);

	  utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
	  g_set_error (error, G_FILE_ERROR,
		       g_file_error_from_errno (errno),
		       _("Error reading `%s': %s"), utf8_filename,
		       g_strerror (errno));
	  g_free (utf8_filename);
	  return NULL;
	}
      /* Die on loader error. */
      else if (result > 0 &&
	       !gdk_pixbuf_loader_write (loader, buffer, result,
					 error))
	{
	  gdk_pixbuf_loader_close (loader, NULL);
	  close (fd);
	  g_object_unref (loader);
	  return NULL;
	}
    }
  while (result > 0);

  close (fd);

  if (!gdk_pixbuf_loader_close (loader, error))
    {
      g_object_unref (loader);
      return NULL;
    }

  retval = gdk_pixbuf_loader_get_pixbuf (loader);

  if (!retval)
    {
      gchar *utf8_filename;

      utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);

      g_set_error (error,
		   GDK_PIXBUF_ERROR,
		   GDK_PIXBUF_ERROR_FAILED,
		   _("Failed to load image '%s': reason not known, probably a corrupt image file"),
		   utf8_filename ? utf8_filename : "???");
      g_free (utf8_filename);
    }
  else
    {
      g_object_ref (retval);

      if (mime_type)
	{
	  GdkPixbufFormat *format;

	  format = gdk_pixbuf_loader_get_format (loader);
	  *mime_type = g_strdup (format->mime_types[0]);
	}
    }

  g_object_unref (loader);

  return retval;
}


/**
 * egg_pixbuf_get_thumbnail_for_file:
 * @filename: the path to the original file.
 * @size: the desired size of the thumnail.
 * @error: a pointer to a location to store errors in.
 * 
 * Convenience function which first checks if a failure attempt for @filename
 * exists. If it does, @error will be set to the reason for that failure. If it
 * does not, this function attempts to load the thumbnail for @filename at
 * @size. If that load fails, this function will attempt to create a new
 * thumbnail. If creating a new thumbnail fails, then a new failure attempt
 * will be saved.
 * 
 * In other words, this function handles all the intricasies of thumbnailing
 * local images internally, and you should see if using it makes sense before
 * trying more complicated schemes.
 * 
 * Returns: the thumbnail of @filename, or %NULL.
 * 
 * Since: 2.6
 **/
GdkPixbuf *
egg_pixbuf_get_thumbnail_for_file (const gchar         *filename,
				   EggPixbufThumbSize   size,
				   GError             **error)
{
  GdkPixbuf *retval;
  gchar *uri;
  struct stat st;

  g_return_val_if_fail (filename != NULL && filename[0] == '/', NULL);
  g_return_val_if_fail (size == EGG_PIXBUF_THUMB_NORMAL || size == EGG_PIXBUF_THUMB_LARGE, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (stat (filename, &st) < 0)
    {
      gchar *utf8_filename;

      utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
		   _("Error verifying `%s': %s"), utf8_filename, g_strerror (errno));
      g_free (utf8_filename);
      return NULL;
    }

  if (!S_ISREG (st.st_mode) && !S_ISLNK (st.st_mode))
    return NULL;

  uri = g_strconcat ("file://", filename, NULL);

  if (egg_pixbuf_has_failed_thumbnail (uri, st.st_mtime, error))
    {
      g_free (uri);
      return NULL;
    }

  retval = egg_pixbuf_load_thumbnail (uri, st.st_mtime, size);
  if (!retval)
    {
      GError *real_error;
      gchar *mime_type;
      ImageInfo info;

      info.size = size;
      mime_type = NULL;
      real_error = NULL;

      retval = load_image_at_max_size (filename, &info, &mime_type, &real_error);

      if (!retval)
	{
	  /*
	   * Don't save failures for filetypes not supported by GdkPixbuf.
	   * 
	   * I *think* this is the proper way to go, as there's no need to
	   * spill a half-gig of disk space telling the thumbnailing world
	   * that GdkPixbuf doesn't understand "blah/autogen.sh".
	   */
	  if (real_error->domain != GDK_PIXBUF_ERROR ||
	      real_error->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE)
	    egg_pixbuf_save_failed_thumbnail (uri, st.st_mtime, real_error);

	  if (error != NULL)
	    *error = real_error;
	  else
	    g_error_free (real_error);
	}
      else
	{
	  egg_pixbuf_set_thumb_size (retval, size);
	  egg_pixbuf_set_thumb_uri (retval, uri);
	  egg_pixbuf_set_thumb_mtime (retval, st.st_mtime);
	  egg_pixbuf_set_thumb_mime_type (retval, mime_type);
	  egg_pixbuf_set_thumb_image_width (retval, info.orig_width);
	  egg_pixbuf_set_thumb_image_height (retval, info.orig_height);
	  egg_pixbuf_set_thumb_filesize (retval, st.st_size);

	  egg_pixbuf_save_thumbnailv (retval, NULL, NULL, NULL);
	}

      g_free (mime_type);
    }

  g_free (uri);

  return retval;
}


static gboolean
check_uri_and_mtime (GdkPixbuf   *thumb,
		     const gchar *uri,
		     time_t       mtime)
{
  const gchar *tmp;

  tmp = egg_pixbuf_get_thumb_uri (thumb);
  if (tmp == NULL || strcmp (tmp, uri) == 0)
    return FALSE;

  if (egg_pixbuf_get_thumb_mtime (thumb) != mtime)
    return FALSE;

  return TRUE;
}


/**
 * egg_pixbuf_load_thumbnail:
 * @uri: the URI of the thumbnailed file.
 * @size: the size of the thumbnail to load.
 * @mtime: the last modified time of @uri, or %-1 if unknown.
 * 
 * Attempts to load the thumbnail for @uri at @size. If the thumbnail for @uri
 * at @size does not already exist, %NULL will be returned.
 * 
 * Returns: the thumbnail pixbuf of @uri at @size which must be un-referenced
 *  with g_object_unref() when no longer needed, or %NULL.
 * 
 * Since: 2.6
 **/
GdkPixbuf *
egg_pixbuf_load_thumbnail (const gchar        *uri,
			   time_t              mtime,
			   EggPixbufThumbSize  size)
{
  GdkPixbuf *retval;
  gchar *filename;

  g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
  g_return_val_if_fail (size == EGG_PIXBUF_THUMB_NORMAL || size == EGG_PIXBUF_THUMB_LARGE, NULL);

  filename = egg_pixbuf_get_thumb_filename (uri, size);
  retval = gdk_pixbuf_new_from_file (filename, NULL);
  g_free (filename);

  if (retval != NULL && !check_uri_and_mtime (retval, uri, mtime))
    {
      g_object_unref (retval);
      return NULL;
    }
  
  return retval;
}


/**
 * egg_pixbuf_load_thumbnail_at_size:
 * @uri: the URI of the thumbnailed file.
 * @mtime: the last modified time of @uri, or %-1 if unknown.
 * @pixel_size: the desired pixel size.
 * 
 * Attempts to load the thumbnail for @uri at @size. If the thumbnail for @uri
 * at @size does not already exist, %NULL will be returned.
 * 
 * Returns: the thumbnail pixbuf of @uri at @size which must be un-referenced
 *  with g_object_unref() when no longer needed, or %NULL.
 * 
 * Since: 2.6
 **/
GdkPixbuf *
egg_pixbuf_load_thumbnail_at_size (const gchar        *uri,
				   time_t              mtime,
				   gint                pixel_size)
{
  ImageInfo info;
  GdkPixbuf *retval;
  gchar *filename;

  g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);

  if (pixel_size <= EGG_PIXBUF_THUMB_NORMAL)
    {
      info.size = EGG_PIXBUF_THUMB_NORMAL;
      filename = egg_pixbuf_get_thumb_filename (uri, EGG_PIXBUF_THUMB_NORMAL);
    }
  else if (pixel_size <= EGG_PIXBUF_THUMB_LARGE)
    {
      info.size = EGG_PIXBUF_THUMB_LARGE;
      filename = egg_pixbuf_get_thumb_filename (uri, EGG_PIXBUF_THUMB_LARGE);
    }
  else if (strncmp (uri, "file://", 7) == 0)
    {
      info.size = -1;
      filename = g_strdup (uri + 7);
    }
  else
    {
      info.size = -1;
      filename = egg_pixbuf_get_thumb_filename (uri, EGG_PIXBUF_THUMB_LARGE);
    }

  retval = load_image_at_max_size (filename, &info, NULL, NULL);
  g_free (filename);

  if (retval != NULL && !check_uri_and_mtime (retval, uri, mtime))
    {
      g_object_unref (retval);
      return NULL;
    }

  return retval;
}


static void
collect_save_options (va_list   opts,
                      gchar  ***keys,
                      gchar  ***vals)
{
  gchar *key;
  gchar *val;
  gchar *next;
  gint count;

  count = 0;
  *keys = NULL;
  *vals = NULL;
  
  next = va_arg (opts, gchar*);
  while (next)
    {
      key = next;
      val = va_arg (opts, gchar*);

      ++count;

      /* woo, slow */
      *keys = g_realloc (*keys, sizeof(gchar*) * (count + 1));
      *vals = g_realloc (*vals, sizeof(gchar*) * (count + 1));
      
      (*keys)[count-1] = g_strdup (key);
      (*vals)[count-1] = g_strdup (val);

      (*keys)[count] = NULL;
      (*vals)[count] = NULL;
      
      next = va_arg (opts, gchar*);
    }
}


/**
 * egg_pixbuf_save_thumbnail:
 * @thumbnail: the valid thumbnail pixbuf to save.
 * @error: a location to a pointer to store an error.
 * @Varargs: a %NULL-terminated metadata key/value pair list.
 * 
 * Saves @thumbnail to it's appropriate file. Note that @thumbnail must have
 * it's URI, mtime, and size metadata set before this function is called. Any
 * additional metadata can be saved using the %NULL-terminated
 * variable-arguments list. If an error occurs while saving the thumbnail, a
 * failure thumbnail will be automatically created and saved.
 * 
 * Returns: %TRUE if the thumbnail was successfully saved, %FALSE if it was not.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_save_thumbnail (GdkPixbuf    *thumbnail,
			   GError      **error,
			   ...)
{
  va_list args;
  gboolean retval;
  gchar **keys, **values;

  g_return_val_if_fail (egg_pixbuf_has_thumbnail_data (thumbnail), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  keys = NULL;
  values = NULL;

  va_start (args, error);
  collect_save_options (args, &keys, &values);
  va_end (args);

  retval = egg_pixbuf_save_thumbnailv (thumbnail, keys, values, error);

  g_strfreev (values);
  g_strfreev (keys);

  return retval;
}


/**
 * egg_pixbuf_save_thumbnailv:
 * @thumbnail: the thumbnail to save.
 * @keys: a NULL-terminated array of metadata keys.
 * @values: a NULL-terminated array of metadata values.
 * @error: a pointer to a location to store errors in.
 * 
 * This function is primarily useful to language bindings. Applications should
 * use egg_pixbuf_save_thumbnail().
 * 
 * Returns: %TRUE if the thumbnail was successfully saved, %FALSE if it was not.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_save_thumbnailv (GdkPixbuf  *thumbnail,
			    gchar     **keys,
			    gchar     **values,
			    GError    **error)
{
  const gchar *uri;
  gchar *filename, *tmp_filename;
  gint fd;
  gboolean retval;
  GError *real_error;

  g_return_val_if_fail (egg_pixbuf_has_thumbnail_data (thumbnail), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!ensure_thumbnail_dirs (error))
    return FALSE;

  uri = egg_pixbuf_get_thumb_uri (thumbnail);
  filename =
    egg_pixbuf_get_thumb_filename (uri, egg_pixbuf_get_thumb_size (thumbnail));
  tmp_filename = g_strconcat (filename, ".XXXXXX", NULL);

  fd = g_mkstemp (tmp_filename);
  if (fd < 0)
    {
      real_error =
	g_error_new (G_FILE_ERROR,
		     g_file_error_from_errno (errno),
		     _("Error creating temporary thumbnail file for `%s': %s"),
		     uri, g_strerror (errno));
      g_free (tmp_filename);
      g_free (filename);
				  
      egg_pixbuf_save_failed_thumbnail (gdk_pixbuf_get_option (thumbnail,
							       THUMB_URI_KEY),
					egg_pixbuf_get_thumb_mtime (thumbnail),
					real_error);
      if (error != NULL)
	*error = real_error;
      else
	g_error_free (real_error);

      return FALSE;
    }
  else
    close (fd);

  real_error = NULL;
  retval = gdk_pixbuf_savev (thumbnail, tmp_filename, "png", keys, values,
			     &real_error);

  if (retval)
    {
      chmod (tmp_filename, 0600);
      rename (tmp_filename, filename);
    }
  else
    {
      egg_pixbuf_save_failed_thumbnail (gdk_pixbuf_get_option (thumbnail,
							       THUMB_URI_KEY),
					egg_pixbuf_get_thumb_mtime (thumbnail),
					real_error);

      if (error != NULL)
	*error = real_error;
      else
	g_error_free (real_error);
    }

  g_free (tmp_filename);
  g_free (filename);

  return retval;
}


/**
 * egg_pixbuf_has_failed_thumbnail:
 * @uri: the URI of the thumbnailed file to check.
 * @mtime: the last modified time of @uri.
 * @error: a pointer to a location to store an error.
 * 
 * Checks to see if creating a thumbnail for @uri which was changed on @mtime
 * has already been tried and failed. If it has, the error which prevented the
 * thumbnail from being created will be stored in @error.
 * 
 * Returns: %TRUE if the thumbnail creation has already failed, %FALSE if it
 *  has not.
 *
 * Since: 2.6
 **/
gboolean
egg_pixbuf_has_failed_thumbnail (const gchar  *uri,
				 time_t        mtime,
				 GError      **error)
{
  gchar *md5, *basename, *filename;
  gboolean retval;
  GdkPixbuf *thumb;

  g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  retval = FALSE;
  
  md5 = egg_str_get_md5_str (uri);
  basename = g_strconcat (md5, ".png", NULL);
  g_free (md5);
  filename = g_build_filename (g_get_home_dir (), ".thumbnails", FAIL_DIR_NAME,
			       APP_DIR_NAME, basename, NULL);
  thumb = gdk_pixbuf_new_from_file (filename, NULL);
  g_free (filename);

  if (thumb != NULL && check_uri_and_mtime (thumb, uri, mtime))
    {
      const gchar *tmp, *message;
      GQuark domain;
      gint code;

      domain = 0;
      code = G_MININT;
      message = NULL;

      tmp = gdk_pixbuf_get_option (thumb, THUMB_ERROR_DOMAIN_KEY);
      if (tmp != NULL)
	{
	  if (strcmp (tmp, FILE_ERROR_NAME) == 0)
	    domain = G_FILE_ERROR;
	  else if (strcmp (tmp, PIXBUF_ERROR_NAME) == 0)
	    domain = GDK_PIXBUF_ERROR;
	}

      tmp = gdk_pixbuf_get_option (thumb, THUMB_ERROR_CODE_KEY);
      if (tmp != NULL)
	code = atoi (tmp);

      message = gdk_pixbuf_get_option (thumb, THUMB_ERROR_MESSAGE_KEY);

      if (domain != 0 && code > G_MININT && message != NULL)
	g_set_error (error, domain, code, message);

      retval = TRUE;
    }

  return retval;
}


/**
 * egg_pixbuf_thumbnail_save_failed:
 * @uri: the URI which the thumbnail creation failed for.
 * @mtime: the time that @uri was last modified.
 * @error: the error which occurred while trying to create the thumbnail.
 * 
 * Saves a "failure thumbnail" for the @uri with @mtime. This lets other
 * applications using the EggPixbufThumbnail API know that a thumbnail attempt
 * was tried for @uri when it was modified last at @mtime. The @error parameter
 * lets other applications know exactly why the thumbnail creation failed.
 * 
 * <note>@error must be in either the #G_FILE_ERROR or #GDK_PIXBUF_ERROR
 * domain.</note>
 * 
 * Since: 2.6
 **/
void
egg_pixbuf_save_failed_thumbnail (const gchar  *uri,
				  time_t        mtime,
				  const GError *error)
{
  GdkPixbuf *pixbuf;
  GError *err_ret;
  gchar *md5, *basename, *filename, *tmp_filename, *mtime_str;
  gboolean saved_ok;
  gint fd;

  g_return_if_fail (uri != NULL && uri[0] != '\0');
  g_return_if_fail (error == NULL ||
		    error->domain == G_FILE_ERROR ||
		    error->domain == GDK_PIXBUF_ERROR);

  err_ret = NULL;
  if (!ensure_thumbnail_dirs (&err_ret))
    {
      g_warning ("%s", err_ret->message);
      g_error_free (err_ret);
      return;
    }

  md5 = egg_str_get_md5_str (uri);
  basename = g_strconcat (md5, ".png", NULL);
  g_free (md5);
  filename = g_build_filename (g_get_home_dir (), ".thumbnails", FAIL_DIR_NAME,
			       APP_DIR_NAME, basename, NULL);
  g_free (basename);

  tmp_filename = g_strconcat (filename, ".XXXXXX", NULL);
  fd = g_mkstemp (tmp_filename);
  if (fd < 0)
    {
      g_free (tmp_filename);
      g_free (filename);
      return;
    }
  else
    close (fd);

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);

  mtime_str = g_strdup_printf ("%ld", mtime);

  if (error != NULL)
    {
      gchar *code_str;
      const gchar *domain;

      if (error->domain == G_FILE_ERROR)
	domain = FILE_ERROR_NAME;
      else
	domain = PIXBUF_ERROR_NAME;

      code_str = g_strdup_printf ("%d", error->code);
      saved_ok = gdk_pixbuf_save (pixbuf, tmp_filename, "png", &err_ret,
				  THUMB_URI_KEY, uri,
				  THUMB_MTIME_KEY, mtime_str,
				  THUMB_ERROR_DOMAIN_KEY, domain,
				  THUMB_ERROR_CODE_KEY, code_str,
				  THUMB_ERROR_MESSAGE_KEY, error->message,
				  NULL);
      g_free (code_str);
    }
  else
    {
      saved_ok = gdk_pixbuf_save (pixbuf, tmp_filename, "png", &err_ret,
				  THUMB_URI_KEY, uri,
				  THUMB_MTIME_KEY, mtime_str,
				  NULL);
    }

  if (!saved_ok)
    {
      g_message ("Error saving fail thumbnail: %s", err_ret->message);
      g_error_free (err_ret);
    }
  else
    {
      chmod (tmp_filename, 0600);
      rename (tmp_filename, filename);
    }

  g_free (tmp_filename);
  g_free (filename);
  g_free (mtime_str);
}


/**
 * egg_pixbuf_create_thumbnail:
 * @pixbuf: the full-sized source pixbuf.
 * @size: the size of the thumbnailnail to generate.
 * @uri: the URI location of @pixbuf.
 * @mtime: the last-modified time of @uri.
 * 
 * Creates a thumbnail of the in-memory @pixbuf at @size, using @uri and
 * @mtime for the pre-requisite metadata.
 * 
 * Returns: a new thumbnail of @pixbuf which must be un-referenced with
 *  g_object_unref() when no longer needed, or %NULL.
 * 
 * Since: 2.6
 **/
GdkPixbuf *
egg_pixbuf_create_thumbnail (GdkPixbuf          *pixbuf,
			     const gchar        *uri,
			     time_t              mtime,
			     EggPixbufThumbSize  size)
{
  GdkPixbuf *retval;
  gint width, height;

  g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
  g_return_val_if_fail (size == EGG_PIXBUF_THUMB_NORMAL || size == EGG_PIXBUF_THUMB_LARGE, NULL);
  g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);

  width = gdk_pixbuf_get_width (pixbuf);
  height = gdk_pixbuf_get_height (pixbuf);

  if (width > size || height > size)
    {
      gdouble scale;

      if (width > height)
	scale = (gdouble) size / width;
      else
	scale = (gdouble) size / height;

      retval = gdk_pixbuf_scale_simple (pixbuf, scale * width, scale *height, GDK_INTERP_BILINEAR);
    }
  else
    {
      retval = gdk_pixbuf_copy (pixbuf);
    }

  egg_pixbuf_add_thumbnail_data (retval, uri, mtime, size);

  return retval;
}


/**
 * egg_pixbuf_add_thumbnail_data:
 * @pixbuf: the pixbuf to modify.
 * @size: the desired thumbnail size.
 * @uri: the escaped URI of the filename that @pixbuf is a thumbnail of.
 * @mtime: the last-modified time of @uri.
 * 
 * Convenience function to add the required metadata to @pixbuf so it can be
 * used as a thumbnail. Note that the dimensions of @pixbuf must be less than or
 * equal to @size pixels, and that some metadata may still have been added even
 * if this function returns %FALSE.
 * 
 * Returns: %TRUE if all the metadata was added, %FALSE if it was not.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_add_thumbnail_data (GdkPixbuf          *pixbuf,
			       const gchar        *uri,
			       time_t              mtime,
			       EggPixbufThumbSize  size)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);
  g_return_val_if_fail (size == EGG_PIXBUF_THUMB_NORMAL || size == EGG_PIXBUF_THUMB_LARGE, FALSE);
  g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);
  g_return_val_if_fail (gdk_pixbuf_get_height (pixbuf) <= size && gdk_pixbuf_get_width (pixbuf) <= size, FALSE);

  if (!egg_pixbuf_set_thumb_size (pixbuf, size))
    return FALSE;

  if (!egg_pixbuf_set_thumb_uri (pixbuf, uri))
    return FALSE;

  if (!egg_pixbuf_set_thumb_mtime (pixbuf, mtime))
    return FALSE;

  return TRUE;
}


/**
 * egg_pixbuf_has_thumbnail_data:
 * @pixbuf: the pixbuf to test.
 * 
 * Checks if @pixbuf has the minimum required metadata to be used as a thumbnail
 * by egg_pixbuf_save_thumbnail().
 * 
 * Returns: %TRUE if @pixbuf can be used as a thumbnail, %FALSE if it cannot.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_has_thumbnail_data (GdkPixbuf *pixbuf)
{
  const gchar *tmp;

  g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);

  if (gdk_pixbuf_get_option (pixbuf, THUMB_URI_KEY) == NULL)
    return FALSE;

  if (gdk_pixbuf_get_option (pixbuf, THUMB_MTIME_KEY) == NULL)
    return FALSE;

  tmp = gdk_pixbuf_get_option (pixbuf, THUMB_SIZE_KEY);
  if (tmp == NULL || (strcmp (tmp, NORMAL_SIZE_NAME) != 0 && strcmp (tmp, LARGE_SIZE_NAME) != 0))
    return FALSE;

  return TRUE;
}


/**
 * egg_pixbuf_get_thumb_size:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the escaped URI that @thumbnail is a preview of.
 * 
 * Returns: a character array which should not be modified or freed.
 * 
 * Since: 2.6
 **/
EggPixbufThumbSize
egg_pixbuf_get_thumb_size (GdkPixbuf *thumbnail)
{
  const gchar *tmp;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), EGG_PIXBUF_THUMB_UNKNOWN);

  tmp = gdk_pixbuf_get_option (thumbnail, THUMB_SIZE_KEY);
  if (tmp != NULL)
    {
      if (strcmp (tmp, NORMAL_SIZE_NAME) == 0)
	return EGG_PIXBUF_THUMB_NORMAL;
      else if (strcmp (tmp, LARGE_SIZE_NAME) == 0)
	return EGG_PIXBUF_THUMB_LARGE;
    }

  return EGG_PIXBUF_THUMB_UNKNOWN;
}


/**
 * egg_pixbuf_set_thumb_size:
 * @thumbnail: the thumbnail to modify.
 * @size: the size of @thumbnail.
 * 
 * Sets the size metadata for @thumbnail. This function may only be
 * called once for a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_size (GdkPixbuf          *thumbnail,
			   EggPixbufThumbSize  size)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);
  g_return_val_if_fail (size == EGG_PIXBUF_THUMB_NORMAL || size == EGG_PIXBUF_THUMB_LARGE, FALSE);

  return gdk_pixbuf_set_option (thumbnail, THUMB_SIZE_KEY, SIZE_TO_DIR (size));
}


/**
 * egg_pixbuf_get_thumb_uri:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the escaped URI that @thumbnail is a preview of.
 * 
 * Returns: a character array which should not be modified or freed.
 * 
 * Since: 2.6
 **/
G_CONST_RETURN gchar *
egg_pixbuf_get_thumb_uri (GdkPixbuf *thumbnail)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);

  return gdk_pixbuf_get_option (thumbnail, THUMB_URI_KEY);
}


/**
 * egg_pixbuf_set_thumb_uri:
 * @thumbnail: the thumbnail to modify.
 * @uri: the escaped URI that @thumbnail is a preview of.
 * 
 * Sets the URI metadata for @thumbnail. This function may only be
 * called once for a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_uri (GdkPixbuf   *thumbnail,
			  const gchar *uri)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);
  g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);

  return gdk_pixbuf_set_option (thumbnail, THUMB_URI_KEY, uri);
}


/**
 * egg_pixbuf_get_thumb_mime_type:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the mime-type of the file that @thumbnail is a preview of.
 * 
 * Returns: a character array which should not be modified or freed.
 * 
 * Since: 2.6
 **/
G_CONST_RETURN gchar *
egg_pixbuf_get_thumb_mime_type (GdkPixbuf *thumbnail)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);

  return gdk_pixbuf_get_option (thumbnail, THUMB_MIME_TYPE_KEY);
}


/**
 * egg_pixbuf_set_thumb_mime_type:
 * @thumbnail: the thumbnail to modify.
 * @mime_type: the mime type of the file that @thumbnail is a preview of.
 * 
 * Sets the @mime_type metadata for @thumbnail. This function may only be called
 * once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_mime_type (GdkPixbuf   *thumbnail,
				const gchar *mime_type)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);
  g_return_val_if_fail (mime_type != NULL && mime_type[0] != '\0', FALSE);

  return gdk_pixbuf_set_option (thumbnail, THUMB_MIME_TYPE_KEY, mime_type);
}


/**
 * egg_pixbuf_get_thumb_description:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the user-specified description (comment) for the file that
 * @thumbnail is a preview of. If this metadata was not saved (or does not
 * exist for @thumbnail), NULL will be returned.
 * 
 * Returns: a character array which should not be modified or freed.
 * 
 * Since: 2.6
 **/
G_CONST_RETURN gchar *
egg_pixbuf_get_thumb_description (GdkPixbuf *thumbnail)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);

  return gdk_pixbuf_get_option (thumbnail, THUMB_DESCRIPTION_KEY);
}


/**
 * egg_pixbuf_set_thumb_description:
 * @thumbnail: the thumbnail to modify.
 * @description: the description of the file that @thumbnail is a preview of.
 * 
 * Sets the user-specified @description metadata for @thumbnail. This function
 * may only be called once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_description (GdkPixbuf   *thumbnail,
				  const gchar *description)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);
  g_return_val_if_fail (description != NULL && description[0] != '\0', FALSE);

  return gdk_pixbuf_set_option (thumbnail, THUMB_DESCRIPTION_KEY, description);
}


/**
 * egg_pixbuf_get_thumb_mtime:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the UNIX time (seconds since the epoch/time_t) the file which
 * @thumbnail is a preview of was modified on when the @thumbnail was created.
 * If this metadata was not saved with @thumbnail, %-1 will be returned.
 * 
 * Returns: a 64-bit integer.
 * 
 * Since: 2.6
 **/
time_t
egg_pixbuf_get_thumb_mtime (GdkPixbuf *thumbnail)
{
  const gchar *tmp;
  time_t retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);

  tmp = gdk_pixbuf_get_option (thumbnail, THUMB_MTIME_KEY);
  if (tmp != NULL)
    {
      retval = g_ascii_strtoull (tmp, NULL, 10);

      if ((retval == G_MAXLONG) || (retval == G_MINLONG))
	retval = -1;
    }
  else
    {
      retval = -1;
    }

  return retval;
}


/**
 * egg_pixbuf_set_thumb_mtime:
 * @thumbnail: the thumbnail to modify.
 * @mtime: the last-modified time of the file that @thumbnail is a preview of.
 * 
 * Sets the last-modified @mtime metadata for @thumbnail. This function
 * may only be called once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_mtime (GdkPixbuf *thumbnail,
			    time_t     mtime)
{
  gchar *str;
  gboolean retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);

  str = g_strdup_printf ("%ld", mtime);
  retval = gdk_pixbuf_set_option (thumbnail, THUMB_MTIME_KEY, str);
  g_free (str);

  return retval;
}


/**
 * egg_pixbuf_get_thumb_filesize:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the size in bytes of the file which @thumbnail is a preview of. If
 * this metadata was not saved with @thumbnail, %-1 will be returned.
 * 
 * Returns: a 64-bit integer.
 * 
 * Since: 2.6
 **/
gssize
egg_pixbuf_get_thumb_filesize (GdkPixbuf *thumbnail)
{
  const gchar *tmp;
  gssize retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);

  tmp = gdk_pixbuf_get_option (thumbnail, THUMB_FILESIZE_KEY);
  if (tmp != NULL)
    {
      retval = g_ascii_strtoull (tmp, NULL, 10);

      if ((retval == G_MAXLONG) || (retval == G_MAXLONG))
	retval = -1;
    }
  else
    {
      retval = -1;
    }

  return retval;
}


/**
 * egg_pixbuf_set_thumb_filesize:
 * @thumbnail: the thumbnail to modify.
 * @mtime: the size (in bytes) of the file that @thumbnail is a preview of.
 * 
 * Sets the @filesize metadata for @thumbnail. This function may only be called
 * once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_filesize (GdkPixbuf *thumbnail,
			       gssize     filesize)
{
  gchar *str;
  gboolean retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);

//  str = g_strdup_printf ("%" G_GSSIZE_FORMAT, filesize);
  str = g_strdup_printf ("%u", filesize);
  retval = gdk_pixbuf_set_option (thumbnail, THUMB_FILESIZE_KEY, str);
  g_free (str);

  return retval;
}


/**
 * egg_pixbuf_get_thumb_image_width:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the width (in pixels) of the image contained in the file that
 * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
 * (e.g. if the original file was not an image), %-1 will be returned.
 * 
 * Returns: an integer.
 * 
 * Since: 2.6
 **/
gint
egg_pixbuf_get_thumb_image_width (GdkPixbuf *thumbnail)
{
  const gchar *tmp;
  gint retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);

  tmp =  gdk_pixbuf_get_option (thumbnail, THUMB_WIDTH_KEY);
  if (tmp != NULL)
    {
      retval = atoi (tmp);
      if (retval <= 0)
	retval = -1;
    }
  else
    {
      retval = -1;
    }

  return retval;
}


/**
 * egg_pixbuf_set_thumb_image_width:
 * @thumbnail: the thumbnail to modify.
 * @image_width: the width (in pixels) of the image file that @thumbnail is a preview of.
 * 
 * Sets the @image_width metadata for @thumbnail. This function may only be
 * called once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_image_width (GdkPixbuf *thumbnail,
				  gint       image_width)
{
  gchar *str;
  gboolean retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);

  str = g_strdup_printf ("%d", image_width);
  retval = gdk_pixbuf_set_option (thumbnail, THUMB_WIDTH_KEY, str);
  g_free (str);

  return retval;
}


/**
 * egg_pixbuf_get_thumb_image_height:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the height (in pixels) of the image contained in the file that
 * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
 * (e.g. if the original file was not an image), %-1 will be returned.
 * 
 * Returns: an integer.
 * 
 * Since: 2.6
 **/
gint
egg_pixbuf_get_thumb_image_height (GdkPixbuf *thumbnail)
{
  const gchar *tmp;
  gint retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);

  tmp =  gdk_pixbuf_get_option (thumbnail, THUMB_HEIGHT_KEY);
  if (tmp != NULL)
    {
      retval = atoi (tmp);
      if (retval <= 0)
	retval = -1;
    }
  else
    {
      retval = -1;
    }

  return retval;
}


/**
 * egg_pixbuf_set_thumb_image_height:
 * @thumbnail: the thumbnail to modify.
 * @image_height: the height (in pixels) of the image file that @thumbnail is a preview of.
 * 
 * Sets the @image_height metadata for @thumbnail. This function may only be
 * called once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_image_height (GdkPixbuf *thumbnail,
				   gint       image_height)
{
  gchar *str;
  gboolean retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);

  str = g_strdup_printf ("%d", image_height);
  retval = gdk_pixbuf_set_option (thumbnail, THUMB_HEIGHT_KEY, str);
  g_free (str);

  return retval;
}


/**
 * egg_pixbuf_get_thumb_document_pages:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the number of pages in the document contained in the file that
 * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
 * (e.g. if the original file was not a paged document), %-1 will be returned.
 * 
 * Returns: an integer.
 * 
 * Since: 2.6
 **/
gint
egg_pixbuf_get_thumb_document_pages (GdkPixbuf *thumbnail)
{
  const gchar *tmp;
  gint retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);

  tmp =  gdk_pixbuf_get_option (thumbnail, THUMB_PAGES_KEY);
  if (tmp != NULL)
    {
      retval = atoi (tmp);
      if (retval <= 0)
	retval = -1;
    }
  else
    {
      retval = -1;
    }

  return retval;
}


/**
 * egg_pixbuf_set_thumb_document_pages:
 * @thumbnail: the thumbnail to modify.
 * @document_pages: the number of pages in the document file that @thumbnail is
 *   a preview of.
 * 
 * Sets the @document_pages metadata for @thumbnail. This function may only be
 * called once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_document_pages (GdkPixbuf *thumbnail,
				     gint       document_pages)
{
  gchar *str;
  gboolean retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);

  str = g_strdup_printf ("%d", document_pages);
  retval = gdk_pixbuf_set_option (thumbnail, THUMB_PAGES_KEY, str);
  g_free (str);

  return retval;
}


/**
 * egg_pixbuf_get_thumb_movie_length:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the length (in seconds) of the movie contained in the file that
 * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
 * (e.g. if the original file was not a movie), %-1 will be returned.
 * 
 * Returns: a 64-bit integer.
 * 
 * Since: 2.6
 **/
time_t
egg_pixbuf_get_thumb_movie_length (GdkPixbuf *thumbnail)
{
  const gchar *tmp;
  time_t retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);

  tmp = gdk_pixbuf_get_option (thumbnail, THUMB_LENGTH_KEY);
  if (tmp != NULL)
    {
      retval = g_ascii_strtoull (tmp, NULL, 10);

      if ((retval == G_MAXLONG) || (retval == G_MINLONG))
	retval = -1;
    }
  else
    {
      retval = -1;
    }

  return retval;
}


/**
 * egg_pixbuf_set_thumb_filesize:
 * @thumbnail: the thumbnail to modify.
 * @movie_length: the length (in seconds) of the movie file that @thumbnail is a preview of.
 * 
 * Sets the @movie_length metadata for @thumbnail. This function may only be
 * called once on a particular pixbuf.
 * 
 * Returns: whether or not the metadata for @thumbnail was changed.
 * 
 * Since: 2.6
 **/
gboolean
egg_pixbuf_set_thumb_movie_length (GdkPixbuf *thumbnail,
				   time_t     movie_length)
{
  gchar *str;
  gboolean retval;

  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);

  str = g_strdup_printf ("%ld", movie_length);
  retval = gdk_pixbuf_set_option (thumbnail, THUMB_LENGTH_KEY, str);
  g_free (str);

  return retval;
}


/**
 * egg_pixbuf_get_thumb_software:
 * @thumbnail: the thumbnail to examine.
 * 
 * Retreives the name of the software which originally created @thumbnail. If
 * this metadata was not saved (or does not exist for @thumbnail), NULL will be
 * returned.
 * 
 * Returns: a character array which should not be modified or freed.
 * 
 * Since: 2.6
 **/
G_CONST_RETURN gchar *
egg_pixbuf_get_thumb_software (GdkPixbuf *thumbnail)
{
  g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);

  return gdk_pixbuf_get_option (thumbnail, THUMB_SOFTWARE_KEY);
}


/**
 * egg_pixbuf_get_thumb_filename:
 * @uri: the URI of the file to thumbnail.
 * @size: the desired size of the thumbnail.
 * 
 * Constructs the correct thumbnail filename for @uri at @size.
 * 
 * Returns: a newly-allocated character array which should be freed with
 *  g_free() when no longer needed.
 *
 * Since: 2.6
 **/
gchar *
egg_pixbuf_get_thumb_filename (const gchar        *uri,
			       EggPixbufThumbSize  size)
{
  const gchar *home_dir = NULL;
  gchar *md5, *basename, *filename;

  g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
  g_return_val_if_fail (size == EGG_PIXBUF_THUMB_NORMAL || size == EGG_PIXBUF_THUMB_LARGE, FALSE);

  if (home_dir == NULL)
    home_dir = g_get_home_dir ();
  else
    home_dir = g_get_tmp_dir ();

  md5 = egg_str_get_md5_str (uri);
  basename = g_strconcat (md5, ".png", NULL);
  filename = g_build_filename (home_dir, ".thumbnails", SIZE_TO_DIR (size), basename, NULL);
  g_free (basename);

  return filename;
}


static gboolean
ensure_one_dir (const gchar *path, GError **error)
{
  if (!g_file_test (path, G_FILE_TEST_IS_DIR) && mkdir (path, 0700) < 0)
    {
      gchar *utf8_path;

      utf8_path = g_filename_to_utf8 (path, -1, NULL, NULL, NULL);
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
		   _("Error creating directory `%s': %s"), utf8_path,
		   g_strerror (errno));
      g_free (utf8_path);
      return FALSE;
    }

  return TRUE;
}


static gboolean
ensure_thumbnail_dirs (GError **error)
{
  gchar *path, *tmp;

  path = g_build_filename (g_get_home_dir (), ".thumbnails", NULL);
  if (!ensure_one_dir (path, error))
    {
      g_free (path);
      return FALSE;
    }

  tmp = path;
  path = g_build_filename (tmp, NORMAL_SIZE_NAME, NULL);
  if (!ensure_one_dir (path, error))
    {
      g_free (path);
      g_free (tmp);
      return FALSE;
    }

  g_free (path);

  path = g_build_filename (tmp, LARGE_SIZE_NAME, NULL);
  if (!ensure_one_dir (path, error))
    {
      g_free (path);
      g_free (tmp);
      return FALSE;
    }

  g_free (path);

  path = g_build_filename (tmp, FAIL_DIR_NAME, NULL);
  if (!ensure_one_dir (path, error))
    {
      g_free (path);
      g_free (tmp);
      return FALSE;
    }

  g_free (path);

  path = g_build_filename (tmp, FAIL_DIR_NAME, APP_DIR_NAME, NULL);
  if (!ensure_one_dir (path, error))
    {
      g_free (path);
      g_free (tmp);
      return FALSE;
    }

  g_free (tmp);
  g_free (path);
  return TRUE;
}
