nautilus-3.6.3/libnautilus-private/nautilus-thumbnails.c

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 (&current_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 }