/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2009-2011 Jannis Pohlmann * Copyright (c) 2012 Nick Schermer * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "thunar/thunar-marshal.h" #include "thunar/thunar-preferences.h" #include "thunar/thunar-private.h" #include "thunar/thunar-thumbnailer-proxy.h" #include "thunar/thunar-thumbnailer.h" #include /** * WARNING: The source code in this file may do harm to animals. Dead kittens * are to be expected. * * * ThunarThumbnailer is a class for requesting thumbnails from org.xfce.tumbler.* * D-Bus services. This header contains an in-depth description of its design to make * it easier to understand why things are the way they are. * * Please note that all D-Bus calls are performed asynchronously. * * The proxy and the supported file types are intialized asynchronously, but * this is nearly completely transparent to the calling code because all requests * that arrive beforehand will be cached and sent out as soon as the initialization * is completed. * * When a request call is sent out, an internal request ID is created and tied * to the dbus call. While the call is running, an additional reference is held * to the thumbnailer so it cannot be finalized. * * The D-Bus reply handler then checks if there was an delivery error or * not. If the request method was sent successfully, the handle returned by the * D-Bus thumbnailer is associated bidirectionally with the internal request ID via * the request and handle values in the job structure. * * * Ready / Error * ============= * * The Ready and Error signal handlers work exactly like Started except that * the Ready idle function sets the thumb state of the corresponding * ThunarFile objects to _READY and the Error signal sets the state to _NONE. * * * Finished * ======== * * The Finished signal handler looks up the internal request ID based on * the D-Bus thumbnailer handle. It then drops all corresponding information * from handle_request_mapping and request_handle_mapping. */ typedef enum { THUNAR_THUMBNAILER_IDLE_ERROR, THUNAR_THUMBNAILER_IDLE_READY, } ThunarThumbnailerIdleType; typedef enum { THUNAR_THUMBNAILER_PROXY_WAITING = 0, THUNAR_THUMBNAILER_PROXY_AVAILABLE, THUNAR_THUMBNAILER_PROXY_FAILED } ThunarThumbnailerProxyState; enum { DBUS_THUMBNAIL_ERROR_UNSUPPORTED, DBUS_THUMBNAIL_ERROR_CONNECTION_ERROR, DBUS_THUMBNAIL_ERROR_INVALID_FORMAT, DBUS_THUMBNAIL_ERROR_IS_THUMBNAIL, DBUS_THUMBNAIL_ERROR_SAVE_FAILED, DBUS_THUMBNAIL_ERROR_UNSUPPORTED_FLAVOR, }; typedef struct _ThunarThumbnailerJob ThunarThumbnailerJob; /* Signal identifiers */ enum { REQUEST_FINISHED, LAST_SIGNAL, }; /* Property identifiers */ enum { PROP_0, PROP_THUMBNAIL_SIZE, PROP_THUMBNAIL_MAX_FILE_SIZE, }; static void thunar_thumbnailer_finalize (GObject *object); static gboolean thunar_thumbnailer_init_thumbnailer_proxy (ThunarThumbnailer *thumbnailer); static gboolean thunar_thumbnailer_file_is_supported (ThunarThumbnailer *thumbnailer, ThunarFile *file); static void thunar_thumbnailer_thumbnailer_finished (GDBusProxy *proxy, guint handle, ThunarThumbnailer *thumbnailer); static void thunar_thumbnailer_thumbnailer_error (GDBusProxy *proxy, guint handle, const gchar **uris, gint code, const gchar *message, ThunarThumbnailer *thumbnailer); static void thunar_thumbnailer_thumbnailer_ready (GDBusProxy *proxy, guint32 handle, const gchar **uris, ThunarThumbnailer *thumbnailer); static void thunar_thumbnailer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void thunar_thumbnailer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); #define _thumbnailer_lock(thumbnailer) g_mutex_lock (&((thumbnailer)->lock)) #define _thumbnailer_unlock(thumbnailer) g_mutex_unlock (&((thumbnailer)->lock)) struct _ThunarThumbnailerClass { GObjectClass __parent__; }; struct _ThunarThumbnailer { GObject __parent__; /* proxies to communicate with D-Bus services */ ThunarThumbnailerDBus *thumbnailer_proxy; ThunarThumbnailerProxyState proxy_state; ThunarPreferences *preferences; /* running jobs */ GSList *jobs; GMutex lock; /* cached MIME types -> URI schemes for which thumbs can be generated */ GHashTable *supported; /* last ThunarThumbnailer request ID */ guint last_request; /* size to use to store thumbnails */ ThunarThumbnailSize thumbnail_size; /* maximum file size (in bytes) allowed to be thumbnailed */ guint64 thumbnail_max_file_size; /* Agregated thumbnailing requests per size, to be queued on timeout */ ThunarThumbnailerJob *jobs_to_queue[N_THUMBNAIL_SIZES]; /* Id's of the timeout sources per size, used to agrregate requets */ guint jobs_to_queue_source_id[N_THUMBNAIL_SIZES]; }; struct _ThunarThumbnailerJob { ThunarThumbnailer *thumbnailer; /* if this job is cancelled */ guint cancelled : 1; /* data is saved here in case the queueing is delayed */ /* If this is NULL, the request has been sent off. */ GList *files; /* element type: ThunarFile */ /* request number returned by ThunarThumbnailer */ guint request; /* handle returned by the tumbler dbus service */ guint handle; /* used to override the thumbnail size of ThunarThumbnailer */ ThunarThumbnailSize thumbnail_size; }; static guint thumbnailer_signals[LAST_SIGNAL]; G_DEFINE_TYPE (ThunarThumbnailer, thunar_thumbnailer, G_TYPE_OBJECT); static void thunar_thumbnailer_class_init (ThunarThumbnailerClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = thunar_thumbnailer_finalize; gobject_class->get_property = thunar_thumbnailer_get_property; gobject_class->set_property = thunar_thumbnailer_set_property; /** * ThunarThumbnailer:request-finished: * @thumbnailer : a #ThunarThumbnailer. * @request : id of the request that is finished. * * Emitted by @thumbnailer, when a request is finished * by the thumbnail generator **/ thumbnailer_signals[REQUEST_FINISHED] = g_signal_new (I_ ("request-finished"), G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); /** * ThunarThumbnailer:thumbnail-size: * * Size of the thumbnails to load **/ g_object_class_install_property (gobject_class, PROP_THUMBNAIL_SIZE, g_param_spec_enum ("thumbnail-size", "thumbnail-size", "thumbnail-size", THUNAR_TYPE_THUMBNAIL_SIZE, THUNAR_THUMBNAIL_SIZE_NORMAL, EXO_PARAM_READWRITE)); /** * ThunarThumbnailer:thumbnail-max-file-size: * * Maximum file size (in bytes) allowed to be thumbnailed **/ g_object_class_install_property (gobject_class, PROP_THUMBNAIL_MAX_FILE_SIZE, g_param_spec_uint64 ("thumbnail-max-file-size", "thumbnail-max-file-size", "thumbnail-max-file-size", 0, G_MAXUINT64, 0, EXO_PARAM_READWRITE)); } static void thunar_thumbnailer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ThunarThumbnailer *thumbnailer = THUNAR_THUMBNAILER (object); switch (prop_id) { case PROP_THUMBNAIL_SIZE: g_value_set_enum (value, thumbnailer->thumbnail_size); break; case PROP_THUMBNAIL_MAX_FILE_SIZE: g_value_set_uint64 (value, thumbnailer->thumbnail_max_file_size); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void thunar_thumbnailer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ThunarThumbnailer *thumbnailer = THUNAR_THUMBNAILER (object); switch (prop_id) { case PROP_THUMBNAIL_SIZE: thumbnailer->thumbnail_size = g_value_get_enum (value); break; case PROP_THUMBNAIL_MAX_FILE_SIZE: thumbnailer->thumbnail_max_file_size = g_value_get_uint64 (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void thunar_thumbnailer_free_job (ThunarThumbnailerJob *job) { if (job->files) g_list_free_full (job->files, g_object_unref); if (job->thumbnailer && job->thumbnailer->thumbnailer_proxy && job->handle) thunar_thumbnailer_dbus_call_dequeue (job->thumbnailer->thumbnailer_proxy, job->handle, NULL, NULL, NULL); g_slice_free (ThunarThumbnailerJob, job); } static void thunar_thumbnailer_queue_async_reply (GObject *proxy, GAsyncResult *res, gpointer user_data) { ThunarThumbnailerJob *job = user_data; ThunarThumbnailer *thumbnailer; GError *error = NULL; guint handle; _thunar_return_if_fail (THUNAR_IS_THUMBNAILER_DBUS (proxy)); _thunar_return_if_fail (job != NULL); thumbnailer = THUNAR_THUMBNAILER (job->thumbnailer); _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); _thumbnailer_lock (thumbnailer); thunar_thumbnailer_dbus_call_queue_finish (THUNAR_THUMBNAILER_DBUS (proxy), &handle, res, &error); if (job->cancelled) { /* job is cancelled while there was no handle jet, so dequeue it now */ thunar_thumbnailer_dbus_call_dequeue (THUNAR_THUMBNAILER_DBUS (proxy), handle, NULL, NULL, NULL); /* cleanup */ thumbnailer->jobs = g_slist_remove (thumbnailer->jobs, job); thunar_thumbnailer_free_job (job); } else if (error == NULL) { if (handle == 0) { g_warning ("ThunarThumbnailer: got 0 handle (Queue)"); } else { /* store the handle returned by tumbler */ job->handle = handle; } } else { g_warning ("ThunarThumbnailer: Queue failed: %s", error->message); } _thumbnailer_unlock (thumbnailer); g_clear_error (&error); /* remove the additional reference we held during the call */ g_object_unref (thumbnailer); } /* NOTE: assumes that the lock is held by the caller */ static gboolean thunar_thumbnailer_begin_job (ThunarThumbnailer *thumbnailer, ThunarThumbnailerJob *job) { gboolean success = FALSE; const gchar **mime_hints; gchar **uris; GList *lp; GList *supported_files = NULL; guint n; guint n_items = 0; const gchar *thumbnail_path; ThunarThumbnailSize thumbnail_size; if (thumbnailer->proxy_state == THUNAR_THUMBNAILER_PROXY_WAITING) { /* all pending jobs will be queued automatically once the proxy is available */ return TRUE; } else if (thumbnailer->proxy_state == THUNAR_THUMBNAILER_PROXY_FAILED) { /* give another chance to the proxy */ g_warning ("Thumbnailer Proxy Failed ... starting attempt to re-initialize"); if (thumbnailer->thumbnailer_proxy != NULL) { /* disconnect from the thumbnailer proxy */ g_signal_handlers_disconnect_by_data (thumbnailer->thumbnailer_proxy, thumbnailer); thumbnailer->proxy_state = THUNAR_THUMBNAILER_PROXY_WAITING; g_object_unref (thumbnailer->thumbnailer_proxy); thumbnailer->thumbnailer_proxy = NULL; } /* Only try once a second, in order to prevent flooding in case the proxy always fails */ g_timeout_add_seconds (1, G_SOURCE_FUNC (thunar_thumbnailer_init_thumbnailer_proxy), thumbnailer); return FALSE; } thumbnail_size = job->thumbnail_size == THUNAR_THUMBNAIL_SIZE_DEFAULT ? thumbnailer->thumbnail_size : job->thumbnail_size; /* collect all supported files from the list that are neither in the * about to be queued (wait queue), nor already queued, nor already * processed (and awaiting to be refreshed) */ for (lp = job->files; lp != NULL; lp = lp->next) { /* the icon factory only loads icons for regular files, symlinks and folders */ if (!thunar_file_is_regular (lp->data) && !thunar_file_is_directory (lp->data) && !thunar_file_is_symlink (lp->data)) { thunar_file_update_thumbnail (lp->data, THUNAR_FILE_THUMB_STATE_NONE, thumbnail_size); continue; } if (thunar_thumbnailer_file_is_supported (thumbnailer, lp->data)) { guint max_size = thumbnailer->thumbnail_max_file_size; /* skip large files */ if (max_size > 0 && thunar_file_get_size (lp->data) > max_size) continue; supported_files = g_list_prepend (supported_files, g_object_ref (lp->data)); n_items++; } else { /* still a regular file, but the type is now known to tumbler but * maybe the application created a thumbnail */ thumbnail_path = thunar_file_get_thumbnail_path (lp->data, thumbnail_size); /* test if a thumbnail can be found */ if (thumbnail_path != NULL && g_file_test (thumbnail_path, G_FILE_TEST_EXISTS)) thunar_file_update_thumbnail (lp->data, THUNAR_FILE_THUMB_STATE_READY, thumbnail_size); else thunar_file_update_thumbnail (lp->data, THUNAR_FILE_THUMB_STATE_NONE, thumbnail_size); } } /* check if we have any supported files */ if (n_items > 0) { /* allocate arrays for URIs and mime hints */ uris = g_new0 (gchar *, n_items + 1); mime_hints = g_new0 (const gchar *, n_items + 1); /* fill URI and MIME hint arrays with items from the wait queue */ for (lp = supported_files, n = 0; lp != NULL; lp = lp->next, ++n) { /* save URI and MIME hint in the arrays */ uris[n] = thunar_file_dup_uri (lp->data); mime_hints[n] = thunar_file_get_content_type (lp->data); } /* NULL-terminate both arrays */ uris[n] = NULL; mime_hints[n] = NULL; /* increase the reference count while the dbus call is running */ g_object_ref (thumbnailer); /* queue the request - asynchronously, of course */ thunar_thumbnailer_dbus_call_queue (thumbnailer->thumbnailer_proxy, (const gchar *const *) uris, (const gchar *const *) mime_hints, thunar_thumbnail_size_get_nick (thumbnail_size), "foreground", 0, NULL, thunar_thumbnailer_queue_async_reply, job); /* free mime hints array */ g_free (mime_hints); g_strfreev (uris); /* free the list of files passed in */ g_list_free_full (job->files, g_object_unref); job->files = NULL; /* we need to reload the files after the job is finished */ job->files = supported_files; /* we assume success if we've come so far */ success = TRUE; } return success; } static void thunar_thumbnailer_init (ThunarThumbnailer *thumbnailer) { g_mutex_init (&thumbnailer->lock); _thumbnailer_lock (thumbnailer); /* initialize the proxies */ thunar_thumbnailer_init_thumbnailer_proxy (thumbnailer); _thumbnailer_unlock (thumbnailer); /* grab a reference on the preferences */ thumbnailer->preferences = thunar_preferences_get (); for (gint i = 0; i < N_THUMBNAIL_SIZES; i++) { thumbnailer->jobs_to_queue[i] = NULL; thumbnailer->jobs_to_queue_source_id[i] = 0; } g_object_bind_property (G_OBJECT (thumbnailer->preferences), "misc-thumbnail-max-file-size", G_OBJECT (thumbnailer), "thumbnail-max-file-size", G_BINDING_SYNC_CREATE); } static void thunar_thumbnailer_finalize (GObject *object) { ThunarThumbnailer *thumbnailer = THUNAR_THUMBNAILER (object); /* acquire the thumbnailer lock */ _thumbnailer_lock (thumbnailer); for (gint i = 0; i < N_THUMBNAIL_SIZES; i++) { if (thumbnailer->jobs_to_queue_source_id[i] != 0) { g_source_remove (thumbnailer->jobs_to_queue_source_id[i]); thunar_thumbnailer_free_job (thumbnailer->jobs_to_queue[i]); thumbnailer->jobs_to_queue[i] = NULL; thumbnailer->jobs_to_queue_source_id[i] = 0; } } if (thumbnailer->thumbnailer_proxy != NULL) { /* disconnect from the thumbnailer proxy */ g_signal_handlers_disconnect_by_data (thumbnailer->thumbnailer_proxy, thumbnailer); } /* remove all jobs */ g_slist_free_full (thumbnailer->jobs, (GDestroyNotify) thunar_thumbnailer_free_job); /* release the thumbnailer proxy */ if (thumbnailer->thumbnailer_proxy != NULL) g_object_unref (thumbnailer->thumbnailer_proxy); /* free the cached URI schemes and MIME types table */ if (thumbnailer->supported != NULL) g_hash_table_unref (thumbnailer->supported); /* release the thumbnailer lock */ _thumbnailer_unlock (thumbnailer); /* release the mutex */ g_mutex_clear (&thumbnailer->lock); /* release our reference on the preferences */ g_object_unref (thumbnailer->preferences); (*G_OBJECT_CLASS (thunar_thumbnailer_parent_class)->finalize) (object); } static gint thunar_thumbnailer_file_schemes_compare (gconstpointer a, gconstpointer b) { const gchar *scheme_a = *(gconstpointer *) a; const gchar *scheme_b = *(gconstpointer *) b; /* sort file before other schemes */ if (strcmp (scheme_a, "file") == 0) return -1; if (strcmp (scheme_b, "file") == 0) return 1; /* sort trash before other schemes */ if (strcmp (scheme_a, "trash") == 0) return -1; if (strcmp (scheme_b, "trash") == 0) return 1; /* other order is just fine */ return 0; } static void thunar_thumbnailer_file_sort_schemes (gpointer mime_type, gpointer schemes, gpointer user_data) { g_ptr_array_sort (schemes, thunar_thumbnailer_file_schemes_compare); } static void thunar_thumbnailer_received_supported_types (ThunarThumbnailerDBus *proxy, GAsyncResult *result, ThunarThumbnailer *thumbnailer) { guint n; gchar **schemes = NULL; gchar **types = NULL; GPtrArray *schemes_array; GSList *lp = NULL; GError *error = NULL; _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); _thunar_return_if_fail (THUNAR_IS_THUMBNAILER_DBUS (proxy)); _thumbnailer_lock (thumbnailer); if (!thunar_thumbnailer_dbus_call_get_supported_finish (proxy, &schemes, &types, result, &error)) { g_slist_free_full (thumbnailer->jobs, (GDestroyNotify) thunar_thumbnailer_free_job); thumbnailer->jobs = NULL; g_warning ("ThunarThumbnailer: Failed to retrieve supported types: %s", error->message); g_clear_error (&error); thumbnailer->proxy_state = THUNAR_THUMBNAILER_PROXY_FAILED; if (thumbnailer->thumbnailer_proxy != NULL) { /* disconnect from the thumbnailer proxy */ g_signal_handlers_disconnect_by_data (thumbnailer->thumbnailer_proxy, thumbnailer); g_object_unref (thumbnailer->thumbnailer_proxy); thumbnailer->thumbnailer_proxy = NULL; } _thumbnailer_unlock (thumbnailer); return; } if (G_LIKELY (schemes != NULL && types != NULL)) { /* combine content types and uri schemes */ for (n = 0; types[n] != NULL; ++n) { schemes_array = g_hash_table_lookup (thumbnailer->supported, types[n]); if (G_UNLIKELY (schemes_array == NULL)) { /* create an array for the uri schemes this content type supports */ schemes_array = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (schemes_array, schemes[n]); g_hash_table_insert (thumbnailer->supported, types[n], schemes_array); } else { /* add the uri scheme to the array of the content type */ g_ptr_array_add (schemes_array, schemes[n]); /* cleanup */ g_free (types[n]); } } /* remove arrays, we stole the values */ g_free (types); g_free (schemes); /* sort array to optimize for local files */ g_hash_table_foreach (thumbnailer->supported, thunar_thumbnailer_file_sort_schemes, NULL); } thumbnailer->proxy_state = THUNAR_THUMBNAILER_PROXY_AVAILABLE; /* now start delayed jobs */ for (lp = thumbnailer->jobs; lp; lp = lp->next) { if (!thunar_thumbnailer_begin_job (thumbnailer, lp->data)) { thunar_thumbnailer_free_job (lp->data); lp->data = NULL; } } thumbnailer->jobs = g_slist_remove_all (thumbnailer->jobs, NULL); g_clear_error (&error); _thumbnailer_unlock (thumbnailer); } static void thunar_thumbnailer_proxy_created (GObject *object, GAsyncResult *result, gpointer userdata) { ThunarThumbnailer *thumbnailer = THUNAR_THUMBNAILER (userdata); GError *error = NULL; thumbnailer->thumbnailer_proxy = thunar_thumbnailer_dbus_proxy_new_finish (result, &error); if (thumbnailer->thumbnailer_proxy == NULL) { _thumbnailer_lock (thumbnailer); thumbnailer->proxy_state = THUNAR_THUMBNAILER_PROXY_FAILED; g_warning ("ThunarThumbnailer: failed to create proxy: %s", error->message); g_clear_error (&error); g_slist_free_full (thumbnailer->jobs, (GDestroyNotify) thunar_thumbnailer_free_job); thumbnailer->jobs = NULL; _thumbnailer_unlock (thumbnailer); return; } /* More detailed information about the Thumbnailer sigbnals can be found here */ /* https://wiki.gnome.org/Attic/DraftThumbnailerSpec */ /* 'error' is signaled when thumnailing failed for some uris */ /* Note that 'finished' will still be signaled after the whole thumbnailing request finished */ g_signal_connect (thumbnailer->thumbnailer_proxy, "error", G_CALLBACK (thunar_thumbnailer_thumbnailer_error), thumbnailer); /* 'finished' is signaled after a thumbnailing request finished .. no matter if sucessfull or not */ g_signal_connect (thumbnailer->thumbnailer_proxy, "finished", G_CALLBACK (thunar_thumbnailer_thumbnailer_finished), thumbnailer); /* 'ready' is signaled when thumnailing was sucessfull for some uris */ /* Note that 'finished' will still be signaled after the whole thumbnailing request finished */ g_signal_connect (thumbnailer->thumbnailer_proxy, "ready", G_CALLBACK (thunar_thumbnailer_thumbnailer_ready), thumbnailer); /* begin retrieving supported file types */ _thumbnailer_lock (thumbnailer); /* prepare table */ g_clear_pointer (&thumbnailer->supported, g_hash_table_unref); thumbnailer->supported = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); /* request the supported types from the thumbnailer D-Bus service. */ thunar_thumbnailer_dbus_call_get_supported (thumbnailer->thumbnailer_proxy, NULL, (GAsyncReadyCallback) thunar_thumbnailer_received_supported_types, thumbnailer); _thumbnailer_unlock (thumbnailer); } static gboolean thunar_thumbnailer_init_thumbnailer_proxy (ThunarThumbnailer *thumbnailer) { thumbnailer->thumbnailer_proxy = NULL; thumbnailer->proxy_state = THUNAR_THUMBNAILER_PROXY_WAITING; /* create the thumbnailer proxy */ thunar_thumbnailer_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, "org.freedesktop.thumbnails.Thumbnailer1", "/org/freedesktop/thumbnails/Thumbnailer1", NULL, (GAsyncReadyCallback) thunar_thumbnailer_proxy_created, thumbnailer); return G_SOURCE_REMOVE; } /* NOTE: assumes the lock is being held by the caller*/ static gboolean thunar_thumbnailer_file_is_supported (ThunarThumbnailer *thumbnailer, ThunarFile *file) { gchar *content_type; gboolean supported = FALSE; guint n; GPtrArray *schemes_array; const gchar *scheme; _thunar_return_val_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer), FALSE); _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); _thunar_return_val_if_fail (THUNAR_IS_THUMBNAILER_DBUS (thumbnailer->thumbnailer_proxy), FALSE); _thunar_return_val_if_fail (thumbnailer->supported != NULL, FALSE); /* determine the content type of the passed file */ if (thunar_file_is_symlink (file)) { GFile *link_target; link_target = thunar_g_file_resolve_symlink (thunar_file_get_file (file)); if (link_target == NULL) return FALSE; content_type = thunar_g_file_get_content_type (link_target); g_object_unref (link_target); } else content_type = g_strdup (thunar_file_get_content_type (file)); /* abort if the content type is unknown */ if (content_type == NULL) return FALSE; /* lookup the content type, no difficult parent type matching here */ schemes_array = g_hash_table_lookup (thumbnailer->supported, content_type); if (schemes_array != NULL) { /* go through all the URI schemes this type supports */ for (n = 0; !supported && n < schemes_array->len; ++n) { /* check if the file has the current URI scheme */ scheme = g_ptr_array_index (schemes_array, n); if (thunar_file_has_uri_scheme (file, scheme)) supported = TRUE; } } g_free (content_type); return supported; } static void thunar_thumbnailer_thumbnailer_error (GDBusProxy *proxy, guint handle, const gchar **uris, gint code, const gchar *message, ThunarThumbnailer *thumbnailer) { GFile *gfile; ThunarFile *file; ThunarThumbnailerJob *job; _thunar_return_if_fail (G_IS_DBUS_PROXY (proxy)); _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); _thumbnailer_lock (thumbnailer); for (GSList *lp = thumbnailer->jobs; lp != NULL; lp = lp->next) { job = lp->data; if (job->handle == handle) { for (const gchar **uri = uris; *uri != NULL; ++uri) { /* look up the corresponding ThunarFile from the cache */ gfile = g_file_new_for_uri (*uri); file = thunar_file_cache_lookup (gfile); g_object_unref (gfile); /* check if we have a file for this URI in the cache */ if (file != NULL) { if (code == DBUS_THUMBNAIL_ERROR_IS_THUMBNAIL) { /* the file itself is a thumbnail, so use it as a thumbnail */ thunar_file_set_is_thumbnail (file, TRUE); thunar_file_update_thumbnail (file, THUNAR_FILE_THUMB_STATE_READY, job->thumbnail_size); } else { /* tell everybody we're done here */ thunar_file_update_thumbnail (file, THUNAR_FILE_THUMB_STATE_NONE, job->thumbnail_size); g_debug ("Failed to generate thumbnail for '%s': Error Code: %i - Error: %s\n", thunar_file_get_basename (file), code, message); } g_object_unref (file); } } break; } } _thumbnailer_unlock (thumbnailer); } static void thunar_thumbnailer_thumbnailer_ready (GDBusProxy *proxy, guint32 handle, const gchar **uris, ThunarThumbnailer *thumbnailer) { GFile *gfile; ThunarFile *file; ThunarThumbnailerJob *job; _thunar_return_if_fail (G_IS_DBUS_PROXY (proxy)); _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); _thumbnailer_lock (thumbnailer); for (GSList *lp = thumbnailer->jobs; lp != NULL; lp = lp->next) { job = lp->data; if (job->handle == handle) { for (const gchar **uri = uris; *uri != NULL; ++uri) { /* look up the corresponding ThunarFile from the cache */ gfile = g_file_new_for_uri (*uri); file = thunar_file_cache_lookup (gfile); g_object_unref (gfile); if (file != NULL) { thunar_file_update_thumbnail (file, THUNAR_FILE_THUMB_STATE_READY, job->thumbnail_size); g_object_unref (file); } } } } _thumbnailer_unlock (thumbnailer); } static void thunar_thumbnailer_thumbnailer_finished (GDBusProxy *proxy, guint handle, ThunarThumbnailer *thumbnailer) { ThunarThumbnailerJob *job; GSList *lp; _thunar_return_if_fail (G_IS_DBUS_PROXY (proxy)); _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); if (handle == 0) { g_warning ("ThunarThumbnailer: got 0 handle (Finished)"); return; } _thumbnailer_lock (thumbnailer); for (lp = thumbnailer->jobs; lp != NULL; lp = lp->next) { job = lp->data; if (job->handle == handle) { /* this job is finished, forget about the handle */ job->handle = 0; /* tell everybody we're done here */ g_signal_emit (G_OBJECT (thumbnailer), thumbnailer_signals[REQUEST_FINISHED], 0, job->request); /* remove job from the list */ thumbnailer->jobs = g_slist_delete_link (thumbnailer->jobs, lp); thunar_thumbnailer_free_job (job); break; } } _thumbnailer_unlock (thumbnailer); } /** * thunar_thumbnailer_get: * * Return a shared new #ThunarThumbnailer object, which can be used to * generate and store thumbnails for files. * * The caller is responsible to free the returned * object using g_object_unref() when no longer needed. * * Return value: a #ThunarThumbnailer. **/ ThunarThumbnailer * thunar_thumbnailer_get (void) { static ThunarThumbnailer *thumbnailer = NULL; if (G_UNLIKELY (thumbnailer == NULL)) { thumbnailer = g_object_new (THUNAR_TYPE_THUMBNAILER, NULL); g_object_add_weak_pointer (G_OBJECT (thumbnailer), (gpointer) &thumbnailer); } return g_object_ref_sink (thumbnailer); } static gboolean thunar_thumbnailer_queue_job_after_timeout (gpointer user_data) { ThunarThumbnailerJob *job = user_data; ThunarThumbnailer *thumbnailer = THUNAR_THUMBNAILER (job->thumbnailer); ThunarThumbnailSize thumbnail_size = job->thumbnail_size; gboolean success = FALSE; _thunar_return_val_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer), G_SOURCE_REMOVE); /* acquire the thumbnailer lock */ _thumbnailer_lock (thumbnailer); success = thunar_thumbnailer_begin_job (thumbnailer, job); if (success) { thumbnailer->jobs = g_slist_prepend (thumbnailer->jobs, job); } else { for (GList *lp = job->files; lp != NULL; lp = lp->next) { /* This job failed .. inform all files which are waiting for the result */ thunar_file_update_thumbnail (lp->data, THUNAR_FILE_THUMB_STATE_NONE, job->thumbnail_size); } /* tell everybody we're done for that job */ g_signal_emit (G_OBJECT (thumbnailer), thumbnailer_signals[REQUEST_FINISHED], 0, job->request); /* and drop it */ thunar_thumbnailer_free_job (job); } thumbnailer->jobs_to_queue[thumbnail_size] = NULL; thumbnailer->jobs_to_queue_source_id[thumbnail_size] = 0; /* release the lock */ _thumbnailer_unlock (thumbnailer); return G_SOURCE_REMOVE; } void thunar_thumbnailer_queue_file (ThunarThumbnailer *thumbnailer, ThunarFile *file, guint *request, ThunarThumbnailSize size) { gint request_no; _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); _thunar_return_if_fail (THUNAR_IS_FILE (file)); if (thumbnailer->jobs_to_queue_source_id[size] == 0) { ThunarThumbnailerJob *job; /* allocate a job */ job = g_slice_new0 (ThunarThumbnailerJob); job->thumbnailer = thumbnailer; job->files = g_list_append (job->files, g_object_ref (file)); job->thumbnail_size = size; /* queue a thumbnail request for the URIs from the wait queue */ /* compute the next request ID, making sure it's never 0 */ request_no = thumbnailer->last_request + 1; request_no = MAX (request_no, 1); /* remember the ID for the next request */ thumbnailer->last_request = request_no; /* save the request number */ job->request = request_no; thumbnailer->jobs_to_queue[size] = job; thumbnailer->jobs_to_queue_source_id[size] = g_timeout_add (100, thunar_thumbnailer_queue_job_after_timeout, job); } else { if (thumbnailer->jobs_to_queue[size]->files == NULL) g_warn_if_reached (); thumbnailer->jobs_to_queue[size]->files = g_list_prepend (thumbnailer->jobs_to_queue[size]->files, g_object_ref (file)); } *request = thumbnailer->jobs_to_queue[size]->request; return; } void thunar_thumbnailer_dequeue (ThunarThumbnailer *thumbnailer, guint request) { ThunarThumbnailerJob *job; GSList *lp; _thunar_return_if_fail (THUNAR_IS_THUMBNAILER (thumbnailer)); /* acquire the thumbnailer lock */ _thumbnailer_lock (thumbnailer); for (lp = thumbnailer->jobs; lp != NULL; lp = lp->next) { job = lp->data; /* find the request in the list */ if (job->request == request) { /* this job is cancelled */ job->cancelled = TRUE; if (job->handle != 0) { /* remove job */ thumbnailer->jobs = g_slist_delete_link (thumbnailer->jobs, lp); thunar_thumbnailer_free_job (job); } break; } } /* release the thumbnailer lock */ _thumbnailer_unlock (thumbnailer); }