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