/* * mate-thumbnail.c: Utilities for handling thumbnails * * Copyright (C) 2002 Red Hat, Inc. * Copyright (C) 2010 Carlos Garcia Campos * * This file is part of the Mate Library. * * The Mate Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The Mate Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with the Mate Library; see the file COPYING.LIB. If not, * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Alexander Larsson */ #include #include #include #include #include #include #define MATE_DESKTOP_USE_UNSTABLE_API #include "mate-desktop-thumbnail.h" static void thumbnailers_directory_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, MateDesktopThumbnailFactory *factory); struct _MateDesktopThumbnailFactoryPrivate { MateDesktopThumbnailSize size; GMutex lock; GList *thumbnailers; GHashTable *mime_types_map; GList *monitors; GSettings *settings; gboolean loaded : 1; gboolean disabled : 1; gchar **disabled_types; }; static const char *appname = "mate-thumbnail-factory"; G_DEFINE_TYPE_WITH_PRIVATE (MateDesktopThumbnailFactory, mate_desktop_thumbnail_factory, G_TYPE_OBJECT) #define parent_class mate_desktop_thumbnail_factory_parent_class #define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry" #define THUMBNAILER_EXTENSION ".thumbnailer" typedef struct { volatile gint ref_count; gchar *path; gchar *try_exec; gchar *command; gchar **mime_types; } Thumbnailer; static Thumbnailer * thumbnailer_ref (Thumbnailer *thumb) { g_return_val_if_fail (thumb != NULL, NULL); g_return_val_if_fail (thumb->ref_count > 0, NULL); g_atomic_int_inc (&thumb->ref_count); return thumb; } static void thumbnailer_unref (Thumbnailer *thumb) { g_return_if_fail (thumb != NULL); g_return_if_fail (thumb->ref_count > 0); if (g_atomic_int_dec_and_test (&thumb->ref_count)) { g_free (thumb->path); g_free (thumb->try_exec); g_free (thumb->command); g_strfreev (thumb->mime_types); g_slice_free (Thumbnailer, thumb); } } static Thumbnailer * thumbnailer_load (Thumbnailer *thumb) { GKeyFile *key_file; GError *error = NULL; key_file = g_key_file_new (); if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error)) { g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message); g_error_free (error); thumbnailer_unref (thumb); g_key_file_free (key_file); return NULL; } if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP)) { g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP); thumbnailer_unref (thumb); g_key_file_free (key_file); return NULL; } thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL); if (!thumb->command) { g_warning ("Invalid thumbnailer: missing Exec key\n"); thumbnailer_unref (thumb); g_key_file_free (key_file); return NULL; } thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL); if (!thumb->mime_types) { g_warning ("Invalid thumbnailer: missing MimeType key\n"); thumbnailer_unref (thumb); g_key_file_free (key_file); return NULL; } thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL); g_key_file_free (key_file); return thumb; } static Thumbnailer * thumbnailer_reload (Thumbnailer *thumb) { g_return_val_if_fail (thumb != NULL, NULL); g_free (thumb->command); thumb->command = NULL; g_strfreev (thumb->mime_types); thumb->mime_types = NULL; g_free (thumb->try_exec); thumb->try_exec = NULL; return thumbnailer_load (thumb); } static Thumbnailer * thumbnailer_new (const gchar *path) { Thumbnailer *thumb; thumb = g_slice_new0 (Thumbnailer); thumb->ref_count = 1; thumb->path = g_strdup (path); return thumbnailer_load (thumb); } static gboolean thumbnailer_try_exec (Thumbnailer *thumb) { gchar *path; gboolean retval; if (G_UNLIKELY (!thumb)) return FALSE; /* TryExec is optional, but Exec isn't, so we assume * the thumbnailer can be run when TryExec is not present */ if (!thumb->try_exec) return TRUE; path = g_find_program_in_path (thumb->try_exec); retval = path != NULL; g_free (path); return retval; } static gpointer init_thumbnailers_dirs (gpointer data) { const gchar * const *data_dirs; GPtrArray *thumbs_dirs; guint i; data_dirs = g_get_system_data_dirs (); thumbs_dirs = g_ptr_array_new (); g_ptr_array_add (thumbs_dirs, g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL)); for (i = 0; data_dirs[i] != NULL; i++) g_ptr_array_add (thumbs_dirs, g_build_filename (data_dirs[i], "thumbnailers", NULL)); g_ptr_array_add (thumbs_dirs, NULL); return g_ptr_array_free (thumbs_dirs, FALSE); } static const gchar * const * get_thumbnailers_dirs (void) { static GOnce once_init = G_ONCE_INIT; return g_once (&once_init, init_thumbnailers_dirs, NULL); } /* These should be called with the lock held */ static void mate_desktop_thumbnail_factory_register_mime_types (MateDesktopThumbnailFactory *factory, Thumbnailer *thumb) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; gint i; for (i = 0; thumb->mime_types[i]; i++) { if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i])) g_hash_table_insert (priv->mime_types_map, g_strdup (thumb->mime_types[i]), thumbnailer_ref (thumb)); } } static void mate_desktop_thumbnail_factory_add_thumbnailer (MateDesktopThumbnailFactory *factory, Thumbnailer *thumb) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; mate_desktop_thumbnail_factory_register_mime_types (factory, thumb); priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb); } static gboolean mate_desktop_thumbnail_factory_is_disabled (MateDesktopThumbnailFactory *factory, const gchar *mime_type) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; guint i; if (priv->disabled) return TRUE; if (!priv->disabled_types) return FALSE; for (i = 0; priv->disabled_types[i]; i++) { if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0) return TRUE; } return FALSE; } static gboolean remove_thumbnailer_from_mime_type_map (gchar *key, Thumbnailer *value, gchar *path) { return (strcmp (value->path, path) == 0); } static void update_or_create_thumbnailer (MateDesktopThumbnailFactory *factory, const gchar *path) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; GList *l; Thumbnailer *thumb; gboolean found = FALSE; g_mutex_lock (&priv->lock); for (l = priv->thumbnailers; l && !found; l = g_list_next (l)) { thumb = (Thumbnailer *)l->data; if (strcmp (thumb->path, path) == 0) { found = TRUE; /* First remove the mime_types associated to this thumbnailer */ g_hash_table_foreach_remove (priv->mime_types_map, (GHRFunc)remove_thumbnailer_from_mime_type_map, (gpointer)path); if (!thumbnailer_reload (thumb)) priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l); else mate_desktop_thumbnail_factory_register_mime_types (factory, thumb); } } if (!found) { thumb = thumbnailer_new (path); if (thumb) mate_desktop_thumbnail_factory_add_thumbnailer (factory, thumb); } g_mutex_unlock (&priv->lock); } static void remove_thumbnailer (MateDesktopThumbnailFactory *factory, const gchar *path) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; GList *l; Thumbnailer *thumb; g_mutex_lock (&priv->lock); for (l = priv->thumbnailers; l; l = g_list_next (l)) { thumb = (Thumbnailer *)l->data; if (strcmp (thumb->path, path) == 0) { priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l); g_hash_table_foreach_remove (priv->mime_types_map, (GHRFunc)remove_thumbnailer_from_mime_type_map, (gpointer)path); thumbnailer_unref (thumb); break; } } g_mutex_unlock (&priv->lock); } static void remove_thumbnailers_for_dir (MateDesktopThumbnailFactory *factory, const gchar *thumbnailer_dir, GFileMonitor *monitor) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; GList *l; Thumbnailer *thumb; g_mutex_lock (&priv->lock); /* Remove all the thumbnailers inside this @thumbnailer_dir. */ for (l = priv->thumbnailers; l; l = g_list_next (l)) { thumb = (Thumbnailer *)l->data; if (g_str_has_prefix (thumb->path, thumbnailer_dir) == TRUE) { priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l); g_hash_table_foreach_remove (priv->mime_types_map, (GHRFunc)remove_thumbnailer_from_mime_type_map, (gpointer)thumb->path); thumbnailer_unref (thumb); break; } } /* Remove the monitor for @thumbnailer_dir. */ priv->monitors = g_list_remove (priv->monitors, monitor); g_signal_handlers_disconnect_by_func (monitor, thumbnailers_directory_changed, factory); g_mutex_unlock (&priv->lock); } static void mate_desktop_thumbnail_factory_load_thumbnailers_for_dir (MateDesktopThumbnailFactory *factory, const gchar *path) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; GDir *dir; GFile *dir_file; GFileMonitor *monitor; const gchar *dirent; dir = g_dir_open (path, 0, NULL); if (!dir) return; /* Monitor dir */ dir_file = g_file_new_for_path (path); monitor = g_file_monitor_directory (dir_file, G_FILE_MONITOR_NONE, NULL, NULL); if (monitor) { g_signal_connect (monitor, "changed", G_CALLBACK (thumbnailers_directory_changed), factory); priv->monitors = g_list_prepend (priv->monitors, monitor); } g_object_unref (dir_file); while ((dirent = g_dir_read_name (dir))) { Thumbnailer *thumb; gchar *filename; if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION)) continue; filename = g_build_filename (path, dirent, NULL); thumb = thumbnailer_new (filename); g_free (filename); if (thumb) mate_desktop_thumbnail_factory_add_thumbnailer (factory, thumb); } g_dir_close (dir); } static void thumbnailers_directory_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, MateDesktopThumbnailFactory *factory) { gchar *path; switch (event_type) { case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_DELETED: path = g_file_get_path (file); if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION)) { g_free (path); return; } if (event_type == G_FILE_MONITOR_EVENT_DELETED) remove_thumbnailer (factory, path); else update_or_create_thumbnailer (factory, path); g_free (path); break; case G_FILE_MONITOR_EVENT_UNMOUNTED: case G_FILE_MONITOR_EVENT_MOVED: path = g_file_get_path (file); remove_thumbnailers_for_dir (factory, path, monitor); if (event_type == G_FILE_MONITOR_EVENT_MOVED) mate_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, path); g_free (path); break; case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: default: break; } } static void mate_desktop_thumbnail_factory_load_thumbnailers (MateDesktopThumbnailFactory *factory) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; const gchar * const *dirs; guint i; if (priv->loaded) return; dirs = get_thumbnailers_dirs (); for (i = 0; dirs[i]; i++) { mate_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, dirs[i]); } priv->loaded = TRUE; } static void external_thumbnailers_disabled_all_changed_cb (GSettings *settings, const gchar *key, MateDesktopThumbnailFactory *factory) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; g_mutex_lock (&priv->lock); priv->disabled = g_settings_get_boolean (priv->settings, "disable-all"); if (priv->disabled) { g_strfreev (priv->disabled_types); priv->disabled_types = NULL; } else { priv->disabled_types = g_settings_get_strv (priv->settings, "disable"); mate_desktop_thumbnail_factory_load_thumbnailers (factory); } g_mutex_unlock (&priv->lock); } static void external_thumbnailers_disabled_changed_cb (GSettings *settings, const gchar *key, MateDesktopThumbnailFactory *factory) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; g_mutex_lock (&priv->lock); if (!priv->disabled) { g_strfreev (priv->disabled_types); priv->disabled_types = g_settings_get_strv (priv->settings, "disable"); } g_mutex_unlock (&priv->lock); } static void mate_desktop_thumbnail_factory_init (MateDesktopThumbnailFactory *factory) { MateDesktopThumbnailFactoryPrivate *priv; factory->priv = mate_desktop_thumbnail_factory_get_instance_private (factory); priv = factory->priv; priv->size = MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL; priv->mime_types_map = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)thumbnailer_unref); g_mutex_init (&priv->lock); priv->settings = g_settings_new ("org.mate.thumbnailers"); g_signal_connect (priv->settings, "changed::disable-all", G_CALLBACK (external_thumbnailers_disabled_all_changed_cb), factory); g_signal_connect (priv->settings, "changed::disable", G_CALLBACK (external_thumbnailers_disabled_changed_cb), factory); priv->disabled = g_settings_get_boolean (priv->settings, "disable-all"); if (!priv->disabled) priv->disabled_types = g_settings_get_strv (priv->settings, "disable"); if (!priv->disabled) mate_desktop_thumbnail_factory_load_thumbnailers (factory); } static void mate_desktop_thumbnail_factory_finalize (GObject *object) { MateDesktopThumbnailFactory *factory; MateDesktopThumbnailFactoryPrivate *priv; factory = MATE_DESKTOP_THUMBNAIL_FACTORY (object); priv = factory->priv; if (priv->thumbnailers) { g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref); priv->thumbnailers = NULL; } g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy); if (priv->monitors) { g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref); priv->monitors = NULL; } g_mutex_clear (&priv->lock); g_clear_pointer (&priv->disabled_types, g_strfreev); if (priv->settings) { g_signal_handlers_disconnect_by_func (priv->settings, external_thumbnailers_disabled_all_changed_cb, factory); g_signal_handlers_disconnect_by_func (priv->settings, external_thumbnailers_disabled_changed_cb, factory); g_clear_object (&priv->settings); } if (G_OBJECT_CLASS (parent_class)->finalize) (* G_OBJECT_CLASS (parent_class)->finalize) (object); } static void mate_desktop_thumbnail_factory_class_init (MateDesktopThumbnailFactoryClass *class) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (class); gobject_class->finalize = mate_desktop_thumbnail_factory_finalize; } /** * mate_desktop_thumbnail_factory_new: * @size: The thumbnail size to use * * Creates a new #MateDesktopThumbnailFactory. * * This function must be called on the main thread. * * Return value: a new #MateDesktopThumbnailFactory * * Since: 2.2 **/ MateDesktopThumbnailFactory * mate_desktop_thumbnail_factory_new (MateDesktopThumbnailSize size) { MateDesktopThumbnailFactory *factory; factory = g_object_new (MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL); factory->priv->size = size; return factory; } static char * thumbnail_filename (const char *uri) { GChecksum *checksum; guint8 digest[16]; gsize digest_len = sizeof (digest); char *file; checksum = g_checksum_new (G_CHECKSUM_MD5); g_checksum_update (checksum, (const guchar *) uri, strlen (uri)); g_checksum_get_digest (checksum, digest, &digest_len); g_assert (digest_len == 16); file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL); g_checksum_free (checksum); return file; } static char * thumbnail_path (const char *uri, MateDesktopThumbnailSize size) { char *path, *file; file = thumbnail_filename (uri); path = g_build_filename (g_get_user_cache_dir (), "thumbnails", size == MATE_DESKTOP_THUMBNAIL_SIZE_LARGE ? "large" : "normal", file, NULL); g_free (file); return path; } static char * thumbnail_failed_path (const char *uri) { char *path, *file; file = thumbnail_filename (uri); /* XXX: appname is only used for failed thumbnails. Is this a mistake? */ path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "fail", appname, file, NULL); g_free (file); return path; } static char * validate_thumbnail_path (char *path, const char *uri, time_t mtime, MateDesktopThumbnailSize size) { GdkPixbuf *pixbuf; pixbuf = gdk_pixbuf_new_from_file (path, NULL); if (pixbuf == NULL || !mate_desktop_thumbnail_is_valid (pixbuf, uri, mtime)) { g_free (path); return NULL; } g_clear_object (&pixbuf); return path; } static char * lookup_thumbnail_path (const char *uri, time_t mtime, MateDesktopThumbnailSize size) { char *path = thumbnail_path (uri, size); return validate_thumbnail_path (path, uri, mtime, size); } static char * lookup_failed_thumbnail_path (const char *uri, time_t mtime, MateDesktopThumbnailSize size) { char *path = thumbnail_failed_path (uri); return validate_thumbnail_path (path, uri, mtime, size); } /** * mate_desktop_thumbnail_factory_lookup: * @factory: a #MateDesktopThumbnailFactory * @uri: the uri of a file * @mtime: the mtime of the file * * Tries to locate an existing thumbnail for the file specified. * * Usage of this function is threadsafe. * * Return value: (transfer full): The absolute path of the thumbnail, or %NULL if none exist. * * Since: 2.2 **/ char * mate_desktop_thumbnail_factory_lookup (MateDesktopThumbnailFactory *factory, const char *uri, time_t mtime) { MateDesktopThumbnailFactoryPrivate *priv = factory->priv; g_return_val_if_fail (uri != NULL, NULL); return lookup_thumbnail_path (uri, mtime, priv->size); } /** * mate_desktop_thumbnail_factory_has_valid_failed_thumbnail: * @factory: a #MateDesktopThumbnailFactory * @uri: the uri of a file * @mtime: the mtime of the file * * Tries to locate an failed thumbnail for the file specified. Writing * and looking for failed thumbnails is important to avoid to try to * thumbnail e.g. broken images several times. * * Usage of this function is threadsafe. * * Return value: TRUE if there is a failed thumbnail for the file. * * Since: 2.2 **/ gboolean mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (MateDesktopThumbnailFactory *factory, const char *uri, time_t mtime) { char *path; g_return_val_if_fail (uri != NULL, FALSE); path = lookup_failed_thumbnail_path (uri, mtime, factory->priv->size); if (path == NULL) return FALSE; g_free (path); return TRUE; } /** * mate_desktop_thumbnail_factory_can_thumbnail: * @factory: a #MateDesktopThumbnailFactory * @uri: the uri of a file * @mime_type: the mime type of the file * @mtime: the mtime of the file * * Returns TRUE if this MateDesktopThumbnailFactory can (at least try) to thumbnail * this file. Thumbnails or files with failed thumbnails won't be thumbnailed. * * Usage of this function is threadsafe. * * Return value: TRUE if the file can be thumbnailed. * * Since: 2.2 **/ gboolean mate_desktop_thumbnail_factory_can_thumbnail (MateDesktopThumbnailFactory *factory, const char *uri, const char *mime_type, time_t mtime) { gboolean have_script = FALSE; /* Don't thumbnail thumbnails */ if (uri && strncmp (uri, "file:/", 6) == 0 && (strstr (uri, "/.thumbnails/") != NULL || strstr (uri, "/.cache/thumbnails/") != NULL)) return FALSE; if (!mime_type) return FALSE; g_mutex_lock (&factory->priv->lock); if (!mate_desktop_thumbnail_factory_is_disabled (factory, mime_type)) { Thumbnailer *thumb; thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type); have_script = thumbnailer_try_exec (thumb); } g_mutex_unlock (&factory->priv->lock); if (uri && (have_script )) { return !mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory, uri, mtime); } return FALSE; } static char * expand_thumbnailing_elem (const char *elem, const int size, const char *inuri, const char *outfile, gboolean *got_input, gboolean *got_output) { GString *str; const char *p, *last; char *localfile; str = g_string_new (NULL); last = elem; while ((p = strchr (last, '%')) != NULL) { g_string_append_len (str, last, p - last); p++; switch (*p) { case 'u': g_string_append (str, inuri); *got_input = TRUE; p++; break; case 'i': localfile = g_filename_from_uri (inuri, NULL, NULL); if (localfile) { g_string_append (str, localfile); *got_input = TRUE; g_free (localfile); } p++; break; case 'o': g_string_append (str, outfile); *got_output = TRUE; p++; break; case 's': g_string_append_printf (str, "%d", size); p++; break; case '%': g_string_append_c (str, '%'); p++; break; case 0: default: break; } last = p; } g_string_append (str, last); return g_string_free (str, FALSE); } static char ** expand_thumbnailing_script (const char *script, const int size, const char *inuri, const char *outfile, GError **error) { GPtrArray *array; char **script_elems; guint i; gboolean got_in, got_out; if (!g_shell_parse_argv (script, NULL, &script_elems, error)) return NULL; array = g_ptr_array_new_with_free_func (g_free); got_in = got_out = FALSE; for (i = 0; script_elems[i] != NULL; i++) { char *expanded; expanded = expand_thumbnailing_elem (script_elems[i], size, inuri, outfile, &got_in, &got_out); g_ptr_array_add (array, expanded); } g_strfreev (script_elems); if (!got_in) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Input file could not be set"); goto bail; } else if (!got_out) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Output file could not be set"); goto bail; } g_ptr_array_add (array, NULL); return (char **) g_ptr_array_free (array, FALSE); bail: g_ptr_array_free (array, TRUE); return NULL; } static GdkPixbuf * get_preview_thumbnail (const char *uri, int size) { GdkPixbuf *pixbuf; GFile *file; GFileInfo *file_info; GInputStream *input_stream; GObject *object; g_return_val_if_fail (uri != NULL, NULL); input_stream = NULL; file = g_file_new_for_uri (uri); /* First see if we can get an input stream via preview::icon */ file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_PREVIEW_ICON, G_FILE_QUERY_INFO_NONE, NULL, /* GCancellable */ NULL); /* return location for GError */ g_object_unref (file); if (file_info == NULL) return NULL; object = g_file_info_get_attribute_object (file_info, G_FILE_ATTRIBUTE_PREVIEW_ICON); if (object) g_object_ref (object); g_object_unref (file_info); if (!object) return NULL; if (!G_IS_LOADABLE_ICON (object)) { g_object_unref (object); return NULL; } input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object), 0, /* size */ NULL, /* return location for type */ NULL, /* GCancellable */ NULL); /* return location for GError */ g_object_unref (object); if (!input_stream) return NULL; pixbuf = gdk_pixbuf_new_from_stream_at_scale (input_stream, size, size, TRUE, NULL, NULL); g_object_unref (input_stream); return pixbuf; } /** * mate_desktop_thumbnail_factory_generate_thumbnail: * @factory: a #MateDesktopThumbnailFactory * @uri: the uri of a file * @mime_type: the mime type of the file * * Tries to generate a thumbnail for the specified file. If it succeeds * it returns a pixbuf that can be used as a thumbnail. * * Usage of this function is threadsafe. * * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise. * * Since: 2.2 **/ GdkPixbuf * mate_desktop_thumbnail_factory_generate_thumbnail (MateDesktopThumbnailFactory *factory, const char *uri, const char *mime_type) { GdkPixbuf *pixbuf; char *script; int size; int exit_status; char *tmpname; g_return_val_if_fail (uri != NULL, NULL); g_return_val_if_fail (mime_type != NULL, NULL); /* Doesn't access any volatile fields in factory, so it's threadsafe */ size = 128; if (factory->priv->size == MATE_DESKTOP_THUMBNAIL_SIZE_LARGE) size = 256; pixbuf = NULL; pixbuf = get_preview_thumbnail (uri, size); if (pixbuf != NULL) return pixbuf; script = NULL; g_mutex_lock (&factory->priv->lock); if (!mate_desktop_thumbnail_factory_is_disabled (factory, mime_type)) { Thumbnailer *thumb; thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type); if (thumb) script = g_strdup (thumb->command); } g_mutex_unlock (&factory->priv->lock); if (script) { int fd; fd = g_file_open_tmp (".mate_desktop_thumbnail.XXXXXX", &tmpname, NULL); if (fd != -1) { char **expanded_script; GError *error = NULL; close (fd); expanded_script = expand_thumbnailing_script (script, size, uri, tmpname, &error); if (expanded_script == NULL) { g_warning ("Failed to expand script '%s': %s", script, error->message); g_error_free (error); } else { gboolean ret; ret = g_spawn_sync (NULL, expanded_script, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &exit_status, NULL); if (ret && exit_status == 0) pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL); g_strfreev (expanded_script); } g_unlink (tmpname); g_free (tmpname); } g_free (script); } return pixbuf; } static gboolean save_thumbnail (GdkPixbuf *pixbuf, char *path, const char *uri, time_t mtime) { char *dirname; char *tmp_path = NULL; int tmp_fd; gchar *mtime_str; gboolean ret = FALSE; GError *error = NULL; const char *width, *height; if (pixbuf == NULL) return FALSE; dirname = g_path_get_dirname (path); if (g_mkdir_with_parents (dirname, 0700) != 0) goto out; tmp_path = g_strconcat (path, ".XXXXXX", NULL); tmp_fd = g_mkstemp (tmp_path); if (tmp_fd == -1) goto out; close (tmp_fd); mtime_str = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) mtime); width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width"); height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height"); error = NULL; if (width != NULL && height != NULL) ret = gdk_pixbuf_save (pixbuf, tmp_path, "png", &error, "tEXt::Thumb::Image::Width", width, "tEXt::Thumb::Image::Height", height, "tEXt::Thumb::URI", uri, "tEXt::Thumb::MTime", mtime_str, "tEXt::Software", "MATE::ThumbnailFactory", NULL); else ret = gdk_pixbuf_save (pixbuf, tmp_path, "png", &error, "tEXt::Thumb::URI", uri, "tEXt::Thumb::MTime", mtime_str, "tEXt::Software", "MATE::ThumbnailFactory", NULL); g_free (mtime_str); if (!ret) goto out; g_chmod (tmp_path, 0600); g_rename (tmp_path, path); out: if (error != NULL) { g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message); g_error_free (error); } g_unlink (tmp_path); g_free (tmp_path); g_free (dirname); return ret; } static GdkPixbuf * make_failed_thumbnail (void) { GdkPixbuf *pixbuf; pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1); gdk_pixbuf_fill (pixbuf, 0x00000000); return pixbuf; } /** * mate_desktop_thumbnail_factory_save_thumbnail: * @factory: a #MateDesktopThumbnailFactory * @thumbnail: the thumbnail as a pixbuf * @uri: the uri of a file * @original_mtime: the modification time of the original file * * Saves @thumbnail at the right place. If the save fails a * failed thumbnail is written. * * Usage of this function is threadsafe. * * Since: 2.2 **/ void mate_desktop_thumbnail_factory_save_thumbnail (MateDesktopThumbnailFactory *factory, GdkPixbuf *thumbnail, const char *uri, time_t original_mtime) { char *path; path = thumbnail_path (uri, factory->priv->size); if (!save_thumbnail (thumbnail, path, uri, original_mtime)) { thumbnail = make_failed_thumbnail (); g_free (path); path = thumbnail_failed_path (uri); save_thumbnail (thumbnail, path, uri, original_mtime); g_object_unref (thumbnail); } g_free (path); } /** * mate_desktop_thumbnail_factory_create_failed_thumbnail: * @factory: a #MateDesktopThumbnailFactory * @uri: the uri of a file * @mtime: the modification time of the file * * Creates a failed thumbnail for the file so that we don't try * to re-thumbnail the file later. * * Usage of this function is threadsafe. * * Since: 2.2 **/ void mate_desktop_thumbnail_factory_create_failed_thumbnail (MateDesktopThumbnailFactory *factory, const char *uri, time_t mtime) { char *path; GdkPixbuf *pixbuf; path = thumbnail_failed_path (uri); pixbuf = make_failed_thumbnail (); save_thumbnail (pixbuf, path, uri, mtime); g_free (path); g_object_unref (pixbuf); } /** * mate_desktop_thumbnail_path_for_uri: * @uri: an uri * @size: a thumbnail size * * Returns the filename that a thumbnail of size @size for @uri would have. * * Return value: an absolute filename * * Since: 2.2 **/ char * mate_desktop_thumbnail_path_for_uri (const char *uri, MateDesktopThumbnailSize size) { return thumbnail_path (uri, size); } /** * mate_desktop_thumbnail_has_uri: * @pixbuf: an loaded thumbnail pixbuf * @uri: a uri * * Returns whether the thumbnail has the correct uri embedded in the * Thumb::URI option in the png. * * Return value: TRUE if the thumbnail is for @uri * * Since: 2.2 **/ gboolean mate_desktop_thumbnail_has_uri (GdkPixbuf *pixbuf, const char *uri) { const char *thumb_uri; thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI"); return (g_strcmp0 (uri, thumb_uri) == 0); } /** * mate_desktop_thumbnail_is_valid: * @pixbuf: an loaded thumbnail #GdkPixbuf * @uri: a uri * @mtime: the mtime * * Returns whether the thumbnail has the correct uri and mtime embedded in the * png options. * * Return value: TRUE if the thumbnail has the right @uri and @mtime * * Since: 2.2 **/ gboolean mate_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf, const char *uri, time_t mtime) { const char *thumb_uri, *thumb_mtime_str; time_t thumb_mtime; thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI"); if (g_strcmp0 (uri, thumb_uri) != 0) return FALSE; thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime"); if (!thumb_mtime_str) return FALSE; thumb_mtime = (time_t)g_ascii_strtoll (thumb_mtime_str, (gchar**)NULL, 10); if (mtime != thumb_mtime) return FALSE; return TRUE; }