1 /*
2  * mate-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 Mate Library.
8  *
9  * The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB.  If not,
21  * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  * Author: Alexander Larsson <alexl@redhat.com>
25  */
26 
27 #include <config.h>
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 
31 #include <gdk-pixbuf/gdk-pixbuf.h>
32 #include <string.h>
33 #include <stdlib.h>
34 
35 #define MATE_DESKTOP_USE_UNSTABLE_API
36 #include "mate-desktop-thumbnail.h"
37 
38 static void
39 thumbnailers_directory_changed (GFileMonitor                 *monitor,
40                                 GFile                        *file,
41                                 GFile                        *other_file,
42                                 GFileMonitorEvent             event_type,
43                                 MateDesktopThumbnailFactory  *factory);
44 
45 struct _MateDesktopThumbnailFactoryPrivate {
46   MateDesktopThumbnailSize size;
47 
48   GMutex lock;
49 
50   GList *thumbnailers;
51   GHashTable *mime_types_map;
52   GList *monitors;
53 
54   GSettings *settings;
55   gboolean loaded : 1;
56   gboolean disabled : 1;
57   gchar **disabled_types;
58 };
59 
60 static const char *appname = "mate-thumbnail-factory";
61 
62 G_DEFINE_TYPE_WITH_PRIVATE (MateDesktopThumbnailFactory,
63                             mate_desktop_thumbnail_factory,
64                             G_TYPE_OBJECT)
65 
66 #define parent_class mate_desktop_thumbnail_factory_parent_class
67 
68 #define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
69 #define THUMBNAILER_EXTENSION   ".thumbnailer"
70 
71 typedef struct {
72     volatile gint ref_count;
73     gchar  *path;
74     gchar  *try_exec;
75     gchar  *command;
76     gchar **mime_types;
77 } Thumbnailer;
78 
79 static Thumbnailer *
thumbnailer_ref(Thumbnailer * thumb)80 thumbnailer_ref (Thumbnailer *thumb)
81 {
82   g_return_val_if_fail (thumb != NULL, NULL);
83   g_return_val_if_fail (thumb->ref_count > 0, NULL);
84 
85   g_atomic_int_inc (&thumb->ref_count);
86   return thumb;
87 }
88 
89 static void
thumbnailer_unref(Thumbnailer * thumb)90 thumbnailer_unref (Thumbnailer *thumb)
91 {
92   g_return_if_fail (thumb != NULL);
93   g_return_if_fail (thumb->ref_count > 0);
94 
95   if (g_atomic_int_dec_and_test (&thumb->ref_count))
96     {
97       g_free (thumb->path);
98       g_free (thumb->try_exec);
99       g_free (thumb->command);
100       g_strfreev (thumb->mime_types);
101       g_slice_free (Thumbnailer, thumb);
102     }
103 }
104 
105 static Thumbnailer *
thumbnailer_load(Thumbnailer * thumb)106 thumbnailer_load (Thumbnailer *thumb)
107 {
108   GKeyFile *key_file;
109   GError *error = NULL;
110 
111   key_file = g_key_file_new ();
112   if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
113     {
114       g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
115       g_error_free (error);
116       thumbnailer_unref (thumb);
117       g_key_file_free (key_file);
118 
119       return NULL;
120     }
121 
122   if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
123     {
124       g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
125       thumbnailer_unref (thumb);
126       g_key_file_free (key_file);
127 
128       return NULL;
129     }
130 
131   thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
132   if (!thumb->command)
133     {
134       g_warning ("Invalid thumbnailer: missing Exec key\n");
135       thumbnailer_unref (thumb);
136       g_key_file_free (key_file);
137 
138       return NULL;
139     }
140 
141   thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
142   if (!thumb->mime_types)
143     {
144       g_warning ("Invalid thumbnailer: missing MimeType key\n");
145       thumbnailer_unref (thumb);
146       g_key_file_free (key_file);
147 
148       return NULL;
149     }
150 
151   thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL);
152 
153   g_key_file_free (key_file);
154 
155   return thumb;
156 }
157 
158 static Thumbnailer *
thumbnailer_reload(Thumbnailer * thumb)159 thumbnailer_reload (Thumbnailer *thumb)
160 {
161   g_return_val_if_fail (thumb != NULL, NULL);
162 
163   g_free (thumb->command);
164   thumb->command = NULL;
165   g_strfreev (thumb->mime_types);
166   thumb->mime_types = NULL;
167   g_free (thumb->try_exec);
168   thumb->try_exec = NULL;
169 
170   return thumbnailer_load (thumb);
171 }
172 
173 static Thumbnailer *
thumbnailer_new(const gchar * path)174 thumbnailer_new (const gchar *path)
175 {
176   Thumbnailer *thumb;
177 
178   thumb = g_slice_new0 (Thumbnailer);
179   thumb->ref_count = 1;
180   thumb->path = g_strdup (path);
181 
182   return thumbnailer_load (thumb);
183 }
184 
185 static gboolean
thumbnailer_try_exec(Thumbnailer * thumb)186 thumbnailer_try_exec (Thumbnailer *thumb)
187 {
188   gchar *path;
189   gboolean retval;
190 
191   if (G_UNLIKELY (!thumb))
192     return FALSE;
193 
194   /* TryExec is optional, but Exec isn't, so we assume
195    * the thumbnailer can be run when TryExec is not present
196    */
197   if (!thumb->try_exec)
198     return TRUE;
199 
200   path = g_find_program_in_path (thumb->try_exec);
201   retval = path != NULL;
202   g_free (path);
203 
204   return retval;
205 }
206 
207 static gpointer
init_thumbnailers_dirs(gpointer data)208 init_thumbnailers_dirs (gpointer data)
209 {
210   const gchar * const *data_dirs;
211   GPtrArray *thumbs_dirs;
212   guint i;
213 
214   data_dirs = g_get_system_data_dirs ();
215   thumbs_dirs = g_ptr_array_new ();
216 
217   g_ptr_array_add (thumbs_dirs, g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL));
218   for (i = 0; data_dirs[i] != NULL; i++)
219     g_ptr_array_add (thumbs_dirs, g_build_filename (data_dirs[i], "thumbnailers", NULL));
220   g_ptr_array_add (thumbs_dirs, NULL);
221 
222   return g_ptr_array_free (thumbs_dirs, FALSE);
223 }
224 
225 static const gchar * const *
get_thumbnailers_dirs(void)226 get_thumbnailers_dirs (void)
227 {
228   static GOnce once_init = G_ONCE_INIT;
229   return g_once (&once_init, init_thumbnailers_dirs, NULL);
230 }
231 
232 /* These should be called with the lock held */
233 static void
mate_desktop_thumbnail_factory_register_mime_types(MateDesktopThumbnailFactory * factory,Thumbnailer * thumb)234 mate_desktop_thumbnail_factory_register_mime_types (MateDesktopThumbnailFactory *factory,
235                                                      Thumbnailer                  *thumb)
236 {
237   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
238   gint i;
239 
240   for (i = 0; thumb->mime_types[i]; i++)
241     {
242       if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
243         g_hash_table_insert (priv->mime_types_map,
244                              g_strdup (thumb->mime_types[i]),
245                              thumbnailer_ref (thumb));
246     }
247 }
248 
249 static void
mate_desktop_thumbnail_factory_add_thumbnailer(MateDesktopThumbnailFactory * factory,Thumbnailer * thumb)250 mate_desktop_thumbnail_factory_add_thumbnailer (MateDesktopThumbnailFactory *factory,
251                                                  Thumbnailer                  *thumb)
252 {
253   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
254 
255   mate_desktop_thumbnail_factory_register_mime_types (factory, thumb);
256   priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
257 }
258 
259 static gboolean
mate_desktop_thumbnail_factory_is_disabled(MateDesktopThumbnailFactory * factory,const gchar * mime_type)260 mate_desktop_thumbnail_factory_is_disabled (MateDesktopThumbnailFactory *factory,
261                                              const gchar                  *mime_type)
262 {
263   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
264   guint i;
265 
266   if (priv->disabled)
267     return TRUE;
268 
269   if (!priv->disabled_types)
270     return FALSE;
271 
272   for (i = 0; priv->disabled_types[i]; i++)
273     {
274       if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
275         return TRUE;
276     }
277 
278   return FALSE;
279 }
280 
281 static gboolean
remove_thumbnailer_from_mime_type_map(gchar * key,Thumbnailer * value,gchar * path)282 remove_thumbnailer_from_mime_type_map (gchar       *key,
283                                        Thumbnailer *value,
284                                        gchar       *path)
285 {
286   return (strcmp (value->path, path) == 0);
287 }
288 
289 static void
update_or_create_thumbnailer(MateDesktopThumbnailFactory * factory,const gchar * path)290 update_or_create_thumbnailer (MateDesktopThumbnailFactory *factory,
291                               const gchar                 *path)
292 {
293   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
294   GList *l;
295   Thumbnailer *thumb;
296   gboolean found = FALSE;
297 
298   g_mutex_lock (&priv->lock);
299 
300   for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
301     {
302       thumb = (Thumbnailer *)l->data;
303 
304       if (strcmp (thumb->path, path) == 0)
305         {
306           found = TRUE;
307 
308           /* First remove the mime_types associated to this thumbnailer */
309           g_hash_table_foreach_remove (priv->mime_types_map,
310                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
311                                        (gpointer)path);
312           if (!thumbnailer_reload (thumb))
313               priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
314           else
315               mate_desktop_thumbnail_factory_register_mime_types (factory, thumb);
316         }
317     }
318 
319   if (!found)
320     {
321       thumb = thumbnailer_new (path);
322       if (thumb)
323         mate_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
324     }
325 
326   g_mutex_unlock (&priv->lock);
327 }
328 
329 static void
remove_thumbnailer(MateDesktopThumbnailFactory * factory,const gchar * path)330 remove_thumbnailer (MateDesktopThumbnailFactory *factory,
331                     const gchar                 *path)
332 {
333   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
334   GList *l;
335   Thumbnailer *thumb;
336 
337   g_mutex_lock (&priv->lock);
338 
339   for (l = priv->thumbnailers; l; l = g_list_next (l))
340     {
341       thumb = (Thumbnailer *)l->data;
342 
343       if (strcmp (thumb->path, path) == 0)
344         {
345           priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
346           g_hash_table_foreach_remove (priv->mime_types_map,
347                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
348                                        (gpointer)path);
349           thumbnailer_unref (thumb);
350 
351           break;
352         }
353     }
354 
355   g_mutex_unlock (&priv->lock);
356 }
357 
358 static void
remove_thumbnailers_for_dir(MateDesktopThumbnailFactory * factory,const gchar * thumbnailer_dir,GFileMonitor * monitor)359 remove_thumbnailers_for_dir (MateDesktopThumbnailFactory *factory,
360                              const gchar                 *thumbnailer_dir,
361                              GFileMonitor                *monitor)
362 {
363   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
364   GList *l;
365   Thumbnailer *thumb;
366 
367   g_mutex_lock (&priv->lock);
368 
369   /* Remove all the thumbnailers inside this @thumbnailer_dir. */
370   for (l = priv->thumbnailers; l; l = g_list_next (l))
371     {
372       thumb = (Thumbnailer *)l->data;
373 
374       if (g_str_has_prefix (thumb->path, thumbnailer_dir) == TRUE)
375         {
376           priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
377           g_hash_table_foreach_remove (priv->mime_types_map,
378                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
379                                        (gpointer)thumb->path);
380           thumbnailer_unref (thumb);
381 
382           break;
383         }
384     }
385 
386   /* Remove the monitor for @thumbnailer_dir. */
387   priv->monitors = g_list_remove (priv->monitors, monitor);
388   g_signal_handlers_disconnect_by_func (monitor, thumbnailers_directory_changed, factory);
389 
390   g_mutex_unlock (&priv->lock);
391 }
392 
393 static void
mate_desktop_thumbnail_factory_load_thumbnailers_for_dir(MateDesktopThumbnailFactory * factory,const gchar * path)394 mate_desktop_thumbnail_factory_load_thumbnailers_for_dir (MateDesktopThumbnailFactory *factory,
395                                                           const gchar                 *path)
396 {
397   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
398   GDir *dir;
399   GFile *dir_file;
400   GFileMonitor *monitor;
401   const gchar *dirent;
402 
403   dir = g_dir_open (path, 0, NULL);
404   if (!dir)
405       return;
406 
407   /* Monitor dir */
408   dir_file = g_file_new_for_path (path);
409   monitor = g_file_monitor_directory (dir_file,
410                                       G_FILE_MONITOR_NONE,
411                                       NULL, NULL);
412   if (monitor)
413     {
414       g_signal_connect (monitor, "changed",
415                         G_CALLBACK (thumbnailers_directory_changed),
416                         factory);
417       priv->monitors = g_list_prepend (priv->monitors, monitor);
418     }
419   g_object_unref (dir_file);
420 
421   while ((dirent = g_dir_read_name (dir)))
422     {
423       Thumbnailer *thumb;
424       gchar       *filename;
425 
426       if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
427           continue;
428 
429       filename = g_build_filename (path, dirent, NULL);
430       thumb = thumbnailer_new (filename);
431       g_free (filename);
432 
433       if (thumb)
434           mate_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
435     }
436 
437   g_dir_close (dir);
438 }
439 
440 static void
thumbnailers_directory_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,MateDesktopThumbnailFactory * factory)441 thumbnailers_directory_changed (GFileMonitor                *monitor,
442                                 GFile                       *file,
443                                 GFile                       *other_file,
444                                 GFileMonitorEvent            event_type,
445                                 MateDesktopThumbnailFactory *factory)
446 {
447   gchar *path;
448 
449   switch (event_type)
450     {
451     case G_FILE_MONITOR_EVENT_CREATED:
452     case G_FILE_MONITOR_EVENT_CHANGED:
453     case G_FILE_MONITOR_EVENT_DELETED:
454       path = g_file_get_path (file);
455       if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
456         {
457           g_free (path);
458           return;
459         }
460 
461       if (event_type == G_FILE_MONITOR_EVENT_DELETED)
462         remove_thumbnailer (factory, path);
463       else
464         update_or_create_thumbnailer (factory, path);
465 
466       g_free (path);
467       break;
468     case G_FILE_MONITOR_EVENT_UNMOUNTED:
469     case G_FILE_MONITOR_EVENT_MOVED:
470       path = g_file_get_path (file);
471       remove_thumbnailers_for_dir (factory, path, monitor);
472 
473       if (event_type == G_FILE_MONITOR_EVENT_MOVED)
474           mate_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, path);
475 
476       g_free (path);
477       break;
478     case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
479     case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
480     case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
481     default:
482       break;
483     }
484 }
485 
486 static void
mate_desktop_thumbnail_factory_load_thumbnailers(MateDesktopThumbnailFactory * factory)487 mate_desktop_thumbnail_factory_load_thumbnailers (MateDesktopThumbnailFactory *factory)
488 {
489   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
490   const gchar * const *dirs;
491   guint i;
492 
493   if (priv->loaded)
494     return;
495 
496   dirs = get_thumbnailers_dirs ();
497   for (i = 0; dirs[i]; i++)
498     {
499       mate_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, dirs[i]);
500     }
501 
502   priv->loaded = TRUE;
503 }
504 
505 static void
external_thumbnailers_disabled_all_changed_cb(GSettings * settings,const gchar * key,MateDesktopThumbnailFactory * factory)506 external_thumbnailers_disabled_all_changed_cb (GSettings                   *settings,
507                                                const gchar                 *key,
508                                                MateDesktopThumbnailFactory *factory)
509 {
510   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
511 
512   g_mutex_lock (&priv->lock);
513 
514   priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
515   if (priv->disabled)
516     {
517       g_strfreev (priv->disabled_types);
518       priv->disabled_types = NULL;
519     }
520   else
521     {
522       priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
523       mate_desktop_thumbnail_factory_load_thumbnailers (factory);
524     }
525 
526   g_mutex_unlock (&priv->lock);
527 }
528 
529 static void
external_thumbnailers_disabled_changed_cb(GSettings * settings,const gchar * key,MateDesktopThumbnailFactory * factory)530 external_thumbnailers_disabled_changed_cb (GSettings                   *settings,
531                                            const gchar                 *key,
532                                            MateDesktopThumbnailFactory *factory)
533 {
534   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
535 
536   g_mutex_lock (&priv->lock);
537 
538   if (!priv->disabled)
539     {
540       g_strfreev (priv->disabled_types);
541       priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
542     }
543 
544   g_mutex_unlock (&priv->lock);
545 }
546 
547 static void
mate_desktop_thumbnail_factory_init(MateDesktopThumbnailFactory * factory)548 mate_desktop_thumbnail_factory_init (MateDesktopThumbnailFactory *factory)
549 {
550   MateDesktopThumbnailFactoryPrivate *priv;
551 
552   factory->priv = mate_desktop_thumbnail_factory_get_instance_private (factory);
553 
554   priv = factory->priv;
555 
556   priv->size = MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL;
557 
558   priv->mime_types_map = g_hash_table_new_full (g_str_hash,
559                                                 g_str_equal,
560                                                 (GDestroyNotify)g_free,
561                                                 (GDestroyNotify)thumbnailer_unref);
562 
563   g_mutex_init (&priv->lock);
564 
565   priv->settings = g_settings_new ("org.mate.thumbnailers");
566 
567   g_signal_connect (priv->settings, "changed::disable-all",
568                     G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
569                     factory);
570   g_signal_connect (priv->settings, "changed::disable",
571                     G_CALLBACK (external_thumbnailers_disabled_changed_cb),
572                     factory);
573 
574   priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
575 
576   if (!priv->disabled)
577     priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
578 
579   if (!priv->disabled)
580     mate_desktop_thumbnail_factory_load_thumbnailers (factory);
581 }
582 
583 static void
mate_desktop_thumbnail_factory_finalize(GObject * object)584 mate_desktop_thumbnail_factory_finalize (GObject *object)
585 {
586   MateDesktopThumbnailFactory *factory;
587   MateDesktopThumbnailFactoryPrivate *priv;
588 
589   factory = MATE_DESKTOP_THUMBNAIL_FACTORY (object);
590 
591   priv = factory->priv;
592 
593   if (priv->thumbnailers)
594     {
595       g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref);
596       priv->thumbnailers = NULL;
597     }
598 
599   g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy);
600 
601   if (priv->monitors)
602     {
603       g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
604       priv->monitors = NULL;
605     }
606 
607   g_mutex_clear (&priv->lock);
608 
609   g_clear_pointer (&priv->disabled_types, g_strfreev);
610 
611   if (priv->settings)
612     {
613       g_signal_handlers_disconnect_by_func (priv->settings,
614                                             external_thumbnailers_disabled_all_changed_cb,
615                                             factory);
616       g_signal_handlers_disconnect_by_func (priv->settings,
617                                             external_thumbnailers_disabled_changed_cb,
618                                             factory);
619       g_clear_object (&priv->settings);
620     }
621 
622   if (G_OBJECT_CLASS (parent_class)->finalize)
623     (* G_OBJECT_CLASS (parent_class)->finalize) (object);
624 }
625 
626 static void
mate_desktop_thumbnail_factory_class_init(MateDesktopThumbnailFactoryClass * class)627 mate_desktop_thumbnail_factory_class_init (MateDesktopThumbnailFactoryClass *class)
628 {
629   GObjectClass *gobject_class;
630 
631   gobject_class = G_OBJECT_CLASS (class);
632 
633   gobject_class->finalize = mate_desktop_thumbnail_factory_finalize;
634 }
635 
636 /**
637  * mate_desktop_thumbnail_factory_new:
638  * @size: The thumbnail size to use
639  *
640  * Creates a new #MateDesktopThumbnailFactory.
641  *
642  * This function must be called on the main thread.
643  *
644  * Return value: a new #MateDesktopThumbnailFactory
645  *
646  * Since: 2.2
647  **/
648 MateDesktopThumbnailFactory *
mate_desktop_thumbnail_factory_new(MateDesktopThumbnailSize size)649 mate_desktop_thumbnail_factory_new (MateDesktopThumbnailSize size)
650 {
651   MateDesktopThumbnailFactory *factory;
652 
653   factory = g_object_new (MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
654 
655   factory->priv->size = size;
656 
657   return factory;
658 }
659 
660 static char *
thumbnail_filename(const char * uri)661 thumbnail_filename (const char *uri)
662 {
663   GChecksum *checksum;
664   guint8 digest[16];
665   gsize digest_len = sizeof (digest);
666   char *file;
667 
668   checksum = g_checksum_new (G_CHECKSUM_MD5);
669   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
670 
671   g_checksum_get_digest (checksum, digest, &digest_len);
672   g_assert (digest_len == 16);
673 
674   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
675 
676   g_checksum_free (checksum);
677 
678   return file;
679 }
680 
681 static char *
thumbnail_path(const char * uri,MateDesktopThumbnailSize size)682 thumbnail_path (const char               *uri,
683                 MateDesktopThumbnailSize  size)
684 {
685   char *path, *file;
686 
687   file = thumbnail_filename (uri);
688   path = g_build_filename (g_get_user_cache_dir (),
689                            "thumbnails",
690                            size == MATE_DESKTOP_THUMBNAIL_SIZE_LARGE ? "large" : "normal",
691                            file,
692                            NULL);
693   g_free (file);
694   return path;
695 }
696 
697 static char *
thumbnail_failed_path(const char * uri)698 thumbnail_failed_path (const char *uri)
699 {
700   char *path, *file;
701 
702   file = thumbnail_filename (uri);
703   /* XXX: appname is only used for failed thumbnails. Is this a mistake? */
704   path = g_build_filename (g_get_user_cache_dir (),
705                            "thumbnails",
706                            "fail",
707                            appname,
708                            file,
709                            NULL);
710   g_free (file);
711   return path;
712 }
713 
714 static char *
validate_thumbnail_path(char * path,const char * uri,time_t mtime,MateDesktopThumbnailSize size)715 validate_thumbnail_path (char                     *path,
716                          const char               *uri,
717                          time_t                    mtime,
718                          MateDesktopThumbnailSize  size)
719 {
720   GdkPixbuf *pixbuf;
721 
722   pixbuf = gdk_pixbuf_new_from_file (path, NULL);
723   if (pixbuf == NULL ||
724       !mate_desktop_thumbnail_is_valid (pixbuf, uri, mtime)) {
725       g_free (path);
726       return NULL;
727   }
728 
729   g_clear_object (&pixbuf);
730 
731   return path;
732 }
733 
734 static char *
lookup_thumbnail_path(const char * uri,time_t mtime,MateDesktopThumbnailSize size)735 lookup_thumbnail_path (const char               *uri,
736                        time_t                    mtime,
737                        MateDesktopThumbnailSize  size)
738 {
739   char *path = thumbnail_path (uri, size);
740   return validate_thumbnail_path (path, uri, mtime, size);
741 }
742 
743 static char *
lookup_failed_thumbnail_path(const char * uri,time_t mtime,MateDesktopThumbnailSize size)744 lookup_failed_thumbnail_path (const char               *uri,
745                               time_t                    mtime,
746                               MateDesktopThumbnailSize  size)
747 {
748   char *path = thumbnail_failed_path (uri);
749   return validate_thumbnail_path (path, uri, mtime, size);
750 }
751 
752 /**
753  * mate_desktop_thumbnail_factory_lookup:
754  * @factory: a #MateDesktopThumbnailFactory
755  * @uri: the uri of a file
756  * @mtime: the mtime of the file
757  *
758  * Tries to locate an existing thumbnail for the file specified.
759  *
760  * Usage of this function is threadsafe.
761  *
762  * Return value: (transfer full): The absolute path of the thumbnail, or %NULL if none exist.
763  *
764  * Since: 2.2
765  **/
766 char *
mate_desktop_thumbnail_factory_lookup(MateDesktopThumbnailFactory * factory,const char * uri,time_t mtime)767 mate_desktop_thumbnail_factory_lookup (MateDesktopThumbnailFactory *factory,
768                                        const char                  *uri,
769                                        time_t                       mtime)
770 {
771   MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
772 
773   g_return_val_if_fail (uri != NULL, NULL);
774 
775   return lookup_thumbnail_path (uri, mtime, priv->size);
776 }
777 
778 /**
779  * mate_desktop_thumbnail_factory_has_valid_failed_thumbnail:
780  * @factory: a #MateDesktopThumbnailFactory
781  * @uri: the uri of a file
782  * @mtime: the mtime of the file
783  *
784  * Tries to locate an failed thumbnail for the file specified. Writing
785  * and looking for failed thumbnails is important to avoid to try to
786  * thumbnail e.g. broken images several times.
787  *
788  * Usage of this function is threadsafe.
789  *
790  * Return value: TRUE if there is a failed thumbnail for the file.
791  *
792  * Since: 2.2
793  **/
794 gboolean
mate_desktop_thumbnail_factory_has_valid_failed_thumbnail(MateDesktopThumbnailFactory * factory,const char * uri,time_t mtime)795 mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (MateDesktopThumbnailFactory *factory,
796                                                            const char                  *uri,
797                                                            time_t                       mtime)
798 {
799   char *path;
800 
801   g_return_val_if_fail (uri != NULL, FALSE);
802 
803   path = lookup_failed_thumbnail_path (uri, mtime, factory->priv->size);
804   if (path == NULL)
805     return FALSE;
806 
807   g_free (path);
808 
809   return TRUE;
810 }
811 
812 /**
813  * mate_desktop_thumbnail_factory_can_thumbnail:
814  * @factory: a #MateDesktopThumbnailFactory
815  * @uri: the uri of a file
816  * @mime_type: the mime type of the file
817  * @mtime: the mtime of the file
818  *
819  * Returns TRUE if this MateDesktopThumbnailFactory can (at least try) to thumbnail
820  * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
821  *
822  * Usage of this function is threadsafe.
823  *
824  * Return value: TRUE if the file can be thumbnailed.
825  *
826  * Since: 2.2
827  **/
828 gboolean
mate_desktop_thumbnail_factory_can_thumbnail(MateDesktopThumbnailFactory * factory,const char * uri,const char * mime_type,time_t mtime)829 mate_desktop_thumbnail_factory_can_thumbnail (MateDesktopThumbnailFactory *factory,
830                                               const char                  *uri,
831                                               const char                  *mime_type,
832                                               time_t                       mtime)
833 {
834   gboolean have_script = FALSE;
835 
836   /* Don't thumbnail thumbnails */
837   if (uri &&
838       strncmp (uri, "file:/", 6) == 0 &&
839       (strstr (uri, "/.thumbnails/") != NULL ||
840       strstr (uri, "/.cache/thumbnails/") != NULL))
841     return FALSE;
842 
843   if (!mime_type)
844     return FALSE;
845 
846   g_mutex_lock (&factory->priv->lock);
847   if (!mate_desktop_thumbnail_factory_is_disabled (factory, mime_type))
848     {
849       Thumbnailer *thumb;
850 
851       thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
852       have_script = thumbnailer_try_exec (thumb);
853     }
854   g_mutex_unlock (&factory->priv->lock);
855 
856   if (uri && (have_script ))
857     {
858       return !mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
859                                                                          uri,
860                                                                          mtime);
861     }
862 
863   return FALSE;
864 }
865 
866 static char *
expand_thumbnailing_elem(const char * elem,const int size,const char * inuri,const char * outfile,gboolean * got_input,gboolean * got_output)867 expand_thumbnailing_elem (const char *elem,
868                           const int   size,
869                           const char *inuri,
870                           const char *outfile,
871                           gboolean   *got_input,
872                           gboolean   *got_output)
873 {
874   GString *str;
875   const char *p, *last;
876   char *localfile;
877 
878   str = g_string_new (NULL);
879 
880   last = elem;
881   while ((p = strchr (last, '%')) != NULL)
882     {
883       g_string_append_len (str, last, p - last);
884       p++;
885 
886       switch (*p) {
887       case 'u':
888        g_string_append (str, inuri);
889        *got_input = TRUE;
890        p++;
891        break;
892       case 'i':
893        localfile = g_filename_from_uri (inuri, NULL, NULL);
894        if (localfile)
895          {
896            g_string_append (str, localfile);
897            *got_input = TRUE;
898            g_free (localfile);
899          }
900        p++;
901        break;
902       case 'o':
903        g_string_append (str, outfile);
904        *got_output = TRUE;
905        p++;
906        break;
907       case 's':
908        g_string_append_printf (str, "%d", size);
909        p++;
910        break;
911       case '%':
912        g_string_append_c (str, '%');
913        p++;
914        break;
915       case 0:
916       default:
917         break;
918       }
919       last = p;
920     }
921   g_string_append (str, last);
922 
923   return g_string_free (str, FALSE);
924 }
925 
926 static char **
expand_thumbnailing_script(const char * script,const int size,const char * inuri,const char * outfile,GError ** error)927 expand_thumbnailing_script (const char  *script,
928                             const int    size,
929                             const char  *inuri,
930                             const char  *outfile,
931                             GError     **error)
932 {
933   GPtrArray *array;
934   char **script_elems;
935   guint i;
936   gboolean got_in, got_out;
937 
938   if (!g_shell_parse_argv (script, NULL, &script_elems, error))
939     return NULL;
940 
941   array = g_ptr_array_new_with_free_func (g_free);
942 
943   got_in = got_out = FALSE;
944   for (i = 0; script_elems[i] != NULL; i++)
945     {
946       char *expanded;
947 
948       expanded = expand_thumbnailing_elem (script_elems[i],
949                                            size,
950                                            inuri,
951                                            outfile,
952                                            &got_in,
953                                            &got_out);
954 
955       g_ptr_array_add (array, expanded);
956     }
957 
958   if (!got_in)
959     {
960       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
961                            "Input file could not be set");
962       goto bail;
963     }
964   else if (!got_out)
965     {
966       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
967                            "Output file could not be set");
968       goto bail;
969     }
970 
971   g_ptr_array_add (array, NULL);
972 
973   return (char **) g_ptr_array_free (array, FALSE);
974 
975 bail:
976   g_ptr_array_free (array, TRUE);
977   return NULL;
978 }
979 
980 static GdkPixbuf *
get_preview_thumbnail(const char * uri,int size)981 get_preview_thumbnail (const char *uri,
982                        int         size)
983 {
984     GdkPixbuf *pixbuf;
985     GFile *file;
986     GFileInfo *file_info;
987     GInputStream *input_stream;
988     GObject *object;
989 
990     g_return_val_if_fail (uri != NULL, NULL);
991 
992     input_stream = NULL;
993 
994     file = g_file_new_for_uri (uri);
995 
996     /* First see if we can get an input stream via preview::icon  */
997     file_info = g_file_query_info (file,
998                                    G_FILE_ATTRIBUTE_PREVIEW_ICON,
999                                    G_FILE_QUERY_INFO_NONE,
1000                                    NULL,  /* GCancellable */
1001                                    NULL); /* return location for GError */
1002     g_object_unref (file);
1003 
1004     if (file_info == NULL)
1005       return NULL;
1006 
1007     object = g_file_info_get_attribute_object (file_info,
1008                                                G_FILE_ATTRIBUTE_PREVIEW_ICON);
1009     if (object)
1010         g_object_ref (object);
1011     g_object_unref (file_info);
1012 
1013     if (!object)
1014       return NULL;
1015     if (!G_IS_LOADABLE_ICON (object)) {
1016       g_object_unref (object);
1017       return NULL;
1018     }
1019 
1020     input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
1021                                          0,     /* size */
1022                                          NULL,  /* return location for type */
1023                                          NULL,  /* GCancellable */
1024                                          NULL); /* return location for GError */
1025     g_object_unref (object);
1026 
1027     if (!input_stream)
1028       return NULL;
1029 
1030     pixbuf = gdk_pixbuf_new_from_stream_at_scale (input_stream,
1031                                                   size, size,
1032                                                   TRUE, NULL, NULL);
1033     g_object_unref (input_stream);
1034 
1035     return pixbuf;
1036 }
1037 
1038 /**
1039  * mate_desktop_thumbnail_factory_generate_thumbnail:
1040  * @factory: a #MateDesktopThumbnailFactory
1041  * @uri: the uri of a file
1042  * @mime_type: the mime type of the file
1043  *
1044  * Tries to generate a thumbnail for the specified file. If it succeeds
1045  * it returns a pixbuf that can be used as a thumbnail.
1046  *
1047  * Usage of this function is threadsafe.
1048  *
1049  * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
1050  *
1051  * Since: 2.2
1052  **/
1053 GdkPixbuf *
mate_desktop_thumbnail_factory_generate_thumbnail(MateDesktopThumbnailFactory * factory,const char * uri,const char * mime_type)1054 mate_desktop_thumbnail_factory_generate_thumbnail (MateDesktopThumbnailFactory *factory,
1055                                                    const char                  *uri,
1056                                                    const char                  *mime_type)
1057 {
1058   GdkPixbuf *pixbuf;
1059   char *script;
1060   int size;
1061   int exit_status;
1062   char *tmpname;
1063 
1064   g_return_val_if_fail (uri != NULL, NULL);
1065   g_return_val_if_fail (mime_type != NULL, NULL);
1066 
1067   /* Doesn't access any volatile fields in factory, so it's threadsafe */
1068 
1069   size = 128;
1070   if (factory->priv->size == MATE_DESKTOP_THUMBNAIL_SIZE_LARGE)
1071     size = 256;
1072 
1073   pixbuf = NULL;
1074 
1075   pixbuf = get_preview_thumbnail (uri, size);
1076   if (pixbuf != NULL)
1077     return pixbuf;
1078 
1079   script = NULL;
1080   g_mutex_lock (&factory->priv->lock);
1081   if (!mate_desktop_thumbnail_factory_is_disabled (factory, mime_type))
1082     {
1083       Thumbnailer *thumb;
1084 
1085       thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1086       if (thumb)
1087         script = g_strdup (thumb->command);
1088     }
1089   g_mutex_unlock (&factory->priv->lock);
1090 
1091   if (script)
1092     {
1093       int fd;
1094 
1095       fd = g_file_open_tmp (".mate_desktop_thumbnail.XXXXXX", &tmpname, NULL);
1096 
1097       if (fd != -1)
1098     {
1099       char **expanded_script;
1100       GError *error = NULL;
1101 
1102       close (fd);
1103 
1104       expanded_script = expand_thumbnailing_script (script, size, uri, tmpname, &error);
1105       if (expanded_script == NULL)
1106         {
1107           g_warning ("Failed to expand script '%s': %s", script, error->message);
1108           g_error_free (error);
1109         }
1110       else
1111         {
1112           gboolean ret;
1113 
1114           ret = g_spawn_sync (NULL, expanded_script, NULL, G_SPAWN_SEARCH_PATH,
1115                               NULL, NULL, NULL, NULL, &exit_status, NULL);
1116           if (ret && exit_status == 0)
1117             pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
1118 
1119           g_strfreev (expanded_script);
1120         }
1121 
1122       g_unlink (tmpname);
1123       g_free (tmpname);
1124     }
1125 
1126       g_free (script);
1127     }
1128 
1129   return pixbuf;
1130 }
1131 
1132 static gboolean
save_thumbnail(GdkPixbuf * pixbuf,char * path,const char * uri,time_t mtime)1133 save_thumbnail (GdkPixbuf  *pixbuf,
1134                 char       *path,
1135                 const char *uri,
1136                 time_t      mtime)
1137 {
1138   char *dirname;
1139   char *tmp_path = NULL;
1140   int tmp_fd;
1141   gchar *mtime_str;
1142   gboolean ret = FALSE;
1143   GError *error = NULL;
1144   const char *width, *height;
1145 
1146   if (pixbuf == NULL)
1147     return FALSE;
1148 
1149   dirname = g_path_get_dirname (path);
1150 
1151   if (g_mkdir_with_parents (dirname, 0700) != 0)
1152 
1153     goto out;
1154 
1155   tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1156   tmp_fd = g_mkstemp (tmp_path);
1157 
1158   if (tmp_fd == -1)
1159     goto out;
1160   close (tmp_fd);
1161 
1162   mtime_str = g_strdup_printf ("%" G_GINT64_FORMAT,  (gint64) mtime);
1163   width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
1164   height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
1165 
1166   error = NULL;
1167   if (width != NULL && height != NULL)
1168     ret = gdk_pixbuf_save (pixbuf,
1169                            tmp_path,
1170                            "png", &error,
1171                            "tEXt::Thumb::Image::Width", width,
1172                            "tEXt::Thumb::Image::Height", height,
1173                            "tEXt::Thumb::URI", uri,
1174                            "tEXt::Thumb::MTime", mtime_str,
1175                            "tEXt::Software", "MATE::ThumbnailFactory",
1176                            NULL);
1177   else
1178     ret = gdk_pixbuf_save (pixbuf,
1179                            tmp_path,
1180                            "png", &error,
1181                            "tEXt::Thumb::URI", uri,
1182                            "tEXt::Thumb::MTime", mtime_str,
1183                            "tEXt::Software", "MATE::ThumbnailFactory",
1184                            NULL);
1185   g_free (mtime_str);
1186 
1187   if (!ret)
1188     goto out;
1189 
1190   g_chmod (tmp_path, 0600);
1191   g_rename (tmp_path, path);
1192 
1193  out:
1194   if (error != NULL)
1195     {
1196       g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message);
1197       g_error_free (error);
1198     }
1199   g_unlink (tmp_path);
1200   g_free (tmp_path);
1201   g_free (dirname);
1202   return ret;
1203 }
1204 
1205 static GdkPixbuf *
make_failed_thumbnail(void)1206 make_failed_thumbnail (void)
1207 {
1208   GdkPixbuf *pixbuf;
1209 
1210   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
1211   gdk_pixbuf_fill (pixbuf, 0x00000000);
1212   return pixbuf;
1213 }
1214 
1215 /**
1216  * mate_desktop_thumbnail_factory_save_thumbnail:
1217  * @factory: a #MateDesktopThumbnailFactory
1218  * @thumbnail: the thumbnail as a pixbuf
1219  * @uri: the uri of a file
1220  * @original_mtime: the modification time of the original file
1221  *
1222  * Saves @thumbnail at the right place. If the save fails a
1223  * failed thumbnail is written.
1224  *
1225  * Usage of this function is threadsafe.
1226  *
1227  * Since: 2.2
1228  **/
1229 void
mate_desktop_thumbnail_factory_save_thumbnail(MateDesktopThumbnailFactory * factory,GdkPixbuf * thumbnail,const char * uri,time_t original_mtime)1230 mate_desktop_thumbnail_factory_save_thumbnail (MateDesktopThumbnailFactory *factory,
1231                                                GdkPixbuf                   *thumbnail,
1232                                                const char                  *uri,
1233                                                time_t                       original_mtime)
1234 {
1235   char *path;
1236 
1237   path = thumbnail_path (uri, factory->priv->size);
1238   if (!save_thumbnail (thumbnail, path, uri, original_mtime))
1239     {
1240       thumbnail = make_failed_thumbnail ();
1241       g_free (path);
1242       path = thumbnail_failed_path (uri);
1243       save_thumbnail (thumbnail, path, uri, original_mtime);
1244       g_object_unref (thumbnail);
1245     }
1246   g_free (path);
1247 }
1248 
1249 /**
1250  * mate_desktop_thumbnail_factory_create_failed_thumbnail:
1251  * @factory: a #MateDesktopThumbnailFactory
1252  * @uri: the uri of a file
1253  * @mtime: the modification time of the file
1254  *
1255  * Creates a failed thumbnail for the file so that we don't try
1256  * to re-thumbnail the file later.
1257  *
1258  * Usage of this function is threadsafe.
1259  *
1260  * Since: 2.2
1261  **/
1262 void
mate_desktop_thumbnail_factory_create_failed_thumbnail(MateDesktopThumbnailFactory * factory,const char * uri,time_t mtime)1263 mate_desktop_thumbnail_factory_create_failed_thumbnail (MateDesktopThumbnailFactory *factory,
1264                                                         const char                  *uri,
1265                                                         time_t                      mtime)
1266 {
1267   char *path;
1268   GdkPixbuf *pixbuf;
1269 
1270   path = thumbnail_failed_path (uri);
1271   pixbuf = make_failed_thumbnail ();
1272   save_thumbnail (pixbuf, path, uri, mtime);
1273 
1274   g_free (path);
1275   g_object_unref (pixbuf);
1276 }
1277 
1278 /**
1279  * mate_desktop_thumbnail_path_for_uri:
1280  * @uri: an uri
1281  * @size: a thumbnail size
1282  *
1283  * Returns the filename that a thumbnail of size @size for @uri would have.
1284  *
1285  * Return value: an absolute filename
1286  *
1287  * Since: 2.2
1288  **/
1289 char *
mate_desktop_thumbnail_path_for_uri(const char * uri,MateDesktopThumbnailSize size)1290 mate_desktop_thumbnail_path_for_uri (const char               *uri,
1291                                      MateDesktopThumbnailSize  size)
1292 {
1293   return thumbnail_path (uri, size);
1294 }
1295 
1296 /**
1297  * mate_desktop_thumbnail_has_uri:
1298  * @pixbuf: an loaded thumbnail pixbuf
1299  * @uri: a uri
1300  *
1301  * Returns whether the thumbnail has the correct uri embedded in the
1302  * Thumb::URI option in the png.
1303  *
1304  * Return value: TRUE if the thumbnail is for @uri
1305  *
1306  * Since: 2.2
1307  **/
1308 gboolean
mate_desktop_thumbnail_has_uri(GdkPixbuf * pixbuf,const char * uri)1309 mate_desktop_thumbnail_has_uri (GdkPixbuf          *pixbuf,
1310                                 const char         *uri)
1311 {
1312   const char *thumb_uri;
1313 
1314   thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1315   return (g_strcmp0 (uri, thumb_uri) == 0);
1316 }
1317 
1318 /**
1319  * mate_desktop_thumbnail_is_valid:
1320  * @pixbuf: an loaded thumbnail #GdkPixbuf
1321  * @uri: a uri
1322  * @mtime: the mtime
1323  *
1324  * Returns whether the thumbnail has the correct uri and mtime embedded in the
1325  * png options.
1326  *
1327  * Return value: TRUE if the thumbnail has the right @uri and @mtime
1328  *
1329  * Since: 2.2
1330  **/
1331 gboolean
mate_desktop_thumbnail_is_valid(GdkPixbuf * pixbuf,const char * uri,time_t mtime)1332 mate_desktop_thumbnail_is_valid (GdkPixbuf          *pixbuf,
1333                                  const char         *uri,
1334                                  time_t              mtime)
1335 {
1336   const char *thumb_uri, *thumb_mtime_str;
1337   time_t thumb_mtime;
1338 
1339   thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1340   if (g_strcmp0 (uri, thumb_uri) != 0)
1341     return FALSE;
1342 
1343   thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
1344   if (!thumb_mtime_str)
1345     return FALSE;
1346   thumb_mtime = (time_t)g_ascii_strtoll (thumb_mtime_str, (gchar**)NULL, 10);
1347   if (mtime != thumb_mtime)
1348     return FALSE;
1349 
1350   return TRUE;
1351 }
1352