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