No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2
3 nautilus-thumbnails.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., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.
22
23 Author: Andy Hertzfeld <andy@eazel.com>
24 */
25
26 #include <config.h>
27 #include "nautilus-thumbnails.h"
28
29 #define GNOME_DESKTOP_USE_UNSTABLE_API
30
31 #include "nautilus-directory-notify.h"
32 #include "nautilus-global-preferences.h"
33 #include "nautilus-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 <pthread.h>
44 #include <sys/wait.h>
45 #include <unistd.h>
46 #include <signal.h>
47 #include <libgnome-desktop/gnome-desktop-thumbnail.h>
48
49 #include "nautilus-file-private.h"
50
51 /* turn this on to see messages about thumbnail creation */
52 #if 0
53 #define DEBUG_THUMBNAILS
54 #endif
55
56 /* Should never be a reasonable actual mtime */
57 #define INVALID_MTIME 0
58
59 /* Cool-off period between last file modification time and thumbnail creation */
60 #define THUMBNAIL_CREATION_DELAY_SECS 3
61
62 static gpointer thumbnail_thread_start (gpointer data);
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 } NautilusThumbnailInfo;
71
72 /*
73 * Thumbnail thread state.
74 */
75
76 /* The id of the idle handler used to start the thumbnail thread, or 0 if no
77 idle handler is currently registered. */
78 static guint thumbnail_thread_starter_id = 0;
79
80 /* Our mutex used when accessing data shared between the main thread and the
81 thumbnail thread, i.e. the thumbnail_thread_is_running flag and the
82 thumbnails_to_make list. */
83 static pthread_mutex_t thumbnails_mutex = PTHREAD_MUTEX_INITIALIZER;
84
85 /* A flag to indicate whether a thumbnail thread is running, so we don't
86 start more than one. Lock thumbnails_mutex when accessing this. */
87 static volatile gboolean thumbnail_thread_is_running = FALSE;
88
89 /* The list of NautilusThumbnailInfo structs containing information about the
90 thumbnails we are making. Lock thumbnails_mutex when accessing this. */
91 static volatile GQueue thumbnails_to_make = G_QUEUE_INIT;
92
93 /* Quickly check if uri is in thumbnails_to_make list */
94 static GHashTable *thumbnails_to_make_hash = NULL;
95
96 /* The currently thumbnailed icon. it also exists in the thumbnails_to_make list
97 * to avoid adding it again. Lock thumbnails_mutex when accessing this. */
98 static NautilusThumbnailInfo *currently_thumbnailing = NULL;
99
100 static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL;
101
102 static gboolean
103 get_file_mtime (const char *file_uri, time_t* mtime)
104 {
105 GFile *file;
106 GFileInfo *info;
107 gboolean ret;
108
109 ret = FALSE;
110 *mtime = INVALID_MTIME;
111
112 file = g_file_new_for_uri (file_uri);
113 info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
114 if (info) {
115 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) {
116 *mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
117 ret = TRUE;
118 }
119
120 g_object_unref (info);
121 }
122 g_object_unref (file);
123
124 return ret;
125 }
126
127 static void
128 free_thumbnail_info (NautilusThumbnailInfo *info)
129 {
130 g_free (info->image_uri);
131 g_free (info->mime_type);
132 g_free (info);
133 }
134
135 static GnomeDesktopThumbnailFactory *
136 get_thumbnail_factory (void)
137 {
138 static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL;
139
140 if (thumbnail_factory == NULL) {
141 thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
142 }
143
144 return thumbnail_factory;
145 }
146
147
148 /* This function is added as a very low priority idle function to start the
149 thread to create any needed thumbnails. It is added with a very low priority
150 so that it doesn't delay showing the directory in the icon/list views.
151 We want to show the files in the directory as quickly as possible. */
152 static gboolean
153 thumbnail_thread_starter_cb (gpointer data)
154 {
155 pthread_attr_t thread_attributes;
156 pthread_t thumbnail_thread;
157
158 /* Don't do this in thread, since g_object_ref is not threadsafe */
159 if (thumbnail_factory == NULL) {
160 thumbnail_factory = get_thumbnail_factory ();
161 }
162
163 /* We create the thread in the detached state, as we don't need/want
164 to join with it at any point. */
165 pthread_attr_init (&thread_attributes);
166 pthread_attr_setdetachstate (&thread_attributes,
167 PTHREAD_CREATE_DETACHED);
168 #ifdef _POSIX_THREAD_ATTR_STACKSIZE
169 pthread_attr_setstacksize (&thread_attributes, 128*1024);
170 #endif
171 #ifdef DEBUG_THUMBNAILS
172 g_message ("(Main Thread) Creating thumbnails thread\n");
173 #endif
174 /* We set a flag to indicate the thread is running, so we don't create
175 a new one. We don't need to lock a mutex here, as the thumbnail
176 thread isn't running yet. And we know we won't create the thread
177 twice, as we also check thumbnail_thread_starter_id before
178 scheduling this idle function. */
179 thumbnail_thread_is_running = TRUE;
180 pthread_create (&thumbnail_thread, &thread_attributes,
181 thumbnail_thread_start, NULL);
182
183 thumbnail_thread_starter_id = 0;
184
185 return FALSE;
186 }
187
188 void
189 nautilus_thumbnail_remove_from_queue (const char *file_uri)
190 {
191 GList *node;
192
193 #ifdef DEBUG_THUMBNAILS
194 g_message ("(Remove from queue) Locking mutex\n");
195 #endif
196 pthread_mutex_lock (&thumbnails_mutex);
197
198 /*********************************
199 * MUTEX LOCKED
200 *********************************/
201
202 if (thumbnails_to_make_hash) {
203 node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
204
205 if (node && node->data != currently_thumbnailing) {
206 g_hash_table_remove (thumbnails_to_make_hash, file_uri);
207 free_thumbnail_info (node->data);
208 g_queue_delete_link ((GQueue *)&thumbnails_to_make, node);
209 }
210 }
211
212 /*********************************
213 * MUTEX UNLOCKED
214 *********************************/
215
216 #ifdef DEBUG_THUMBNAILS
217 g_message ("(Remove from queue) Unlocking mutex\n");
218 #endif
219 pthread_mutex_unlock (&thumbnails_mutex);
220 }
221
222 void
223 nautilus_thumbnail_prioritize (const char *file_uri)
224 {
225 GList *node;
226
227 #ifdef DEBUG_THUMBNAILS
228 g_message ("(Prioritize) Locking mutex\n");
229 #endif
230 pthread_mutex_lock (&thumbnails_mutex);
231
232 /*********************************
233 * MUTEX LOCKED
234 *********************************/
235
236 if (thumbnails_to_make_hash) {
237 node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri);
238
239 if (node && node->data != currently_thumbnailing) {
240 g_queue_unlink ((GQueue *)&thumbnails_to_make, node);
241 g_queue_push_head_link ((GQueue *)&thumbnails_to_make, node);
242 }
243 }
244
245 /*********************************
246 * MUTEX UNLOCKED
247 *********************************/
248
249 #ifdef DEBUG_THUMBNAILS
250 g_message ("(Prioritize) Unlocking mutex\n");
251 #endif
252 pthread_mutex_unlock (&thumbnails_mutex);
253 }
254
255
256 /***************************************************************************
257 * Thumbnail Thread Functions.
258 ***************************************************************************/
259
260
261 /* This is a one-shot idle callback called from the main loop to call
262 notify_file_changed() for a thumbnail. It frees the uri afterwards.
263 We do this in an idle callback as I don't think nautilus_file_changed() is
264 thread-safe. */
265 static gboolean
266 thumbnail_thread_notify_file_changed (gpointer image_uri)
267 {
268 NautilusFile *file;
269
270 file = nautilus_file_get_by_uri ((char *) image_uri);
271 #ifdef DEBUG_THUMBNAILS
272 g_message ("(Thumbnail Thread) Notifying file changed file:%p uri: %s\n", file, (char*) image_uri);
273 #endif
274
275 if (file != NULL) {
276 nautilus_file_set_is_thumbnailing (file, FALSE);
277 nautilus_file_invalidate_attributes (file,
278 NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL |
279 NAUTILUS_FILE_ATTRIBUTE_INFO);
280 nautilus_file_unref (file);
281 }
282 g_free (image_uri);
283
284 return FALSE;
285 }
286
287 static GHashTable *
288 get_types_table (void)
289 {
290 static GHashTable *image_mime_types = NULL;
291 GSList *format_list, *l;
292 char **types;
293 int i;
294
295 if (image_mime_types == NULL) {
296 image_mime_types =
297 g_hash_table_new_full (g_str_hash, g_str_equal,
298 g_free, NULL);
299
300 format_list = gdk_pixbuf_get_formats ();
301 for (l = format_list; l; l = l->next) {
302 types = gdk_pixbuf_format_get_mime_types (l->data);
303
304 for (i = 0; types[i] != NULL; i++) {
305 g_hash_table_insert (image_mime_types,
306 types [i],
307 GUINT_TO_POINTER (1));
308 }
309
310 g_free (types);
311 }
312
313 g_slist_free (format_list);
314 }
315
316 return image_mime_types;
317 }
318
319 static gboolean
320 pixbuf_can_load_type (const char *mime_type)
321 {
322 GHashTable *image_mime_types;
323
324 image_mime_types = get_types_table ();
325 if (g_hash_table_lookup (image_mime_types, mime_type)) {
326 return TRUE;
327 }
328
329 return FALSE;
330 }
331
332 gboolean
333 nautilus_can_thumbnail_internally (NautilusFile *file)
334 {
335 char *mime_type;
336 gboolean res;
337
338 mime_type = nautilus_file_get_mime_type (file);
339 res = pixbuf_can_load_type (mime_type);
340 g_free (mime_type);
341 return res;
342 }
343
344 gboolean
345 nautilus_thumbnail_is_mimetype_limited_by_size (const char *mime_type)
346 {
347 return pixbuf_can_load_type (mime_type);
348 }
349
350 gboolean
351 nautilus_can_thumbnail (NautilusFile *file)
352 {
353 GnomeDesktopThumbnailFactory *factory;
354 gboolean res;
355 char *uri;
356 time_t mtime;
357 char *mime_type;
358
359 uri = nautilus_file_get_uri (file);
360 mime_type = nautilus_file_get_mime_type (file);
361 mtime = nautilus_file_get_mtime (file);
362
363 factory = get_thumbnail_factory ();
364 res = gnome_desktop_thumbnail_factory_can_thumbnail (factory,
365 uri,
366 mime_type,
367 mtime);
368 g_free (mime_type);
369 g_free (uri);
370
371 return res;
372 }
373
374 void
375 nautilus_create_thumbnail (NautilusFile *file)
376 {
377 time_t file_mtime = 0;
378 NautilusThumbnailInfo *info;
379 NautilusThumbnailInfo *existing_info;
380 GList *existing, *node;
381
382 nautilus_file_set_is_thumbnailing (file, TRUE);
383
384 info = g_new0 (NautilusThumbnailInfo, 1);
385 info->image_uri = nautilus_file_get_uri (file);
386 info->mime_type = nautilus_file_get_mime_type (file);
387
388 /* Hopefully the NautilusFile will already have the image file mtime,
389 so we can just use that. Otherwise we have to get it ourselves. */
390 if (file->details->got_file_info &&
391 file->details->file_info_is_up_to_date &&
392 file->details->mtime != 0) {
393 file_mtime = file->details->mtime;
394 } else {
395 get_file_mtime (info->image_uri, &file_mtime);
396 }
397
398 info->original_file_mtime = file_mtime;
399
400
401 #ifdef DEBUG_THUMBNAILS
402 g_message ("(Main Thread) Locking mutex\n");
403 #endif
404 pthread_mutex_lock (&thumbnails_mutex);
405
406 /*********************************
407 * MUTEX LOCKED
408 *********************************/
409
410 if (thumbnails_to_make_hash == NULL) {
411 thumbnails_to_make_hash = g_hash_table_new (g_str_hash,
412 g_str_equal);
413 }
414
415 /* Check if it is already in the list of thumbnails to make. */
416 existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
417 if (existing == NULL) {
418 /* Add the thumbnail to the list. */
419 #ifdef DEBUG_THUMBNAILS
420 g_message ("(Main Thread) Adding thumbnail: %s\n",
421 info->image_uri);
422 #endif
423 g_queue_push_tail ((GQueue *)&thumbnails_to_make, info);
424 node = g_queue_peek_tail_link ((GQueue *)&thumbnails_to_make);
425 g_hash_table_insert (thumbnails_to_make_hash,
426 info->image_uri,
427 node);
428 /* If the thumbnail thread isn't running, and we haven't
429 scheduled an idle function to start it up, do that now.
430 We don't want to start it until all the other work is done,
431 so the GUI will be updated as quickly as possible.*/
432 if (thumbnail_thread_is_running == FALSE &&
433 thumbnail_thread_starter_id == 0) {
434 thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_LOW, thumbnail_thread_starter_cb, NULL, NULL);
435 }
436 } else {
437 #ifdef DEBUG_THUMBNAILS
438 g_message ("(Main Thread) Updating non-current mtime: %s\n",
439 info->image_uri);
440 #endif
441 /* The file in the queue might need a new original mtime */
442 existing_info = existing->data;
443 existing_info->original_file_mtime = info->original_file_mtime;
444 free_thumbnail_info (info);
445 }
446
447 /*********************************
448 * MUTEX UNLOCKED
449 *********************************/
450
451 #ifdef DEBUG_THUMBNAILS
452 g_message ("(Main Thread) Unlocking mutex\n");
453 #endif
454 pthread_mutex_unlock (&thumbnails_mutex);
455 }
456
457 /* thumbnail_thread is invoked as a separate thread to to make thumbnails. */
458 static gpointer
459 thumbnail_thread_start (gpointer data)
460 {
461 NautilusThumbnailInfo *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 #ifdef DEBUG_THUMBNAILS
471 g_message ("(Thumbnail Thread) Locking mutex\n");
472 #endif
473 pthread_mutex_lock (&thumbnails_mutex);
474
475 /*********************************
476 * MUTEX LOCKED
477 *********************************/
478
479 /* Pop the last thumbnail we just made off the head of the
480 list and free it. I did this here so we only have to lock
481 the mutex once per thumbnail, rather than once before
482 creating it and once after.
483 Don't pop the thumbnail off the queue if the original file
484 mtime of the request changed. Then we need to redo the thumbnail.
485 */
486 if (currently_thumbnailing &&
487 currently_thumbnailing->original_file_mtime == current_orig_mtime) {
488 g_assert (info == currently_thumbnailing);
489 node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri);
490 g_assert (node != NULL);
491 g_hash_table_remove (thumbnails_to_make_hash, info->image_uri);
492 free_thumbnail_info (info);
493 g_queue_delete_link ((GQueue *)&thumbnails_to_make, node);
494 }
495 currently_thumbnailing = NULL;
496
497 /* If there are no more thumbnails to make, reset the
498 thumbnail_thread_is_running flag, unlock the mutex, and
499 exit the thread. */
500 if (g_queue_is_empty ((GQueue *)&thumbnails_to_make)) {
501 #ifdef DEBUG_THUMBNAILS
502 g_message ("(Thumbnail Thread) Exiting\n");
503 #endif
504 thumbnail_thread_is_running = FALSE;
505 pthread_mutex_unlock (&thumbnails_mutex);
506 pthread_exit (NULL);
507 }
508
509 /* Get the next one to make. We leave it on the list until it
510 is created so the main thread doesn't add it again while we
511 are creating it. */
512 info = g_queue_peek_head ((GQueue *)&thumbnails_to_make);
513 currently_thumbnailing = info;
514 current_orig_mtime = info->original_file_mtime;
515 /*********************************
516 * MUTEX UNLOCKED
517 *********************************/
518
519 #ifdef DEBUG_THUMBNAILS
520 g_message ("(Thumbnail Thread) Unlocking mutex\n");
521 #endif
522 pthread_mutex_unlock (&thumbnails_mutex);
523
524 time (¤t_time);
525
526 /* Don't try to create a thumbnail if the file was modified recently.
527 This prevents constant re-thumbnailing of changing files. */
528 if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS &&
529 current_time >= current_orig_mtime) {
530 #ifdef DEBUG_THUMBNAILS
531 g_message ("(Thumbnail Thread) Skipping: %s\n",
532 info->image_uri);
533 #endif
534 /* Reschedule thumbnailing via a change notification */
535 g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed,
536 g_strdup (info->image_uri));
537 continue;
538 }
539
540 /* Create the thumbnail. */
541 #ifdef DEBUG_THUMBNAILS
542 g_message ("(Thumbnail Thread) Creating thumbnail: %s\n",
543 info->image_uri);
544 #endif
545
546 pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
547 info->image_uri,
548 info->mime_type);
549
550 if (pixbuf) {
551 #ifdef DEBUG_THUMBNAILS
552 g_message ("(Thumbnail Thread) Saving thumbnail: %s\n",
553 info->image_uri);
554 #endif
555 gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory,
556 pixbuf,
557 info->image_uri,
558 current_orig_mtime);
559 g_object_unref (pixbuf);
560 } else {
561 #ifdef DEBUG_THUMBNAILS
562 g_message ("(Thumbnail Thread) Thumbnail failed: %s\n",
563 info->image_uri);
564 #endif
565 gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory,
566 info->image_uri,
567 current_orig_mtime);
568 }
569 /* We need to call nautilus_file_changed(), but I don't think that is
570 thread safe. So add an idle handler and do it from the main loop. */
571 g_idle_add_full (G_PRIORITY_HIGH_IDLE,
572 thumbnail_thread_notify_file_changed,
573 g_strdup (info->image_uri), NULL);
574 }
575 }