1 /* Egg Libraries: egg-pixbuf-thumbnail.c
2  *
3  * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
4  * Copyright (c) 2002 Red Hat, Inc.
5  *
6  * Minor changes by AMR for compatibility with GTK+-2.0
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif /* HAVE_CONFIG_H */
27 
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #include <stdio.h>
33 #include <fcntl.h>
34 
35 #include <stdlib.h>
36 #include <string.h>
37 
38 
39 // AMR - not available with GTK+ <2.4/2.6
40 #if 0
41 #include <glib/gi18n.h>
42 #include <glib/gstdio.h>
43 #else
44 #define _
45 #define g_filename_display_name g_strdup
46 #define g_fopen fopen
47 #endif
48 
49 #include <gdk-pixbuf/gdk-pixbuf-io.h>
50 
51 #include "eggmd5.h"
52 #include "egg-pixbuf-thumbnail.h"
53 
54 
55 /* **************** *
56  *  Private Macros  *
57  * **************** */
58 
59 /* 30 days in seconds */
60 #define EXPIRATION_TIME		2592000
61 
62 /* Metadata Keys */
63 /* Standard */
64 #define THUMB_URI_KEY		"tEXt::Thumb::URI"
65 #define THUMB_MTIME_KEY		"tEXt::Thumb::MTime"
66 #define THUMB_MIME_TYPE_KEY	"tEXt::Thumb::Mimetype"
67 #define THUMB_FILESIZE_KEY	"tEXt::Thumb::Size"
68 #define THUMB_WIDTH_KEY		"tEXt::Thumb::Image::Width"
69 #define THUMB_HEIGHT_KEY	"tEXt::Thumb::Image::Height"
70 #define THUMB_PAGES_KEY		"tEXt::Thumb::Document::Pages"
71 #define THUMB_LENGTH_KEY	"tEXt::Thumb::Movie::Length"
72 #define THUMB_DESCRIPTION_KEY	"tEXt::Description"
73 #define THUMB_SOFTWARE_KEY	"tEXt::Software"
74 
75 /* Used by failed creation attempts */
76 #define THUMB_ERROR_DOMAIN_KEY	"tEXt::X-GdkPixbuf::FailDomain"
77 #define FILE_ERROR_NAME		"file"
78 #define PIXBUF_ERROR_NAME	"pixbuf"
79 #define THUMB_ERROR_CODE_KEY	"tEXt::X-GdkPixbuf::FailCode"
80 #define THUMB_ERROR_MESSAGE_KEY	"tEXt::X-GdkPixbuf::FailMessage"
81 
82 /* Misc Strings */
83 #define NORMAL_DIR_NAME		"normal"
84 #define LARGE_DIR_NAME		"large"
85 #define FAIL_DIR_NAME		"fail"
86 #define APP_DIR_NAME		"gdk-pixbuf-2"
87 #define THUMB_SOFTWARE_VALUE	"GdkPixbuf"
88 
89 
90 #define THUMBNAIL_DATA_QUARK	(thumbnail_data_get_quark ())
91 #define SIZE_TO_DIR(size) ({\
92   const gchar *__r; \
93   if (size == EGG_PIXBUF_THUMBNAIL_NORMAL) \
94     __r = NORMAL_DIR_NAME; \
95   else if (size == EGG_PIXBUF_THUMBNAIL_LARGE) \
96     __r = LARGE_DIR_NAME; \
97   else \
98     __r = NULL; \
99   __r; \
100 })
101 
102 
103 /* ******************** *
104  *  Private Structures  *
105  * ******************** */
106 
107 typedef struct
108 {
109   /* Required */
110   gint size;
111   gchar *uri;
112   time_t mtime;
113 
114   /* Optional Generic */
115   long filesize;  // Change by AMR for GTK+-2.0 compatibility
116   gchar *mime_type;
117   gchar *description;
118   gchar *software;
119 
120   /* Optional Image */
121   gint image_width;
122   gint image_height;
123 
124   /* Optional Document */
125   gint document_pages;
126 
127   /* Optional Movie */
128   time_t movie_length;
129 }
130 ThumbnailData;
131 
132 typedef struct
133 {
134   gint orig_width;
135   gint orig_height;
136   gint size;
137 }
138 ImageInfo;
139 
140 
141 /* ************************* *
142  *  ThumbnailData Functions  *
143  * ************************* */
144 
145 static GQuark
thumbnail_data_get_quark(void)146 thumbnail_data_get_quark (void)
147 {
148   static GQuark quark = 0;
149 
150   if (G_UNLIKELY (quark == 0))
151     quark = g_quark_from_static_string ("egg-pixbuf-thumbnail-data");
152 
153   return quark;
154 }
155 
156 static void
thumbnail_data_free(ThumbnailData * data)157 thumbnail_data_free (ThumbnailData *data)
158 {
159   if (!data)
160     return;
161 
162   g_free (data->uri);
163   g_free (data->mime_type);
164   g_free (data->description);
165   g_free (data->software);
166   g_free (data);
167 }
168 
169 static ThumbnailData *
get_thumbnail_data(GdkPixbuf * thumbnail)170 get_thumbnail_data (GdkPixbuf *thumbnail)
171 {
172   return g_object_get_qdata (G_OBJECT (thumbnail), THUMBNAIL_DATA_QUARK);
173 }
174 
175 static ThumbnailData *
ensure_thumbnail_data(GdkPixbuf * thumbnail)176 ensure_thumbnail_data (GdkPixbuf *thumbnail)
177 {
178   ThumbnailData *data;
179 
180   data = get_thumbnail_data (thumbnail);
181 
182   if (!data)
183     {
184       data = g_new (ThumbnailData, 1);
185       data->uri = NULL;
186       data->mtime = -1;
187       data->size = EGG_PIXBUF_THUMBNAIL_UNKNOWN;
188       data->filesize = -1;
189       data->mime_type = NULL;
190       data->description = NULL;
191       data->software = NULL;
192       data->image_width = -1;
193       data->image_height = -1;
194       data->document_pages = -1;
195       data->movie_length = -1;
196 
197       g_object_set_qdata_full (G_OBJECT (thumbnail), THUMBNAIL_DATA_QUARK, data,
198 			       (GDestroyNotify) thumbnail_data_free);
199     }
200 
201   return data;
202 }
203 
204 static gboolean
parse_thumbnail_data(GdkPixbuf * thumbnail,EggPixbufThumbnailSize size,GError ** error)205 parse_thumbnail_data (GdkPixbuf              *thumbnail,
206 		      EggPixbufThumbnailSize  size,
207 		      GError                **error)
208 {
209   ThumbnailData *data;
210   const gchar *tmp;
211   glong value;
212   guint8 data_required;
213 
214   data_required = 2;
215   data = NULL;
216 
217   if (error)
218     {
219       GQuark domain;
220 
221       tmp = gdk_pixbuf_get_option (thumbnail, THUMB_ERROR_DOMAIN_KEY);
222       if (tmp)
223 	domain = g_quark_from_string (tmp);
224       else
225 	domain = g_quark_from_static_string ("egg-pixbuf-thumbnail-error");
226 
227       tmp = gdk_pixbuf_get_option (thumbnail, THUMB_ERROR_CODE_KEY);
228       if (tmp)
229 	value = strtol (tmp, NULL, 10);
230       else
231 	value = -1;
232 
233       tmp = gdk_pixbuf_get_option (thumbnail, THUMB_ERROR_MESSAGE_KEY);
234 
235       *error = g_error_new_literal (domain, value, tmp);
236     }
237 
238   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_URI_KEY);
239   if (tmp)
240     {
241       data = ensure_thumbnail_data (thumbnail);
242       data->uri = g_strdup (tmp);
243       data_required--;
244     }
245 
246   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_MTIME_KEY);
247   if (tmp)
248     {
249       data = ensure_thumbnail_data (thumbnail);
250       value = strtol (tmp, NULL, 10);
251       if (value != G_MAXINT && value != G_MININT)
252 	{
253 	  data->mtime = value;
254 	  data_required--;
255 	}
256     }
257 
258   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_MIME_TYPE_KEY);
259   if (tmp)
260     {
261       data = ensure_thumbnail_data (thumbnail);
262       data->mime_type = g_strdup (tmp);
263     }
264 
265   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_DESCRIPTION_KEY);
266   if (tmp)
267     {
268       data = ensure_thumbnail_data (thumbnail);
269       data->description = g_strdup (tmp);
270     }
271 
272   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_SOFTWARE_KEY);
273   if (tmp)
274     {
275       data = ensure_thumbnail_data (thumbnail);
276       data->software = g_strdup (tmp);
277     }
278 
279   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_FILESIZE_KEY);
280   if (tmp)
281     {
282       data = ensure_thumbnail_data (thumbnail);
283       value = strtol (tmp, NULL, 10);
284       if (value != G_MAXINT && value != G_MININT)
285 	data->filesize = value;
286     }
287 
288   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_WIDTH_KEY);
289   if (tmp)
290     {
291       data = ensure_thumbnail_data (thumbnail);
292       value = strtol (tmp, NULL, 10);
293       if (value != G_MAXINT && value != G_MININT)
294 	data->image_width = value;
295     }
296 
297   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_HEIGHT_KEY);
298   if (tmp)
299     {
300       data = ensure_thumbnail_data (thumbnail);
301       value = strtol (tmp, NULL, 10);
302       if (value != G_MAXINT && value != G_MININT)
303 	data->image_height = value;
304     }
305 
306   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_PAGES_KEY);
307   if (tmp)
308     {
309       data = ensure_thumbnail_data (thumbnail);
310       value = strtol (tmp, NULL, 10);
311       if (value != G_MAXINT && value != G_MININT)
312 	data->document_pages = value;
313     }
314 
315   tmp = gdk_pixbuf_get_option (thumbnail, THUMB_LENGTH_KEY);
316   if (tmp)
317     {
318       data = ensure_thumbnail_data (thumbnail);
319       value = strtol (tmp, NULL, 10);
320       if (value != G_MAXINT && value != G_MININT)
321 	data->movie_length = value;
322     }
323 
324   if (!data_required)
325     data->size = size;
326 
327   return (!data_required);
328 }
329 
330 
331 /* *************************************** *
332  *  Global Thumbnails Directory Functions  *
333  * *************************************** */
334 
335 static gboolean
ensure_one_dir(const gchar * path,GError ** error)336 ensure_one_dir (const gchar *path,
337 		GError     **error)
338 {
339 #ifdef WIN32
340   if (!g_file_test (path, G_FILE_TEST_IS_DIR) && mkdir (path) < 0)
341 #else
342   if (!g_file_test (path, G_FILE_TEST_IS_DIR) && mkdir (path, 0700) < 0)
343 #endif
344     {
345       gchar *utf8_path;
346 
347       utf8_path = g_filename_to_utf8 (path, -1, NULL, NULL, NULL);
348       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
349 		   _("Error creating directory `%s': %s"), utf8_path,
350 		   g_strerror (errno));
351       g_free (utf8_path);
352       return FALSE;
353     }
354 
355   return TRUE;
356 }
357 
358 static gboolean
ensure_thumbnail_dirs(GError ** error)359 ensure_thumbnail_dirs (GError **error)
360 {
361   gchar *path, *tmp;
362 
363   path = g_build_filename (g_get_home_dir (), ".thumbnails", NULL);
364   if (!ensure_one_dir (path, error))
365     {
366       g_free (path);
367       return FALSE;
368     }
369 
370   tmp = path;
371   path = g_build_filename (tmp, NORMAL_DIR_NAME, NULL);
372   if (!ensure_one_dir (path, error))
373     {
374       g_free (path);
375       g_free (tmp);
376       return FALSE;
377     }
378 
379   g_free (path);
380 
381   path = g_build_filename (tmp, LARGE_DIR_NAME, NULL);
382   if (!ensure_one_dir (path, error))
383     {
384       g_free (path);
385       g_free (tmp);
386       return FALSE;
387     }
388 
389   g_free (path);
390 
391   path = g_build_filename (tmp, FAIL_DIR_NAME, NULL);
392   if (!ensure_one_dir (path, error))
393     {
394       g_free (path);
395       g_free (tmp);
396       return FALSE;
397     }
398 
399   g_free (path);
400 
401   path = g_build_filename (tmp, FAIL_DIR_NAME, APP_DIR_NAME, NULL);
402   if (!ensure_one_dir (path, error))
403     {
404       g_free (path);
405       g_free (tmp);
406       return FALSE;
407     }
408 
409   g_free (tmp);
410   g_free (path);
411   return TRUE;
412 }
413 
414 
415 /* ****************************** *
416  *  Thumbnail Creation Functions  *
417  * ****************************** */
418 
419 static void
loader_size_prepared_cb(GdkPixbufLoader * loader,gint width,gint height,gpointer user_data)420 loader_size_prepared_cb (GdkPixbufLoader *loader,
421 			 gint             width,
422 			 gint             height,
423 			 gpointer         user_data)
424 {
425   ImageInfo *info;
426 
427   info = user_data;
428   info->orig_width = width;
429   info->orig_height = height;
430 
431   if (info->size > 0 && (width > info->size || height > info->size))
432     {
433       gdouble scale;
434 
435       if (width > height)
436 	scale = (gdouble) info->size / width;
437       else
438 	scale = (gdouble) info->size / height;
439 
440       gdk_pixbuf_loader_set_size (loader, width * scale, height * scale);
441     }
442 }
443 
444 static GdkPixbuf *
load_image_at_max_size(const gchar * filename,ImageInfo * info,gchar ** mime_type,GError ** error)445 load_image_at_max_size (const gchar *filename,
446 			ImageInfo   *info,
447 			gchar      **mime_type,
448 			GError     **error)
449 {
450   GdkPixbuf *retval;
451   GdkPixbufLoader *loader;
452   FILE *f;
453   gsize result;
454   guchar buffer[8192];
455 
456   f = g_fopen (filename, "rb");
457   if (!f)
458     {
459       gchar *display_name;
460 
461       display_name = g_filename_display_name (filename);
462       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
463 		   _("Error opening `%s': %s"), display_name,
464 		   g_strerror (errno));
465       g_free (display_name);
466 
467       return NULL;
468     }
469 
470   loader = gdk_pixbuf_loader_new ();
471   g_signal_connect (loader, "size-prepared",
472 		    G_CALLBACK (loader_size_prepared_cb), info);
473 
474   result = fread (buffer, 1, sizeof (buffer), f);
475   if (!result)
476     {
477       gchar *display_name;
478 
479       display_name = g_filename_display_name (filename);
480       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
481 		   _("Error reading `%s': file contains no data."),
482 		   display_name);
483       g_free (display_name);
484       fclose (f);
485       return NULL;
486     }
487 
488   fseek (f, 0, SEEK_SET);
489 
490   do
491     {
492       result = fread (buffer, 1, sizeof (buffer), f);
493 
494       /* Die on read() error. */
495       if (!result && ferror (f))
496 	{
497 	  gchar *display_name;
498 
499 	  gdk_pixbuf_loader_close (loader, NULL);
500 	  fclose (f);
501 	  g_object_unref (loader);
502 
503 	  display_name = g_filename_display_name (filename);
504 	  g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
505 		       _("Error reading `%s': %s"), display_name,
506 		       g_strerror (errno));
507 	  g_free (display_name);
508 	  return NULL;
509 	}
510       /* Die on loader error. */
511       else if (!gdk_pixbuf_loader_write (loader, buffer, result, error))
512 	{
513 	  gdk_pixbuf_loader_close (loader, NULL);
514 	  fclose (f);
515 	  g_object_unref (loader);
516 	  return NULL;
517 	}
518     }
519   while (!feof (f));
520 
521   fclose (f);
522 
523   if (!gdk_pixbuf_loader_close (loader, error))
524     {
525       g_object_unref (loader);
526       return NULL;
527     }
528 
529   retval = gdk_pixbuf_loader_get_pixbuf (loader);
530 
531   if (!retval)
532     {
533       gchar *display_name;
534 
535       display_name = g_filename_display_name (filename);
536 
537       g_set_error (error,
538 		   GDK_PIXBUF_ERROR,
539 		   GDK_PIXBUF_ERROR_FAILED,
540 		   _("Failed to load image '%s': reason not known, probably a corrupt image file"),
541 		   display_name ? display_name : "???");
542       g_free (display_name);
543     }
544   else
545     {
546       g_object_ref (retval);
547 
548       if (mime_type)
549 	{
550 	  GdkPixbufFormat *format;
551 	  gchar **mime_types;
552 
553 	  format = gdk_pixbuf_loader_get_format (loader);
554 	  mime_types = gdk_pixbuf_format_get_mime_types (format);
555 	  if (mime_types && mime_types[0])
556 	    *mime_type = g_strdup (mime_types[0]);
557 	  g_strfreev (mime_types);
558 	}
559     }
560 
561   g_object_unref (loader);
562 
563   return retval;
564 }
565 
566 /**
567  * egg_pixbuf_get_thumbnail_for_file:
568  * @filename: the path to the original file.
569  * @size: the desired size of the thumnail.
570  * @error: a pointer to a location to store errors in.
571  *
572  * Convenience function which first checks if a failure attempt for @filename
573  * exists. If it does, @error will be set to the reason for that failure. If it
574  * does not, this function attempts to load the thumbnail for @filename at
575  * @size. If that load fails, this function will attempt to create a new
576  * thumbnail. If creating a new thumbnail fails, then a new failure attempt
577  * will be saved.
578  *
579  * In other words, this function handles all the intricasies of thumbnailing
580  * local images internally, and you should see if using it makes sense before
581  * trying more complicated schemes.
582  *
583  * Returns: the thumbnail of @filename, or %NULL.
584  *
585  * Since: 2.6
586  **/
587 GdkPixbuf *
egg_pixbuf_get_thumbnail_for_file(const gchar * filename,EggPixbufThumbnailSize size,GError ** error)588 egg_pixbuf_get_thumbnail_for_file (const gchar            *filename,
589 				   EggPixbufThumbnailSize  size,
590 				   GError                **error)
591 {
592   GdkPixbuf *retval;
593   gchar *uri;
594   struct stat st;
595 
596 #ifdef WIN32
597 //  g_return_val_if_fail (filename != NULL && (filename[1] == ':' || filename[1] == '\\'), NULL);
598 #else
599 //  g_return_val_if_fail (filename != NULL && filename[0] == '/', NULL);
600 #endif
601   g_return_val_if_fail(g_path_is_absolute(filename),NULL);
602 
603   g_return_val_if_fail (size == EGG_PIXBUF_THUMBNAIL_NORMAL ||
604 			size == EGG_PIXBUF_THUMBNAIL_LARGE, NULL);
605   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
606 
607   if (stat (filename, &st) < 0)
608     {
609       gchar *display_name;
610 
611       display_name = g_filename_display_name (filename);
612       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
613 		   _("Error verifying `%s': %s"), display_name,
614 		   g_strerror (errno));
615       g_free (display_name);
616       return NULL;
617     }
618 
619 #ifdef WIN32
620   if (!S_ISREG (st.st_mode))
621 #else
622   if (!S_ISREG (st.st_mode) && !S_ISLNK (st.st_mode))
623 #endif
624     {
625       gchar *display_name;
626 
627       display_name = g_filename_display_name (filename);
628       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
629 		   _("Error reading `%s': file is not a regular file or symbolic link."),
630 		   display_name);
631       g_free (display_name);
632       return NULL;
633     }
634 
635   uri = g_filename_to_uri (filename, NULL, error);
636   if (!uri)
637     {
638       return NULL;
639     }
640 
641   if (egg_pixbuf_has_failed_thumbnail (uri, st.st_mtime, error))
642     {
643       g_free (uri);
644       return NULL;
645     }
646 
647   retval = egg_pixbuf_load_thumbnail (uri, st.st_mtime, size);
648   if (!retval)
649     {
650       GError *real_error;
651       gchar *mime_type;
652       ImageInfo info;
653 
654       info.size = size;
655       mime_type = NULL;
656       real_error = NULL;
657 
658       retval = load_image_at_max_size (filename, &info, &mime_type,
659 				       &real_error);
660 
661       if (!retval)
662 	{
663 	  /*
664 	   * Don't save failures for filetypes not supported by GdkPixbuf.
665 	   *
666 	   * I *think* this is the proper way to go, as there's no need to
667 	   * spill a half-gig of disk space telling the thumbnailing world
668 	   * that GdkPixbuf doesn't understand "blah/autogen.sh".
669 	   */
670 	  if (real_error->domain != GDK_PIXBUF_ERROR ||
671 	      real_error->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE)
672 	    egg_pixbuf_save_failed_thumbnail (uri, st.st_mtime, real_error);
673 
674 	  if (error != NULL)
675 	    *error = real_error;
676 	  else
677 	    g_error_free (real_error);
678 	}
679       else
680 	{
681 	  ThumbnailData *data;
682 
683 	  data = ensure_thumbnail_data (retval);
684 	  data->size = size;
685 	  data->uri = g_strdup (uri);
686 	  data->mtime = st.st_mtime;
687 	  data->mime_type = g_strdup (mime_type);
688 	  data->description = g_strdup (gdk_pixbuf_get_option (retval,
689 							       THUMB_DESCRIPTION_KEY));
690 	  data->mime_type = g_strdup (mime_type);
691 	  data->image_width = info.orig_width;
692 	  data->image_height = info.orig_height;
693 	  data->filesize = st.st_size;
694 
695 	  egg_pixbuf_save_thumbnailv (retval, NULL, NULL, NULL);
696 	}
697 
698       g_free (mime_type);
699     }
700 
701   g_free (uri);
702 
703   return retval;
704 }
705 
706 /**
707  * egg_pixbuf_load_thumbnail:
708  * @uri: the URI of the thumbnailed file.
709  * @size: the size of the thumbnail to load.
710  * @mtime: the last modified time of @uri, or %-1 if unknown.
711  *
712  * Attempts to load the thumbnail for @uri at @size. If the thumbnail for @uri
713  * at @size does not already exist, %NULL will be returned.
714  *
715  * Returns: the thumbnail pixbuf of @uri at @size which must be un-referenced
716  *  with g_object_unref() when no longer needed, or %NULL.
717  *
718  * Since: 2.6
719  **/
720 GdkPixbuf *
egg_pixbuf_load_thumbnail(const gchar * uri,time_t mtime,EggPixbufThumbnailSize size)721 egg_pixbuf_load_thumbnail (const gchar           *uri,
722 			   time_t                 mtime,
723 			   EggPixbufThumbnailSize size)
724 {
725   GdkPixbuf *retval;
726   gchar *filename;
727 
728   g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
729   g_return_val_if_fail (mtime >= 0, NULL);
730   g_return_val_if_fail (size == EGG_PIXBUF_THUMBNAIL_NORMAL ||
731 			size == EGG_PIXBUF_THUMBNAIL_LARGE, NULL);
732 
733   filename = egg_pixbuf_get_thumbnail_filename (uri, size);
734   retval = gdk_pixbuf_new_from_file (filename, NULL);
735   g_free (filename);
736 
737   if (retval != NULL &&
738       (!parse_thumbnail_data (retval, size, NULL) ||
739        !egg_pixbuf_is_thumbnail (retval, uri, mtime)))
740     {
741       g_object_unref (retval);
742       return NULL;
743     }
744 
745   return retval;
746 }
747 
748 /**
749  * egg_pixbuf_load_thumbnail_at_size:
750  * @uri: the URI of the thumbnailed file.
751  * @mtime: the last modified time of @uri, or %-1 if unknown.
752  * @pixel_size: the desired pixel size.
753  *
754  * Attempts to load the thumbnail for @uri at @size. If the thumbnail for @uri
755  * at @size does not already exist, %NULL will be returned.
756  *
757  * Returns: the thumbnail pixbuf of @uri at @size which must be un-referenced
758  *  with g_object_unref() when no longer needed, or %NULL.
759  *
760  * Since: 2.6
761  **/
762 GdkPixbuf *
egg_pixbuf_load_thumbnail_at_size(const gchar * uri,time_t mtime,gint pixel_size)763 egg_pixbuf_load_thumbnail_at_size (const gchar *uri,
764 				   time_t       mtime,
765 				   gint         pixel_size)
766 {
767   ImageInfo info;
768   GdkPixbuf *retval;
769   gchar *filename;
770 
771   g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
772   g_return_val_if_fail (mtime >= 0, NULL);
773 
774   if (pixel_size <= EGG_PIXBUF_THUMBNAIL_NORMAL)
775     {
776       info.size = EGG_PIXBUF_THUMBNAIL_NORMAL;
777       filename = egg_pixbuf_get_thumbnail_filename (uri, EGG_PIXBUF_THUMBNAIL_NORMAL);
778     }
779   else if (pixel_size <= EGG_PIXBUF_THUMBNAIL_LARGE)
780     {
781       info.size = EGG_PIXBUF_THUMBNAIL_LARGE;
782       filename = egg_pixbuf_get_thumbnail_filename (uri, EGG_PIXBUF_THUMBNAIL_LARGE);
783     }
784   else if (strncmp (uri, "file://", 7) == 0)
785     {
786       info.size = -1;
787       filename = g_strdup (uri + 7);
788     }
789   else
790     {
791       info.size = -1;
792       filename = egg_pixbuf_get_thumbnail_filename (uri, EGG_PIXBUF_THUMBNAIL_LARGE);
793     }
794 
795   retval = load_image_at_max_size (filename, &info, NULL, NULL);
796   g_free (filename);
797 
798   if (retval != NULL &&
799       (!parse_thumbnail_data (retval, info.size, NULL) ||
800        !egg_pixbuf_is_thumbnail (retval, uri, mtime)))
801     {
802       g_object_unref (retval);
803       return NULL;
804     }
805 
806   return retval;
807 }
808 
809 
810 /* ****************** *
811  *  Saving Functions  *
812  * ****************** */
813 
814 static void
collect_save_options(va_list opts,gchar *** keys,gchar *** vals)815 collect_save_options (va_list   opts,
816                       gchar  ***keys,
817                       gchar  ***vals)
818 {
819   gchar *key;
820   gchar *val;
821   gchar *next;
822   gint count;
823 
824   count = 0;
825   *keys = NULL;
826   *vals = NULL;
827 
828   next = va_arg (opts, gchar*);
829   while (next)
830     {
831       key = next;
832       val = va_arg (opts, gchar*);
833 
834       ++count;
835 
836       /* woo, slow */
837       *keys = g_realloc (*keys, sizeof(gchar*) * (count + 1));
838       *vals = g_realloc (*vals, sizeof(gchar*) * (count + 1));
839 
840       (*keys)[count-1] = g_strdup (key);
841       (*vals)[count-1] = g_strdup (val);
842 
843       (*keys)[count] = NULL;
844       (*vals)[count] = NULL;
845 
846       next = va_arg (opts, gchar*);
847     }
848 }
849 
850 /**
851  * egg_pixbuf_save_thumbnail:
852  * @thumbnail: the valid thumbnail pixbuf to save.
853  * @error: a location to a pointer to store an error.
854  * @Varargs: a %NULL-terminated metadata key/value pair list.
855  *
856  * Saves @thumbnail to it's appropriate file. Note that @thumbnail must have
857  * it's URI, mtime, and size metadata set before this function is called. Any
858  * additional metadata can be saved using the %NULL-terminated
859  * variable-arguments list. If an error occurs while saving the thumbnail, a
860  * failure thumbnail will be automatically created and saved.
861  *
862  * Returns: %TRUE if the thumbnail was successfully saved, %FALSE if it was not.
863  *
864  * Since: 2.6
865  **/
866 gboolean
egg_pixbuf_save_thumbnail(GdkPixbuf * thumbnail,GError ** error,...)867 egg_pixbuf_save_thumbnail (GdkPixbuf *thumbnail,
868 			   GError   **error,
869 			   ...)
870 {
871   va_list args;
872   gboolean retval;
873   gchar **keys, **values;
874 
875   g_return_val_if_fail (egg_pixbuf_is_thumbnail (thumbnail, NULL, -1), FALSE);
876   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
877 
878   keys = NULL;
879   values = NULL;
880 
881   va_start (args, error);
882   collect_save_options (args, &keys, &values);
883   va_end (args);
884 
885   retval = egg_pixbuf_save_thumbnailv (thumbnail, keys, values, error);
886 
887   g_strfreev (values);
888   g_strfreev (keys);
889 
890   return retval;
891 }
892 
893 static gchar **
create_pair_array(const gchar * key,const gchar * value)894 create_pair_array (const gchar *key,
895 		   const gchar *value)
896 {
897   gchar **retval;
898 
899   retval = g_new (gchar *, 2);
900   retval[0] = g_strdup (key);
901   retval[1] = g_strdup (value);
902 
903   return retval;
904 }
905 
906 static inline void
merge_keys_values_and_thumbnail_data(GdkPixbuf * thumbnail,gchar ** src_keys,gchar ** src_values,gchar *** dest_keys,gchar *** dest_values)907 merge_keys_values_and_thumbnail_data (GdkPixbuf   *thumbnail,
908 				      gchar      **src_keys,
909 				      gchar      **src_values,
910 				      gchar     ***dest_keys,
911 				      gchar     ***dest_values)
912 {
913   ThumbnailData *data;
914   GSList *list;
915   guint n_pairs, i;
916   gchar *tmp;
917 
918   data = g_object_get_qdata (G_OBJECT (thumbnail), THUMBNAIL_DATA_QUARK);
919   if (!data)
920     {
921       *dest_keys = g_strdupv (src_keys);
922       *dest_values = g_strdupv (src_values);
923       return;
924     }
925 
926   list = g_slist_prepend (NULL, create_pair_array (THUMB_SOFTWARE_KEY,
927 						   THUMB_SOFTWARE_VALUE));
928 
929   if (data->uri)
930     list = g_slist_prepend (list, create_pair_array (THUMB_URI_KEY, data->uri));
931 
932   if (data->mtime >= 0)
933     {
934       tmp = g_strdup_printf ("%ld", data->mtime);
935       list = g_slist_prepend (list, create_pair_array (THUMB_MTIME_KEY, tmp));
936       g_free (tmp);
937     }
938 
939   if (data->description)
940     list = g_slist_prepend (list, create_pair_array (THUMB_DESCRIPTION_KEY,
941 						     data->description));
942 
943   if (data->mime_type)
944     list = g_slist_prepend (list, create_pair_array (THUMB_MIME_TYPE_KEY,
945 						     data->mime_type));
946 
947   if (data->software)
948     list = g_slist_prepend (list, create_pair_array (THUMB_SOFTWARE_KEY,
949 						     data->software));
950 
951   if (data->filesize > 0)
952     {
953 //      tmp = g_strdup_printf ("%" G_GSSIZE_FORMAT, data->filesize);
954 // Change by AMR for GTK+-2.0 compatibility
955       tmp = g_strdup_printf ("%ld", data->filesize);
956       list = g_slist_prepend (list, create_pair_array (THUMB_FILESIZE_KEY, tmp));
957       g_free (tmp);
958     }
959 
960   if (data->image_width > 0)
961     {
962       tmp = g_strdup_printf ("%d", data->image_width);
963       list = g_slist_prepend (list, create_pair_array (THUMB_WIDTH_KEY, tmp));
964       g_free (tmp);
965     }
966 
967   if (data->image_height > 0)
968     {
969       tmp = g_strdup_printf ("%d", data->image_height);
970       list = g_slist_prepend (list, create_pair_array (THUMB_HEIGHT_KEY, tmp));
971       g_free (tmp);
972     }
973 
974   if (data->document_pages > 0)
975     {
976       tmp = g_strdup_printf ("%d", data->document_pages);
977       list = g_slist_prepend (list, create_pair_array (THUMB_PAGES_KEY, tmp));
978       g_free (tmp);
979     }
980 
981   if (data->movie_length >= 0)
982     {
983       tmp = g_strdup_printf ("%ld", data->movie_length);
984       list = g_slist_prepend (list, create_pair_array (THUMB_MTIME_KEY, tmp));
985       g_free (tmp);
986     }
987 
988   /* Get a count of the keys in src_keys */
989   if (src_keys)
990     {
991       for (n_pairs = 0; src_keys[n_pairs] != NULL; n_pairs++);
992     }
993   else
994     n_pairs = 0;
995 
996   n_pairs += g_slist_length (list);
997 
998   *dest_keys = g_new0 (gchar *, n_pairs + 1);
999   *dest_values = g_new0 (gchar *, n_pairs + 1);
1000 
1001   if (src_keys)
1002     {
1003       for (i = 0; src_keys[i] != NULL; i++)
1004 	{
1005 	  (*dest_keys)[i] = g_strdup (src_keys[i]);
1006 	  (*dest_values)[i] = g_strdup (src_values[i]);
1007 	}
1008     }
1009   else
1010     i = 0;
1011 
1012   while (list)
1013     {
1014       gchar **pair;
1015 
1016       pair = list->data;
1017       (*dest_keys)[i] = pair[0];
1018       (*dest_values)[i] = pair[1];
1019       g_free (pair);
1020       list = g_slist_remove_link (list, list);
1021       i++;
1022     }
1023 }
1024 
1025 /**
1026  * egg_pixbuf_save_thumbnailv:
1027  * @thumbnail: the thumbnail to save.
1028  * @keys: a NULL-terminated array of metadata keys.
1029  * @values: a NULL-terminated array of metadata values.
1030  * @error: a pointer to a location to store errors in.
1031  *
1032  * This function is primarily useful to language bindings. Applications should
1033  * use egg_pixbuf_save_thumbnail().
1034  *
1035  * Returns: %TRUE if the thumbnail was successfully saved, %FALSE if it was not.
1036  *
1037  * Since: 2.6
1038  **/
1039 gboolean
egg_pixbuf_save_thumbnailv(GdkPixbuf * thumbnail,gchar ** keys,gchar ** values,GError ** error)1040 egg_pixbuf_save_thumbnailv (GdkPixbuf *thumbnail,
1041 			    gchar    **keys,
1042 			    gchar    **values,
1043 			    GError   **error)
1044 {
1045   const gchar *uri;
1046   gchar *filename, *tmp_filename;
1047   gchar **real_keys, **real_values;
1048   gint fd;
1049   gboolean retval;
1050   GError *real_error;
1051 
1052   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), FALSE);
1053   g_return_val_if_fail (egg_pixbuf_is_thumbnail (thumbnail, NULL, -1), FALSE);
1054   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1055 
1056   if (!ensure_thumbnail_dirs (error))
1057     return FALSE;
1058 
1059   uri = egg_pixbuf_get_thumbnail_uri (thumbnail);
1060   filename =
1061     egg_pixbuf_get_thumbnail_filename (uri,
1062 				       egg_pixbuf_get_thumbnail_size (thumbnail));
1063   tmp_filename = g_strconcat (filename, ".XXXXXX", NULL);
1064 
1065   fd = g_mkstemp (tmp_filename);
1066   if (fd < 0)
1067     {
1068       real_error =
1069 	g_error_new (G_FILE_ERROR,
1070 		     g_file_error_from_errno (errno),
1071 		     _("Error creating temporary thumbnail file for `%s': %s"),
1072 		     uri, g_strerror (errno));
1073       g_free (tmp_filename);
1074       g_free (filename);
1075 
1076       egg_pixbuf_save_failed_thumbnail (egg_pixbuf_get_thumbnail_uri (thumbnail),
1077 					egg_pixbuf_get_thumbnail_mtime (thumbnail),
1078 					real_error);
1079       if (error != NULL)
1080 	*error = real_error;
1081       else
1082 	g_error_free (real_error);
1083 
1084       return FALSE;
1085     }
1086   else
1087     {
1088       close (fd);
1089       chmod (tmp_filename, 0600);
1090     }
1091 
1092   real_error = NULL;
1093   merge_keys_values_and_thumbnail_data (thumbnail, keys, values,
1094 					&real_keys, &real_values);
1095   retval = gdk_pixbuf_savev (thumbnail, tmp_filename, "png",
1096 			     real_keys, real_values, &real_error);
1097   g_strfreev (real_keys);
1098   g_strfreev (real_values);
1099 
1100   if (retval)
1101     rename (tmp_filename, filename);
1102   else
1103     {
1104       egg_pixbuf_save_failed_thumbnail (egg_pixbuf_get_thumbnail_uri (thumbnail),
1105 					egg_pixbuf_get_thumbnail_mtime (thumbnail),
1106 					real_error);
1107 
1108       if (error != NULL)
1109 	*error = real_error;
1110       else
1111 	g_error_free (real_error);
1112     }
1113 
1114   g_free (tmp_filename);
1115   g_free (filename);
1116 
1117   return retval;
1118 }
1119 
1120 
1121 /* ******************* *
1122  *  Failure Functions  *
1123  * ******************* */
1124 
1125 /**
1126  * egg_pixbuf_has_failed_thumbnail:
1127  * @uri: the URI of the thumbnailed file to check.
1128  * @mtime: the last modified time of @uri.
1129  * @error: a pointer to a location to store an error.
1130  *
1131  * Checks to see if creating a thumbnail for @uri which was changed on @mtime
1132  * has already been tried and failed. If it has, the error which prevented the
1133  * thumbnail from being created will be stored in @error.
1134  *
1135  * Returns: %TRUE if the thumbnail creation has already failed, %FALSE if it
1136  *  has not.
1137  *
1138  * Since: 2.6
1139  **/
1140 gboolean
egg_pixbuf_has_failed_thumbnail(const gchar * uri,time_t mtime,GError ** error)1141 egg_pixbuf_has_failed_thumbnail (const gchar *uri,
1142 				 time_t       mtime,
1143 				 GError     **error)
1144 {
1145   gchar *md5, *basename, *filename;
1146   gboolean retval;
1147   GdkPixbuf *thumb;
1148 
1149   g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);
1150   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1151 
1152   retval = FALSE;
1153 
1154   md5 = egg_str_get_md5_str (uri);
1155   basename = g_strconcat (md5, ".png", NULL);
1156   g_free (md5);
1157   filename = g_build_filename (g_get_home_dir (), ".thumbnails", FAIL_DIR_NAME,
1158 			       APP_DIR_NAME, basename, NULL);
1159   thumb = gdk_pixbuf_new_from_file (filename, NULL);
1160   g_free (filename);
1161 
1162   if (thumb)
1163     {
1164       retval = (parse_thumbnail_data (thumb, EGG_PIXBUF_THUMBNAIL_UNKNOWN,
1165 				      error) &&
1166 		egg_pixbuf_is_thumbnail (thumb, uri, mtime));
1167       g_object_unref (thumb);
1168     }
1169   else
1170     retval = FALSE;
1171 
1172   return retval;
1173 }
1174 
1175 /**
1176  * egg_pixbuf_save_failed_thumbnail:
1177  * @uri: the URI which the thumbnail creation failed for.
1178  * @mtime: the time that @uri was last modified.
1179  * @error: the error which occurred while trying to create the thumbnail.
1180  *
1181  * Saves a "failure thumbnail" for the @uri with @mtime. This lets other
1182  * applications using the EggPixbufThumbnail API know that a thumbnail attempt
1183  * was tried for @uri when it was modified last at @mtime. The @error parameter
1184  * lets other applications know exactly why the thumbnail creation failed.
1185  *
1186  * <note>@error must be in either the #G_FILE_ERROR or #GDK_PIXBUF_ERROR
1187  * domain.</note>
1188  *
1189  * Since: 2.6
1190  **/
1191 void
egg_pixbuf_save_failed_thumbnail(const gchar * uri,time_t mtime,const GError * error)1192 egg_pixbuf_save_failed_thumbnail (const gchar  *uri,
1193 				  time_t        mtime,
1194 				  const GError *error)
1195 {
1196   GdkPixbuf *pixbuf;
1197   GError *err_ret;
1198   gchar *md5, *basename, *filename, *tmp_filename, *mtime_str;
1199   gboolean saved_ok;
1200   gint fd;
1201 
1202   g_return_if_fail (uri != NULL && uri[0] != '\0');
1203   g_return_if_fail (error == NULL ||
1204 		    error->domain == G_FILE_ERROR ||
1205 		    error->domain == GDK_PIXBUF_ERROR);
1206 
1207   err_ret = NULL;
1208   if (!ensure_thumbnail_dirs (&err_ret))
1209     {
1210       g_warning ("%s", err_ret->message);
1211       g_error_free (err_ret);
1212       return;
1213     }
1214 
1215   md5 = egg_str_get_md5_str (uri);
1216   basename = g_strconcat (md5, ".png", NULL);
1217   g_free (md5);
1218   filename = g_build_filename (g_get_home_dir (), ".thumbnails", FAIL_DIR_NAME,
1219 			       APP_DIR_NAME, basename, NULL);
1220   g_free (basename);
1221 
1222   tmp_filename = g_strconcat (filename, ".XXXXXX", NULL);
1223   fd = g_mkstemp (tmp_filename);
1224   if (fd < 0)
1225     {
1226       g_free (tmp_filename);
1227       g_free (filename);
1228       return;
1229     }
1230   else
1231     close (fd);
1232 
1233   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
1234 
1235   mtime_str = g_strdup_printf ("%ld", mtime);
1236 
1237   if (error)
1238     {
1239       gchar *code_str;
1240       const gchar *domain;
1241 
1242       if (error->domain == G_FILE_ERROR)
1243 	domain = FILE_ERROR_NAME;
1244       else
1245 	domain = PIXBUF_ERROR_NAME;
1246 
1247       code_str = g_strdup_printf ("%d", error->code);
1248       saved_ok = gdk_pixbuf_save (pixbuf, tmp_filename, "png", &err_ret,
1249 				  THUMB_URI_KEY, uri,
1250 				  THUMB_MTIME_KEY, mtime_str,
1251 				  THUMB_SOFTWARE_KEY, THUMB_SOFTWARE_VALUE,
1252 				  THUMB_ERROR_DOMAIN_KEY, domain,
1253 				  THUMB_ERROR_CODE_KEY, code_str,
1254 				  THUMB_ERROR_MESSAGE_KEY, error->message,
1255 				  NULL);
1256       g_free (code_str);
1257     }
1258   else
1259     {
1260       saved_ok = gdk_pixbuf_save (pixbuf, tmp_filename, "png", &err_ret,
1261 				  THUMB_URI_KEY, uri,
1262 				  THUMB_MTIME_KEY, mtime_str,
1263 				  THUMB_SOFTWARE_KEY, THUMB_SOFTWARE_VALUE,
1264 				  NULL);
1265     }
1266 
1267   if (!saved_ok)
1268     {
1269       g_message ("Error saving fail thumbnail: %s", err_ret->message);
1270       g_error_free (err_ret);
1271     }
1272   else
1273     {
1274       chmod (tmp_filename, 0600);
1275       rename (tmp_filename, filename);
1276     }
1277 
1278   g_free (tmp_filename);
1279   g_free (filename);
1280   g_free (mtime_str);
1281 }
1282 
1283 
1284 /**
1285  * egg_pixbuf_get_thumbnail_for_pixbuf:
1286  * @pixbuf: the full-sized source pixbuf.
1287  * @size: the size of the thumbnailnail to generate.
1288  * @uri: the URI location of @pixbuf.
1289  * @mtime: the last-modified time of @uri.
1290  *
1291  * Creates a thumbnail of the in-memory @pixbuf at @size, using @uri and
1292  * @mtime for the pre-requisite metadata.
1293  *
1294  * Returns: a new thumbnail of @pixbuf which must be un-referenced with
1295  *  g_object_unref() when no longer needed, or %NULL.
1296  *
1297  * Since: 2.6
1298  **/
1299 GdkPixbuf *
egg_pixbuf_get_thumbnail_for_pixbuf(GdkPixbuf * pixbuf,const gchar * uri,time_t mtime,EggPixbufThumbnailSize size)1300 egg_pixbuf_get_thumbnail_for_pixbuf (GdkPixbuf             *pixbuf,
1301 				     const gchar           *uri,
1302 				     time_t                 mtime,
1303 				     EggPixbufThumbnailSize size)
1304 {
1305   GdkPixbuf *retval;
1306   gint width, height;
1307 
1308   g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
1309   g_return_val_if_fail (size == EGG_PIXBUF_THUMBNAIL_NORMAL ||
1310 			size == EGG_PIXBUF_THUMBNAIL_LARGE, NULL);
1311   g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
1312 
1313   width = gdk_pixbuf_get_width (pixbuf);
1314   height = gdk_pixbuf_get_height (pixbuf);
1315 
1316   if (width > size || height > size)
1317     {
1318       gdouble scale;
1319 
1320       if (width > height)
1321 	scale = (gdouble) size / width;
1322       else
1323 	scale = (gdouble) size / height;
1324 
1325       retval = gdk_pixbuf_scale_simple (pixbuf, scale * width, scale *height,
1326 					GDK_INTERP_BILINEAR);
1327     }
1328   else
1329     {
1330       retval = gdk_pixbuf_copy (pixbuf);
1331     }
1332 
1333   egg_pixbuf_set_thumbnail_uri (retval, uri);
1334   egg_pixbuf_set_thumbnail_mtime (retval, mtime);
1335   egg_pixbuf_set_thumbnail_size (retval, size);
1336   egg_pixbuf_set_thumbnail_description (retval,
1337 					gdk_pixbuf_get_option (pixbuf,
1338 							       THUMB_DESCRIPTION_KEY));
1339 
1340   return retval;
1341 }
1342 
1343 /* ******************* *
1344  *  Testing Functions  *
1345  * ******************* */
1346 
1347 /**
1348  * egg_pixbuf_is_thumbnail:
1349  * @pixbuf: the pixbuf to test.
1350  * @uri: the source URI of pixbuf (or %NULL to ignore this test).
1351  * @mtime: the modification time of @uri (or %-1 to ignore this test).
1352  *
1353  * This function will always check for thumbnail metadata attached to @pixbuf,
1354  * specifically the existance of a URI value. If @uri is non-%NULL, then the URI
1355  * value on @pixbuf will be compared with it. If @mtime is greater than or equal
1356  * to %0, then the modification time value will also be checked.
1357  *
1358  * See also: egg_pixbuf_set_thumbnail_uri(), egg_pixbuf_set_thumbnail_mtime().
1359  *
1360  * Returns: %TRUE if @pixbuf can be used as a thumbnail, %FALSE if it cannot.
1361  *
1362  * Since: 2.6
1363  **/
1364 gboolean
egg_pixbuf_is_thumbnail(GdkPixbuf * pixbuf,const gchar * uri,time_t mtime)1365 egg_pixbuf_is_thumbnail (GdkPixbuf   *pixbuf,
1366 			 const gchar *uri,
1367 			 time_t       mtime)
1368 {
1369   ThumbnailData *data;
1370 
1371   g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), FALSE);
1372   g_return_val_if_fail (uri == NULL || uri[0] != '\0', FALSE);
1373 
1374   data = get_thumbnail_data (pixbuf);
1375 
1376   /* Must have thumbnail data and matching URIs. */
1377   if (!data || !data->uri || (uri && strcmp (data->uri, uri) != 0))
1378     return FALSE;
1379 
1380   /* Thumbnails of local URIs must have matching mtime. */
1381   if (mtime >= 0 && uri && g_ascii_strncasecmp (data->uri, "file:", 5) == 0)
1382     {
1383       if (data->mtime != mtime)
1384 	return FALSE;
1385     }
1386   /* Thumbnails of remote URIs expire after 30 days. We could require the user
1387      pass the proper mtime value for remote URIs, but why? */
1388   else if (mtime >= 0)
1389     {
1390       time_t current_time;
1391 
1392       current_time = time (NULL);
1393 
1394       if (data->mtime + EXPIRATION_TIME < current_time)
1395 	return FALSE;
1396     }
1397 
1398   return TRUE;
1399 }
1400 
1401 
1402 /* ***************** *
1403  *  Getters/Setters  *
1404  * ***************** */
1405 
1406 /**
1407  * egg_pixbuf_get_thumbnail_size:
1408  * @thumbnail: the thumbnail to examine.
1409  *
1410  * Retreives the escaped URI that @thumbnail is a preview of.
1411  *
1412  * Returns: a character array which should not be modified or freed.
1413  *
1414  * Since: 2.6
1415  **/
1416 EggPixbufThumbnailSize
egg_pixbuf_get_thumbnail_size(GdkPixbuf * thumbnail)1417 egg_pixbuf_get_thumbnail_size (GdkPixbuf *thumbnail)
1418 {
1419   ThumbnailData *data;
1420 
1421   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail),
1422 			EGG_PIXBUF_THUMBNAIL_UNKNOWN);
1423 
1424   data = get_thumbnail_data (thumbnail);
1425 
1426   return (data ? data->size : EGG_PIXBUF_THUMBNAIL_UNKNOWN);
1427 }
1428 
1429 /**
1430  * egg_pixbuf_set_thumbnail_size:
1431  * @thumbnail: the thumbnail to modify.
1432  * @size: the size of @thumbnail.
1433  *
1434  * Sets the size metadata for @thumbnail. This function may only be
1435  * called once for a particular pixbuf.
1436  *
1437  * Since: 2.6
1438  **/
1439 void
egg_pixbuf_set_thumbnail_size(GdkPixbuf * thumbnail,EggPixbufThumbnailSize size)1440 egg_pixbuf_set_thumbnail_size (GdkPixbuf             *thumbnail,
1441 			       EggPixbufThumbnailSize size)
1442 {
1443   ThumbnailData *data;
1444 
1445   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1446   g_return_if_fail (size == EGG_PIXBUF_THUMBNAIL_NORMAL ||
1447 		    size == EGG_PIXBUF_THUMBNAIL_LARGE);
1448 
1449   data = ensure_thumbnail_data (thumbnail);
1450   data->size = size;
1451 }
1452 
1453 /**
1454  * egg_pixbuf_get_thumbnail_uri:
1455  * @thumbnail: the thumbnail to examine.
1456  *
1457  * Retreives the escaped URI that @thumbnail is a preview of.
1458  *
1459  * Returns: a character array which should not be modified or freed.
1460  *
1461  * Since: 2.6
1462  **/
1463 G_CONST_RETURN gchar *
egg_pixbuf_get_thumbnail_uri(GdkPixbuf * thumbnail)1464 egg_pixbuf_get_thumbnail_uri (GdkPixbuf *thumbnail)
1465 {
1466   ThumbnailData *data;
1467 
1468   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);
1469 
1470   data = get_thumbnail_data (thumbnail);
1471 
1472   return (data ? data->uri : NULL);
1473 }
1474 
1475 /**
1476  * egg_pixbuf_set_thumbnail_uri:
1477  * @thumbnail: the thumbnail to modify.
1478  * @uri: the escaped URI that @thumbnail is a preview of.
1479  *
1480  * Sets the URI metadata for @thumbnail. This function may only be
1481  * called once for a particular pixbuf.
1482  *
1483  * Since: 2.6
1484  **/
1485 void
egg_pixbuf_set_thumbnail_uri(GdkPixbuf * thumbnail,const gchar * uri)1486 egg_pixbuf_set_thumbnail_uri (GdkPixbuf   *thumbnail,
1487 			      const gchar *uri)
1488 {
1489   ThumbnailData *data;
1490 
1491   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1492   g_return_if_fail (uri != NULL && uri[0] != '\0');
1493 
1494   data = ensure_thumbnail_data (thumbnail);
1495   g_free (data->uri);
1496   data->uri = g_strdup (uri);
1497 }
1498 
1499 /**
1500  * egg_pixbuf_get_thumbnail_mime_type:
1501  * @thumbnail: the thumbnail to examine.
1502  *
1503  * Retreives the mime-type of the file that @thumbnail is a preview of.
1504  *
1505  * Returns: a character array which should not be modified or freed.
1506  *
1507  * Since: 2.6
1508  **/
1509 G_CONST_RETURN gchar *
egg_pixbuf_get_thumbnail_mime_type(GdkPixbuf * thumbnail)1510 egg_pixbuf_get_thumbnail_mime_type (GdkPixbuf *thumbnail)
1511 {
1512   ThumbnailData *data;
1513 
1514   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);
1515 
1516   data = get_thumbnail_data (thumbnail);
1517 
1518   return (data ? data->mime_type : NULL);
1519 }
1520 
1521 /**
1522  * egg_pixbuf_set_thumbnail_mime_type:
1523  * @thumbnail: the thumbnail to modify.
1524  * @mime_type: the mime type of the file that @thumbnail is a preview of.
1525  *
1526  * Sets the @mime_type metadata for @thumbnail. This function may only be called
1527  * once on a particular pixbuf.
1528  *
1529  * Since: 2.6
1530  **/
1531 void
egg_pixbuf_set_thumbnail_mime_type(GdkPixbuf * thumbnail,const gchar * mime_type)1532 egg_pixbuf_set_thumbnail_mime_type (GdkPixbuf   *thumbnail,
1533 				    const gchar *mime_type)
1534 {
1535   ThumbnailData *data;
1536 
1537   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1538   g_return_if_fail (mime_type != NULL && mime_type[0] != '\0');
1539 
1540   data = ensure_thumbnail_data (thumbnail);
1541   g_free (data->mime_type);
1542   data->mime_type = g_strdup (mime_type);
1543 }
1544 
1545 /**
1546  * egg_pixbuf_get_thumbnail_description:
1547  * @thumbnail: the thumbnail to examine.
1548  *
1549  * Retreives the user-specified description (comment) for the file that
1550  * @thumbnail is a preview of. If this metadata was not saved (or does not
1551  * exist for @thumbnail), NULL will be returned.
1552  *
1553  * Returns: a character array which should not be modified or freed.
1554  *
1555  * Since: 2.6
1556  **/
1557 G_CONST_RETURN gchar *
egg_pixbuf_get_thumbnail_description(GdkPixbuf * thumbnail)1558 egg_pixbuf_get_thumbnail_description (GdkPixbuf *thumbnail)
1559 {
1560   ThumbnailData *data;
1561 
1562   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);
1563 
1564   data = get_thumbnail_data (thumbnail);
1565 
1566   return (data ? data->description : NULL);
1567 }
1568 
1569 /**
1570  * egg_pixbuf_set_thumbnail_description:
1571  * @thumbnail: the thumbnail to modify.
1572  * @description: the description of the file that @thumbnail is a preview of.
1573  *
1574  * Sets the user-specified @description metadata for @thumbnail. This function
1575  * may only be called once on a particular pixbuf.
1576  *
1577  * Since: 2.6
1578  **/
1579 void
egg_pixbuf_set_thumbnail_description(GdkPixbuf * thumbnail,const gchar * description)1580 egg_pixbuf_set_thumbnail_description (GdkPixbuf   *thumbnail,
1581 				      const gchar *description)
1582 {
1583   ThumbnailData *data;
1584 
1585   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1586   g_return_if_fail (description != NULL && description[0] != '\0');
1587 
1588   data = ensure_thumbnail_data (thumbnail);
1589   g_free (data->description);
1590   data->description = g_strdup (description);
1591 }
1592 
1593 /**
1594  * egg_pixbuf_get_thumbnail_mtime:
1595  * @thumbnail: the thumbnail to examine.
1596  *
1597  * Retreives the UNIX time (seconds since the epoch/time_t) the file which
1598  * @thumbnail is a preview of was modified on when the @thumbnail was created.
1599  * If this metadata was not saved with @thumbnail, %-1 will be returned.
1600  *
1601  * Returns: a UNIX seconds-since-epoch time value, or %-1.
1602  *
1603  * Since: 2.6
1604  **/
1605 time_t
egg_pixbuf_get_thumbnail_mtime(GdkPixbuf * thumbnail)1606 egg_pixbuf_get_thumbnail_mtime (GdkPixbuf *thumbnail)
1607 {
1608   ThumbnailData *data;
1609 
1610   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);
1611 
1612   data = get_thumbnail_data (thumbnail);
1613 
1614   return (data ? data->mtime : -1);
1615 }
1616 
1617 /**
1618  * egg_pixbuf_set_thumbnail_mtime:
1619  * @thumbnail: the thumbnail to modify.
1620  * @mtime: the last-modified time of the file that @thumbnail is a preview of.
1621  *
1622  * Sets the last-modified @mtime metadata for @thumbnail. This function
1623  * may only be called once on a particular pixbuf.
1624  *
1625  * Since: 2.6
1626  **/
1627 void
egg_pixbuf_set_thumbnail_mtime(GdkPixbuf * thumbnail,time_t mtime)1628 egg_pixbuf_set_thumbnail_mtime (GdkPixbuf *thumbnail,
1629 				time_t     mtime)
1630 {
1631   ThumbnailData *data;
1632 
1633   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1634 
1635   data = ensure_thumbnail_data (thumbnail);
1636   data->mtime = mtime;
1637 }
1638 
1639 /**
1640  * egg_pixbuf_get_thumbnail_filesize:
1641  * @thumbnail: the thumbnail to examine.
1642  *
1643  * Retreives the size in bytes of the file which @thumbnail is a preview of. If
1644  * this metadata was not saved with @thumbnail, %-1 will be returned.
1645  *
1646  * Returns: a 64-bit integer.
1647  *
1648  * Since: 2.6
1649  **/
1650 gssize
egg_pixbuf_get_thumbnail_filesize(GdkPixbuf * thumbnail)1651 egg_pixbuf_get_thumbnail_filesize (GdkPixbuf *thumbnail)
1652 {
1653   ThumbnailData *data;
1654 
1655   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);
1656 
1657   data = get_thumbnail_data (thumbnail);
1658 
1659   return (data ? data->filesize : -1);
1660 }
1661 
1662 /**
1663  * egg_pixbuf_set_thumbnail_filesize:
1664  * @thumbnail: the thumbnail to modify.
1665  * @filesize: the size (in bytes) of the file that @thumbnail is a preview of.
1666  *
1667  * Sets the @filesize metadata for @thumbnail. This function may only be called
1668  * once on a particular pixbuf.
1669  *
1670  * Since: 2.6
1671  **/
1672 void
egg_pixbuf_set_thumbnail_filesize(GdkPixbuf * thumbnail,gssize filesize)1673 egg_pixbuf_set_thumbnail_filesize (GdkPixbuf *thumbnail,
1674 				   gssize     filesize)
1675 {
1676   ThumbnailData *data;
1677 
1678   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1679 
1680   data = ensure_thumbnail_data (thumbnail);
1681   data->filesize = filesize;
1682 }
1683 
1684 /**
1685  * egg_pixbuf_get_thumbnail_image_width:
1686  * @thumbnail: the thumbnail to examine.
1687  *
1688  * Retreives the width (in pixels) of the image contained in the file that
1689  * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
1690  * (e.g. if the original file was not an image), %-1 will be returned.
1691  *
1692  * Returns: an integer.
1693  *
1694  * Since: 2.6
1695  **/
1696 gint
egg_pixbuf_get_thumbnail_image_width(GdkPixbuf * thumbnail)1697 egg_pixbuf_get_thumbnail_image_width (GdkPixbuf *thumbnail)
1698 {
1699   ThumbnailData *data;
1700 
1701   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);
1702 
1703   data = get_thumbnail_data (thumbnail);
1704 
1705   return (data ? data->image_width : -1);
1706 }
1707 
1708 /**
1709  * egg_pixbuf_set_thumbnail_image_width:
1710  * @thumbnail: the thumbnail to modify.
1711  * @image_width: the width (in pixels) of the image file that @thumbnail is a preview of.
1712  *
1713  * Sets the @image_width metadata for @thumbnail. This function may only be
1714  * called once on a particular pixbuf.
1715  *
1716  * Since: 2.6
1717  **/
1718 void
egg_pixbuf_set_thumbnail_image_width(GdkPixbuf * thumbnail,gint image_width)1719 egg_pixbuf_set_thumbnail_image_width (GdkPixbuf *thumbnail,
1720 				      gint       image_width)
1721 {
1722   ThumbnailData *data;
1723 
1724   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1725 
1726   data = ensure_thumbnail_data (thumbnail);
1727   data->image_width = image_width;
1728 }
1729 
1730 /**
1731  * egg_pixbuf_get_thumbnail_image_height:
1732  * @thumbnail: the thumbnail to examine.
1733  *
1734  * Retreives the height (in pixels) of the image contained in the file that
1735  * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
1736  * (e.g. if the original file was not an image), %-1 will be returned.
1737  *
1738  * Returns: an integer.
1739  *
1740  * Since: 2.6
1741  **/
1742 gint
egg_pixbuf_get_thumbnail_image_height(GdkPixbuf * thumbnail)1743 egg_pixbuf_get_thumbnail_image_height (GdkPixbuf *thumbnail)
1744 {
1745   ThumbnailData *data;
1746 
1747   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);
1748 
1749   data = get_thumbnail_data (thumbnail);
1750 
1751   return (data ? data->image_height : -1);
1752 }
1753 
1754 /**
1755  * egg_pixbuf_set_thumbnail_image_height:
1756  * @thumbnail: the thumbnail to modify.
1757  * @image_height: the height (in pixels) of the image file that @thumbnail is a preview of.
1758  *
1759  * Sets the @image_height metadata for @thumbnail. This function may only be
1760  * called once on a particular pixbuf.
1761  *
1762  * Since: 2.6
1763  **/
1764 void
egg_pixbuf_set_thumbnail_image_height(GdkPixbuf * thumbnail,gint image_height)1765 egg_pixbuf_set_thumbnail_image_height (GdkPixbuf *thumbnail,
1766 				       gint       image_height)
1767 {
1768   ThumbnailData *data;
1769 
1770   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1771 
1772   data = ensure_thumbnail_data (thumbnail);
1773   data->image_height = image_height;
1774 }
1775 
1776 /**
1777  * egg_pixbuf_get_thumbnail_document_pages:
1778  * @thumbnail: the thumbnail to examine.
1779  *
1780  * Retreives the number of pages in the document contained in the file that
1781  * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
1782  * (e.g. if the original file was not a paged document), %-1 will be returned.
1783  *
1784  * Returns: an integer.
1785  *
1786  * Since: 2.6
1787  **/
1788 gint
egg_pixbuf_get_thumbnail_document_pages(GdkPixbuf * thumbnail)1789 egg_pixbuf_get_thumbnail_document_pages (GdkPixbuf *thumbnail)
1790 {
1791   ThumbnailData *data;
1792 
1793   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);
1794 
1795   data = get_thumbnail_data (thumbnail);
1796 
1797   return (data ? data->document_pages : -1);
1798 }
1799 
1800 /**
1801  * egg_pixbuf_set_thumbnail_document_pages:
1802  * @thumbnail: the thumbnail to modify.
1803  * @document_pages: the number of pages in the document file that @thumbnail is
1804  *   a preview of.
1805  *
1806  * Sets the @document_pages metadata for @thumbnail. This function may only be
1807  * called once on a particular pixbuf.
1808  *
1809  * Since: 2.6
1810  **/
1811 void
egg_pixbuf_set_thumbnail_document_pages(GdkPixbuf * thumbnail,gint document_pages)1812 egg_pixbuf_set_thumbnail_document_pages (GdkPixbuf *thumbnail,
1813 					 gint       document_pages)
1814 {
1815   ThumbnailData *data;
1816 
1817   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1818 
1819   data = ensure_thumbnail_data (thumbnail);
1820   data->document_pages = document_pages;
1821 }
1822 
1823 /**
1824  * egg_pixbuf_get_thumbnail_movie_length:
1825  * @thumbnail: the thumbnail to examine.
1826  *
1827  * Retreives the length (in seconds) of the movie contained in the file that
1828  * @thumbnail is a preview of. If this metadata was not saved with @thumbnail
1829  * (e.g. if the original file was not a movie), %-1 will be returned.
1830  *
1831  * Returns: a 64-bit integer.
1832  *
1833  * Since: 2.6
1834  **/
1835 time_t
egg_pixbuf_get_thumbnail_movie_length(GdkPixbuf * thumbnail)1836 egg_pixbuf_get_thumbnail_movie_length (GdkPixbuf *thumbnail)
1837 {
1838   ThumbnailData *data;
1839 
1840   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), -1);
1841 
1842   data = get_thumbnail_data (thumbnail);
1843 
1844   return (data ? data->movie_length : -1);
1845 }
1846 
1847 /**
1848  * egg_pixbuf_set_thumbnail_movie_length:
1849  * @thumbnail: the thumbnail to modify.
1850  * @movie_length: the length (in seconds) of the movie file that @thumbnail is a preview of.
1851  *
1852  * Sets the @movie_length metadata for @thumbnail. This function may only be
1853  * called once on a particular pixbuf.
1854  *
1855  * Since: 2.6
1856  **/
1857 void
egg_pixbuf_set_thumbnail_movie_length(GdkPixbuf * thumbnail,time_t movie_length)1858 egg_pixbuf_set_thumbnail_movie_length (GdkPixbuf *thumbnail,
1859 				       time_t     movie_length)
1860 {
1861   ThumbnailData *data;
1862 
1863   g_return_if_fail (GDK_IS_PIXBUF (thumbnail));
1864 
1865   data = ensure_thumbnail_data (thumbnail);
1866   data->movie_length = movie_length;
1867 }
1868 
1869 /**
1870  * egg_pixbuf_get_thumbnail_software:
1871  * @thumbnail: the thumbnail to examine.
1872  *
1873  * Retreives the name of the software which originally created @thumbnail. If
1874  * this metadata was not saved (or does not exist for @thumbnail), NULL will be
1875  * returned.
1876  *
1877  * Returns: a character array which should not be modified or freed.
1878  *
1879  * Since: 2.6
1880  **/
1881 G_CONST_RETURN gchar *
egg_pixbuf_get_thumbnail_software(GdkPixbuf * thumbnail)1882 egg_pixbuf_get_thumbnail_software (GdkPixbuf *thumbnail)
1883 {
1884   ThumbnailData *data;
1885 
1886   g_return_val_if_fail (GDK_IS_PIXBUF (thumbnail), NULL);
1887 
1888   data = get_thumbnail_data (thumbnail);
1889 
1890   return (data ? data->software : NULL);
1891 }
1892 
1893 
1894 /* ******************** *
1895  *  Filename Functions  *
1896  * ******************** */
1897 
1898 /**
1899  * egg_pixbuf_get_thumbnail_filename:
1900  * @uri: the URI of the file to thumbnail.
1901  * @size: the desired size of the thumbnail.
1902  *
1903  * Constructs the global thumbnail filename for @uri at @size. This filename
1904  * can be used to open and save the thumbnail.
1905  *
1906  * Returns: a newly-allocated character array which should be freed with
1907  *  g_free() when no longer needed.
1908  *
1909  * Since: 2.6
1910  **/
1911 gchar *
egg_pixbuf_get_thumbnail_filename(const gchar * uri,EggPixbufThumbnailSize size)1912 egg_pixbuf_get_thumbnail_filename (const gchar           *uri,
1913 				   EggPixbufThumbnailSize size)
1914 {
1915   const gchar *home_dir = NULL;
1916   gchar *md5, *basename, *filename;
1917 
1918   g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
1919   g_return_val_if_fail (size == EGG_PIXBUF_THUMBNAIL_NORMAL ||
1920 			size == EGG_PIXBUF_THUMBNAIL_LARGE, FALSE);
1921 
1922   if (home_dir == NULL)
1923     home_dir = g_get_home_dir ();
1924   else
1925     home_dir = g_get_tmp_dir ();
1926 
1927   md5 = egg_str_get_md5_str (uri);
1928   basename = g_strconcat (md5, ".png", NULL);
1929   filename = g_build_filename (home_dir, ".thumbnails", SIZE_TO_DIR (size),
1930 			       basename, NULL);
1931   g_free (basename);
1932 
1933   return filename;
1934 }
1935 
1936 #if 0
1937 
1938 /**
1939  * egg_pixbuf_get_local_thumbnail_uri:
1940  * @uri: the URI of the file to thumbnail.
1941  * @size: the desired size of the thumbnail.
1942  *
1943  * Constructs the correct thumbnail URI for the "local" thumbnail directory,
1944  * intended for removable media.
1945  *
1946  * Returns: a string URI which must be freed with g_free() when no longer
1947  *  needed, or %NULL.
1948  *
1949  * Since: 2.6
1950  **/
1951 gchar *
1952 egg_pixbuf_get_local_thumbnail_uri (const gchar           *uri,
1953 				    EggPixbufThumbnailSize size)
1954 {
1955   gchar *retval;
1956 
1957   g_return_val_if_fail (uri != NULL && uri[0] != '\0', NULL);
1958   g_return_val_if_fail (size == EGG_PIXBUF_THUMBNAIL_NORMAL ||
1959 			size == EGG_PIXBUF_THUMBNAIL_LARGE, FALSE);
1960 
1961   retval = NULL;
1962 
1963   return retval;
1964 }
1965 
1966 #endif /* Disabled */
1967