1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3    nemo-thumbnail-cache.h: Thumbnail code for icon factory.
4 
5    Copyright (C) 2000, 2001 Eazel, Inc.
6    Copyright (C) 2002, 2003 Red Hat, Inc.
7 
8    This program is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12 
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    General Public License for more details.
17 
18    You should have received a copy of the GNU General Public
19    License along with this program; if not, write to the
20    Free Software Foundation, Inc., 51 Franklin Street - Suite 500,
21    Boston, MA 02110-1335, USA.
22 
23    Author: Andy Hertzfeld <andy@eazel.com>
24 */
25 
26 #include <config.h>
27 #include "nemo-thumbnails.h"
28 
29 #define GNOME_DESKTOP_USE_UNSTABLE_API
30 
31 #include "nemo-directory-notify.h"
32 #include "nemo-global-preferences.h"
33 #include "nemo-file-utilities.h"
34 #include <math.h>
35 #include <eel/eel-graphic-effects.h>
36 #include <eel/eel-string.h>
37 #include <eel/eel-debug.h>
38 #include <eel/eel-vfs-extensions.h>
39 #include <gtk/gtk.h>
40 #include <errno.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <sys/wait.h>
44 #include <unistd.h>
45 #include <signal.h>
46 #include <libcinnamon-desktop/gnome-desktop-thumbnail.h>
47 
48 #define DEBUG_FLAG NEMO_DEBUG_THUMBNAILS
49 #include <libnemo-private/nemo-debug.h>
50 
51 #include "nemo-file-private.h"
52 
53 /* Should never be a reasonable actual mtime */
54 #define INVALID_MTIME 0
55 
56 /* Cool-off period between last file modification time and thumbnail creation */
57 #define THUMBNAIL_CREATION_DELAY_SECS 3
58 
59 #define NEMO_THUMBNAIL_FRAME_LEFT 3
60 #define NEMO_THUMBNAIL_FRAME_TOP 3
61 #define NEMO_THUMBNAIL_FRAME_RIGHT 3
62 #define NEMO_THUMBNAIL_FRAME_BOTTOM 3
63 
64 /* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */
65 
66 typedef struct {
67     char *image_uri;
68     char *mime_type;
69     time_t original_file_mtime;
70     gint throttle_count;
71 } NemoThumbnailInfo;
72 
73 /*
74  * Thumbnail thread state.
75  */
76 
77 /* The id of the idle handler used to start the thumbnail thread, or 0 if no
78    idle handler is currently registered. */
79 static guint thumbnail_thread_starter_id = 0;
80 
81 /* Our mutex used when accessing data shared between the main thread and the
82    thumbnail thread, i.e. the thumbnail_thread_is_running flag and the
83    thumbnails_to_make list. */
84 static GMutex thumbnails_mutex;
85 static GCancellable *thumbnails_cancellable;
86 
87 /* A flag to indicate whether a thumbnail thread is running, so we don't
88    start more than one. Lock thumbnails_mutex when accessing this. */
89 static volatile gboolean thumbnail_thread_is_running = FALSE;
90 
91 /* The list of NemoThumbnailInfo structs containing information about the
92    thumbnails we are making. Lock thumbnails_mutex when accessing this. */
93 static volatile GQueue thumbnails_to_make = G_QUEUE_INIT;
94 
95 /* Quickly check if uri is in thumbnails_to_make list */
96 static GHashTable *thumbnails_to_make_hash = NULL;
97 
98 /* The currently thumbnailed icon. it also exists in the thumbnails_to_make list
99  * to avoid adding it again. Lock thumbnails_mutex when accessing this. */
100 static NemoThumbnailInfo *currently_thumbnailing = NULL;
101 
102 static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL;
103 
104 static gboolean
get_file_mtime(const char * file_uri,time_t * mtime)105 get_file_mtime (const char *file_uri, time_t* mtime)
106 {
107     GFile *file;
108     GFileInfo *info;
109     gboolean ret;
110 
111     ret = FALSE;
112     *mtime = INVALID_MTIME;
113 
114     file = g_file_new_for_uri (file_uri);
115     info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
116     if (info) {
117         if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) {
118             *mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
119             ret = TRUE;
120         }
121 
122         g_object_unref (info);
123     }
124     g_object_unref (file);
125 
126     return ret;
127 }
128 
129 static void
free_thumbnail_info(NemoThumbnailInfo * info)130 free_thumbnail_info (NemoThumbnailInfo *info)
131 {
132     g_free (info->image_uri);
133     g_free (info->mime_type);
134     g_free (info);
135 }
136 
137 static GnomeDesktopThumbnailFactory *
get_thumbnail_factory(void)138 get_thumbnail_factory (void)
139 {
140     if (thumbnail_factory == NULL) {
141         thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
142     }
143 
144     return thumbnail_factory;
145 }
146 
147 static GdkPixbuf *
nemo_get_thumbnail_frame(void)148 nemo_get_thumbnail_frame (void)
149 {
150     static GdkPixbuf *thumbnail_frame = NULL;
151 
152     if (thumbnail_frame == NULL) {
153         GInputStream *stream = g_resources_open_stream ("/org/nemo/icons/thumbnail_frame.png", 0, NULL);
154         if (stream != NULL) {
155             thumbnail_frame = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
156             g_object_unref (stream);
157         }
158     }
159 
160     return thumbnail_frame;
161 }
162 
163 void
nemo_thumbnail_frame_image(GdkPixbuf ** pixbuf)164 nemo_thumbnail_frame_image (GdkPixbuf **pixbuf)
165 {
166     GdkPixbuf *pixbuf_with_frame, *frame;
167     int left_offset, top_offset, right_offset, bottom_offset;
168 
169     /* The pixbuf isn't already framed (i.e., it was not made by
170      * an old Nemo), so we must embed it in a frame.
171      */
172 
173     frame = nemo_get_thumbnail_frame ();
174     if (frame == NULL) {
175         return;
176     }
177 
178     left_offset = NEMO_THUMBNAIL_FRAME_LEFT;
179     top_offset = NEMO_THUMBNAIL_FRAME_TOP;
180     right_offset = NEMO_THUMBNAIL_FRAME_RIGHT;
181     bottom_offset = NEMO_THUMBNAIL_FRAME_BOTTOM;
182 
183     pixbuf_with_frame = eel_embed_image_in_frame
184         (*pixbuf, frame,
185          left_offset, top_offset, right_offset, bottom_offset);
186     g_object_unref (*pixbuf);
187 
188     *pixbuf = pixbuf_with_frame;
189 }
190 
191 void
nemo_thumbnail_pad_top_and_bottom(GdkPixbuf ** pixbuf,gint extra_height)192 nemo_thumbnail_pad_top_and_bottom (GdkPixbuf **pixbuf,
193                                    gint        extra_height)
194 {
195     GdkPixbuf *pixbuf_with_padding;
196     GdkRectangle rect;
197     GdkRGBA transparent = { 0, 0, 0, 0.0 };
198     cairo_surface_t *surface;
199     cairo_t *cr;
200     gint width, height;
201 
202     width = gdk_pixbuf_get_width (*pixbuf);
203     height = gdk_pixbuf_get_height (*pixbuf);
204 
205     surface = gdk_window_create_similar_image_surface (NULL,
206                                                        CAIRO_FORMAT_ARGB32,
207                                                        width,
208                                                        height + extra_height,
209                                                        0);
210 
211     cr = cairo_create (surface);
212 
213     rect.x = 0;
214     rect.y = 0;
215     rect.width = width;
216     rect.height = height + extra_height;
217 
218     gdk_cairo_rectangle (cr, &rect);
219     gdk_cairo_set_source_rgba (cr, &transparent);
220     cairo_fill (cr);
221 
222     gdk_cairo_set_source_pixbuf (cr,
223                                  *pixbuf,
224                                  0,
225                                  extra_height / 2);
226     cairo_paint (cr);
227 
228     pixbuf_with_padding = gdk_pixbuf_get_from_surface (surface,
229                                                        0,
230                                                        0,
231                                                        width,
232                                                        height + extra_height);
233 
234     g_object_unref (*pixbuf);
235     cairo_surface_destroy (surface);
236     cairo_destroy (cr);
237 
238     *pixbuf = pixbuf_with_padding;
239 }
240 
241 static GHashTable *
get_types_table(void)242 get_types_table (void)
243 {
244     static GHashTable *image_mime_types = NULL;
245     GSList *format_list, *l;
246     char **types;
247     int i;
248 
249     if (image_mime_types == NULL) {
250         image_mime_types =
251             g_hash_table_new_full (g_str_hash, g_str_equal,
252                            g_free, NULL);
253 
254         format_list = gdk_pixbuf_get_formats ();
255         for (l = format_list; l; l = l->next) {
256             types = gdk_pixbuf_format_get_mime_types (l->data);
257 
258             for (i = 0; types[i] != NULL; i++) {
259                 g_hash_table_insert (image_mime_types,
260                              types [i],
261                              GUINT_TO_POINTER (1));
262             }
263 
264             g_free (types);
265         }
266 
267         g_slist_free (format_list);
268     }
269 
270     return image_mime_types;
271 }
272 
273 static gboolean
pixbuf_can_load_type(const char * mime_type)274 pixbuf_can_load_type (const char *mime_type)
275 {
276     GHashTable *image_mime_types;
277 
278     image_mime_types = get_types_table ();
279     if (g_hash_table_lookup (image_mime_types, mime_type)) {
280         return TRUE;
281     }
282 
283     return FALSE;
284 }
285 
286 gboolean
nemo_can_thumbnail_internally(NemoFile * file)287 nemo_can_thumbnail_internally (NemoFile *file)
288 {
289     char *mime_type;
290     gboolean res;
291 
292     mime_type = nemo_file_get_mime_type (file);
293     res = pixbuf_can_load_type (mime_type);
294     g_free (mime_type);
295     return res;
296 }
297 
298 gboolean
nemo_thumbnail_is_mimetype_limited_by_size(const char * mime_type)299 nemo_thumbnail_is_mimetype_limited_by_size (const char *mime_type)
300 {
301     return pixbuf_can_load_type (mime_type);
302 }
303 
304 gboolean
nemo_can_thumbnail(NemoFile * file)305 nemo_can_thumbnail (NemoFile *file)
306 {
307     GnomeDesktopThumbnailFactory *factory;
308     gboolean res;
309     char *uri;
310     time_t mtime;
311     char *mime_type;
312 
313     uri = nemo_file_get_uri (file);
314     mime_type = nemo_file_get_mime_type (file);
315     mtime = nemo_file_get_mtime (file);
316 
317     factory = get_thumbnail_factory ();
318     res = gnome_desktop_thumbnail_factory_can_thumbnail (factory,
319                                  uri,
320                                  mime_type,
321                                  mtime);
322     g_free (mime_type);
323     g_free (uri);
324 
325     return res;
326 }
327 
328 /***************************************************************************
329  * Thumbnail Thread Functions.
330  ***************************************************************************/
331 
332 void
nemo_thumbnail_remove_from_queue(const char * file_uri)333 nemo_thumbnail_remove_from_queue (const char *file_uri)
334 {
335     GList *node;
336 
337     if (DEBUGGING) {
338         g_message ("(Remove from queue) Locking mutex");
339     }
340 
341     g_mutex_lock (&thumbnails_mutex);
342 
343     /*********************************
344      * MUTEX LOCKED
345      *********************************/
346 
347     if (thumbnails_to_make_hash) {
348         node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
349 
350         if (node && node->data != currently_thumbnailing) {
351             g_hash_table_remove (thumbnails_to_make_hash, file_uri);
352             free_thumbnail_info (node->data);
353             g_queue_delete_link ((GQueue *)&thumbnails_to_make, node);
354         }
355     }
356 
357     /*********************************
358      * MUTEX UNLOCKED
359      *********************************/
360 
361     if (DEBUGGING) {
362         g_message ("(Remove from queue) Unlocking mutex");
363     }
364 
365     g_mutex_unlock (&thumbnails_mutex);
366 }
367 
368 void
nemo_thumbnail_prioritize(const char * file_uri)369 nemo_thumbnail_prioritize (const char *file_uri)
370 {
371     GList *node;
372 
373     if (DEBUGGING) {
374         g_message ("(Prioritize) Locking mutex");
375     }
376 
377     g_mutex_lock (&thumbnails_mutex);
378 
379     /*********************************
380      * MUTEX LOCKED
381      *********************************/
382 
383     if (thumbnails_to_make_hash) {
384         node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
385 
386         if (node && node->data != currently_thumbnailing) {
387             g_queue_unlink ((GQueue *)&thumbnails_to_make, node);
388             g_queue_push_head_link ((GQueue *)&thumbnails_to_make, node);
389         }
390     }
391 
392     /*********************************
393      * MUTEX UNLOCKED
394      *********************************/
395 
396     if (DEBUGGING) {
397         g_message ("(Prioritize) Unlocking mutex");
398     }
399 
400     g_mutex_unlock (&thumbnails_mutex);
401 }
402 
403 /* This is a one-shot idle callback called from the main loop to call
404    notify_file_changed() for a thumbnail. It frees the uri afterwards.
405    We do this in an idle callback as I don't think nemo_file_changed() is
406    thread-safe. */
407 static gboolean
thumbnail_thread_notify_file_changed(gpointer image_uri)408 thumbnail_thread_notify_file_changed (gpointer image_uri)
409 {
410     NemoFile *file;
411 
412     file = nemo_file_get_by_uri ((char *) image_uri);
413 
414     if (DEBUGGING) {
415         g_message ("(Thumbnail Thread) Notifying file changed file:%p uri: %s", file, (char*) image_uri);
416     }
417 
418     if (file != NULL) {
419         nemo_file_set_is_thumbnailing (file, FALSE);
420         nemo_file_invalidate_attributes (file,
421                              NEMO_FILE_ATTRIBUTE_THUMBNAIL |
422                              NEMO_FILE_ATTRIBUTE_INFO);
423         nemo_file_unref (file);
424     }
425     g_free (image_uri);
426     // g_printerr ("length: %d  REMOVE\n" , g_hash_table_size (thumbnails_to_make_hash));
427 
428     return FALSE;
429 }
430 
431 static void
on_thumbnail_thread_finished(GObject * source,GAsyncResult * res,gpointer user_data)432 on_thumbnail_thread_finished (GObject      *source,
433                               GAsyncResult *res,
434                               gpointer      user_data)
435 {
436     GError *error;
437 
438     error = NULL;
439     g_task_propagate_boolean (G_TASK (res), &error);
440 
441     if (error != NULL) {
442         g_warning ("Error thumbnailing: %s", error->message);
443         g_error_free (error);
444     }
445 
446     if (DEBUGGING) {
447         g_message ("(Main Thread) Thumbnail thread finished");
448     }
449 
450     /* Thread is no longer running, no need to lock mutex */
451     thumbnail_thread_is_running = FALSE;
452 }
453 
454 /* thumbnail_thread is invoked as a separate thread to to make thumbnails. */
455 static void
thumbnail_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)456 thumbnail_thread (GTask        *task,
457                         gpointer      source_object,
458                         gpointer      task_data,
459                         GCancellable *cancellable)
460 {
461     NemoThumbnailInfo *info = NULL;
462     GdkPixbuf *pixbuf;
463     time_t current_orig_mtime = 0;
464     time_t current_time;
465     GList *node;
466 
467     /* We loop until there are no more thumbails to make, at which point
468        we exit the thread. */
469     for (;;) {
470         if (DEBUGGING) {
471             g_message ("(Thumbnail Thread) Locking mutex");
472         }
473 
474         g_mutex_lock (&thumbnails_mutex);
475 
476         /*********************************
477          * MUTEX LOCKED
478          *********************************/
479 
480         /* Pop the last thumbnail we just made off the head of the
481            list and free it. I did this here so we only have to lock
482            the mutex once per thumbnail, rather than once before
483            creating it and once after.
484            Don't pop the thumbnail off the queue if the original file
485            mtime of the request changed. Then we need to redo the thumbnail.
486         */
487         if (currently_thumbnailing &&
488             currently_thumbnailing->original_file_mtime == current_orig_mtime) {
489             g_assert (info == currently_thumbnailing);
490 
491             node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
492 
493             g_assert (node != NULL);
494 
495             g_hash_table_remove (thumbnails_to_make_hash, info->image_uri);
496             free_thumbnail_info (info);
497             g_queue_delete_link ((GQueue *)&thumbnails_to_make, node);
498         }
499 
500         currently_thumbnailing = NULL;
501 
502         /* If there are no more thumbnails to make, reset the
503            thumbnail_thread_is_running flag, unlock the mutex, and
504            exit the thread. */
505         if (g_queue_is_empty ((GQueue *)&thumbnails_to_make)) {
506             if (DEBUGGING) {
507                 g_message ("(Thumbnail Thread) Exiting");
508             }
509 
510             g_mutex_unlock (&thumbnails_mutex);
511             g_task_return_boolean (task, TRUE);
512             return;
513         }
514 
515         /* Get the next one to make. We leave it on the list until it
516            is created so the main thread doesn't add it again while we
517            are creating it. */
518         info = g_queue_peek_head ((GQueue *)&thumbnails_to_make);
519         currently_thumbnailing = info;
520         current_orig_mtime = info->original_file_mtime;
521 
522         /*********************************
523          * MUTEX UNLOCKED
524          *********************************/
525 
526         if (DEBUGGING) {
527             g_message ("(Thumbnail Thread) Unlocking mutex");
528         }
529 
530         g_mutex_unlock (&thumbnails_mutex);
531 
532         time (&current_time);
533 
534         /* Don't try to create a thumbnail if the file was modified recently.
535            This prevents constant re-thumbnailing of changing files. */
536         if (current_time < current_orig_mtime + (THUMBNAIL_CREATION_DELAY_SECS * info->throttle_count) &&
537             current_time >= current_orig_mtime) {
538             if (DEBUGGING) {
539                 g_message ("(Thumbnail Thread) Skipping for %d seconds: %s",
540                            THUMBNAIL_CREATION_DELAY_SECS * info->throttle_count,
541                            info->image_uri);
542             }
543 
544             /* Reschedule thumbnailing via a change notification */
545             g_timeout_add_seconds (THUMBNAIL_CREATION_DELAY_SECS * info->throttle_count, thumbnail_thread_notify_file_changed,
546                        g_strdup (info->image_uri));
547             continue;
548         }
549 
550         /* Create the thumbnail. */
551         if (DEBUGGING) {
552             g_message ("(Thumbnail Thread) Creating thumbnail: %s",
553                        info->image_uri);
554         }
555 
556         pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
557                                          info->image_uri,
558                                          info->mime_type);
559 
560         if (pixbuf) {
561             gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory,
562                                     pixbuf,
563                                     info->image_uri,
564                                     current_orig_mtime);
565             g_object_unref (pixbuf);
566         } else {
567             gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory,
568                                          info->image_uri,
569                                          current_orig_mtime);
570         }
571 
572         /* We need to call nemo_file_changed(), but I don't think that is
573            thread safe. So add an idle handler and do it from the main loop. */
574         g_idle_add_full (G_PRIORITY_HIGH_IDLE,
575                  thumbnail_thread_notify_file_changed,
576                  g_strdup (info->image_uri), NULL);
577     }
578 
579     g_task_return_boolean (task, TRUE);
580 }
581 
582 /* This function is added as a very low priority idle function to start the
583    thread to create any needed thumbnails. It is added with a very low priority
584    so that it doesn't delay showing the directory in the icon/list views.
585    We want to show the files in the directory as quickly as possible. */
586 static gboolean
thumbnail_thread_starter_cb(gpointer data)587 thumbnail_thread_starter_cb (gpointer data)
588 {
589     GTask *thumbnail_task;
590 
591     /* Don't do this in thread, since g_object_ref is not threadsafe */
592     if (thumbnail_factory == NULL) {
593         thumbnail_factory = get_thumbnail_factory ();
594     }
595 
596     thumbnails_cancellable = g_cancellable_new ();
597 
598     if (DEBUGGING) {
599         g_message ("(Main Thread) Creating thumbnails thread");
600     }
601 
602     thumbnail_task = g_task_new (thumbnail_factory,
603                                  thumbnails_cancellable,
604                                  on_thumbnail_thread_finished,
605                                  NULL);
606 
607     /* We set a flag to indicate the thread is running, so we don't create
608        a new one. We don't need to lock a mutex here, as the thumbnail
609        thread isn't running yet. And we know we won't create the thread
610        twice, as we also check thumbnail_thread_starter_id before
611        scheduling this idle function. */
612     thumbnail_thread_is_running = TRUE;
613 
614     g_task_run_in_thread (thumbnail_task, thumbnail_thread);
615     g_object_unref (thumbnail_task);
616 
617     thumbnail_thread_starter_id = 0;
618     return FALSE;
619 }
620 
621 void
nemo_create_thumbnail(NemoFile * file,gint throttle_count,gboolean prioritize)622 nemo_create_thumbnail (NemoFile      *file,
623                        gint           throttle_count,
624                        gboolean       prioritize)
625 {
626     time_t file_mtime = 0;
627     NemoThumbnailInfo *info;
628     NemoThumbnailInfo *existing_info;
629     GList *existing, *node;
630 
631     /* The gdk-pixbuf-thumbnailer tool has special hardcoded handling for recent: and trash: uris.
632      * we need to find the activation uri here instead */
633     if (nemo_file_is_in_favorites (file)) {
634         NemoFile *real_file;
635         gchar *uri;
636 
637         uri = nemo_file_get_symbolic_link_target_uri (file);
638 
639         real_file = nemo_file_get_by_uri (uri);
640         nemo_create_thumbnail (real_file, 0, FALSE);
641 
642         nemo_file_unref (real_file);
643         return;
644     }
645 
646     nemo_file_set_is_thumbnailing (file, TRUE);
647 
648     info = g_new0 (NemoThumbnailInfo, 1);
649     info->image_uri = nemo_file_get_uri (file);
650 
651     info->mime_type = nemo_file_get_mime_type (file);    // info->image_uri = nemo_file_is_in_favorites (file) ? nemo_file_get_activation_uri (file) :
652     info->throttle_count = MIN (10, throttle_count);
653 
654     /* Hopefully the NemoFile will already have the image file mtime,
655        so we can just use that. Otherwise we have to get it ourselves. */
656     if (file->details->got_file_info &&
657         file->details->file_info_is_up_to_date &&
658         file->details->mtime != 0) {
659         file_mtime = file->details->mtime;
660     } else {
661         get_file_mtime (info->image_uri, &file_mtime);
662     }
663 
664     info->original_file_mtime = file_mtime;
665 
666     if (DEBUGGING) {
667         g_message ("(Main Thread) Locking mutex");
668     }
669 
670     g_mutex_lock (&thumbnails_mutex);
671 
672     /*********************************
673      * MUTEX LOCKED
674      *********************************/
675 
676     if (thumbnails_to_make_hash == NULL) {
677         thumbnails_to_make_hash = g_hash_table_new (g_str_hash,
678                                 g_str_equal);
679     }
680     // g_printerr ("length: %d  ADD\n" , g_hash_table_size (thumbnails_to_make_hash));
681     /* Check if it is already in the list of thumbnails to make. */
682     existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
683 
684     if (existing == NULL) {
685         /* Add the thumbnail to the list. */
686 
687         if (DEBUGGING) {
688             g_message ("(Main Thread) Adding thumbnail: %s",
689                        info->image_uri);
690         }
691 
692         if (prioritize) {
693             g_queue_push_head ((GQueue *)&thumbnails_to_make, info);
694             node = g_queue_peek_head_link ((GQueue *)&thumbnails_to_make);
695         } else {
696             g_queue_push_tail ((GQueue *)&thumbnails_to_make, info);
697             node = g_queue_peek_tail_link ((GQueue *)&thumbnails_to_make);
698         }
699 
700         g_hash_table_insert (thumbnails_to_make_hash,
701                      info->image_uri,
702                      node);
703 
704         /* If the thumbnail thread isn't running, and we haven't
705            scheduled an idle function to start it up, do that now.
706            We don't want to start it until all the other work is done,
707            so the GUI will be updated as quickly as possible.*/
708 
709         if (thumbnail_thread_is_running == FALSE &&
710             thumbnail_thread_starter_id == 0) {
711             thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
712                                                            thumbnail_thread_starter_cb,
713                                                            NULL, NULL);
714         }
715     } else {
716         if (DEBUGGING) {
717             g_message ("(Main Thread) Updating non-current mtime: %s",
718                        info->image_uri);
719         }
720 
721         /* The file in the queue might need a new original mtime */
722         existing_info = existing->data;
723         existing_info->original_file_mtime = info->original_file_mtime;
724         free_thumbnail_info (info);
725 
726         if (existing && existing->data != currently_thumbnailing) {
727             g_queue_unlink ((GQueue *)&thumbnails_to_make, existing);
728             g_queue_push_head_link ((GQueue *)&thumbnails_to_make, existing);
729         }
730     }
731 
732     /*********************************
733      * MUTEX UNLOCKED
734      *********************************/
735 
736     if (DEBUGGING) {
737         g_message ("(Main Thread) Unlocking mutex");
738     }
739 
740     g_mutex_unlock (&thumbnails_mutex);
741 }
742 
743 gboolean
nemo_thumbnail_factory_check_status(void)744 nemo_thumbnail_factory_check_status (void)
745 {
746     return gnome_desktop_thumbnail_cache_check_permissions (get_thumbnail_factory (), TRUE);
747 }
748