1 /*
2  * gnome-thumbnail.c: Utilities for handling thumbnails
3  *
4  * Copyright (C) 2002 Red Hat, Inc.
5  * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
6  *
7  * This file is part of the Gnome Library.
8  *
9  * The Gnome Library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public License as
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * The Gnome Library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
21  * see <http://www.gnu.org/licenses/>.
22  *
23  * Author: Alexander Larsson <alexl@redhat.com>
24  */
25 
26 #include <config.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/time.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <dirent.h>
33 #include <time.h>
34 #include <math.h>
35 #include <string.h>
36 #include <glib.h>
37 #include <stdio.h>
38 
39 #define GDK_PIXBUF_ENABLE_BACKEND
40 #include <gdk-pixbuf/gdk-pixbuf.h>
41 
42 #define GNOME_DESKTOP_USE_UNSTABLE_API
43 #include "gnome-desktop-thumbnail.h"
44 #include <glib/gstdio.h>
45 
46 #define SECONDS_BETWEEN_STATS 10
47 
48 struct _GnomeDesktopThumbnailFactoryPrivate {
49   GnomeDesktopThumbnailSize size;
50 
51   GMutex  lock;
52 
53   GList *thumbnailers;
54   GHashTable *mime_types_map;
55   GList *monitors;
56 
57   GSettings *settings;
58   gboolean loaded : 1;
59   gboolean disabled : 1;
60   gchar **disabled_types;
61 };
62 
63 static const char *appname = "gnome-thumbnail-factory";
64 
65 static void gnome_desktop_thumbnail_factory_init          (GnomeDesktopThumbnailFactory      *factory);
66 static void gnome_desktop_thumbnail_factory_class_init    (GnomeDesktopThumbnailFactoryClass *class);
67 
68 G_DEFINE_TYPE_WITH_CODE (GnomeDesktopThumbnailFactory,
69 			 gnome_desktop_thumbnail_factory,
70 			 G_TYPE_OBJECT,
71 			 G_ADD_PRIVATE (GnomeDesktopThumbnailFactory))
72 #define parent_class gnome_desktop_thumbnail_factory_parent_class
73 
74 #define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
75   (G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate))
76 
77 typedef struct {
78     gint width;
79     gint height;
80     gint input_width;
81     gint input_height;
82     gboolean preserve_aspect_ratio;
83 } SizePrepareContext;
84 
85 #define LOAD_BUFFER_SIZE 4096
86 
87 #define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
88 #define THUMBNAILER_EXTENSION   ".thumbnailer"
89 
90 typedef struct {
91     volatile gint ref_count;
92 
93     gchar *path;
94 
95     gchar  *try_exec;
96     gchar  *command;
97     gchar **mime_types;
98 } Thumbnailer;
99 
100 static Thumbnailer *
thumbnailer_ref(Thumbnailer * thumb)101 thumbnailer_ref (Thumbnailer *thumb)
102 {
103   g_return_val_if_fail (thumb != NULL, NULL);
104   g_return_val_if_fail (thumb->ref_count > 0, NULL);
105 
106   g_atomic_int_inc (&thumb->ref_count);
107   return thumb;
108 }
109 
110 static void
thumbnailer_unref(Thumbnailer * thumb)111 thumbnailer_unref (Thumbnailer *thumb)
112 {
113   g_return_if_fail (thumb != NULL);
114   g_return_if_fail (thumb->ref_count > 0);
115 
116   if (g_atomic_int_dec_and_test (&thumb->ref_count))
117     {
118       g_free (thumb->path);
119       g_free (thumb->try_exec);
120       g_free (thumb->command);
121       g_strfreev (thumb->mime_types);
122 
123       g_slice_free (Thumbnailer, thumb);
124     }
125 }
126 
127 static Thumbnailer *
thumbnailer_load(Thumbnailer * thumb)128 thumbnailer_load (Thumbnailer *thumb)
129 {
130   GKeyFile *key_file;
131   GError *error = NULL;
132 
133   key_file = g_key_file_new ();
134   if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
135     {
136       g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
137       g_error_free (error);
138       thumbnailer_unref (thumb);
139       g_key_file_free (key_file);
140 
141       return NULL;
142     }
143 
144   if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
145     {
146       g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
147       thumbnailer_unref (thumb);
148       g_key_file_free (key_file);
149 
150       return NULL;
151     }
152 
153   thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
154   if (!thumb->command)
155     {
156       g_warning ("Invalid thumbnailer: missing Exec key\n");
157       thumbnailer_unref (thumb);
158       g_key_file_free (key_file);
159 
160       return NULL;
161     }
162 
163   thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
164   if (!thumb->mime_types)
165     {
166       g_warning ("Invalid thumbnailer: missing MimeType key\n");
167       thumbnailer_unref (thumb);
168       g_key_file_free (key_file);
169 
170       return NULL;
171     }
172 
173   thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL);
174 
175   g_key_file_free (key_file);
176 
177   return thumb;
178 }
179 
180 static Thumbnailer *
thumbnailer_reload(Thumbnailer * thumb)181 thumbnailer_reload (Thumbnailer *thumb)
182 {
183   g_return_val_if_fail (thumb != NULL, NULL);
184 
185   g_free (thumb->command);
186   thumb->command = NULL;
187   g_strfreev (thumb->mime_types);
188   thumb->mime_types = NULL;
189   g_free (thumb->try_exec);
190   thumb->try_exec = NULL;
191 
192   return thumbnailer_load (thumb);
193 }
194 
195 static Thumbnailer *
thumbnailer_new(const gchar * path)196 thumbnailer_new (const gchar *path)
197 {
198   Thumbnailer *thumb;
199 
200   thumb = g_slice_new0 (Thumbnailer);
201   thumb->ref_count = 1;
202   thumb->path = g_strdup (path);
203 
204   return thumbnailer_load (thumb);
205 }
206 
207 static gboolean
thumbnailer_try_exec(Thumbnailer * thumb)208 thumbnailer_try_exec (Thumbnailer *thumb)
209 {
210   gchar *path;
211   gboolean retval;
212 
213   if (G_UNLIKELY (!thumb))
214     return FALSE;
215 
216   /* TryExec is optinal, but Exec isn't, so we assume
217    * the thumbnailer can be run when TryExec is not present
218    */
219   if (!thumb->try_exec)
220     return TRUE;
221 
222   path = g_find_program_in_path (thumb->try_exec);
223   retval = path != NULL;
224   g_free (path);
225 
226   return retval;
227 }
228 
229 static gpointer
init_thumbnailers_dirs(gpointer data)230 init_thumbnailers_dirs (gpointer data)
231 {
232   const gchar * const *data_dirs;
233   gchar **thumbs_dirs;
234   guint i, length;
235 
236   data_dirs = g_get_system_data_dirs ();
237   length = g_strv_length ((char **) data_dirs);
238 
239   thumbs_dirs = g_new (gchar *, length + 2);
240   thumbs_dirs[0] = g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL);
241   for (i = 0; i < length; i++)
242     thumbs_dirs[i + 1] = g_build_filename (data_dirs[i], "thumbnailers", NULL);
243   thumbs_dirs[length + 1] = NULL;
244 
245   return thumbs_dirs;
246 }
247 
248 static const gchar * const *
get_thumbnailers_dirs(void)249 get_thumbnailers_dirs (void)
250 {
251   static GOnce once_init = G_ONCE_INIT;
252   return g_once (&once_init, init_thumbnailers_dirs, NULL);
253 }
254 
255 static void
size_prepared_cb(GdkPixbufLoader * loader,int width,int height,gpointer data)256 size_prepared_cb (GdkPixbufLoader *loader,
257 		  int              width,
258 		  int              height,
259 		  gpointer         data)
260 {
261   SizePrepareContext *info = data;
262 
263   g_return_if_fail (width > 0 && height > 0);
264 
265   info->input_width = width;
266   info->input_height = height;
267 
268   if (width < info->width && height < info->height) return;
269 
270   if (info->preserve_aspect_ratio &&
271       (info->width > 0 || info->height > 0)) {
272     if (info->width < 0)
273       {
274 	width = width * (double)info->height/(double)height;
275 	height = info->height;
276       }
277     else if (info->height < 0)
278       {
279 	height = height * (double)info->width/(double)width;
280 	width = info->width;
281       }
282     else if ((double)height * (double)info->width >
283 	     (double)width * (double)info->height) {
284       width = 0.5 + (double)width * (double)info->height / (double)height;
285       height = info->height;
286     } else {
287       height = 0.5 + (double)height * (double)info->width / (double)width;
288       width = info->width;
289     }
290   } else {
291     if (info->width > 0)
292       width = info->width;
293     if (info->height > 0)
294       height = info->height;
295   }
296 
297   gdk_pixbuf_loader_set_size (loader, width, height);
298 }
299 
300 static GdkPixbuf *
_gdk_pixbuf_new_from_uri_at_scale(const char * uri,gint width,gint height,gboolean preserve_aspect_ratio)301 _gdk_pixbuf_new_from_uri_at_scale (const char *uri,
302 				   gint        width,
303 				   gint        height,
304 				   gboolean    preserve_aspect_ratio)
305 {
306     gboolean result;
307     char buffer[LOAD_BUFFER_SIZE];
308     gsize bytes_read;
309     GdkPixbufLoader *loader;
310     GdkPixbuf *pixbuf;
311     GdkPixbufAnimation *animation;
312     GdkPixbufAnimationIter *iter;
313     gboolean has_frame;
314     SizePrepareContext info;
315     GFile *file;
316     GFileInfo *file_info;
317     GInputStream *input_stream;
318 
319     g_return_val_if_fail (uri != NULL, NULL);
320 
321     input_stream = NULL;
322 
323     file = g_file_new_for_uri (uri);
324 
325     /* First see if we can get an input stream via preview::icon  */
326     file_info = g_file_query_info (file,
327                                    G_FILE_ATTRIBUTE_PREVIEW_ICON,
328                                    G_FILE_QUERY_INFO_NONE,
329                                    NULL,  /* GCancellable */
330                                    NULL); /* return location for GError */
331     if (file_info != NULL) {
332         GObject *object;
333 
334         object = g_file_info_get_attribute_object (file_info,
335                                                    G_FILE_ATTRIBUTE_PREVIEW_ICON);
336         if (object != NULL && G_IS_LOADABLE_ICON (object)) {
337             input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
338                                                  0,     /* size */
339                                                  NULL,  /* return location for type */
340                                                  NULL,  /* GCancellable */
341                                                  NULL); /* return location for GError */
342         }
343         g_object_unref (file_info);
344     }
345 
346     if (input_stream == NULL) {
347         input_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
348         if (input_stream == NULL) {
349 	    g_object_unref (file);
350             return NULL;
351         }
352     }
353 
354     loader = gdk_pixbuf_loader_new ();
355     if (1 <= width || 1 <= height) {
356         info.width = width;
357         info.height = height;
358 	info.input_width = info.input_height = 0;
359         info.preserve_aspect_ratio = preserve_aspect_ratio;
360         g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
361     }
362 
363     has_frame = FALSE;
364 
365     result = FALSE;
366     while (!has_frame) {
367 
368 	bytes_read = g_input_stream_read (input_stream,
369 					  buffer,
370 					  sizeof (buffer),
371 					  NULL,
372 					  NULL);
373 	if (bytes_read == -1) {
374 	    break;
375 	}
376 	result = TRUE;
377 	if (bytes_read == 0) {
378 	    break;
379 	}
380 
381 	if (!gdk_pixbuf_loader_write (loader,
382 				      (unsigned char *)buffer,
383 				      bytes_read,
384 				      NULL)) {
385 	    result = FALSE;
386 	    break;
387 	}
388 
389 	animation = gdk_pixbuf_loader_get_animation (loader);
390 	if (animation) {
391 		iter = gdk_pixbuf_animation_get_iter (animation, NULL);
392 		if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
393 			has_frame = TRUE;
394 		}
395 		g_object_unref (iter);
396 	}
397     }
398 
399     gdk_pixbuf_loader_close (loader, NULL);
400 
401     if (!result) {
402 	g_object_unref (G_OBJECT (loader));
403 	g_input_stream_close (input_stream, NULL, NULL);
404 	g_object_unref (input_stream);
405 	g_object_unref (file);
406 	return NULL;
407     }
408 
409     g_input_stream_close (input_stream, NULL, NULL);
410     g_object_unref (input_stream);
411     g_object_unref (file);
412 
413     pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
414     if (pixbuf != NULL) {
415 	g_object_ref (G_OBJECT (pixbuf));
416 	g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width",
417 			   GINT_TO_POINTER (info.input_width));
418 	g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height",
419 			   GINT_TO_POINTER (info.input_height));
420     }
421     g_object_unref (G_OBJECT (loader));
422 
423     return pixbuf;
424 }
425 
426 static void
gnome_desktop_thumbnail_factory_finalize(GObject * object)427 gnome_desktop_thumbnail_factory_finalize (GObject *object)
428 {
429   GnomeDesktopThumbnailFactory *factory;
430   GnomeDesktopThumbnailFactoryPrivate *priv;
431 
432   factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object);
433 
434   priv = factory->priv;
435 
436   if (priv->thumbnailers)
437     {
438       g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref);
439       priv->thumbnailers = NULL;
440     }
441 
442   if (priv->mime_types_map)
443     {
444       g_hash_table_destroy (priv->mime_types_map);
445       priv->mime_types_map = NULL;
446     }
447 
448   if (priv->monitors)
449     {
450       g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
451       priv->monitors = NULL;
452     }
453 
454   g_mutex_clear (&priv->lock);
455 
456   if (priv->disabled_types)
457     {
458       g_strfreev (priv->disabled_types);
459       priv->disabled_types = NULL;
460     }
461 
462   if (priv->settings)
463     {
464       g_object_unref (priv->settings);
465       priv->settings = NULL;
466     }
467 
468   if (G_OBJECT_CLASS (parent_class)->finalize)
469     (* G_OBJECT_CLASS (parent_class)->finalize) (object);
470 }
471 
472 /* These should be called with the lock held */
473 static void
gnome_desktop_thumbnail_factory_register_mime_types(GnomeDesktopThumbnailFactory * factory,Thumbnailer * thumb)474 gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory,
475                                                      Thumbnailer                  *thumb)
476 {
477   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
478   gint i;
479 
480   for (i = 0; thumb->mime_types[i]; i++)
481     {
482       if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
483         g_hash_table_insert (priv->mime_types_map,
484                              g_strdup (thumb->mime_types[i]),
485                              thumbnailer_ref (thumb));
486     }
487 }
488 
489 static void
gnome_desktop_thumbnail_factory_add_thumbnailer(GnomeDesktopThumbnailFactory * factory,Thumbnailer * thumb)490 gnome_desktop_thumbnail_factory_add_thumbnailer (GnomeDesktopThumbnailFactory *factory,
491                                                  Thumbnailer                  *thumb)
492 {
493   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
494 
495   gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
496   priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
497 }
498 
499 static gboolean
gnome_desktop_thumbnail_factory_is_disabled(GnomeDesktopThumbnailFactory * factory,const gchar * mime_type)500 gnome_desktop_thumbnail_factory_is_disabled (GnomeDesktopThumbnailFactory *factory,
501                                              const gchar                  *mime_type)
502 {
503   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
504   guint i;
505 
506   if (priv->disabled)
507     return TRUE;
508 
509   if (!priv->disabled_types)
510     return FALSE;
511 
512   for (i = 0; priv->disabled_types[i]; i++)
513     {
514       if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
515         return TRUE;
516     }
517 
518   return FALSE;
519 }
520 
521 static gboolean
remove_thumbnailer_from_mime_type_map(gchar * key,Thumbnailer * value,gchar * path)522 remove_thumbnailer_from_mime_type_map (gchar       *key,
523                                        Thumbnailer *value,
524                                        gchar       *path)
525 {
526   return (strcmp (value->path, path) == 0);
527 }
528 
529 
530 static void
update_or_create_thumbnailer(GnomeDesktopThumbnailFactory * factory,const gchar * path)531 update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory,
532                               const gchar                  *path)
533 {
534   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
535   GList *l;
536   Thumbnailer *thumb;
537   gboolean found = FALSE;
538 
539   g_mutex_lock (&priv->lock);
540 
541   for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
542     {
543       thumb = (Thumbnailer *)l->data;
544 
545       if (strcmp (thumb->path, path) == 0)
546         {
547           found = TRUE;
548 
549           /* First remove the mime_types associated to this thumbnailer */
550           g_hash_table_foreach_remove (priv->mime_types_map,
551                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
552                                        (gpointer)path);
553           if (!thumbnailer_reload (thumb))
554               priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
555           else
556               gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
557         }
558     }
559 
560   if (!found)
561     {
562       thumb = thumbnailer_new (path);
563       if (thumb)
564         gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
565     }
566 
567   g_mutex_unlock (&priv->lock);
568 }
569 
570 static void
remove_thumbnailer(GnomeDesktopThumbnailFactory * factory,const gchar * path)571 remove_thumbnailer (GnomeDesktopThumbnailFactory *factory,
572                     const gchar                  *path)
573 {
574   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
575   GList *l;
576   Thumbnailer *thumb;
577 
578   g_mutex_lock (&priv->lock);
579 
580   for (l = priv->thumbnailers; l; l = g_list_next (l))
581     {
582       thumb = (Thumbnailer *)l->data;
583 
584       if (strcmp (thumb->path, path) == 0)
585         {
586           priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
587           g_hash_table_foreach_remove (priv->mime_types_map,
588                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
589                                        (gpointer)path);
590           thumbnailer_unref (thumb);
591 
592           break;
593         }
594     }
595 
596   g_mutex_unlock (&priv->lock);
597 }
598 
599 static void
thumbnailers_directory_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,GnomeDesktopThumbnailFactory * factory)600 thumbnailers_directory_changed (GFileMonitor                 *monitor,
601                                 GFile                        *file,
602                                 GFile                        *other_file,
603                                 GFileMonitorEvent             event_type,
604                                 GnomeDesktopThumbnailFactory *factory)
605 {
606   gchar *path;
607 
608   switch (event_type)
609     {
610     case G_FILE_MONITOR_EVENT_CREATED:
611     case G_FILE_MONITOR_EVENT_CHANGED:
612     case G_FILE_MONITOR_EVENT_DELETED:
613       path = g_file_get_path (file);
614       if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
615         {
616           g_free (path);
617           return;
618         }
619 
620       if (event_type == G_FILE_MONITOR_EVENT_DELETED)
621         remove_thumbnailer (factory, path);
622       else
623         update_or_create_thumbnailer (factory, path);
624 
625       g_free (path);
626       break;
627     default:
628       break;
629     }
630 }
631 
632 static void
gnome_desktop_thumbnail_factory_load_thumbnailers(GnomeDesktopThumbnailFactory * factory)633 gnome_desktop_thumbnail_factory_load_thumbnailers (GnomeDesktopThumbnailFactory *factory)
634 {
635   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
636   const gchar * const *dirs;
637   guint i;
638 
639   if (priv->loaded)
640     return;
641 
642   dirs = get_thumbnailers_dirs ();
643   for (i = 0; dirs[i]; i++)
644     {
645       const gchar *path = dirs[i];
646       GDir *dir;
647       GFile *dir_file;
648       GFileMonitor *monitor;
649       const gchar *dirent;
650 
651       dir = g_dir_open (path, 0, NULL);
652       if (!dir)
653         continue;
654 
655       /* Monitor dir */
656       dir_file = g_file_new_for_path (path);
657       monitor = g_file_monitor_directory (dir_file,
658                                           G_FILE_MONITOR_NONE,
659                                           NULL, NULL);
660       if (monitor)
661         {
662           g_signal_connect (monitor, "changed",
663                             G_CALLBACK (thumbnailers_directory_changed),
664                             factory);
665           priv->monitors = g_list_prepend (priv->monitors, monitor);
666         }
667       g_object_unref (dir_file);
668 
669       while ((dirent = g_dir_read_name (dir)))
670         {
671           Thumbnailer *thumb;
672           gchar       *filename;
673 
674           if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
675             continue;
676 
677           filename = g_build_filename (path, dirent, NULL);
678           thumb = thumbnailer_new (filename);
679           g_free (filename);
680 
681           if (thumb)
682             gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
683         }
684 
685       g_dir_close (dir);
686     }
687 
688   priv->loaded = TRUE;
689 }
690 
691 static void
external_thumbnailers_disabled_all_changed_cb(GSettings * settings,const gchar * key,GnomeDesktopThumbnailFactory * factory)692 external_thumbnailers_disabled_all_changed_cb (GSettings                    *settings,
693                                                const gchar                  *key,
694                                                GnomeDesktopThumbnailFactory *factory)
695 {
696   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
697 
698   g_mutex_lock (&priv->lock);
699 
700   priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
701   if (priv->disabled)
702     {
703       g_strfreev (priv->disabled_types);
704       priv->disabled_types = NULL;
705     }
706   else
707     {
708       priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
709       gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
710     }
711 
712   g_mutex_unlock (&priv->lock);
713 }
714 
715 static void
external_thumbnailers_disabled_changed_cb(GSettings * settings,const gchar * key,GnomeDesktopThumbnailFactory * factory)716 external_thumbnailers_disabled_changed_cb (GSettings                    *settings,
717                                            const gchar                  *key,
718                                            GnomeDesktopThumbnailFactory *factory)
719 {
720   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
721 
722   g_mutex_lock (&priv->lock);
723 
724   if (priv->disabled)
725     return;
726   g_strfreev (priv->disabled_types);
727   priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
728 
729   g_mutex_unlock (&priv->lock);
730 }
731 
732 static void
gnome_desktop_thumbnail_factory_init(GnomeDesktopThumbnailFactory * factory)733 gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory)
734 {
735   GnomeDesktopThumbnailFactoryPrivate *priv;
736 
737   factory->priv = gnome_desktop_thumbnail_factory_get_instance_private (factory);
738 
739   priv = factory->priv;
740 
741   priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
742 
743   priv->mime_types_map = g_hash_table_new_full (g_str_hash,
744                                                 g_str_equal,
745                                                 (GDestroyNotify)g_free,
746                                                 (GDestroyNotify)thumbnailer_unref);
747 
748   g_mutex_init (&priv->lock);
749 
750   priv->settings = g_settings_new ("org.gnome.desktop.thumbnailers");
751   priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
752   if (!priv->disabled)
753     priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
754   g_signal_connect (priv->settings, "changed::disable-all",
755                     G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
756                     factory);
757   g_signal_connect (priv->settings, "changed::disable",
758                     G_CALLBACK (external_thumbnailers_disabled_changed_cb),
759                     factory);
760 
761   if (!priv->disabled)
762     gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
763 }
764 
765 static void
gnome_desktop_thumbnail_factory_class_init(GnomeDesktopThumbnailFactoryClass * class)766 gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class)
767 {
768   GObjectClass *gobject_class;
769 
770   gobject_class = G_OBJECT_CLASS (class);
771 
772   gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize;
773 }
774 
775 /**
776  * gnome_desktop_thumbnail_factory_new:
777  * @size: The thumbnail size to use
778  *
779  * Creates a new #GnomeDesktopThumbnailFactory.
780  *
781  * This function must be called on the main thread.
782  *
783  * Return value: a new #GnomeDesktopThumbnailFactory
784  *
785  * Since: 2.2
786  **/
787 GnomeDesktopThumbnailFactory *
gnome_desktop_thumbnail_factory_new(GnomeDesktopThumbnailSize size)788 gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size)
789 {
790   GnomeDesktopThumbnailFactory *factory;
791 
792   factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
793 
794   factory->priv->size = size;
795 
796   return factory;
797 }
798 
799 /**
800  * gnome_desktop_thumbnail_factory_lookup:
801  * @factory: a #GnomeDesktopThumbnailFactory
802  * @uri: the uri of a file
803  * @mtime: the mtime of the file
804  *
805  * Tries to locate an existing thumbnail for the file specified.
806  *
807  * Usage of this function is threadsafe.
808  *
809  * Return value: The absolute path of the thumbnail, or %NULL if none exist.
810  *
811  * Since: 2.2
812  **/
813 char *
gnome_desktop_thumbnail_factory_lookup(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)814 gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
815 					const char            *uri,
816 					time_t                 mtime)
817 {
818   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
819   char *path, *file;
820   GChecksum *checksum;
821   guint8 digest[16];
822   gsize digest_len = sizeof (digest);
823   GdkPixbuf *pixbuf;
824   gboolean res;
825 
826   g_return_val_if_fail (uri != NULL, NULL);
827 
828   res = FALSE;
829 
830   checksum = g_checksum_new (G_CHECKSUM_MD5);
831   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
832 
833   g_checksum_get_digest (checksum, digest, &digest_len);
834   g_assert (digest_len == 16);
835 
836   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
837 
838   path = g_build_filename (g_get_home_dir (),
839 			   ".thumbnails",
840 			   (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
841 			   file,
842 			   NULL);
843   g_free (file);
844 
845   pixbuf = gdk_pixbuf_new_from_file (path, NULL);
846   if (pixbuf != NULL)
847     {
848       res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
849       g_object_unref (pixbuf);
850     }
851 
852   g_checksum_free (checksum);
853 
854   if (res)
855     return path;
856 
857   g_free (path);
858   return FALSE;
859 }
860 
861 /**
862  * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail:
863  * @factory: a #GnomeDesktopThumbnailFactory
864  * @uri: the uri of a file
865  * @mtime: the mtime of the file
866  *
867  * Tries to locate an failed thumbnail for the file specified. Writing
868  * and looking for failed thumbnails is important to avoid to try to
869  * thumbnail e.g. broken images several times.
870  *
871  * Usage of this function is threadsafe.
872  *
873  * Return value: TRUE if there is a failed thumbnail for the file.
874  *
875  * Since: 2.2
876  **/
877 gboolean
gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)878 gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
879 							    const char            *uri,
880 							    time_t                 mtime)
881 {
882   char *path, *file;
883   GdkPixbuf *pixbuf;
884   gboolean res;
885   GChecksum *checksum;
886   guint8 digest[16];
887   gsize digest_len = sizeof (digest);
888 
889   checksum = g_checksum_new (G_CHECKSUM_MD5);
890   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
891 
892   g_checksum_get_digest (checksum, digest, &digest_len);
893   g_assert (digest_len == 16);
894 
895   res = FALSE;
896 
897   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
898 
899   path = g_build_filename (g_get_home_dir (),
900 			   ".thumbnails/fail",
901 			   appname,
902 			   file,
903 			   NULL);
904   g_free (file);
905 
906   pixbuf = gdk_pixbuf_new_from_file (path, NULL);
907   g_free (path);
908 
909   if (pixbuf)
910     {
911       res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
912       g_object_unref (pixbuf);
913     }
914 
915   g_checksum_free (checksum);
916 
917   return res;
918 }
919 
920 static gboolean
mimetype_supported_by_gdk_pixbuf(const char * mime_type)921 mimetype_supported_by_gdk_pixbuf (const char *mime_type)
922 {
923         guint i;
924         static gsize formats_hash = 0;
925         gchar *key;
926         gboolean result;
927 
928 	if (g_once_init_enter (&formats_hash)) {
929                 GSList *formats, *list;
930 		GHashTable *hash;
931 
932                 hash = g_hash_table_new_full (g_str_hash,
933 					      (GEqualFunc) g_content_type_equals,
934 					      g_free, NULL);
935 
936                 formats = gdk_pixbuf_get_formats ();
937                 list = formats;
938 
939                 while (list) {
940                         GdkPixbufFormat *format = list->data;
941                         gchar **mime_types;
942 
943                         mime_types = gdk_pixbuf_format_get_mime_types (format);
944 
945                         for (i = 0; mime_types[i] != NULL; i++)
946                                 g_hash_table_insert (hash,
947                                                      (gpointer) g_content_type_from_mime_type (mime_types[i]),
948                                                      GUINT_TO_POINTER (1));
949 
950                         g_strfreev (mime_types);
951                         list = list->next;
952                 }
953                 g_slist_free (formats);
954 
955 		g_once_init_leave (&formats_hash, (gsize) hash);
956         }
957 
958         key = g_content_type_from_mime_type (mime_type);
959         if (g_hash_table_lookup ((void*)formats_hash, key))
960                 result = TRUE;
961         else
962                 result = FALSE;
963         g_free (key);
964 
965         return result;
966 }
967 
968 /**
969  * gnome_desktop_thumbnail_factory_can_thumbnail:
970  * @factory: a #GnomeDesktopThumbnailFactory
971  * @uri: the uri of a file
972  * @mime_type: the mime type of the file
973  * @mtime: the mtime of the file
974  *
975  * Returns TRUE if this GnomeIconFactory can (at least try) to thumbnail
976  * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
977  *
978  * Usage of this function is threadsafe.
979  *
980  * Return value: TRUE if the file can be thumbnailed.
981  *
982  * Since: 2.2
983  **/
984 gboolean
gnome_desktop_thumbnail_factory_can_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,const char * mime_type,time_t mtime)985 gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
986 					       const char            *uri,
987 					       const char            *mime_type,
988 					       time_t                 mtime)
989 {
990   gboolean have_script = FALSE;
991 
992   /* Don't thumbnail thumbnails */
993   if (uri &&
994       strncmp (uri, "file:/", 6) == 0 &&
995       strstr (uri, "/.thumbnails/") != NULL)
996     return FALSE;
997 
998   if (!mime_type)
999     return FALSE;
1000 
1001   g_mutex_lock (&factory->priv->lock);
1002   if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
1003     {
1004       Thumbnailer *thumb;
1005 
1006       thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1007       have_script = thumbnailer_try_exec (thumb);
1008     }
1009   g_mutex_unlock (&factory->priv->lock);
1010 
1011   if (have_script || mimetype_supported_by_gdk_pixbuf (mime_type))
1012     {
1013       return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
1014                                                                           uri,
1015                                                                           mtime);
1016     }
1017 
1018   return FALSE;
1019 }
1020 
1021 static char *
expand_thumbnailing_script(const char * script,const int size,const char * inuri,const char * outfile)1022 expand_thumbnailing_script (const char *script,
1023 			    const int   size,
1024 			    const char *inuri,
1025 			    const char *outfile)
1026 {
1027   GString *str;
1028   const char *p, *last;
1029   char *localfile, *quoted;
1030   gboolean got_in;
1031 
1032   str = g_string_new (NULL);
1033 
1034   got_in = FALSE;
1035   last = script;
1036   while ((p = strchr (last, '%')) != NULL)
1037     {
1038       g_string_append_len (str, last, p - last);
1039       p++;
1040 
1041       switch (*p) {
1042       case 'u':
1043 	quoted = g_shell_quote (inuri);
1044 	g_string_append (str, quoted);
1045 	g_free (quoted);
1046 	got_in = TRUE;
1047 	p++;
1048 	break;
1049       case 'i':
1050 	localfile = g_filename_from_uri (inuri, NULL, NULL);
1051 	if (localfile)
1052 	  {
1053 	    quoted = g_shell_quote (localfile);
1054 	    g_string_append (str, quoted);
1055 	    got_in = TRUE;
1056 	    g_free (quoted);
1057 	    g_free (localfile);
1058 	  }
1059 	p++;
1060 	break;
1061       case 'o':
1062 	quoted = g_shell_quote (outfile);
1063 	g_string_append (str, quoted);
1064 	g_free (quoted);
1065 	p++;
1066 	break;
1067       case 's':
1068 	g_string_append_printf (str, "%d", size);
1069 	p++;
1070 	break;
1071       case '%':
1072 	g_string_append_c (str, '%');
1073 	p++;
1074 	break;
1075       case 0:
1076       default:
1077 	break;
1078       }
1079       last = p;
1080     }
1081   g_string_append (str, last);
1082 
1083   if (got_in)
1084     return g_string_free (str, FALSE);
1085 
1086   g_string_free (str, TRUE);
1087   return NULL;
1088 }
1089 
1090 /**
1091  * gnome_desktop_thumbnail_factory_generate_thumbnail:
1092  * @factory: a #GnomeDesktopThumbnailFactory
1093  * @uri: the uri of a file
1094  * @mime_type: the mime type of the file
1095  *
1096  * Tries to generate a thumbnail for the specified file. If it succeeds
1097  * it returns a pixbuf that can be used as a thumbnail.
1098  *
1099  * Usage of this function is threadsafe.
1100  *
1101  * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
1102  *
1103  * Since: 2.2
1104  **/
1105 GdkPixbuf *
gnome_desktop_thumbnail_factory_generate_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,const char * mime_type)1106 gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
1107 						    const char            *uri,
1108 						    const char            *mime_type)
1109 {
1110   GdkPixbuf *pixbuf, *scaled, *tmp_pixbuf;
1111   char *script, *expanded_script;
1112   int width, height, size;
1113   int original_width = 0;
1114   int original_height = 0;
1115   char dimension[12];
1116   double scale;
1117   int exit_status;
1118   char *tmpname;
1119 
1120   g_return_val_if_fail (uri != NULL, NULL);
1121   g_return_val_if_fail (mime_type != NULL, NULL);
1122 
1123   /* Doesn't access any volatile fields in factory, so it's threadsafe */
1124 
1125   size = 128;
1126   if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE)
1127     size = 256;
1128 
1129   pixbuf = NULL;
1130 
1131   script = NULL;
1132   g_mutex_lock (&factory->priv->lock);
1133   if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
1134     {
1135       Thumbnailer *thumb;
1136 
1137       thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1138       if (thumb)
1139         script = g_strdup (thumb->command);
1140     }
1141   g_mutex_unlock (&factory->priv->lock);
1142 
1143   if (script)
1144     {
1145       int fd;
1146 
1147       fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, NULL);
1148 
1149       if (fd != -1)
1150 	{
1151 	  close (fd);
1152 
1153 	  expanded_script = expand_thumbnailing_script (script, size, uri, tmpname);
1154 	  if (expanded_script != NULL &&
1155 	      g_spawn_command_line_sync (expanded_script,
1156 					 NULL, NULL, &exit_status, NULL) &&
1157 	      exit_status == 0)
1158 	    {
1159 	      pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
1160 	    }
1161 
1162 	  g_free (expanded_script);
1163 	  g_unlink(tmpname);
1164 	  g_free (tmpname);
1165 	}
1166 
1167       g_free (script);
1168     }
1169 
1170   /* Fall back to gdk-pixbuf */
1171   if (pixbuf == NULL)
1172     {
1173       pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, size, size, TRUE);
1174 
1175       if (pixbuf != NULL)
1176         {
1177           original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
1178                                                                "gnome-original-width"));
1179           original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
1180                                                                 "gnome-original-height"));
1181         }
1182     }
1183 
1184   if (pixbuf == NULL)
1185     return NULL;
1186 
1187   /* The pixbuf loader may attach an "orientation" option to the pixbuf,
1188      if the tiff or exif jpeg file had an orientation tag. Rotate/flip
1189      the pixbuf as specified by this tag, if present. */
1190   tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
1191   g_object_unref (pixbuf);
1192   pixbuf = tmp_pixbuf;
1193 
1194   width = gdk_pixbuf_get_width (pixbuf);
1195   height = gdk_pixbuf_get_height (pixbuf);
1196 
1197   if (width > size || height > size)
1198     {
1199       const gchar *orig_width, *orig_height;
1200       scale = (double)size / MAX (width, height);
1201 
1202       scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf,
1203 						  floor (width * scale + 0.5),
1204 						  floor (height * scale + 0.5));
1205 
1206       orig_width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
1207       orig_height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
1208 
1209       if (orig_width != NULL) {
1210 	      gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Width", orig_width);
1211       }
1212       if (orig_height != NULL) {
1213 	      gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Height", orig_height);
1214       }
1215 
1216       g_object_unref (pixbuf);
1217       pixbuf = scaled;
1218     }
1219 
1220   if (original_width > 0) {
1221 	  g_snprintf (dimension, sizeof (dimension), "%i", original_width);
1222 	  gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", dimension);
1223   }
1224   if (original_height > 0) {
1225 	  g_snprintf (dimension, sizeof (dimension), "%i", original_height);
1226 	  gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", dimension);
1227   }
1228 
1229   return pixbuf;
1230 }
1231 
1232 static gboolean
make_thumbnail_dirs(GnomeDesktopThumbnailFactory * factory)1233 make_thumbnail_dirs (GnomeDesktopThumbnailFactory *factory)
1234 {
1235   char *thumbnail_dir;
1236   char *image_dir;
1237   gboolean res;
1238 
1239   res = FALSE;
1240 
1241   thumbnail_dir = g_build_filename (g_get_home_dir (),
1242 				    ".thumbnails",
1243 				    NULL);
1244   if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
1245     {
1246       g_mkdir (thumbnail_dir, 0700);
1247       res = TRUE;
1248     }
1249 
1250   image_dir = g_build_filename (thumbnail_dir,
1251 				(factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1252 				NULL);
1253   if (!g_file_test (image_dir, G_FILE_TEST_IS_DIR))
1254     {
1255       g_mkdir (image_dir, 0700);
1256       res = TRUE;
1257     }
1258 
1259   g_free (thumbnail_dir);
1260   g_free (image_dir);
1261 
1262   return res;
1263 }
1264 
1265 static gboolean
make_thumbnail_fail_dirs(GnomeDesktopThumbnailFactory * factory)1266 make_thumbnail_fail_dirs (GnomeDesktopThumbnailFactory *factory)
1267 {
1268   char *thumbnail_dir;
1269   char *fail_dir;
1270   char *app_dir;
1271   gboolean res;
1272 
1273   res = FALSE;
1274 
1275   thumbnail_dir = g_build_filename (g_get_home_dir (),
1276 				    ".thumbnails",
1277 				    NULL);
1278   if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
1279     {
1280       g_mkdir (thumbnail_dir, 0700);
1281       res = TRUE;
1282     }
1283 
1284   fail_dir = g_build_filename (thumbnail_dir,
1285 			       "fail",
1286 			       NULL);
1287   if (!g_file_test (fail_dir, G_FILE_TEST_IS_DIR))
1288     {
1289       g_mkdir (fail_dir, 0700);
1290       res = TRUE;
1291     }
1292 
1293   app_dir = g_build_filename (fail_dir,
1294 			      appname,
1295 			      NULL);
1296   if (!g_file_test (app_dir, G_FILE_TEST_IS_DIR))
1297     {
1298       g_mkdir (app_dir, 0700);
1299       res = TRUE;
1300     }
1301 
1302   g_free (thumbnail_dir);
1303   g_free (fail_dir);
1304   g_free (app_dir);
1305 
1306   return res;
1307 }
1308 
1309 
1310 /**
1311  * gnome_desktop_thumbnail_factory_save_thumbnail:
1312  * @factory: a #GnomeDesktopThumbnailFactory
1313  * @thumbnail: the thumbnail as a pixbuf
1314  * @uri: the uri of a file
1315  * @original_mtime: the modification time of the original file
1316  *
1317  * Saves @thumbnail at the right place. If the save fails a
1318  * failed thumbnail is written.
1319  *
1320  * Usage of this function is threadsafe.
1321  *
1322  * Since: 2.2
1323  **/
1324 void
gnome_desktop_thumbnail_factory_save_thumbnail(GnomeDesktopThumbnailFactory * factory,GdkPixbuf * thumbnail,const char * uri,time_t original_mtime)1325 gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
1326 						GdkPixbuf             *thumbnail,
1327 						const char            *uri,
1328 						time_t                 original_mtime)
1329 {
1330   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
1331   char *path, *file;
1332   char *tmp_path;
1333   const char *width, *height;
1334   int tmp_fd;
1335   char mtime_str[21];
1336   gboolean saved_ok;
1337   GChecksum *checksum;
1338   guint8 digest[16];
1339   gsize digest_len = sizeof (digest);
1340 
1341   checksum = g_checksum_new (G_CHECKSUM_MD5);
1342   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
1343 
1344   g_checksum_get_digest (checksum, digest, &digest_len);
1345   g_assert (digest_len == 16);
1346 
1347   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1348 
1349   path = g_build_filename (g_get_home_dir (),
1350 			   ".thumbnails",
1351 			   (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1352 			   file,
1353 			   NULL);
1354 
1355   g_free (file);
1356 
1357   g_checksum_free (checksum);
1358 
1359   tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1360 
1361   tmp_fd = g_mkstemp (tmp_path);
1362   if (tmp_fd == -1 &&
1363       make_thumbnail_dirs (factory))
1364     {
1365       g_free (tmp_path);
1366       tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1367       tmp_fd = g_mkstemp (tmp_path);
1368     }
1369 
1370   if (tmp_fd == -1)
1371     {
1372       gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
1373       g_free (tmp_path);
1374       g_free (path);
1375       return;
1376     }
1377   close (tmp_fd);
1378 
1379   g_snprintf (mtime_str, 21, "%ld",  original_mtime);
1380   width = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Width");
1381   height = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Height");
1382 
1383   if (width != NULL && height != NULL)
1384     saved_ok  = gdk_pixbuf_save (thumbnail,
1385 				 tmp_path,
1386 				 "png", NULL,
1387 				 "tEXt::Thumb::Image::Width", width,
1388 				 "tEXt::Thumb::Image::Height", height,
1389 				 "tEXt::Thumb::URI", uri,
1390 				 "tEXt::Thumb::MTime", mtime_str,
1391 				 "tEXt::Software", "GNOME::ThumbnailFactory",
1392 				 NULL);
1393   else
1394     saved_ok  = gdk_pixbuf_save (thumbnail,
1395 				 tmp_path,
1396 				 "png", NULL,
1397 				 "tEXt::Thumb::URI", uri,
1398 				 "tEXt::Thumb::MTime", mtime_str,
1399 				 "tEXt::Software", "GNOME::ThumbnailFactory",
1400 				 NULL);
1401 
1402 
1403   if (saved_ok)
1404     {
1405       g_chmod (tmp_path, 0600);
1406       g_rename(tmp_path, path);
1407     }
1408   else
1409     {
1410       gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
1411     }
1412 
1413   g_free (path);
1414   g_free (tmp_path);
1415 }
1416 
1417 /**
1418  * gnome_desktop_thumbnail_factory_create_failed_thumbnail:
1419  * @factory: a #GnomeDesktopThumbnailFactory
1420  * @uri: the uri of a file
1421  * @mtime: the modification time of the file
1422  *
1423  * Creates a failed thumbnail for the file so that we don't try
1424  * to re-thumbnail the file later.
1425  *
1426  * Usage of this function is threadsafe.
1427  *
1428  * Since: 2.2
1429  **/
1430 void
gnome_desktop_thumbnail_factory_create_failed_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)1431 gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
1432 							 const char            *uri,
1433 							 time_t                 mtime)
1434 {
1435   char *path, *file;
1436   char *tmp_path;
1437   int tmp_fd;
1438   char mtime_str[21];
1439   gboolean saved_ok;
1440   GdkPixbuf *pixbuf;
1441   GChecksum *checksum;
1442   guint8 digest[16];
1443   gsize digest_len = sizeof (digest);
1444 
1445   checksum = g_checksum_new (G_CHECKSUM_MD5);
1446   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
1447 
1448   g_checksum_get_digest (checksum, digest, &digest_len);
1449   g_assert (digest_len == 16);
1450 
1451   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1452 
1453   path = g_build_filename (g_get_home_dir (),
1454 			   ".thumbnails/fail",
1455 			   appname,
1456 			   file,
1457 			   NULL);
1458   g_free (file);
1459 
1460   g_checksum_free (checksum);
1461 
1462   tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1463 
1464   tmp_fd = g_mkstemp (tmp_path);
1465   if (tmp_fd == -1 &&
1466       make_thumbnail_fail_dirs (factory))
1467     {
1468       g_free (tmp_path);
1469       tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1470       tmp_fd = g_mkstemp (tmp_path);
1471     }
1472 
1473   if (tmp_fd == -1)
1474     {
1475       g_free (tmp_path);
1476       g_free (path);
1477       return;
1478     }
1479   close (tmp_fd);
1480 
1481   g_snprintf (mtime_str, 21, "%ld",  mtime);
1482   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
1483   saved_ok  = gdk_pixbuf_save (pixbuf,
1484 			       tmp_path,
1485 			       "png", NULL,
1486 			       "tEXt::Thumb::URI", uri,
1487 			       "tEXt::Thumb::MTime", mtime_str,
1488 			       "tEXt::Software", "GNOME::ThumbnailFactory",
1489 			       NULL);
1490   g_object_unref (pixbuf);
1491   if (saved_ok)
1492     {
1493       g_chmod (tmp_path, 0600);
1494       g_rename(tmp_path, path);
1495     }
1496 
1497   g_free (path);
1498   g_free (tmp_path);
1499 }
1500 
1501 /**
1502  * gnome_desktop_thumbnail_md5:
1503  * @uri: an uri
1504  *
1505  * Calculates the MD5 checksum of the uri. This can be useful
1506  * if you want to manually handle thumbnail files.
1507  *
1508  * Return value: A string with the MD5 digest of the uri string.
1509  *
1510  * Since: 2.2
1511  * Deprecated: 2.22: Use #GChecksum instead
1512  **/
1513 char *
gnome_desktop_thumbnail_md5(const char * uri)1514 gnome_desktop_thumbnail_md5 (const char *uri)
1515 {
1516   return g_compute_checksum_for_data (G_CHECKSUM_MD5,
1517                                       (const guchar *) uri,
1518                                       strlen (uri));
1519 }
1520 
1521 /**
1522  * gnome_desktop_thumbnail_path_for_uri:
1523  * @uri: an uri
1524  * @size: a thumbnail size
1525  *
1526  * Returns the filename that a thumbnail of size @size for @uri would have.
1527  *
1528  * Return value: an absolute filename
1529  *
1530  * Since: 2.2
1531  **/
1532 char *
gnome_desktop_thumbnail_path_for_uri(const char * uri,GnomeDesktopThumbnailSize size)1533 gnome_desktop_thumbnail_path_for_uri (const char         *uri,
1534 				      GnomeDesktopThumbnailSize  size)
1535 {
1536   char *md5;
1537   char *file;
1538   char *path;
1539 
1540   md5 = gnome_desktop_thumbnail_md5 (uri);
1541   file = g_strconcat (md5, ".png", NULL);
1542   g_free (md5);
1543 
1544   path = g_build_filename (g_get_home_dir (),
1545 			   ".thumbnails",
1546 			   (size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1547 			   file,
1548 			   NULL);
1549 
1550   g_free (file);
1551 
1552   return path;
1553 }
1554 
1555 /**
1556  * gnome_desktop_thumbnail_has_uri:
1557  * @pixbuf: an loaded thumbnail pixbuf
1558  * @uri: a uri
1559  *
1560  * Returns whether the thumbnail has the correct uri embedded in the
1561  * Thumb::URI option in the png.
1562  *
1563  * Return value: TRUE if the thumbnail is for @uri
1564  *
1565  * Since: 2.2
1566  **/
1567 gboolean
gnome_desktop_thumbnail_has_uri(GdkPixbuf * pixbuf,const char * uri)1568 gnome_desktop_thumbnail_has_uri (GdkPixbuf          *pixbuf,
1569 				 const char         *uri)
1570 {
1571   const char *thumb_uri;
1572 
1573   thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1574   if (!thumb_uri)
1575     return FALSE;
1576 
1577   return strcmp (uri, thumb_uri) == 0;
1578 }
1579 
1580 /**
1581  * gnome_desktop_thumbnail_is_valid:
1582  * @pixbuf: an loaded thumbnail #GdkPixbuf
1583  * @uri: a uri
1584  * @mtime: the mtime
1585  *
1586  * Returns whether the thumbnail has the correct uri and mtime embedded in the
1587  * png options.
1588  *
1589  * Return value: TRUE if the thumbnail has the right @uri and @mtime
1590  *
1591  * Since: 2.2
1592  **/
1593 gboolean
gnome_desktop_thumbnail_is_valid(GdkPixbuf * pixbuf,const char * uri,time_t mtime)1594 gnome_desktop_thumbnail_is_valid (GdkPixbuf          *pixbuf,
1595 				  const char         *uri,
1596 				  time_t              mtime)
1597 {
1598   const char *thumb_uri, *thumb_mtime_str;
1599   time_t thumb_mtime;
1600 
1601   thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1602   if (!thumb_uri)
1603     return FALSE;
1604   if (strcmp (uri, thumb_uri) != 0)
1605     return FALSE;
1606 
1607   thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
1608   if (!thumb_mtime_str)
1609     return FALSE;
1610   thumb_mtime = atol (thumb_mtime_str);
1611   if (mtime != thumb_mtime)
1612     return FALSE;
1613 
1614   return TRUE;
1615 }
1616