diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c index be8d48c9d2a2e933812f8d09213909b3deb84233..b5f5029c85491e35f0c6aea9822ec4b1df7e79b4 100644 --- a/src/nautilus-thumbnails.c +++ b/src/nautilus-thumbnails.c @@ -56,10 +56,38 @@ static void thumbnail_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable); +static void generate_thumbnails (void); + +static void generate_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void generate_thumbnail_skipped (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void generate_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void save_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void save_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + /* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */ typedef struct { + GdkPixbuf *pixbuf; char *image_uri; char *mime_type; time_t original_file_mtime; @@ -73,25 +101,43 @@ typedef struct * idle handler is currently registered. */ static guint thumbnail_thread_starter_id = 0; -/* Our mutex used when accessing data shared between the main thread and the - * thumbnail thread, i.e. the thumbnail_thread_is_running flag and the - * thumbnails_to_make list. */ +/* Our mutex used when accessing data shared between the main thread, + * the thumbnail thread, and the various generate/save threads + * i.e. the thumbnail_thread_is_running flag and the thumbnails_to_make list. */ static GMutex thumbnails_mutex; /* A flag to indicate whether a thumbnail thread is running, so we don't * start more than one. Lock thumbnails_mutex when accessing this. */ -static volatile gboolean thumbnail_thread_is_running = FALSE; +static gboolean thumbnail_thread_is_running = FALSE; /* The list of NautilusThumbnailInfo structs containing information about the - * thumbnails we are making. Lock thumbnails_mutex when accessing this. */ -static volatile GQueue thumbnails_to_make = G_QUEUE_INIT; + * thumbnails we are going to make. Lock thumbnails_mutex when accessing this. */ +static GQueue thumbnails_to_make = G_QUEUE_INIT; /* Quickly check if uri is in thumbnails_to_make list */ static GHashTable *thumbnails_to_make_hash = NULL; -/* The currently thumbnailed icon. it also exists in the thumbnails_to_make list - * to avoid adding it again. Lock thumbnails_mutex when accessing this. */ -static NautilusThumbnailInfo *currently_thumbnailing = NULL; +/* The maximum number of threads that can generate a thumbnail concurrently. */ +static guint max_generate_threads = 1; + +/* The currently generating thumbnails. + * Lock thumbnails_mutex when accessing this. */ +static GHashTable *thumbnails_generating = NULL; + +/* Occurs when a thumbnail is finished generating. + * A new thumbnail is ready to be generated unless thumbnails_to_make is empty + * (when this signal is sent, not when g_cond_wait locks ). */ +static GCond generate_cond; + +/* The currently saving thumbnails. + * Lock thumbnails_mutex when accessing this. */ +static GHashTable *thumbnails_saving = NULL; + +/* Allows generation and loading of thumbnails. + * Once assigned a non NULL value, pointer never changes. + * If it might not have been created yet, lock thumbnails_mutex when accessing this. */ +static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL; + static gboolean get_file_mtime (const char *file_uri, @@ -124,49 +170,70 @@ get_file_mtime (const char *file_uri, static void free_thumbnail_info (NautilusThumbnailInfo *info) { + if (info->pixbuf != NULL) + { + g_object_unref (info->pixbuf); + } g_free (info->image_uri); g_free (info->mime_type); g_free (info); } -static GnomeDesktopThumbnailFactory * -get_thumbnail_factory (void) +/* Initialize thumbnail_factory if it hasn't been created yet. */ +static void +create_thumbnail_factory (void) { - static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL; - if (thumbnail_factory == NULL) { - GdkDisplay *display = gdk_display_get_default (); - GListModel *monitors = gdk_display_get_monitors (display); - gint max_scale = 1; - GnomeDesktopThumbnailSize size; + DEBUG ("(Main Thread) Locking mutex\n"); - for (guint i = 0; i < g_list_model_get_n_items (monitors); i++) - { - g_autoptr (GdkMonitor) monitor = g_list_model_get_item (monitors, i); + g_mutex_lock (&thumbnails_mutex); - max_scale = MAX (max_scale, gdk_monitor_get_scale_factor (monitor)); - } + /********************************* + * MUTEX LOCKED + ******************** *************/ - if (max_scale <= 1) - { - size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE; - } - else if (max_scale <= 2) - { - size = GNOME_DESKTOP_THUMBNAIL_SIZE_XLARGE; - } - else + if (thumbnail_factory == NULL) { - size = GNOME_DESKTOP_THUMBNAIL_SIZE_XXLARGE; + GdkDisplay *display = gdk_display_get_default (); + GListModel *monitors = gdk_display_get_monitors (display); + gint max_scale = 1; + GnomeDesktopThumbnailSize size; + + for (guint i = 0; i < g_list_model_get_n_items (monitors); i++) + { + g_autoptr (GdkMonitor) monitor = g_list_model_get_item (monitors, i); + + max_scale = MAX (max_scale, gdk_monitor_get_scale_factor (monitor)); + } + + if (max_scale <= 1) + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE; + } + else if (max_scale <= 2) + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_XLARGE; + } + else + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_XXLARGE; + } + + thumbnail_factory = gnome_desktop_thumbnail_factory_new (size); + + thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE); } - thumbnail_factory = gnome_desktop_thumbnail_factory_new (size); - } + /********************************* + * MUTEX UNLOCKED + *********************************/ - return thumbnail_factory; -} + DEBUG ("(Main Thread) Unlocking mutex\n"); + g_mutex_unlock (&thumbnails_mutex); + } +} /* This function is added as a very low priority idle function to start the * thread to create any needed thumbnails. It is added with a very low priority @@ -175,7 +242,7 @@ get_thumbnail_factory (void) static gboolean thumbnail_thread_starter_cb (gpointer data) { - GTask *task; + GTask *thumbnail_thread_task; DEBUG ("(Main Thread) Creating thumbnails thread\n"); @@ -185,11 +252,11 @@ thumbnail_thread_starter_cb (gpointer data) * twice, as we also check thumbnail_thread_starter_id before * scheduling this idle function. */ thumbnail_thread_is_running = TRUE; - task = g_task_new (NULL, NULL, NULL, NULL); - g_task_run_in_thread (task, thumbnail_thread_func); + thumbnail_thread_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_run_in_thread (thumbnail_thread_task, thumbnail_thread_func); thumbnail_thread_starter_id = 0; - g_object_unref (task); + g_object_unref (thumbnail_thread_task); return FALSE; } @@ -211,7 +278,7 @@ nautilus_thumbnail_remove_from_queue (const char *file_uri) { node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri); - if (node && node->data != currently_thumbnailing) + if (node) { g_hash_table_remove (thumbnails_to_make_hash, file_uri); free_thumbnail_info (node->data); @@ -245,7 +312,7 @@ nautilus_thumbnail_prioritize (const char *file_uri) { node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri); - if (node && node->data != currently_thumbnailing) + if (node) { g_queue_unlink ((GQueue *) &thumbnails_to_make, node); g_queue_push_head_link ((GQueue *) &thumbnails_to_make, node); @@ -351,7 +418,6 @@ nautilus_thumbnail_is_mimetype_limited_by_size (const char *mime_type) gboolean nautilus_can_thumbnail (NautilusFile *file) { - GnomeDesktopThumbnailFactory *factory; gboolean res; char *uri; time_t mtime; @@ -361,8 +427,18 @@ nautilus_can_thumbnail (NautilusFile *file) mime_type = nautilus_file_get_mime_type (file); mtime = nautilus_file_get_mtime (file); - factory = get_thumbnail_factory (); - res = gnome_desktop_thumbnail_factory_can_thumbnail (factory, + /********************************* + * MUTEX LOCKED + *********************************/ + + create_thumbnail_factory (); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + g_assert (thumbnail_factory != NULL); + res = gnome_desktop_thumbnail_factory_can_thumbnail (thumbnail_factory, uri, mime_type, mtime); @@ -412,13 +488,42 @@ nautilus_create_thumbnail (NautilusFile *file) if (thumbnails_to_make_hash == NULL) { - thumbnails_to_make_hash = g_hash_table_new (g_str_hash, - g_str_equal); + thumbnails_to_make_hash = g_hash_table_new (g_str_hash, g_str_equal); } + if (thumbnails_generating == NULL) + { + thumbnails_generating = g_hash_table_new (g_str_hash, g_str_equal); + } + + if (thumbnails_saving == NULL) + { + thumbnails_saving = g_hash_table_new (g_str_hash, g_str_equal); + } + + /* A thumbnail goes through data structures: + * thumbnails_to_make(_hash) -> thumbnails_generating (possibly skipped) -> thumbnails_saving + * All three data structures are disjoint for the key image_uri. */ + + /* A thumbnail goes through threads: + * Main -> thumbnail_thread_task -> generate_task -> generate_skipped_task -> Main + * -> generate_done_task -> save_task -> save_done_task -> Main */ + /* Check if it is already in the list of thumbnails to make. */ existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri); - if (existing == NULL) + if (existing != NULL) + { + DEBUG ("(Main Thread) Updating non-current mtime: %s\n", + info->image_uri); + + /* The file in the queue might need a new original mtime */ + existing_info = existing->data; + existing_info->original_file_mtime = info->original_file_mtime; + free_thumbnail_info (info); + } + /* Check if it isn't being thumbnailed yet and also hasn't been added to the list. */ + else if (!g_hash_table_contains (thumbnails_generating, info->image_uri) && + !g_hash_table_contains (thumbnails_saving, info->image_uri)) { /* Add the thumbnail to the list. */ DEBUG ("(Main Thread) Adding thumbnail: %s\n", @@ -438,16 +543,6 @@ nautilus_create_thumbnail (NautilusFile *file) thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_LOW, thumbnail_thread_starter_cb, NULL, NULL); } } - else - { - DEBUG ("(Main Thread) Updating non-current mtime: %s\n", - info->image_uri); - - /* The file in the queue might need a new original mtime */ - existing_info = existing->data; - existing_info->original_file_mtime = info->original_file_mtime; - free_thumbnail_info (info); - } /********************************* * MUTEX UNLOCKED @@ -458,140 +553,383 @@ nautilus_create_thumbnail (NautilusFile *file) g_mutex_unlock (&thumbnails_mutex); } -/* thumbnail_thread is invoked as a separate thread to to make thumbnails. */ +/* thumbnail_thread is invoked as a separate thread to to make thumbnails. + * Makes thumbnails concurrently until thumbnails_to_make is empty */ static void thumbnail_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { - GnomeDesktopThumbnailFactory *thumbnail_factory; - NautilusThumbnailInfo *info = NULL; - GdkPixbuf *pixbuf; - time_t current_orig_mtime = 0; - time_t current_time; - GList *node; - GError *error = NULL; + /********************************* + * MUTEX LOCKED + *********************************/ - thumbnail_factory = get_thumbnail_factory (); + create_thumbnail_factory (); - /* We loop until there are no more thumbails to make, at which point - * we exit the thread. */ - for (;;) - { - DEBUG ("(Thumbnail Thread) Locking mutex\n"); + /********************************* + * MUTEX UNLOCKED + *********************************/ - g_mutex_lock (&thumbnails_mutex); + DEBUG ("(Thumbnail Thread) Locking mutex\n"); - /********************************* - * MUTEX LOCKED - *********************************/ + g_mutex_lock (&thumbnails_mutex); - /* Pop the last thumbnail we just made off the head of the - * list and free it. I did this here so we only have to lock - * the mutex once per thumbnail, rather than once before - * creating it and once after. - * Don't pop the thumbnail off the queue if the original file - * mtime of the request changed. Then we need to redo the thumbnail. - */ - if (currently_thumbnailing && - currently_thumbnailing->original_file_mtime == current_orig_mtime) - { - g_assert (info == currently_thumbnailing); - node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri); - g_assert (node != NULL); - g_hash_table_remove (thumbnails_to_make_hash, info->image_uri); - free_thumbnail_info (info); - g_queue_delete_link ((GQueue *) &thumbnails_to_make, node); - } - currently_thumbnailing = NULL; + /********************************* + * MUTEX LOCKED + *********************************/ + + max_generate_threads = g_get_num_processors (); - /* If there are no more thumbnails to make, reset the - * thumbnail_thread_is_running flag, unlock the mutex, and - * exit the thread. */ + /* Waits for generate_cond signal (when a generate finishes), + * then continues generating thumbnails in a loop until thumbnails_to_make is empty. */ + while (TRUE) + { + /* Exit thread if thumbnails_to_make is empty. */ if (g_queue_is_empty ((GQueue *) &thumbnails_to_make)) { - DEBUG ("(Thumbnail Thread) Exiting\n"); + DEBUG ("(Thumbnail Thread) Exiting from while loop.\n"); thumbnail_thread_is_running = FALSE; g_mutex_unlock (&thumbnails_mutex); return; } - /* Get the next one to make. We leave it on the list until it - * is created so the main thread doesn't add it again while we - * are creating it. */ - info = g_queue_peek_head ((GQueue *) &thumbnails_to_make); - currently_thumbnailing = info; - current_orig_mtime = info->original_file_mtime; + /* If thumbnails_to_make isn't empty then + * at least one generate_thumbnail thread will run. + * Which implies that at least one generate_cond will signal, + * meaning g_cond_wait won't be waiting indefinitely. */ + generate_thumbnails (); + /********************************* * MUTEX UNLOCKED *********************************/ - DEBUG ("(Thumbnail Thread) Unlocking mutex\n"); + DEBUG ("(Thumbnail Thread) Unlocking mutex with g_cond_wait \n"); - g_mutex_unlock (&thumbnails_mutex); + g_cond_wait (&generate_cond, &thumbnails_mutex); - time (¤t_time); + DEBUG ("(Thumbnail Thread) Locked mutex from generate_cond signal\n"); - /* Don't try to create a thumbnail if the file was modified recently. - * This prevents constant re-thumbnailing of changing files. */ - if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS && - current_time >= current_orig_mtime) - { - DEBUG ("(Thumbnail Thread) Skipping: %s\n", - info->image_uri); + /********************************* + * MUTEX LOCKED + *********************************/ + } +} - /* Reschedule thumbnailing via a change notification */ - g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed, - g_strdup (info->image_uri)); - continue; - } +/* Generates thumbnails concurrently. Always called from locked thumbnails_mutex. + * Generates as much thumbnails as possible, limited by max_generate_threads and length of thumbnails_to_make. */ +static void +generate_thumbnails (void) +{ + guint num_thumbs_to_generate; + NautilusThumbnailInfo *info; + GTask *generate_task; + + num_thumbs_to_generate = MIN ((max_generate_threads - g_hash_table_size (thumbnails_generating)), + (g_queue_get_length ((GQueue *) &thumbnails_to_make))); - /* Create the thumbnail. */ - DEBUG ("(Thumbnail Thread) Creating thumbnail: %s\n", + DEBUG ("(Thumbnail Thread (generate_thumbnails)) num_thumbs_to_generate: %d\n", + num_thumbs_to_generate); + + /* Take thumbnail(s) off thumbnails_to_make and insert onto thumbnails_generating. + * Run generate_thumbnail in GTask for each. */ + for (guint index = 0; index < num_thumbs_to_generate; index++) + { + info = (NautilusThumbnailInfo *) g_queue_pop_head ((GQueue *) &thumbnails_to_make); + g_assert (info != NULL); + g_hash_table_remove (thumbnails_to_make_hash, info->image_uri); + g_hash_table_insert (thumbnails_generating, info->image_uri, info); + + DEBUG ("(Thumbnail Thread (generate_thumbnails)) starting generate_task: %s\n", info->image_uri); - pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, - info->image_uri, - info->mime_type, - NULL, - &error); + generate_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (generate_task, info, NULL); + g_task_run_in_thread (generate_task, generate_thumbnail); + g_object_unref (generate_task); + } + + DEBUG ("(Thumbnail Thread (generate_thumbnails)) Finished starting generate tasks"); - if (pixbuf) + g_assert ((g_hash_table_size (thumbnails_generating) == max_generate_threads) || + ((g_hash_table_size (thumbnails_generating) < max_generate_threads) && + ((g_queue_is_empty ((GQueue *) &thumbnails_to_make)) && + (g_hash_table_size (thumbnails_to_make_hash) == 0)))); +} + +/* Generates a thumbnail in a separate thread. CPU bound. + * Only max_generate_threads threads of this function should be ran concurrently. */ +static void +generate_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + time_t current_orig_mtime = 0; + time_t current_time; + GError *error = NULL; + GTask *generate_skipped_task; + GTask *generate_done_task; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + g_assert (thumbnail_factory != NULL); + + current_orig_mtime = info->original_file_mtime; + time (¤t_time); + + /* Don't try to create a thumbnail if the file was modified recently. + * This prevents constant re-thumbnailing of changing files. */ + if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS && + current_time >= current_orig_mtime) + { + DEBUG ("(Generate Thread) Skipping. Starting generate_skipped_task: %s\n", + info->image_uri); + + /* Handle generate skipped in a separate thread. Free thumbnail info on task end. */ + generate_skipped_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (generate_skipped_task, info, + (GDestroyNotify) free_thumbnail_info); + g_task_run_in_thread (generate_skipped_task, generate_thumbnail_skipped); + g_object_unref (generate_skipped_task); + } + else + { + DEBUG ("(Generate Thread) Starting thumbnail: %s\n", + info->image_uri); + + /* CPU bound. */ + info->pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, + info->image_uri, + info->mime_type, + NULL, + &error); + if (error) { - DEBUG ("(Thumbnail Thread) Saving thumbnail: %s\n", - info->image_uri); - - gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, - pixbuf, - info->image_uri, - current_orig_mtime, - NULL, - &error); - if (error) - { - DEBUG ("(Thumbnail Thread) Saving thumbnail failed: %s (%s)\n", - info->image_uri, error->message); - g_clear_error (&error); - } - g_object_unref (pixbuf); + DEBUG ("(Generate Thread) Thumbnail generate failed: %s (%s)\n", + info->image_uri, error->message); + g_clear_error (&error); } - else + + DEBUG ("(Generate Thread) Finished thumbnail. Starting generate_done_task: %s\n", + info->image_uri); + + /* Handle generate done in a separate thread. */ + generate_done_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (generate_done_task, info, NULL); + g_task_run_in_thread (generate_done_task, generate_thumbnail_done); + g_object_unref (generate_done_task); + } +} + +/* Generating a thumbnail was skipped due to a recent modification time. + * End of process of creating a thumbnail. */ +static void +generate_thumbnail_skipped (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + + DEBUG ("(Generate Skipped) Locking mutex: %s\n", + info->image_uri); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + g_hash_table_remove (thumbnails_generating, info->image_uri); + g_assert (g_hash_table_size (thumbnails_generating) < max_generate_threads); + + DEBUG ("(Generate Skipped) Signal generate_cond: %s\n", + info->image_uri); + + g_cond_signal (&generate_cond); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + g_mutex_unlock (&thumbnails_mutex); + + DEBUG ("(Generate Skipped) Unlocking mutex: %s\n", + info->image_uri); + + DEBUG ("(Generate Skipped) Reschedule thumbnailing: %s\n", + info->image_uri); + + /* Reschedule thumbnailing via a change notification. */ + g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed, + g_strdup (info->image_uri)); +} + +/* Finishes generating a thumbnail and starts saving a thumbnail. */ +static void +generate_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + GTask *save_task; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + + /* Remove from thumbnails_generating and insert into thumbnails_saving. + * Signal to thumbnail_thread_func that a generate thread is finished. */ + + DEBUG ("(Generate Done) Locking mutex: %s\n", + info->image_uri); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + g_hash_table_remove (thumbnails_generating, info->image_uri); + g_assert (g_hash_table_size (thumbnails_generating) < max_generate_threads); + g_hash_table_insert (thumbnails_saving, info->image_uri, info); + + DEBUG ("(Generate Done) Signal generate_cond: %s\n", + info->image_uri); + + g_cond_signal (&generate_cond); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Generate Done) Unlocking mutex: %s\n", + info->image_uri); + + g_mutex_unlock (&thumbnails_mutex); + + DEBUG ("(Generate Done) Starting save_task: %s\n", + info->image_uri); + + /* Save thumbnail. Free info with free_thumbnail_info on task end. */ + save_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (save_task, info, NULL); + g_task_run_in_thread (save_task, save_thumbnail); + g_object_unref (save_task); +} + +/* Saves a thumbnail or a failed thumbnail. I/O bound. */ +static void +save_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + GTask *save_done_task; + GError *error = NULL; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + g_assert (thumbnail_factory != NULL); + + /* pixbuf isn't NULL if generate succeeded. */ + if (info->pixbuf != NULL) + { + DEBUG ("(Save) Saving thumbnail: %s\n", + info->image_uri); + + /* I/O bound. */ + gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, + info->pixbuf, + info->image_uri, + info->original_file_mtime, + NULL, + &error); + if (error) { - DEBUG ("(Thumbnail Thread) Thumbnail failed: %s (%s)\n", + DEBUG ("(Save) Saving thumbnail failed: %s (%s)\n", info->image_uri, error->message); g_clear_error (&error); - - gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, - info->image_uri, - current_orig_mtime, - NULL, NULL); } - /* We need to call nautilus_file_changed(), but I don't think that is - * thread safe. So add an idle handler and do it from the main loop. */ - g_idle_add_full (G_PRIORITY_HIGH_IDLE, - thumbnail_thread_notify_file_changed, - g_strdup (info->image_uri), NULL); } + else + { + DEBUG ("(Save) Thumbnail generate failed: %s\n", + info->image_uri); + + /* I/O bound. */ + gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, + info->image_uri, + info->original_file_mtime, + NULL, NULL); + } + + DEBUG ("(Save) Starting save_done_task: %s\n", + info->image_uri); + + /* Save thumbnail done. Free info with free_thumbnail_info on task end. */ + save_done_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (save_done_task, info, + (GDestroyNotify) free_thumbnail_info); + g_task_run_in_thread (save_done_task, save_thumbnail_done); + g_object_unref (save_done_task); +} + +/* Finishes saving a thumbnail. End of process of creating a thumbnail. */ +static void +save_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + GSource *source; + GMainContext *context; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + + DEBUG ("(Save Done) Locking mutex: %s\n", + info->image_uri); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + g_hash_table_remove (thumbnails_saving, info->image_uri); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Save Done) Unlocking mutex: %s\n", + info->image_uri); + + g_mutex_unlock (&thumbnails_mutex); + + DEBUG ("(Save Done) Calling thumbnail_thread_notify_file_changed with HIGH_IDLE from main loop: %s\n", + info->image_uri); + + /* We need to call nautilus_file_changed(), but I don't think that is + * thread safe. So add an idle handler and do it from the main loop. */ + source = g_idle_source_new (); + context = g_main_context_default (); + g_source_set_callback (source, thumbnail_thread_notify_file_changed, g_strdup (info->image_uri), NULL); + g_source_set_priority (source, G_PRIORITY_HIGH_IDLE); + g_source_attach (source, context); + g_source_unref (source); }