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 * 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 <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <dirent.h>
34 #include <time.h>
35 #include <math.h>
36 #include <string.h>
37 #include <glib.h>
38 #include <stdio.h>
39 #include <errno.h>
40
41 #define GDK_PIXBUF_ENABLE_BACKEND
42 #include <gdk-pixbuf/gdk-pixbuf.h>
43
44 #define GNOME_DESKTOP_USE_UNSTABLE_API
45 #include "gnome-desktop-thumbnail.h"
46 #include "gnome-desktop-utils.h"
47 #include <glib/gstdio.h>
48
49 #define SECONDS_BETWEEN_STATS 10
50
51 struct _GnomeDesktopThumbnailFactoryPrivate {
52 GnomeDesktopThumbnailSize size;
53
54 GMutex lock;
55
56 GList *thumbnailers;
57 GHashTable *mime_types_map;
58 GList *monitors;
59
60 GSettings *settings;
61 gboolean loaded : 1;
62 gboolean disabled : 1;
63 gchar **disabled_types;
64
65 gboolean permissions_problem;
66 gboolean needs_chown;
67 uid_t real_uid;
68 gid_t real_gid;
69 };
70
71 static const char *appname = "gnome-thumbnail-factory";
72
73 static void gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory);
74 static void gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class);
75
76 G_DEFINE_TYPE (GnomeDesktopThumbnailFactory,
77 gnome_desktop_thumbnail_factory,
78 G_TYPE_OBJECT)
79 #define parent_class gnome_desktop_thumbnail_factory_parent_class
80
81 #define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
82 (G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate))
83
84 typedef struct {
85 gint width;
86 gint height;
87 gint input_width;
88 gint input_height;
89 gboolean preserve_aspect_ratio;
90 } SizePrepareContext;
91
92 #define LOAD_BUFFER_SIZE 4096
93
94 #define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
95 #define THUMBNAILER_EXTENSION ".thumbnailer"
96
97 typedef struct {
98 volatile gint ref_count;
99
100 gchar *path;
101
102 gchar *try_exec;
103 gchar *command;
104 gchar **mime_types;
105 } Thumbnailer;
106
107 static Thumbnailer *
thumbnailer_ref(Thumbnailer * thumb)108 thumbnailer_ref (Thumbnailer *thumb)
109 {
110 g_return_val_if_fail (thumb != NULL, NULL);
111 g_return_val_if_fail (thumb->ref_count > 0, NULL);
112
113 g_atomic_int_inc (&thumb->ref_count);
114 return thumb;
115 }
116
117 static void
thumbnailer_unref(Thumbnailer * thumb)118 thumbnailer_unref (Thumbnailer *thumb)
119 {
120 g_return_if_fail (thumb != NULL);
121 g_return_if_fail (thumb->ref_count > 0);
122
123 if (g_atomic_int_dec_and_test (&thumb->ref_count))
124 {
125 g_free (thumb->path);
126 g_free (thumb->try_exec);
127 g_free (thumb->command);
128 g_strfreev (thumb->mime_types);
129
130 g_slice_free (Thumbnailer, thumb);
131 }
132 }
133
134 static Thumbnailer *
thumbnailer_load(Thumbnailer * thumb)135 thumbnailer_load (Thumbnailer *thumb)
136 {
137 GKeyFile *key_file;
138 GError *error = NULL;
139
140 key_file = g_key_file_new ();
141 if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
142 {
143 g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
144 g_error_free (error);
145 thumbnailer_unref (thumb);
146 g_key_file_free (key_file);
147
148 return NULL;
149 }
150
151 if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
152 {
153 g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
154 thumbnailer_unref (thumb);
155 g_key_file_free (key_file);
156
157 return NULL;
158 }
159
160 thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
161 if (!thumb->command)
162 {
163 g_warning ("Invalid thumbnailer: missing Exec key\n");
164 thumbnailer_unref (thumb);
165 g_key_file_free (key_file);
166
167 return NULL;
168 }
169
170 thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
171 if (!thumb->mime_types)
172 {
173 g_warning ("Invalid thumbnailer: missing MimeType key\n");
174 thumbnailer_unref (thumb);
175 g_key_file_free (key_file);
176
177 return NULL;
178 }
179
180 thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL);
181
182 g_key_file_free (key_file);
183
184 return thumb;
185 }
186
187 static Thumbnailer *
thumbnailer_reload(Thumbnailer * thumb)188 thumbnailer_reload (Thumbnailer *thumb)
189 {
190 g_return_val_if_fail (thumb != NULL, NULL);
191
192 g_free (thumb->command);
193 thumb->command = NULL;
194 g_strfreev (thumb->mime_types);
195 thumb->mime_types = NULL;
196 g_free (thumb->try_exec);
197 thumb->try_exec = NULL;
198
199 return thumbnailer_load (thumb);
200 }
201
202 static Thumbnailer *
thumbnailer_new(const gchar * path)203 thumbnailer_new (const gchar *path)
204 {
205 Thumbnailer *thumb;
206
207 thumb = g_slice_new0 (Thumbnailer);
208 thumb->ref_count = 1;
209 thumb->path = g_strdup (path);
210
211 return thumbnailer_load (thumb);
212 }
213
214 static gboolean
thumbnailer_try_exec(Thumbnailer * thumb)215 thumbnailer_try_exec (Thumbnailer *thumb)
216 {
217 gchar *path;
218 gboolean retval;
219
220 if (G_UNLIKELY (!thumb))
221 return FALSE;
222
223 /* TryExec is optinal, but Exec isn't, so we assume
224 * the thumbnailer can be run when TryExec is not present
225 */
226 if (!thumb->try_exec)
227 return TRUE;
228
229 path = g_find_program_in_path (thumb->try_exec);
230 retval = path != NULL;
231 g_free (path);
232
233 return retval;
234 }
235
236 static gpointer
init_thumbnailers_dirs(gpointer data)237 init_thumbnailers_dirs (gpointer data)
238 {
239 const gchar * const *data_dirs;
240 gchar **thumbs_dirs;
241 guint i, length;
242
243 data_dirs = g_get_system_data_dirs ();
244 length = g_strv_length ((char **) data_dirs);
245
246 thumbs_dirs = g_new (gchar *, length + 2);
247 thumbs_dirs[0] = g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL);
248 for (i = 0; i < length; i++)
249 thumbs_dirs[i + 1] = g_build_filename (data_dirs[i], "thumbnailers", NULL);
250 thumbs_dirs[length + 1] = NULL;
251
252 return thumbs_dirs;
253 }
254
255 static const gchar * const *
get_thumbnailers_dirs(void)256 get_thumbnailers_dirs (void)
257 {
258 static GOnce once_init = G_ONCE_INIT;
259 return g_once (&once_init, init_thumbnailers_dirs, NULL);
260 }
261
262 static void
size_prepared_cb(GdkPixbufLoader * loader,int width,int height,gpointer data)263 size_prepared_cb (GdkPixbufLoader *loader,
264 int width,
265 int height,
266 gpointer data)
267 {
268 SizePrepareContext *info = data;
269
270 g_return_if_fail (width > 0 && height > 0);
271
272 info->input_width = width;
273 info->input_height = height;
274
275 if (width < info->width && height < info->height) return;
276
277 if (info->preserve_aspect_ratio &&
278 (info->width > 0 || info->height > 0)) {
279 if (info->width < 0)
280 {
281 width = width * (double)info->height/(double)height;
282 height = info->height;
283 }
284 else if (info->height < 0)
285 {
286 height = height * (double)info->width/(double)width;
287 width = info->width;
288 }
289 else if ((double)height * (double)info->width >
290 (double)width * (double)info->height) {
291 width = 0.5 + (double)width * (double)info->height / (double)height;
292 height = info->height;
293 } else {
294 height = 0.5 + (double)height * (double)info->width / (double)width;
295 width = info->width;
296 }
297 } else {
298 if (info->width > 0)
299 width = info->width;
300 if (info->height > 0)
301 height = info->height;
302 }
303
304 gdk_pixbuf_loader_set_size (loader, width, height);
305 }
306
307 static GdkPixbufLoader *
create_loader(GFile * file,const guchar * data,gsize size)308 create_loader (GFile *file,
309 const guchar *data,
310 gsize size)
311 {
312 GdkPixbufLoader *loader;
313 GError *error = NULL;
314 char *mime_type;
315 char *filename;
316
317 loader = NULL;
318
319 /* need to specify the type here because the gdk_pixbuf_loader_write
320 doesn't have access to the filename in order to correct detect
321 the image type. */
322 filename = g_file_get_basename (file);
323 mime_type = g_content_type_guess (filename, data, size, NULL);
324 g_free (filename);
325
326 if (mime_type != NULL) {
327 loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, &error);
328 }
329
330 if (loader == NULL) {
331 g_warning ("Unable to create loader for mime type %s: %s", mime_type, error->message);
332 g_clear_error (&error);
333 loader = gdk_pixbuf_loader_new ();
334 }
335 g_free (mime_type);
336
337 return loader;
338 }
339
340 static GdkPixbuf *
_gdk_pixbuf_new_from_uri_at_scale(const char * uri,gint width,gint height,gboolean preserve_aspect_ratio)341 _gdk_pixbuf_new_from_uri_at_scale (const char *uri,
342 gint width,
343 gint height,
344 gboolean preserve_aspect_ratio)
345 {
346 gboolean result;
347 guchar buffer[LOAD_BUFFER_SIZE];
348 gsize bytes_read;
349 GdkPixbufLoader *loader = NULL;
350 GdkPixbuf *pixbuf;
351 GdkPixbufAnimation *animation;
352 GdkPixbufAnimationIter *iter;
353 gboolean has_frame;
354 SizePrepareContext info;
355 GFile *file;
356 GFileInfo *file_info;
357 GInputStream *input_stream;
358 GError *error = NULL;
359
360 g_return_val_if_fail (uri != NULL, NULL);
361
362 input_stream = NULL;
363
364 file = g_file_new_for_uri (uri);
365
366 /* First see if we can get an input stream via preview::icon */
367 file_info = g_file_query_info (file,
368 G_FILE_ATTRIBUTE_PREVIEW_ICON,
369 G_FILE_QUERY_INFO_NONE,
370 NULL, /* GCancellable */
371 NULL); /* return location for GError */
372 if (file_info != NULL) {
373 GObject *object;
374
375 object = g_file_info_get_attribute_object (file_info,
376 G_FILE_ATTRIBUTE_PREVIEW_ICON);
377 if (object != NULL && G_IS_LOADABLE_ICON (object)) {
378 input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
379 0, /* size */
380 NULL, /* return location for type */
381 NULL, /* GCancellable */
382 NULL); /* return location for GError */
383 }
384 g_object_unref (file_info);
385 }
386
387 if (input_stream == NULL) {
388 input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
389 if (input_stream == NULL) {
390 if (error != NULL) {
391 g_warning ("Unable to create an input stream for %s: %s", uri, error->message);
392 g_clear_error (&error);
393 }
394 g_object_unref (file);
395 return NULL;
396 }
397 }
398
399 has_frame = FALSE;
400
401 result = FALSE;
402 while (!has_frame) {
403
404 bytes_read = g_input_stream_read (input_stream,
405 buffer,
406 sizeof (buffer),
407 NULL,
408 &error);
409 if (error != NULL) {
410 g_warning ("Error reading from %s: %s", uri, error->message);
411 g_clear_error (&error);
412 }
413 if (bytes_read == -1) {
414 break;
415 }
416 result = TRUE;
417 if (bytes_read == 0) {
418 break;
419 }
420
421 if (loader == NULL) {
422 loader = create_loader (file, buffer, bytes_read);
423 if (1 <= width || 1 <= height) {
424 info.width = width;
425 info.height = height;
426 info.input_width = info.input_height = 0;
427 info.preserve_aspect_ratio = preserve_aspect_ratio;
428 g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
429 }
430 g_assert (loader != NULL);
431 }
432
433 if (!gdk_pixbuf_loader_write (loader,
434 (unsigned char *)buffer,
435 bytes_read,
436 &error)) {
437 g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
438 g_clear_error (&error);
439 result = FALSE;
440 break;
441 }
442
443 animation = gdk_pixbuf_loader_get_animation (loader);
444 if (animation) {
445 iter = gdk_pixbuf_animation_get_iter (animation, NULL);
446 if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
447 has_frame = TRUE;
448 }
449 g_object_unref (iter);
450 }
451 }
452
453 if (!GDK_IS_PIXBUF_LOADER (loader)) {
454 g_input_stream_close (input_stream, NULL, NULL);
455 g_object_unref (input_stream);
456 g_object_unref (file);
457 return NULL;
458 }
459
460 gdk_pixbuf_loader_close (loader, NULL);
461
462 if (!result) {
463 g_object_unref (G_OBJECT (loader));
464 g_input_stream_close (input_stream, NULL, NULL);
465 g_object_unref (input_stream);
466 g_object_unref (file);
467 return NULL;
468 }
469
470 g_input_stream_close (input_stream, NULL, NULL);
471 g_object_unref (input_stream);
472 g_object_unref (file);
473
474 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
475 if (pixbuf != NULL) {
476 g_object_ref (G_OBJECT (pixbuf));
477 g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width",
478 GINT_TO_POINTER (info.input_width));
479 g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height",
480 GINT_TO_POINTER (info.input_height));
481 }
482 g_object_unref (G_OBJECT (loader));
483
484 return pixbuf;
485 }
486
487 static void
gnome_desktop_thumbnail_factory_finalize(GObject * object)488 gnome_desktop_thumbnail_factory_finalize (GObject *object)
489 {
490 GnomeDesktopThumbnailFactory *factory;
491 GnomeDesktopThumbnailFactoryPrivate *priv;
492
493 factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object);
494
495 priv = factory->priv;
496
497 g_clear_pointer (&priv->thumbnailers, thumbnailer_unref);
498 g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy);
499
500 if (priv->monitors)
501 {
502 g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
503 priv->monitors = NULL;
504 }
505
506 g_mutex_clear (&priv->lock);
507
508 g_clear_pointer (&priv->disabled_types, g_strfreev);
509 g_clear_object (&priv->settings);
510
511 if (G_OBJECT_CLASS (parent_class)->finalize)
512 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
513 }
514
515 /* These should be called with the lock held */
516 static void
gnome_desktop_thumbnail_factory_register_mime_types(GnomeDesktopThumbnailFactory * factory,Thumbnailer * thumb)517 gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory,
518 Thumbnailer *thumb)
519 {
520 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
521 gint i;
522
523 for (i = 0; thumb->mime_types[i]; i++)
524 {
525 if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
526 g_hash_table_insert (priv->mime_types_map,
527 g_strdup (thumb->mime_types[i]),
528 thumbnailer_ref (thumb));
529 }
530 }
531
532 static void
gnome_desktop_thumbnail_factory_add_thumbnailer(GnomeDesktopThumbnailFactory * factory,Thumbnailer * thumb)533 gnome_desktop_thumbnail_factory_add_thumbnailer (GnomeDesktopThumbnailFactory *factory,
534 Thumbnailer *thumb)
535 {
536 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
537
538 gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
539 priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
540 }
541
542 static gboolean
gnome_desktop_thumbnail_factory_is_disabled(GnomeDesktopThumbnailFactory * factory,const gchar * mime_type)543 gnome_desktop_thumbnail_factory_is_disabled (GnomeDesktopThumbnailFactory *factory,
544 const gchar *mime_type)
545 {
546 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
547 guint i;
548
549 if (priv->disabled)
550 return TRUE;
551
552 if (!priv->disabled_types)
553 return FALSE;
554
555 for (i = 0; priv->disabled_types[i]; i++)
556 {
557 if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
558 return TRUE;
559 }
560
561 return FALSE;
562 }
563
564 static gboolean
remove_thumbnailer_from_mime_type_map(gchar * key,Thumbnailer * value,gchar * path)565 remove_thumbnailer_from_mime_type_map (gchar *key,
566 Thumbnailer *value,
567 gchar *path)
568 {
569 return (strcmp (value->path, path) == 0);
570 }
571
572
573 static void
update_or_create_thumbnailer(GnomeDesktopThumbnailFactory * factory,const gchar * path)574 update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory,
575 const gchar *path)
576 {
577 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
578 GList *l;
579 Thumbnailer *thumb;
580 gboolean found = FALSE;
581
582 g_mutex_lock (&priv->lock);
583
584 for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
585 {
586 thumb = (Thumbnailer *)l->data;
587
588 if (strcmp (thumb->path, path) == 0)
589 {
590 found = TRUE;
591
592 /* First remove the mime_types associated to this thumbnailer */
593 g_hash_table_foreach_remove (priv->mime_types_map,
594 (GHRFunc)remove_thumbnailer_from_mime_type_map,
595 (gpointer)path);
596 if (!thumbnailer_reload (thumb))
597 priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
598 else
599 gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
600 }
601 }
602
603 if (!found)
604 {
605 thumb = thumbnailer_new (path);
606 if (thumb)
607 gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
608 }
609
610 g_mutex_unlock (&priv->lock);
611 }
612
613 static void
remove_thumbnailer(GnomeDesktopThumbnailFactory * factory,const gchar * path)614 remove_thumbnailer (GnomeDesktopThumbnailFactory *factory,
615 const gchar *path)
616 {
617 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
618 GList *l;
619 Thumbnailer *thumb;
620
621 g_mutex_lock (&priv->lock);
622
623 for (l = priv->thumbnailers; l; l = g_list_next (l))
624 {
625 thumb = (Thumbnailer *)l->data;
626
627 if (strcmp (thumb->path, path) == 0)
628 {
629 priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
630 g_hash_table_foreach_remove (priv->mime_types_map,
631 (GHRFunc)remove_thumbnailer_from_mime_type_map,
632 (gpointer)path);
633 thumbnailer_unref (thumb);
634
635 break;
636 }
637 }
638
639 g_mutex_unlock (&priv->lock);
640 }
641
642 static void
thumbnailers_directory_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,GnomeDesktopThumbnailFactory * factory)643 thumbnailers_directory_changed (GFileMonitor *monitor,
644 GFile *file,
645 GFile *other_file,
646 GFileMonitorEvent event_type,
647 GnomeDesktopThumbnailFactory *factory)
648 {
649 gchar *path;
650
651 switch (event_type)
652 {
653 case G_FILE_MONITOR_EVENT_CREATED:
654 case G_FILE_MONITOR_EVENT_CHANGED:
655 case G_FILE_MONITOR_EVENT_DELETED:
656 path = g_file_get_path (file);
657 if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
658 {
659 g_free (path);
660 return;
661 }
662
663 if (event_type == G_FILE_MONITOR_EVENT_DELETED)
664 remove_thumbnailer (factory, path);
665 else
666 update_or_create_thumbnailer (factory, path);
667
668 g_free (path);
669 break;
670 default:
671 break;
672 }
673 }
674
675 static void
gnome_desktop_thumbnail_factory_load_thumbnailers(GnomeDesktopThumbnailFactory * factory)676 gnome_desktop_thumbnail_factory_load_thumbnailers (GnomeDesktopThumbnailFactory *factory)
677 {
678 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
679 const gchar * const *dirs;
680 guint i;
681
682 if (priv->loaded)
683 return;
684
685 dirs = get_thumbnailers_dirs ();
686 for (i = 0; dirs[i]; i++)
687 {
688 const gchar *path = dirs[i];
689 GDir *dir;
690 GFile *dir_file;
691 GFileMonitor *monitor;
692 const gchar *dirent;
693
694 dir = g_dir_open (path, 0, NULL);
695 if (!dir)
696 continue;
697
698 /* Monitor dir */
699 dir_file = g_file_new_for_path (path);
700 monitor = g_file_monitor_directory (dir_file,
701 G_FILE_MONITOR_NONE,
702 NULL, NULL);
703 if (monitor)
704 {
705 g_signal_connect (monitor, "changed",
706 G_CALLBACK (thumbnailers_directory_changed),
707 factory);
708 priv->monitors = g_list_prepend (priv->monitors, monitor);
709 }
710 g_object_unref (dir_file);
711
712 while ((dirent = g_dir_read_name (dir)))
713 {
714 Thumbnailer *thumb;
715 gchar *filename;
716
717 if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
718 continue;
719
720 filename = g_build_filename (path, dirent, NULL);
721 thumb = thumbnailer_new (filename);
722 g_free (filename);
723
724 if (thumb)
725 gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
726 }
727
728 g_dir_close (dir);
729 }
730
731 priv->loaded = TRUE;
732 }
733
734 static void
external_thumbnailers_disabled_all_changed_cb(GSettings * settings,const gchar * key,GnomeDesktopThumbnailFactory * factory)735 external_thumbnailers_disabled_all_changed_cb (GSettings *settings,
736 const gchar *key,
737 GnomeDesktopThumbnailFactory *factory)
738 {
739 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
740
741 g_mutex_lock (&priv->lock);
742
743 priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
744 if (priv->disabled)
745 {
746 g_strfreev (priv->disabled_types);
747 priv->disabled_types = NULL;
748 }
749 else
750 {
751 priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
752 gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
753 }
754
755 g_mutex_unlock (&priv->lock);
756 }
757
758 static void
external_thumbnailers_disabled_changed_cb(GSettings * settings,const gchar * key,GnomeDesktopThumbnailFactory * factory)759 external_thumbnailers_disabled_changed_cb (GSettings *settings,
760 const gchar *key,
761 GnomeDesktopThumbnailFactory *factory)
762 {
763 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
764
765 g_mutex_lock (&priv->lock);
766
767 if (!priv->disabled)
768 {
769 g_strfreev (priv->disabled_types);
770 priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
771 }
772
773 g_mutex_unlock (&priv->lock);
774 }
775
776 /* There are various different behavior depending on whether the application
777 ran with sudo, pkexec, under the root user, and 'sudo su'...
778
779 - sudo (program) keeps the user's home folder (and their .cache folder)
780 - pkexec (program) uses root's .cache folder
781 - root terminal, running (program) uses root's .cache folder
782 - sudo su, then running (program), uses root's .cache folder
783
784 Using sudo, or sudo su, SUDO_UID and friends are set in the environment
785 Using pkexec, PKEXEC_UID is set
786
787 root terminal and pkexec cases don't need any extra work - they're thumbnailing
788 with the correct permissions (root-owned in root's .cache folder)
789
790 sudo and sudo su need help, and each in a different way:
791 - sudo su gives a false positive, since SUDO_UID is set, *but* the program
792 is using root's .cache folder, so we really don't want to fix these
793 - sudo (program) wants to use the user's cache folder, but will end up writing
794 them as root:root files, instead of user:user. So, in this case, we make sure
795 to chmod those files to be owned by the original user, and not root.
796 */
797
798 static void
get_user_info(GnomeDesktopThumbnailFactory * factory,gboolean * adjust,uid_t * uid,gid_t * gid)799 get_user_info (GnomeDesktopThumbnailFactory *factory,
800 gboolean *adjust,
801 uid_t *uid,
802 gid_t *gid)
803 {
804 struct passwd *pwent;
805
806 pwent = gnome_desktop_get_session_user_pwent ();
807
808 *uid = pwent->pw_uid;
809 *gid = pwent->pw_gid;
810
811 /* Only can (and need to) adjust if we're root, but
812 this process will be writing to the session user's
813 home folder */
814
815 *adjust = geteuid () == 0 &&
816 g_strcmp0 (pwent->pw_dir, g_get_home_dir ()) == 0;
817 }
818
819 static void
gnome_desktop_thumbnail_factory_init(GnomeDesktopThumbnailFactory * factory)820 gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory)
821 {
822 GnomeDesktopThumbnailFactoryPrivate *priv;
823
824 factory->priv = GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory);
825
826 priv = factory->priv;
827 priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
828
829 priv->mime_types_map = g_hash_table_new_full (g_str_hash,
830 g_str_equal,
831 (GDestroyNotify)g_free,
832 (GDestroyNotify)thumbnailer_unref);
833
834 get_user_info (factory, &priv->needs_chown, &priv->real_uid, &priv->real_gid);
835
836 priv->permissions_problem = !gnome_desktop_thumbnail_cache_check_permissions (NULL, TRUE);
837
838 g_mutex_init (&priv->lock);
839
840 priv->settings = g_settings_new ("org.cinnamon.desktop.thumbnailers");
841 priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
842 if (!priv->disabled)
843 priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
844 g_signal_connect (priv->settings, "changed::disable-all",
845 G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
846 factory);
847 g_signal_connect (priv->settings, "changed::disable",
848 G_CALLBACK (external_thumbnailers_disabled_changed_cb),
849 factory);
850
851 if (!priv->disabled)
852 gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
853 }
854
855 static void
gnome_desktop_thumbnail_factory_class_init(GnomeDesktopThumbnailFactoryClass * class)856 gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class)
857 {
858 GObjectClass *gobject_class;
859
860 gobject_class = G_OBJECT_CLASS (class);
861
862 gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize;
863
864 g_type_class_add_private (class, sizeof (GnomeDesktopThumbnailFactoryPrivate));
865 }
866
867 /**
868 * gnome_desktop_thumbnail_factory_new:
869 * @size: The thumbnail size to use
870 *
871 * Creates a new #GnomeDesktopThumbnailFactory.
872 *
873 * This function must be called on the main thread.
874 *
875 * Return value: a new #GnomeDesktopThumbnailFactory
876 *
877 * Since: 2.2
878 **/
879 GnomeDesktopThumbnailFactory *
gnome_desktop_thumbnail_factory_new(GnomeDesktopThumbnailSize size)880 gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size)
881 {
882 GnomeDesktopThumbnailFactory *factory;
883
884 factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
885
886 factory->priv->size = size;
887
888 return factory;
889 }
890
891 /**
892 * gnome_desktop_thumbnail_factory_lookup:
893 * @factory: a #GnomeDesktopThumbnailFactory
894 * @uri: the uri of a file
895 * @mtime: the mtime of the file
896 *
897 * Tries to locate an existing thumbnail for the file specified.
898 *
899 * Usage of this function is threadsafe.
900 *
901 * Return value: The absolute path of the thumbnail, or %NULL if none exist.
902 *
903 * Since: 2.2
904 **/
905 char *
gnome_desktop_thumbnail_factory_lookup(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)906 gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
907 const char *uri,
908 time_t mtime)
909 {
910 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
911 char *path, *file;
912 GChecksum *checksum;
913 guint8 digest[16];
914 gsize digest_len = sizeof (digest);
915 GdkPixbuf *pixbuf;
916 gboolean res;
917
918 g_return_val_if_fail (uri != NULL, NULL);
919
920 res = FALSE;
921
922 checksum = g_checksum_new (G_CHECKSUM_MD5);
923 g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
924
925 g_checksum_get_digest (checksum, digest, &digest_len);
926 g_assert (digest_len == 16);
927
928 file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
929
930 path = g_build_filename (g_get_user_cache_dir (),
931 "thumbnails",
932 (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
933 file,
934 NULL);
935 g_free (file);
936
937 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
938 if (pixbuf != NULL)
939 {
940 res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
941 g_object_unref (pixbuf);
942 }
943
944 g_checksum_free (checksum);
945
946 if (res)
947 return path;
948
949 g_free (path);
950 return FALSE;
951 }
952
953 /**
954 * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail:
955 * @factory: a #GnomeDesktopThumbnailFactory
956 * @uri: the uri of a file
957 * @mtime: the mtime of the file
958 *
959 * Tries to locate an failed thumbnail for the file specified. Writing
960 * and looking for failed thumbnails is important to avoid to try to
961 * thumbnail e.g. broken images several times.
962 *
963 * Usage of this function is threadsafe.
964 *
965 * Return value: TRUE if there is a failed thumbnail for the file.
966 *
967 * Since: 2.2
968 **/
969 gboolean
gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)970 gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
971 const char *uri,
972 time_t mtime)
973 {
974 char *path, *file;
975 GdkPixbuf *pixbuf;
976 gboolean res;
977 GChecksum *checksum;
978 guint8 digest[16];
979 gsize digest_len = sizeof (digest);
980
981 checksum = g_checksum_new (G_CHECKSUM_MD5);
982 g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
983
984 g_checksum_get_digest (checksum, digest, &digest_len);
985 g_assert (digest_len == 16);
986
987 res = FALSE;
988
989 file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
990
991 path = g_build_filename (g_get_user_cache_dir (),
992 "thumbnails/fail",
993 appname,
994 file,
995 NULL);
996 g_free (file);
997
998 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
999 g_free (path);
1000
1001 if (pixbuf)
1002 {
1003 res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
1004 g_object_unref (pixbuf);
1005 }
1006
1007 g_checksum_free (checksum);
1008
1009 return res;
1010 }
1011
1012 static gboolean
mimetype_supported_by_gdk_pixbuf(const char * mime_type)1013 mimetype_supported_by_gdk_pixbuf (const char *mime_type)
1014 {
1015 guint i;
1016 static gsize formats_hash = 0;
1017 gchar *key;
1018 gboolean result;
1019
1020 if (g_once_init_enter (&formats_hash)) {
1021 GSList *formats, *list;
1022 GHashTable *hash;
1023
1024 hash = g_hash_table_new_full (g_str_hash,
1025 (GEqualFunc) g_content_type_equals,
1026 g_free, NULL);
1027
1028 formats = gdk_pixbuf_get_formats ();
1029 list = formats;
1030
1031 while (list) {
1032 GdkPixbufFormat *format = list->data;
1033 gchar **mime_types;
1034
1035 mime_types = gdk_pixbuf_format_get_mime_types (format);
1036
1037 for (i = 0; mime_types[i] != NULL; i++)
1038 {
1039 g_hash_table_insert (hash,
1040 (gpointer) g_content_type_from_mime_type (mime_types[i]),
1041 GUINT_TO_POINTER (1));
1042 }
1043
1044 g_strfreev (mime_types);
1045 list = list->next;
1046 }
1047
1048 g_slist_free (formats);
1049
1050 g_once_init_leave (&formats_hash, (gsize) hash);
1051 }
1052
1053 key = g_content_type_from_mime_type (mime_type);
1054 if (g_hash_table_lookup ((void*)formats_hash, key))
1055 result = TRUE;
1056 else
1057 result = FALSE;
1058 g_free (key);
1059
1060 return result;
1061 }
1062
1063 /**
1064 * gnome_desktop_thumbnail_factory_can_thumbnail:
1065 * @factory: a #GnomeDesktopThumbnailFactory
1066 * @uri: the uri of a file
1067 * @mime_type: the mime type of the file
1068 * @mtime: the mtime of the file
1069 *
1070 * Returns TRUE if this GnomeIconFactory can (at least try) to thumbnail
1071 * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
1072 *
1073 * Usage of this function is threadsafe.
1074 *
1075 * Return value: TRUE if the file can be thumbnailed.
1076 *
1077 * Since: 2.2
1078 **/
1079 gboolean
gnome_desktop_thumbnail_factory_can_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,const char * mime_type,time_t mtime)1080 gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
1081 const char *uri,
1082 const char *mime_type,
1083 time_t mtime)
1084 {
1085 gboolean have_script = FALSE;
1086
1087
1088 if (factory->priv->permissions_problem)
1089 return FALSE;
1090
1091 /* Don't thumbnail thumbnails */
1092 if (uri &&
1093 strncmp (uri, "file:/", 6) == 0 &&
1094 strstr (uri, "/thumbnails/") != NULL)
1095 return FALSE;
1096
1097 if (!mime_type)
1098 return FALSE;
1099
1100 if (gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
1101 {
1102 return FALSE;
1103 }
1104
1105 g_mutex_lock (&factory->priv->lock);
1106
1107 Thumbnailer *thumb;
1108 thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1109 have_script = thumbnailer_try_exec (thumb);
1110
1111 g_mutex_unlock (&factory->priv->lock);
1112
1113 if (have_script || mimetype_supported_by_gdk_pixbuf (mime_type))
1114 {
1115 return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
1116 uri,
1117 mtime);
1118 }
1119
1120 return FALSE;
1121 }
1122
1123 static char *
expand_thumbnailing_script(const char * script,const int size,const char * inuri,const char * outfile)1124 expand_thumbnailing_script (const char *script,
1125 const int size,
1126 const char *inuri,
1127 const char *outfile)
1128 {
1129 GString *str;
1130 const char *p, *last;
1131 char *localfile, *quoted;
1132 gboolean got_in;
1133
1134 str = g_string_new (NULL);
1135
1136 got_in = FALSE;
1137 last = script;
1138 while ((p = strchr (last, '%')) != NULL)
1139 {
1140 g_string_append_len (str, last, p - last);
1141 p++;
1142
1143 switch (*p) {
1144 case 'u':
1145 quoted = g_shell_quote (inuri);
1146 g_string_append (str, quoted);
1147 g_free (quoted);
1148 got_in = TRUE;
1149 p++;
1150 break;
1151 case 'i':
1152 localfile = g_filename_from_uri (inuri, NULL, NULL);
1153 if (localfile)
1154 {
1155 quoted = g_shell_quote (localfile);
1156 g_string_append (str, quoted);
1157 got_in = TRUE;
1158 g_free (quoted);
1159 g_free (localfile);
1160 }
1161 p++;
1162 break;
1163 case 'o':
1164 quoted = g_shell_quote (outfile);
1165 g_string_append (str, quoted);
1166 g_free (quoted);
1167 p++;
1168 break;
1169 case 's':
1170 g_string_append_printf (str, "%d", size);
1171 p++;
1172 break;
1173 case '%':
1174 g_string_append_c (str, '%');
1175 p++;
1176 break;
1177 case 0:
1178 default:
1179 break;
1180 }
1181 last = p;
1182 }
1183 g_string_append (str, last);
1184
1185 if (got_in)
1186 return g_string_free (str, FALSE);
1187
1188 g_string_free (str, TRUE);
1189 return NULL;
1190 }
1191
1192 /**
1193 * gnome_desktop_thumbnail_factory_generate_thumbnail:
1194 * @factory: a #GnomeDesktopThumbnailFactory
1195 * @uri: the uri of a file
1196 * @mime_type: the mime type of the file
1197 *
1198 * Tries to generate a thumbnail for the specified file. If it succeeds
1199 * it returns a pixbuf that can be used as a thumbnail.
1200 *
1201 * Usage of this function is threadsafe.
1202 *
1203 * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
1204 *
1205 * Since: 2.2
1206 **/
1207 GdkPixbuf *
gnome_desktop_thumbnail_factory_generate_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,const char * mime_type)1208 gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
1209 const char *uri,
1210 const char *mime_type)
1211 {
1212 GdkPixbuf *pixbuf, *scaled, *tmp_pixbuf;
1213 char *script, *expanded_script;
1214 int width, height, size;
1215 int original_width = 0;
1216 int original_height = 0;
1217 char dimension[12];
1218 double scale;
1219 int exit_status;
1220 char *tmpname;
1221 gboolean disabled = FALSE;
1222
1223 g_return_val_if_fail (uri != NULL, NULL);
1224 g_return_val_if_fail (mime_type != NULL, NULL);
1225
1226 /* Doesn't access any volatile fields in factory, so it's threadsafe */
1227
1228 size = 128;
1229 if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE)
1230 size = 256;
1231
1232 pixbuf = NULL;
1233
1234 script = NULL;
1235 g_mutex_lock (&factory->priv->lock);
1236
1237 disabled = gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type);
1238
1239 if (!disabled)
1240 {
1241 Thumbnailer *thumb;
1242
1243 thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1244 if (thumb)
1245 script = g_strdup (thumb->command);
1246 }
1247 g_mutex_unlock (&factory->priv->lock);
1248
1249 if (script)
1250 {
1251 int fd;
1252
1253 fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, NULL);
1254
1255 if (fd != -1)
1256 {
1257 close (fd);
1258
1259 expanded_script = expand_thumbnailing_script (script, size, uri, tmpname);
1260 if (expanded_script != NULL &&
1261 g_spawn_command_line_sync (expanded_script,
1262 NULL, NULL, &exit_status, NULL) &&
1263 exit_status == 0)
1264 {
1265 pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
1266 }
1267
1268 g_free (expanded_script);
1269 g_unlink (tmpname);
1270 g_free (tmpname);
1271 }
1272
1273 g_free (script);
1274 }
1275
1276 /* Fall back to gdk-pixbuf */
1277 if (pixbuf == NULL && !disabled)
1278 {
1279 pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, size, size, TRUE);
1280
1281 if (pixbuf != NULL)
1282 {
1283 original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
1284 "gnome-original-width"));
1285 original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
1286 "gnome-original-height"));
1287 }
1288 }
1289
1290 if (pixbuf == NULL)
1291 return NULL;
1292
1293 /* The pixbuf loader may attach an "orientation" option to the pixbuf,
1294 if the tiff or exif jpeg file had an orientation tag. Rotate/flip
1295 the pixbuf as specified by this tag, if present. */
1296 tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
1297 g_object_unref (pixbuf);
1298 pixbuf = tmp_pixbuf;
1299
1300 width = gdk_pixbuf_get_width (pixbuf);
1301 height = gdk_pixbuf_get_height (pixbuf);
1302
1303 if (width > size || height > size)
1304 {
1305 const gchar *orig_width, *orig_height;
1306 scale = (double)size / MAX (width, height);
1307
1308 scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf,
1309 floor (width * scale + 0.5),
1310 floor (height * scale + 0.5));
1311
1312 orig_width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
1313 orig_height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
1314
1315 if (orig_width != NULL) {
1316 gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Width", orig_width);
1317 }
1318 if (orig_height != NULL) {
1319 gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Height", orig_height);
1320 }
1321
1322 g_object_unref (pixbuf);
1323 pixbuf = scaled;
1324 }
1325
1326 if (original_width > 0) {
1327 g_snprintf (dimension, sizeof (dimension), "%i", original_width);
1328 gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", dimension);
1329 }
1330 if (original_height > 0) {
1331 g_snprintf (dimension, sizeof (dimension), "%i", original_height);
1332 gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", dimension);
1333 }
1334
1335 return pixbuf;
1336 }
1337
1338 static void
maybe_fix_ownership(GnomeDesktopThumbnailFactory * factory,const gchar * path)1339 maybe_fix_ownership (GnomeDesktopThumbnailFactory *factory, const gchar *path)
1340 {
1341 if (factory->priv->needs_chown) {
1342 G_GNUC_UNUSED int res;
1343
1344 res = chown (path,
1345 factory->priv->real_uid,
1346 factory->priv->real_gid);
1347 }
1348 }
1349
1350 static gboolean
make_thumbnail_dirs(GnomeDesktopThumbnailFactory * factory)1351 make_thumbnail_dirs (GnomeDesktopThumbnailFactory *factory)
1352 {
1353 char *thumbnail_dir;
1354 char *image_dir;
1355 gboolean res;
1356
1357 res = FALSE;
1358
1359 thumbnail_dir = g_build_filename (g_get_user_cache_dir (),
1360 "thumbnails",
1361 NULL);
1362 if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
1363 {
1364 g_mkdir (thumbnail_dir, 0700);
1365 maybe_fix_ownership (factory, thumbnail_dir);
1366 res = TRUE;
1367 }
1368
1369 image_dir = g_build_filename (thumbnail_dir,
1370 (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1371 NULL);
1372 if (!g_file_test (image_dir, G_FILE_TEST_IS_DIR))
1373 {
1374 g_mkdir (image_dir, 0700);
1375 maybe_fix_ownership (factory, image_dir);
1376 res = TRUE;
1377 }
1378
1379 g_free (thumbnail_dir);
1380 g_free (image_dir);
1381
1382 return res;
1383 }
1384
1385 static gboolean
make_thumbnail_fail_dirs(GnomeDesktopThumbnailFactory * factory)1386 make_thumbnail_fail_dirs (GnomeDesktopThumbnailFactory *factory)
1387 {
1388 char *thumbnail_dir;
1389 char *fail_dir;
1390 char *app_dir;
1391 gboolean res;
1392
1393 res = FALSE;
1394
1395 thumbnail_dir = g_build_filename (g_get_user_cache_dir (),
1396 "thumbnails",
1397 NULL);
1398 if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
1399 {
1400 g_mkdir (thumbnail_dir, 0700);
1401 maybe_fix_ownership (factory, thumbnail_dir);
1402 res = TRUE;
1403 }
1404
1405 fail_dir = g_build_filename (thumbnail_dir,
1406 "fail",
1407 NULL);
1408 if (!g_file_test (fail_dir, G_FILE_TEST_IS_DIR))
1409 {
1410 g_mkdir (fail_dir, 0700);
1411 maybe_fix_ownership (factory, fail_dir);
1412 res = TRUE;
1413 }
1414
1415 app_dir = g_build_filename (fail_dir,
1416 appname,
1417 NULL);
1418 if (!g_file_test (app_dir, G_FILE_TEST_IS_DIR))
1419 {
1420 g_mkdir (app_dir, 0700);
1421 maybe_fix_ownership (factory, app_dir);
1422 res = TRUE;
1423 }
1424
1425 g_free (thumbnail_dir);
1426 g_free (fail_dir);
1427 g_free (app_dir);
1428
1429 return res;
1430 }
1431
1432
1433 /**
1434 * gnome_desktop_thumbnail_factory_save_thumbnail:
1435 * @factory: a #GnomeDesktopThumbnailFactory
1436 * @thumbnail: the thumbnail as a pixbuf
1437 * @uri: the uri of a file
1438 * @original_mtime: the modification time of the original file
1439 *
1440 * Saves @thumbnail at the right place. If the save fails a
1441 * failed thumbnail is written.
1442 *
1443 * Usage of this function is threadsafe.
1444 *
1445 * Since: 2.2
1446 **/
1447 void
gnome_desktop_thumbnail_factory_save_thumbnail(GnomeDesktopThumbnailFactory * factory,GdkPixbuf * thumbnail,const char * uri,time_t original_mtime)1448 gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
1449 GdkPixbuf *thumbnail,
1450 const char *uri,
1451 time_t original_mtime)
1452 {
1453 GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
1454 char *path, *file;
1455 char *tmp_path;
1456 const char *width, *height;
1457 int tmp_fd;
1458 char mtime_str[21];
1459 gboolean saved_ok;
1460 GChecksum *checksum;
1461 guint8 digest[16];
1462 gsize digest_len = sizeof (digest);
1463 GError *error;
1464
1465 checksum = g_checksum_new (G_CHECKSUM_MD5);
1466 g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
1467
1468 g_checksum_get_digest (checksum, digest, &digest_len);
1469 g_assert (digest_len == 16);
1470
1471 file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1472
1473 path = g_build_filename (g_get_user_cache_dir (),
1474 "thumbnails",
1475 (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1476 file,
1477 NULL);
1478
1479 g_free (file);
1480
1481 g_checksum_free (checksum);
1482
1483 tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1484
1485 tmp_fd = g_mkstemp (tmp_path);
1486 if (tmp_fd == -1 &&
1487 make_thumbnail_dirs (factory))
1488 {
1489 g_free (tmp_path);
1490 tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1491 tmp_fd = g_mkstemp (tmp_path);
1492 }
1493
1494 if (tmp_fd == -1)
1495 {
1496 gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
1497 g_free (tmp_path);
1498 g_free (path);
1499 return;
1500 }
1501 close (tmp_fd);
1502
1503 g_snprintf (mtime_str, 21, "%ld", original_mtime);
1504 width = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Width");
1505 height = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Height");
1506
1507 error = NULL;
1508 if (width != NULL && height != NULL)
1509 saved_ok = gdk_pixbuf_save (thumbnail,
1510 tmp_path,
1511 "png", &error,
1512 "tEXt::Thumb::Image::Width", width,
1513 "tEXt::Thumb::Image::Height", height,
1514 "tEXt::Thumb::URI", uri,
1515 "tEXt::Thumb::MTime", mtime_str,
1516 "tEXt::Software", "GNOME::ThumbnailFactory",
1517 NULL);
1518 else
1519 saved_ok = gdk_pixbuf_save (thumbnail,
1520 tmp_path,
1521 "png", &error,
1522 "tEXt::Thumb::URI", uri,
1523 "tEXt::Thumb::MTime", mtime_str,
1524 "tEXt::Software", "GNOME::ThumbnailFactory",
1525 NULL);
1526
1527
1528 if (saved_ok)
1529 {
1530 g_chmod (tmp_path, 0600);
1531 g_rename (tmp_path, path);
1532 maybe_fix_ownership (factory, path);
1533 }
1534 else
1535 {
1536 g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message);
1537 gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
1538 g_unlink (tmp_path);
1539 g_clear_error (&error);
1540 }
1541
1542 g_free (path);
1543 g_free (tmp_path);
1544 }
1545
1546 /**
1547 * gnome_desktop_thumbnail_factory_create_failed_thumbnail:
1548 * @factory: a #GnomeDesktopThumbnailFactory
1549 * @uri: the uri of a file
1550 * @mtime: the modification time of the file
1551 *
1552 * Creates a failed thumbnail for the file so that we don't try
1553 * to re-thumbnail the file later.
1554 *
1555 * Usage of this function is threadsafe.
1556 *
1557 * Since: 2.2
1558 **/
1559 void
gnome_desktop_thumbnail_factory_create_failed_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)1560 gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
1561 const char *uri,
1562 time_t mtime)
1563 {
1564 char *path, *file;
1565 char *tmp_path;
1566 int tmp_fd;
1567 char mtime_str[21];
1568 gboolean saved_ok;
1569 GdkPixbuf *pixbuf;
1570 GChecksum *checksum;
1571 guint8 digest[16];
1572 gsize digest_len = sizeof (digest);
1573
1574 checksum = g_checksum_new (G_CHECKSUM_MD5);
1575 g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
1576
1577 g_checksum_get_digest (checksum, digest, &digest_len);
1578 g_assert (digest_len == 16);
1579
1580 file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1581
1582 path = g_build_filename (g_get_user_cache_dir (),
1583 "thumbnails/fail",
1584 appname,
1585 file,
1586 NULL);
1587 g_free (file);
1588
1589 g_checksum_free (checksum);
1590
1591 tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1592
1593 tmp_fd = g_mkstemp (tmp_path);
1594 if (tmp_fd == -1 &&
1595 make_thumbnail_fail_dirs (factory))
1596 {
1597 g_free (tmp_path);
1598 tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1599 tmp_fd = g_mkstemp (tmp_path);
1600 }
1601
1602 if (tmp_fd == -1)
1603 {
1604 g_free (tmp_path);
1605 g_free (path);
1606 return;
1607 }
1608 close (tmp_fd);
1609
1610 g_snprintf (mtime_str, 21, "%ld", mtime);
1611 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
1612 saved_ok = gdk_pixbuf_save (pixbuf,
1613 tmp_path,
1614 "png", NULL,
1615 "tEXt::Thumb::URI", uri,
1616 "tEXt::Thumb::MTime", mtime_str,
1617 "tEXt::Software", "GNOME::ThumbnailFactory",
1618 NULL);
1619 g_object_unref (pixbuf);
1620 if (saved_ok)
1621 {
1622 g_chmod (tmp_path, 0600);
1623 g_rename(tmp_path, path);
1624 maybe_fix_ownership (factory, path);
1625 }
1626
1627 g_free (path);
1628 g_free (tmp_path);
1629 }
1630
1631 /**
1632 * gnome_desktop_thumbnail_md5:
1633 * @uri: an uri
1634 *
1635 * Calculates the MD5 checksum of the uri. This can be useful
1636 * if you want to manually handle thumbnail files.
1637 *
1638 * Return value: A string with the MD5 digest of the uri string.
1639 *
1640 * Since: 2.2
1641 * Deprecated: 2.22: Use #GChecksum instead
1642 **/
1643 char *
gnome_desktop_thumbnail_md5(const char * uri)1644 gnome_desktop_thumbnail_md5 (const char *uri)
1645 {
1646 return g_compute_checksum_for_data (G_CHECKSUM_MD5,
1647 (const guchar *) uri,
1648 strlen (uri));
1649 }
1650
1651 /**
1652 * gnome_desktop_thumbnail_path_for_uri:
1653 * @uri: an uri
1654 * @size: a thumbnail size
1655 *
1656 * Returns the filename that a thumbnail of size @size for @uri would have.
1657 *
1658 * Return value: an absolute filename
1659 *
1660 * Since: 2.2
1661 **/
1662 char *
gnome_desktop_thumbnail_path_for_uri(const char * uri,GnomeDesktopThumbnailSize size)1663 gnome_desktop_thumbnail_path_for_uri (const char *uri,
1664 GnomeDesktopThumbnailSize size)
1665 {
1666 char *md5;
1667 char *file;
1668 char *path;
1669
1670 md5 = gnome_desktop_thumbnail_md5 (uri);
1671 file = g_strconcat (md5, ".png", NULL);
1672 g_free (md5);
1673
1674 path = g_build_filename (g_get_user_cache_dir (),
1675 "thumbnails",
1676 (size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1677 file,
1678 NULL);
1679
1680 g_free (file);
1681
1682 return path;
1683 }
1684
1685 /**
1686 * gnome_desktop_thumbnail_has_uri:
1687 * @pixbuf: an loaded thumbnail pixbuf
1688 * @uri: a uri
1689 *
1690 * Returns whether the thumbnail has the correct uri embedded in the
1691 * Thumb::URI option in the png.
1692 *
1693 * Return value: TRUE if the thumbnail is for @uri
1694 *
1695 * Since: 2.2
1696 **/
1697 gboolean
gnome_desktop_thumbnail_has_uri(GdkPixbuf * pixbuf,const char * uri)1698 gnome_desktop_thumbnail_has_uri (GdkPixbuf *pixbuf,
1699 const char *uri)
1700 {
1701 const char *thumb_uri;
1702
1703 thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1704 if (!thumb_uri)
1705 return FALSE;
1706
1707 return strcmp (uri, thumb_uri) == 0;
1708 }
1709
1710 /**
1711 * gnome_desktop_thumbnail_is_valid:
1712 * @pixbuf: an loaded thumbnail #GdkPixbuf
1713 * @uri: a uri
1714 * @mtime: the mtime
1715 *
1716 * Returns whether the thumbnail has the correct uri and mtime embedded in the
1717 * png options.
1718 *
1719 * Return value: TRUE if the thumbnail has the right @uri and @mtime
1720 *
1721 * Since: 2.2
1722 **/
1723 gboolean
gnome_desktop_thumbnail_is_valid(GdkPixbuf * pixbuf,const char * uri,time_t mtime)1724 gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
1725 const char *uri,
1726 time_t mtime)
1727 {
1728 const char *thumb_uri, *thumb_mtime_str;
1729 time_t thumb_mtime;
1730
1731 thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1732 if (!thumb_uri)
1733 return FALSE;
1734 if (strcmp (uri, thumb_uri) != 0)
1735 return FALSE;
1736
1737 thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
1738 if (!thumb_mtime_str)
1739 return FALSE;
1740 thumb_mtime = atol (thumb_mtime_str);
1741 if (mtime != thumb_mtime)
1742 return FALSE;
1743
1744 return TRUE;
1745 }
1746
1747 static void
fix_owner(const gchar * path,uid_t uid,gid_t gid)1748 fix_owner (const gchar *path, uid_t uid, gid_t gid)
1749 {
1750 G_GNUC_UNUSED int res;
1751
1752 res = chown (path, uid, gid);
1753 }
1754
1755 static gboolean
access_ok(const gchar * path,uid_t uid,gid_t gid)1756 access_ok (const gchar *path, uid_t uid, gid_t gid)
1757 {
1758 /* user mode will always trip on this */
1759 if (g_access (path, R_OK|W_OK) != 0) {
1760 if (errno != ENOENT && errno != EFAULT)
1761 return FALSE;
1762 else
1763 return TRUE;
1764 }
1765
1766 /* root will need to check against the real user */
1767
1768 GStatBuf buf;
1769
1770 gint ret = g_stat (path, &buf);
1771
1772 if (ret == 0) {
1773 if (buf.st_uid != uid || buf.st_gid != gid || (buf.st_mode & (S_IRUSR|S_IWUSR)) == 0)
1774 return FALSE;
1775 }
1776
1777 return TRUE;
1778 }
1779
1780 static void
recursively_fix_file(const gchar * path,uid_t uid,gid_t gid)1781 recursively_fix_file (const gchar *path, uid_t uid, gid_t gid)
1782 {
1783 if (!access_ok (path, uid, gid))
1784 fix_owner (path, uid, gid);
1785
1786 if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
1787 GDir *dir = g_dir_open (path, 0, NULL);
1788
1789 if (dir) {
1790 const char *name;
1791
1792 while ((name = g_dir_read_name (dir))) {
1793 gchar *filename;
1794
1795 filename = g_build_filename (path, name, NULL);
1796 recursively_fix_file (filename, uid, gid);
1797 g_free (filename);
1798 }
1799
1800 g_dir_close (dir);
1801 }
1802 }
1803 }
1804
1805 static gboolean
recursively_check_file(const gchar * path,uid_t uid,gid_t gid)1806 recursively_check_file (const gchar *path, uid_t uid, gid_t gid)
1807 {
1808 if (!access_ok (path, uid, gid))
1809 return FALSE;
1810
1811 gboolean ret = TRUE;
1812
1813 if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
1814 GDir *dir = g_dir_open (path, 0, NULL);
1815
1816 if (dir) {
1817 const char *name;
1818
1819 while ((name = g_dir_read_name (dir))) {
1820 gchar *filename;
1821 filename = g_build_filename (path, name, NULL);
1822
1823 if (!recursively_check_file (filename, uid, gid)) {
1824 ret = FALSE;
1825 }
1826
1827 g_free (filename);
1828
1829 if (!ret)
1830 break;
1831 }
1832
1833 g_dir_close (dir);
1834 }
1835 }
1836
1837 return ret;
1838 }
1839
1840 static gboolean
check_subfolder_permissions_only(const gchar * path,uid_t uid,gid_t gid)1841 check_subfolder_permissions_only (const gchar *path, uid_t uid, gid_t gid)
1842 {
1843 gboolean ret = TRUE;
1844
1845 GDir *dir = g_dir_open (path, 0, NULL);
1846
1847 if (dir) {
1848 const char *name;
1849
1850 while ((name = g_dir_read_name (dir))) {
1851 gchar *filename;
1852 filename = g_build_filename (path, name, NULL);
1853
1854 if (!access_ok (filename, uid, gid)) {
1855 ret = FALSE;
1856 }
1857
1858 g_free (filename);
1859
1860 if (!ret)
1861 break;
1862 }
1863
1864 g_dir_close (dir);
1865 }
1866
1867 return ret;
1868 }
1869
1870 /**
1871 * gnome_desktop_cache_fix_permissions:
1872 *
1873 * Fixes any file or folder ownership issues for the *currently
1874 * logged-in* user. Note - this may not be the same as the uid
1875 * of the user running this operation.
1876 *
1877 **/
1878
1879 void
gnome_desktop_thumbnail_cache_fix_permissions(void)1880 gnome_desktop_thumbnail_cache_fix_permissions (void)
1881 {
1882 struct passwd *pwent;
1883
1884 pwent = gnome_desktop_get_session_user_pwent ();
1885
1886 gchar *cache_dir = g_build_filename (pwent->pw_dir, ".cache", "thumbnails", NULL);
1887
1888 if (!access_ok (cache_dir, pwent->pw_uid, pwent->pw_gid))
1889 fix_owner (cache_dir, pwent->pw_uid, pwent->pw_gid);
1890
1891 recursively_fix_file (cache_dir, pwent->pw_uid, pwent->pw_gid);
1892
1893 g_free (cache_dir);
1894 }
1895
1896 /**
1897 * gnome_desktop_cache_check_permissions:
1898 * @factory: (allow-none): an optional GnomeDesktopThumbnailFactory
1899 * @quick: if TRUE, only do a quick check of directory ownersip
1900 * This is more serious than thumbnail ownership issues, and is faster.
1901 *
1902 * Returns whether there are any ownership issues with the thumbnail
1903 * folders and existing thumbnails for the *currently logged-in* user.
1904 * Note - this may not be the same as the uid of the user running this
1905 * check.
1906 *
1907 * if factory is not NULL, factory->priv->permissions_problem will be
1908 * set to the result of the check.
1909 *
1910 * Return value: TRUE if everything checks out in these folders for the
1911 * logged in user.
1912 *
1913 **/
1914
1915 gboolean
gnome_desktop_thumbnail_cache_check_permissions(GnomeDesktopThumbnailFactory * factory,gboolean quick)1916 gnome_desktop_thumbnail_cache_check_permissions (GnomeDesktopThumbnailFactory *factory, gboolean quick)
1917 {
1918 gboolean checks_out = TRUE;
1919
1920 struct passwd *pwent;
1921 pwent = gnome_desktop_get_session_user_pwent ();
1922
1923 gchar *cache_dir = g_build_filename (pwent->pw_dir, ".cache", "thumbnails", NULL);
1924
1925 if (!access_ok (cache_dir, pwent->pw_uid, pwent->pw_gid)) {
1926 checks_out = FALSE;
1927 goto out;
1928 }
1929
1930 if (quick) {
1931 checks_out = check_subfolder_permissions_only (cache_dir, pwent->pw_uid, pwent->pw_gid);
1932 } else {
1933 checks_out = recursively_check_file (cache_dir, pwent->pw_uid, pwent->pw_gid);
1934 }
1935
1936 out:
1937 g_free (cache_dir);
1938
1939 if (factory)
1940 factory->priv->permissions_problem = !checks_out;
1941
1942 return checks_out;
1943 }
1944