Commit b69fde6f authored by Bastien Nocera's avatar Bastien Nocera

thumbnail: Always use external gdk-pixbuf thumbnailer

Instead of special-casing gdk-pixbuf-supported image formats, use an
external thumbnailer. This will give us the ability to:
- cancel running thumbnail operations
- avoid memory leaks, buffer overflows, double-frees, etc. in the
  image loaders having an impact on the application
- limit resource usage when thumbnailing

https://bugzilla.gnome.org/show_bug.cgi?id=768064
parent 1b96f045
......@@ -103,7 +103,7 @@ GLIB_TESTS
dnl If you add a version number here, you *must* add an AC_SUBST line for
dnl it too, or it will never make it into the spec file!
GDK_PIXBUF_REQUIRED=2.33.0
GDK_PIXBUF_REQUIRED=2.36.1
GTK_REQUIRED=3.3.6
GLIB_REQUIRED=2.44.0
XRANDR_REQUIRED=1.3
......
......@@ -180,16 +180,6 @@ G_DEFINE_TYPE (GnomeDesktopThumbnailFactory,
#define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
(G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate))
typedef struct {
gint width;
gint height;
gint input_width;
gint input_height;
gboolean preserve_aspect_ratio;
} SizePrepareContext;
#define LOAD_BUFFER_SIZE 4096
#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
#define THUMBNAILER_EXTENSION ".thumbnailer"
......@@ -358,229 +348,6 @@ get_thumbnailers_dirs (void)
return g_once (&once_init, init_thumbnailers_dirs, NULL);
}
static void
size_prepared_cb (GdkPixbufLoader *loader,
int width,
int height,
gpointer data)
{
SizePrepareContext *info = data;
g_return_if_fail (width > 0 && height > 0);
info->input_width = width;
info->input_height = height;
if (width < info->width && height < info->height) return;
if (info->preserve_aspect_ratio &&
(info->width > 0 || info->height > 0)) {
if (info->width < 0)
{
width = width * (double)info->height/(double)height;
height = info->height;
}
else if (info->height < 0)
{
height = height * (double)info->width/(double)width;
width = info->width;
}
else if ((double)height * (double)info->width >
(double)width * (double)info->height) {
width = 0.5 + (double)width * (double)info->height / (double)height;
height = info->height;
} else {
height = 0.5 + (double)height * (double)info->width / (double)width;
width = info->width;
}
} else {
if (info->width > 0)
width = info->width;
if (info->height > 0)
height = info->height;
}
gdk_pixbuf_loader_set_size (loader, width, height);
}
static GdkPixbufLoader *
create_loader (GFile *file,
const guchar *data,
gsize size)
{
GdkPixbufLoader *loader;
GError *error = NULL;
char *mime_type;
char *filename;
loader = NULL;
/* need to specify the type here because the gdk_pixbuf_loader_write
doesn't have access to the filename in order to correct detect
the image type. */
filename = g_file_get_basename (file);
mime_type = g_content_type_guess (filename, data, size, NULL);
g_free (filename);
if (mime_type != NULL) {
loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, &error);
}
if (loader == NULL) {
g_debug ("Unable to create loader for mime type %s: %s", mime_type, error->message);
g_clear_error (&error);
loader = gdk_pixbuf_loader_new ();
}
g_free (mime_type);
return loader;
}
static GdkPixbuf *
_gdk_pixbuf_new_from_uri_at_scale (const char *uri,
gint width,
gint height,
gboolean preserve_aspect_ratio)
{
gboolean result;
guchar buffer[LOAD_BUFFER_SIZE];
gssize bytes_read;
GdkPixbufLoader *loader = NULL;
GdkPixbuf *pixbuf;
GdkPixbufAnimation *animation;
GdkPixbufAnimationIter *iter;
gboolean has_frame;
SizePrepareContext info;
GFile *file;
GFileInfo *file_info;
GInputStream *input_stream;
GError *error = NULL;
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 */
if (file_info != NULL) {
GObject *object;
object = g_file_info_get_attribute_object (file_info,
G_FILE_ATTRIBUTE_PREVIEW_ICON);
if (object != NULL && G_IS_LOADABLE_ICON (object)) {
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 (file_info);
}
if (input_stream == NULL) {
input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
if (input_stream == NULL) {
g_warning ("Unable to create an input stream for %s: %s", uri, error->message);
g_clear_error (&error);
g_object_unref (file);
return NULL;
}
}
has_frame = FALSE;
result = FALSE;
while (!has_frame) {
bytes_read = g_input_stream_read (input_stream,
buffer,
sizeof (buffer),
NULL,
&error);
if (bytes_read == -1) {
g_warning ("Error reading from %s: %s", uri, error->message);
g_clear_error (&error);
break;
}
result = TRUE;
if (bytes_read == 0) {
break;
}
if (loader == NULL) {
loader = create_loader (file, buffer, bytes_read);
if (1 <= width || 1 <= height) {
info.width = width;
info.height = height;
info.input_width = info.input_height = 0;
info.preserve_aspect_ratio = preserve_aspect_ratio;
g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
}
g_assert (loader != NULL);
}
if (!gdk_pixbuf_loader_write (loader,
(unsigned char *)buffer,
bytes_read,
&error)) {
g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
g_clear_error (&error);
result = FALSE;
break;
}
animation = gdk_pixbuf_loader_get_animation (loader);
if (animation) {
iter = gdk_pixbuf_animation_get_iter (animation, NULL);
if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
has_frame = TRUE;
}
g_object_unref (iter);
}
}
if (loader == NULL) {
/* This can happen if the above loop was exited due to the
* g_input_stream_read() call failing. */
result = FALSE;
} else if (gdk_pixbuf_loader_close (loader, &error) == FALSE &&
!g_error_matches (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION)) {
g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
result = FALSE;
}
g_clear_error (&error);
if (!result) {
g_clear_object (&loader);
g_input_stream_close (input_stream, NULL, NULL);
g_object_unref (input_stream);
g_object_unref (file);
return NULL;
}
g_input_stream_close (input_stream, NULL, NULL);
g_object_unref (input_stream);
g_object_unref (file);
pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
if (pixbuf != NULL) {
g_object_ref (G_OBJECT (pixbuf));
g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width",
GINT_TO_POINTER (info.input_width));
g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height",
GINT_TO_POINTER (info.input_height));
}
g_object_unref (G_OBJECT (loader));
return pixbuf;
}
/* These should be called with the lock held */
static void
gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory,
......@@ -638,7 +405,6 @@ remove_thumbnailer_from_mime_type_map (gchar *key,
return (strcmp (value->path, path) == 0);
}
static void
update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory,
const gchar *path)
......@@ -1161,54 +927,6 @@ gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnai
return TRUE;
}
static gboolean
mimetype_supported_by_gdk_pixbuf (const char *mime_type)
{
guint i;
static gsize formats_hash = 0;
gchar *key;
gboolean result;
if (g_once_init_enter (&formats_hash)) {
GSList *formats, *list;
GHashTable *hash;
hash = g_hash_table_new_full (g_str_hash,
(GEqualFunc) g_content_type_equals,
g_free, NULL);
formats = gdk_pixbuf_get_formats ();
list = formats;
while (list) {
GdkPixbufFormat *format = list->data;
gchar **mime_types;
mime_types = gdk_pixbuf_format_get_mime_types (format);
for (i = 0; mime_types[i] != NULL; i++)
g_hash_table_insert (hash,
(gpointer) g_content_type_from_mime_type (mime_types[i]),
GUINT_TO_POINTER (1));
g_strfreev (mime_types);
list = list->next;
}
g_slist_free (formats);
g_once_init_leave (&formats_hash, (gsize) hash);
}
key = g_content_type_from_mime_type (mime_type);
if (g_hash_table_lookup ((void*)formats_hash, key))
result = TRUE;
else
result = FALSE;
g_free (key);
return result;
}
/**
* gnome_desktop_thumbnail_factory_can_thumbnail:
* @factory: a #GnomeDesktopThumbnailFactory
......@@ -1252,7 +970,7 @@ gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *fac
}
g_mutex_unlock (&factory->priv->lock);
if (have_script || mimetype_supported_by_gdk_pixbuf (mime_type))
if (have_script)
{
return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
uri,
......@@ -1351,13 +1069,9 @@ gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory
const char *uri,
const char *mime_type)
{
GdkPixbuf *pixbuf, *scaled, *tmp_pixbuf;
GdkPixbuf *pixbuf;
char *script, *expanded_script;
int width, height, size;
int original_width = 0;
int original_height = 0;
char dimension[12];
double scale;
int size;
int exit_status;
char *tmpname;
......@@ -1411,65 +1125,6 @@ gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory
g_free (script);
}
/* Fall back to gdk-pixbuf */
if (pixbuf == NULL)
{
pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, size, size, TRUE);
if (pixbuf != NULL)
{
original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
"gnome-original-width"));
original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
"gnome-original-height"));
}
}
if (pixbuf == NULL)
return NULL;
/* The pixbuf loader may attach an "orientation" option to the pixbuf,
if the tiff or exif jpeg file had an orientation tag. Rotate/flip
the pixbuf as specified by this tag, if present. */
tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
g_object_unref (pixbuf);
pixbuf = tmp_pixbuf;
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
if (width > size || height > size)
{
const gchar *orig_width, *orig_height;
scale = (double)size / MAX (width, height);
scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf,
floor (width * scale + 0.5),
floor (height * scale + 0.5));
orig_width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
orig_height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
if (orig_width != NULL) {
gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Width", orig_width);
}
if (orig_height != NULL) {
gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Height", orig_height);
}
g_object_unref (pixbuf);
pixbuf = scaled;
}
if (original_width > 0) {
g_snprintf (dimension, sizeof (dimension), "%i", original_width);
gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", dimension);
}
if (original_height > 0) {
g_snprintf (dimension, sizeof (dimension), "%i", original_height);
gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", dimension);
}
return pixbuf;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment