Add nautilus_thumbnail_load_image_async and cancel.

2007-09-10  Alexander Larsson  <alexl@redhat.com>

        * libnautilus-private/nautilus-thumbnails.[ch]:
        Add nautilus_thumbnail_load_image_async and cancel.

        * libnautilus-private/nautilus-icon-container.c:
        * libnautilus-private/nautilus-icon-factory.[ch]:
        Load thumbnails asynchronously

        Patch from Christian Neumair


svn path=/trunk/; revision=13137
This commit is contained in:
Alexander Larsson 2007-09-10 11:07:16 +00:00 committed by Alexander Larsson
parent 59970b14fe
commit 5f0893633d
6 changed files with 464 additions and 57 deletions

View file

@ -1,3 +1,14 @@
2007-09-10 Alexander Larsson <alexl@redhat.com>
* libnautilus-private/nautilus-thumbnails.[ch]:
Add nautilus_thumbnail_load_image_async and cancel.
* libnautilus-private/nautilus-icon-container.c:
* libnautilus-private/nautilus-icon-factory.[ch]:
Load thumbnails asynchronously
Patch from Christian Neumair
2007-09-10 Alexander Larsson <alexl@redhat.com>
* libnautilus-private/nautilus-dnd.h:

View file

@ -5678,6 +5678,90 @@ handle_vadjustment_changed (GtkAdjustment *adjustment,
nautilus_icon_container_update_visible_icons (container);
}
/*
* used to resize ICON_NAME_THUMBNAIL_LOADING to the expected thumbnail size.
*/
static void
sanitize_loading_thumbnail_image_size (NautilusIconContainer *container,
const char *mime_type,
GdkPixbuf **image,
NautilusEmblemAttachPoints *attach_points,
GdkRectangle *embedded_text_rect)
{
NautilusIconContainerDetails *details;
double pixels_per_unit;
details = container->details;
pixels_per_unit = (double) nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
/ NAUTILUS_ICON_SIZE_STANDARD;
if (gdk_pixbuf_get_width (*image) < NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit &&
gdk_pixbuf_get_height (*image) < NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit) {
/* TODO? this only handles icons smaller than the expected thumbnail size ATM.
* Should not be a common problem, though */
GdkPixbuf *new_image;
double x_size;
double y_size;
double x_offset;
double y_offset;
int i;
if (g_str_has_prefix (mime_type, "video/")) {
/* assume 4:3 aspect ratio for videos i.e. we'll always occupy the full width. */
x_size = NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit;
y_size = 3./4 * x_size;
} else {
/* scale up to the max. thumbnail size.
* This is correct at least in one dimension, and prevents the icons from jumping
* around as the thumbnail is created, if it is tall for text below icon, and if it
* is wide for text beside icon.
*/
x_size = NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit;
y_size = NAUTILUS_ICON_SIZE_THUMBNAIL * pixels_per_unit;
}
/* maybe the estimated size was smaller than the input pixbuf, so size the surrounding
* image up. This only seems to be relevant in the 4:3 case, for y_size.
*/
x_size = MAX (x_size, gdk_pixbuf_get_width (*image));
y_size = MAX (y_size, gdk_pixbuf_get_height (*image));
x_offset = x_size - gdk_pixbuf_get_width (*image);
y_offset = y_size - gdk_pixbuf_get_height (*image);
/* center wrt "minor" dimension, i.e. horizontally for text below
* and vertically for text besides icon */
if (details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE)
y_offset /= 2;
else
x_offset /= 2;
new_image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE,
gdk_pixbuf_get_bits_per_sample (*image),
x_size, y_size);
gdk_pixbuf_fill (new_image, 0x00000000);
gdk_pixbuf_copy_area (*image,
0, 0,
gdk_pixbuf_get_width (*image),
gdk_pixbuf_get_height (*image),
new_image,
x_offset, y_offset);
g_object_unref (*image);
*image = new_image;
for (i = 0; i < attach_points->num_points; i++) {
attach_points->points[i].x += x_offset;
attach_points->points[i].y += y_offset;
}
embedded_text_rect->x += x_offset;
embedded_text_rect->y += y_offset;
}
}
void
nautilus_icon_container_update_icon (NautilusIconContainer *container,
NautilusIcon *icon)
@ -5732,15 +5816,14 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container,
modifier = "accept";
}
pixbuf = nautilus_icon_factory_get_pixbuf_for_icon
(icon_name,
pixbuf = nautilus_icon_factory_get_pixbuf_for_file_with_icon
((NautilusFile *) icon->data,
icon_name,
modifier,
icon_size,
&attach_points,
&embedded_text_rect,
FALSE, TRUE, NULL);
g_free (icon_name);
if (embedded_text_rect.width > MINIMUM_EMBEDDED_TEXT_RECT_WIDTH &&
embedded_text_rect.height > MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT &&
@ -5794,6 +5877,17 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container,
"additional_text", additional_text,
"highlighted_for_drop", icon == details->drop_target,
NULL);
if (nautilus_file_is_thumbnailing ((NautilusFile *) icon->data)) {
char* mime_type;
mime_type = nautilus_file_get_mime_type ((NautilusFile *)icon->data);
sanitize_loading_thumbnail_image_size (container,
mime_type,
&pixbuf,
&attach_points,
&embedded_text_rect);
g_free (mime_type);
}
nautilus_icon_canvas_item_set_image (icon->item, pixbuf);
nautilus_icon_canvas_item_set_attach_points (icon->item, &attach_points);
@ -5807,6 +5901,8 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container,
g_free (editable_text);
g_free (additional_text);
g_free (icon_name);
}
static gboolean

View file

@ -29,6 +29,7 @@
#include "nautilus-icon-factory.h"
#include "nautilus-default-file-icon.h"
#include "nautilus-directory-notify.h"
#include "nautilus-file-attributes.h"
#include "nautilus-file-private.h"
#include "nautilus-file-utilities.h"
@ -154,6 +155,7 @@ typedef struct {
CacheIcon *fallback_icon;
GHashTable *image_mime_types;
GList *async_thumbnail_load_handles;
} NautilusIconFactory;
#define NAUTILUS_ICON_FACTORY(obj) \
@ -347,6 +349,64 @@ load_thumbnail_frame (NautilusIconFactory *factory)
g_free (image_path);
}
typedef struct {
NautilusFile *file;
char *modifier;
guint nominal_size;
gboolean force_nominal;
} AsnycThumbnailLoadFuncData;
static void
async_thumbnail_load_func (NautilusThumbnailAsyncLoadHandle *handle,
const char *path,
GdkPixbuf *pixbuf,
double scale_x,
double scale_y,
gpointer user_data)
{
NautilusIconFactory *factory;
GHashTable *hash_table;
CacheKey *key;
CacheIcon *cached_icon;
struct stat statbuf;
AsnycThumbnailLoadFuncData *data = user_data;
factory = get_icon_factory ();
hash_table = factory->icon_cache;
nautilus_file_set_is_thumbnailing (data->file, FALSE);
factory->async_thumbnail_load_handles =
g_list_remove (factory->async_thumbnail_load_handles, handle);
if (stat (path, &statbuf) != 0 ||
!S_ISREG (statbuf.st_mode)) {
g_message ("NautilusIconFactory: Failed to determine mtime for %s. Aborting thumbnailing request.", path);
goto out;
}
cached_icon = cache_icon_new (pixbuf, NULL, scale_x, scale_y);
cached_icon->mtime = statbuf.st_mtime;
if (cached_icon != NULL) {
key = g_new (CacheKey, 1);
key->name = g_strdup (path);
key->modifier = g_strdup (data->modifier);
key->nominal_size = data->nominal_size;
key->force_nominal = data->force_nominal;
g_hash_table_insert (hash_table, key, cached_icon);
nautilus_file_changed (data->file);
}
out:
nautilus_file_unref (data->file);
g_free (data->modifier);
g_free (data);
}
static void
nautilus_icon_factory_instance_init (NautilusIconFactory *factory)
{
@ -684,6 +744,14 @@ nautilus_icon_factory_clear (void)
}
static void
cancel_thumbnail_read_foreach (gpointer data,
gpointer user_data)
{
NautilusThumbnailAsyncLoadHandle *handle = data;
nautilus_thumbnail_load_image_cancel (handle);
}
static void
nautilus_icon_factory_finalize (GObject *object)
{
@ -691,6 +759,9 @@ nautilus_icon_factory_finalize (GObject *object)
factory = NAUTILUS_ICON_FACTORY (object);
g_list_foreach (factory->async_thumbnail_load_handles, cancel_thumbnail_read_foreach, NULL);
g_list_free (factory->async_thumbnail_load_handles);
if (factory->icon_cache) {
g_hash_table_destroy (factory->icon_cache);
factory->icon_cache = NULL;
@ -1300,6 +1371,38 @@ create_normal_cache_icon (const char *icon,
return cache_icon;
}
static CacheIcon *
lookup_icon_from_cache (const char *icon,
const char *modifier,
guint nominal_size,
gboolean force_nominal)
{
NautilusIconFactory *factory;
GHashTable *hash_table;
CacheKey lookup_key, *key;
CacheIcon *value;
lookup_key.name = (char *)icon;
lookup_key.modifier = (char *)modifier;
lookup_key.nominal_size = nominal_size;
lookup_key.force_nominal = force_nominal;
factory = get_icon_factory ();
hash_table = factory->icon_cache;
if (g_hash_table_lookup_extended (hash_table, &lookup_key,
(gpointer *) &key, (gpointer *) &value)) {
/* Found it in the table. */
g_assert (key != NULL);
g_assert (value != NULL);
} else {
key = NULL;
value = NULL;
}
return value;
}
/* Get the icon, handling the caching.
* If @picky is true, then only an unscaled icon is acceptable.
@ -1316,34 +1419,17 @@ get_icon_from_cache (const char *icon,
{
NautilusIconFactory *factory;
GHashTable *hash_table;
CacheKey lookup_key;
CacheKey *key;
CacheIcon *cached_icon;
gpointer key_in_table, value;
struct stat statbuf;
g_return_val_if_fail (icon != NULL, NULL);
key = NULL;
cached_icon = NULL;
factory = get_icon_factory ();
hash_table = factory->icon_cache;
/* Check to see if it's already in the table. */
lookup_key.name = (char *)icon;
lookup_key.modifier = (char *)modifier;
lookup_key.nominal_size = nominal_size;
lookup_key.force_nominal = force_nominal;
if (g_hash_table_lookup_extended (hash_table, &lookup_key,
&key_in_table, &value)) {
/* Found it in the table. */
g_assert (key_in_table != NULL);
g_assert (value != NULL);
key = key_in_table;
cached_icon = value;
}
cached_icon = lookup_icon_from_cache (icon, modifier, nominal_size, force_nominal);
/* Make sure that thumbnails and image-as-itself icons gets
reloaded when they change: */
@ -1547,10 +1633,85 @@ nautilus_get_relative_icon_size_for_zoom_level (NautilusZoomLevel zoom_level)
return (float)nautilus_get_icon_size_for_zoom_level (zoom_level) / NAUTILUS_ICON_SIZE_STANDARD;
}
/* Convenience cover for nautilus_icon_factory_get_icon_for_file
* and nautilus_icon_factory_get_pixbuf_for_icon.
*
* If a file has an associated thumbnail, the thumb is loaded asynchronously,
* a loading thumbnail image is returned
* and the file will receive a "changed" event once the thumbnail has been loaded.
*
* The "file" parameter is only used for thumbnailing,
* for the file change notification once the actual thumbnail
* has been loaded.
*/
GdkPixbuf *
nautilus_icon_factory_get_pixbuf_for_file_with_icon (NautilusFile *file,
const char *icon,
const char *modifier,
guint size_in_pixels,
NautilusEmblemAttachPoints *attach_points,
GdkRectangle *embedded_text_rect,
gboolean force_size,
gboolean wants_default,
char **display_name)
{
GdkPixbuf *pixbuf;
NautilusIconFactory *factory;
gboolean is_thumbnail;
factory = get_icon_factory ();
is_thumbnail = strstr (icon, "/.thumbnails/") != NULL;
if (is_thumbnail &&
!lookup_icon_from_cache (icon, modifier, size_in_pixels, force_size)) {
AsnycThumbnailLoadFuncData *data;
/* Asynchronous thumbnail loading.
*
* This heavily improves performance for folders containing lots of
* previously thumbnailed files.
*
* Note: We do not pass the additional thumbnail parameters (attach points etc.)
* to the thread as we don't need them for the cache. The API user may herself
* re-request the loaded thumbnail with the correct parameters, which will be set
* accordingly in nautilus_icon_factory_get_pixbuf_for_icon() on cache hit
* once it is filled.
*/
data = g_new (AsnycThumbnailLoadFuncData, 1);
data->file = nautilus_file_ref (file);
data->modifier = g_strdup (modifier);
data->nominal_size = size_in_pixels;
data->force_nominal = force_size;
nautilus_file_set_is_thumbnailing (file, TRUE);
factory->async_thumbnail_load_handles = g_list_prepend (
factory->async_thumbnail_load_handles,
nautilus_thumbnail_load_image_async (icon,
0, /* base_size */
size_in_pixels,
force_size,
async_thumbnail_load_func,
data));
icon = ICON_NAME_THUMBNAIL_LOADING;
}
pixbuf = nautilus_icon_factory_get_pixbuf_for_icon (icon,
modifier, size_in_pixels,
attach_points, embedded_text_rect,
force_size,
wants_default, display_name);
return pixbuf;
}
/*
* like nautilus_icon_factory_get_pixbuf_for_file_with_icon() but does the icon lookup itself,
* doesn't allow emblem and text rect fetching.
*/
GdkPixbuf *
nautilus_icon_factory_get_pixbuf_for_file (NautilusFile *file,
@ -1558,9 +1719,11 @@ nautilus_icon_factory_get_pixbuf_for_file (NautilusFile *file,
guint size_in_pixels,
gboolean force_size)
{
char *icon;
GdkPixbuf *pixbuf;
NautilusIconFactory *factory;
char *icon;
factory = get_icon_factory ();
/* Get the pixbuf for this file. */
icon = nautilus_icon_factory_get_icon_for_file (file, FALSE);
@ -1568,12 +1731,12 @@ nautilus_icon_factory_get_pixbuf_for_file (NautilusFile *file,
return NULL;
}
pixbuf = nautilus_icon_factory_get_pixbuf_for_icon (icon, modifier,
size_in_pixels,
NULL, NULL,
force_size,
TRUE, NULL);
pixbuf = nautilus_icon_factory_get_pixbuf_for_file_with_icon (file,
icon, modifier,
size_in_pixels,
NULL, NULL,
force_size,
TRUE, NULL);
g_free (icon);
return pixbuf;
@ -1586,7 +1749,7 @@ nautilus_icon_factory_get_pixbuf_for_file_with_stock_size (NautilusFile *file,
{
return nautilus_icon_factory_get_pixbuf_for_file (file, modifier,
gtk_icon_size_to_nominal_size (stock_size),
TRUE); /* force_size */
TRUE /* force_size */);
}

View file

@ -151,6 +151,16 @@ GdkPixbuf *nautilus_icon_factory_get_pixbuf_for_file_with_stock_size (NautilusFi
const char *modifier,
GtkIconSize stock_size);
GdkPixbuf * nautilus_icon_factory_get_pixbuf_for_file_with_icon (NautilusFile *file,
const char *icon,
const char *modifier,
guint size_in_pixels,
NautilusEmblemAttachPoints *attach_points,
GdkRectangle *embedded_text_rect,
gboolean force_size,
gboolean wants_default,
char **display_name);
/* Convenience routine for getting a pixbuf from an icon name
*/

View file

@ -69,6 +69,16 @@ typedef struct {
time_t original_file_mtime;
} NautilusThumbnailInfo;
struct NautilusThumbnailAsyncLoadHandle {
EelReadFileHandle *eel_read_handle;
char *file_path;
guint base_size;
guint nominal_size;
gboolean force_nominal;
NautilusThumbnailAsyncLoadFunc load_func;
gpointer load_func_user_data;
};
/*
* Thumbnail thread state.
@ -330,39 +340,26 @@ thumbnail_loader_area_prepared (GdkPixbufLoader *loader,
*args->scale_y_out = (double) gdk_pixbuf_get_height (pixbuf) / args->original_height;
}
/* routine to load an image from the passed-in path
*/
GdkPixbuf *
nautilus_thumbnail_load_image (const char *path,
guint base_size,
guint nominal_size,
gboolean force_nominal,
double *scale_x_out,
double *scale_y_out)
static GdkPixbuf *
get_pixbuf_from_data (const unsigned char *buffer,
gsize buflen,
const char *path,
guint base_size,
guint nominal_size,
gboolean force_nominal,
double *scale_x_out,
double *scale_y_out)
{
guchar *buffer;
GdkPixbufLoader *loader;
GdkPixbuf *pixbuf;
GError *error;
gsize buflen;
ThumbnailLoadArgs args;
error = NULL;
GError *error;
if (thumbnail_icon_size == 0) {
eel_preferences_add_auto_integer (NAUTILUS_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE,
&thumbnail_icon_size);
}
if (!g_file_get_contents (path, (gchar **) &buffer, &buflen, &error)) {
g_message ("Failed to load %s into memory: %s", path, error->message);
g_error_free (error);
return NULL;
}
loader = gdk_pixbuf_loader_new ();
g_signal_connect (loader, "size-prepared",
G_CALLBACK (thumbnail_loader_size_prepared),
@ -378,17 +375,20 @@ nautilus_thumbnail_load_image (const char *path,
args.scale_x_out = scale_x_out;
args.scale_y_out = scale_y_out;
error = NULL;
if (!gdk_pixbuf_loader_write (loader, buffer, buflen, &error)) {
g_message ("Failed to write %s to thumbnail pixbuf loader: %s", path, error->message);
gdk_pixbuf_loader_close (loader, NULL);
g_object_unref (G_OBJECT (loader));
g_error_free (error);
g_free (buffer);
return NULL;
}
error = NULL;
if (!gdk_pixbuf_loader_close (loader, &error) ||
/* Seems we have to check this even if it returned TRUE (#403255) */
error != NULL) {
@ -396,7 +396,6 @@ nautilus_thumbnail_load_image (const char *path,
g_object_unref (G_OBJECT (loader));
g_error_free (error);
g_free (buffer);
return NULL;
}
@ -404,11 +403,122 @@ nautilus_thumbnail_load_image (const char *path,
pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
g_object_unref (G_OBJECT (loader));
return pixbuf;
}
/* routine to load an image from the passed-in path
*/
GdkPixbuf *
nautilus_thumbnail_load_image (const char *path,
guint base_size,
guint nominal_size,
gboolean force_nominal,
double *scale_x_out,
double *scale_y_out)
{
GdkPixbuf *pixbuf;
guchar *buffer;
gsize buflen;
GError *error;
error = NULL;
if (!g_file_get_contents (path, (gchar **) &buffer, &buflen, &error)) {
g_message ("Failed to load %s into memory: %s", path, error->message);
g_error_free (error);
return NULL;
}
pixbuf = get_pixbuf_from_data (buffer, buflen, path,
base_size, nominal_size, force_nominal,
scale_x_out, scale_y_out);
g_free (buffer);
return pixbuf;
}
static void
async_thumbnail_read_image (GnomeVFSResult result,
GnomeVFSFileSize file_size,
char *file_contents,
gpointer callback_data)
{
GdkPixbuf *pixbuf;
double scale_x, scale_y;
NautilusThumbnailAsyncLoadHandle *handle = callback_data;
pixbuf = NULL;
scale_x = scale_y = 1.0;
if (result == GNOME_VFS_OK) {
pixbuf = get_pixbuf_from_data (file_contents, file_size,
handle->file_path,
handle->base_size,
handle->nominal_size,
handle->force_nominal,
&scale_x, &scale_y);
}
handle->load_func (handle,
handle->file_path,
pixbuf, scale_x, scale_y,
handle->load_func_user_data);
gdk_pixbuf_unref (pixbuf);
g_free (handle->file_path);
g_free (handle);
}
NautilusThumbnailAsyncLoadHandle *
nautilus_thumbnail_load_image_async (const char *path,
guint base_size,
guint nominal_size,
gboolean force_nominal,
NautilusThumbnailAsyncLoadFunc load_func,
gpointer load_func_user_data)
{
NautilusThumbnailAsyncLoadHandle *handle;
char *uri;
uri = gnome_vfs_get_uri_from_local_path (path);
if (uri == NULL) {
return NULL;
}
handle = g_new (NautilusThumbnailAsyncLoadHandle, 1);
handle->eel_read_handle =
eel_read_entire_file_async (uri, GNOME_VFS_PRIORITY_DEFAULT,
(EelReadFileCallback) async_thumbnail_read_image,
handle);
handle->file_path = g_strdup (path);
handle->base_size = base_size;
handle->nominal_size = nominal_size;
handle->force_nominal = force_nominal;
handle->load_func = load_func;
handle->load_func_user_data = load_func_user_data;
g_free (uri);
return handle;
}
void
nautilus_thumbnail_load_image_cancel (NautilusThumbnailAsyncLoadHandle *handle)
{
g_assert (handle != NULL);
eel_read_file_cancel (handle->eel_read_handle);
g_free (handle->file_path);
g_free (handle);
}
void
nautilus_thumbnail_remove_from_queue (const char *file_uri)
{

View file

@ -28,6 +28,15 @@
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libnautilus-private/nautilus-file.h>
typedef struct NautilusThumbnailAsyncLoadHandle NautilusThumbnailAsyncLoadHandle;
typedef void (* NautilusThumbnailAsyncLoadFunc) (NautilusThumbnailAsyncLoadHandle *handle,
const char *path,
GdkPixbuf *pixbuf,
double scale_x,
double scale_y,
gpointer user_data);
/* Returns NULL if there's no thumbnail yet. */
void nautilus_create_thumbnail (NautilusFile *file);
void nautilus_thumbnail_frame_image (GdkPixbuf **pixbuf);
@ -37,6 +46,14 @@ GdkPixbuf *nautilus_thumbnail_load_image (const char *path,
gboolean force_nominal,
double *scale_x_out,
double *scale_y_out);
NautilusThumbnailAsyncLoadHandle *
nautilus_thumbnail_load_image_async (const char *path,
guint base_size,
guint nominal_size,
gboolean force_nominal,
NautilusThumbnailAsyncLoadFunc load_func,
gpointer load_func_user_data);
void nautilus_thumbnail_load_image_cancel (NautilusThumbnailAsyncLoadHandle *handle);
void nautilus_update_thumbnail_file_copied (const char *source_file_uri,
const char *destination_file_uri);
void nautilus_update_thumbnail_file_renamed (const char *source_file_uri,