Better caching for document textures and information

Move thumbnail creation into ShellTextureCache.  It's now asynchronous,
and we cache the result.

Create a DocManager class which keeps around the DocInfo objects between
invocations.  This is also where we ensure we remove thumbnails for
recent items not known anymore.
This commit is contained in:
Colin Walters
2009-07-08 21:06:06 -04:00
parent 1cb6fc004b
commit 5d067ec718
6 changed files with 300 additions and 115 deletions

View File

@ -1,16 +1,20 @@
#include "shell-texture-cache.h"
#include "shell-global.h"
#include <gtk/gtk.h>
#include <libgnomeui/gnome-thumbnail.h>
#include <string.h>
typedef struct
{
GIcon *icon;
gchar *thumbnail_uri;
guint size;
} CacheKey;
struct _ShellTextureCachePrivate
{
GHashTable *gicon_cache; /* CacheKey -> CoglTexture* */
GnomeThumbnailFactory *thumbnails;
};
static void shell_texture_cache_dispose (GObject *object);
@ -25,6 +29,8 @@ cache_key_hash (gconstpointer a)
if (akey->icon)
return g_icon_hash (akey->icon) + 31*akey->size;
else if (akey->thumbnail_uri)
return g_str_hash (akey->thumbnail_uri) + 31*akey->size;
g_assert_not_reached ();
}
@ -39,7 +45,10 @@ cache_key_equal (gconstpointer a,
return FALSE;
if (akey->icon && bkey->icon)
return g_icon_equal (akey->icon, bkey->icon);
g_assert_not_reached ();
else if (akey->thumbnail_uri && bkey->thumbnail_uri)
return strcmp (akey->thumbnail_uri, bkey->thumbnail_uri) == 0;
return FALSE;
}
static void
@ -48,6 +57,8 @@ cache_key_destroy (gpointer a)
CacheKey *akey = (CacheKey*)a;
if (akey->icon)
g_object_unref (akey->icon);
if (akey->thumbnail_uri)
g_free (akey->thumbnail_uri);
g_free (akey);
}
@ -66,6 +77,7 @@ shell_texture_cache_init (ShellTextureCache *self)
self->priv = g_new0 (ShellTextureCachePrivate, 1);
self->priv->gicon_cache = g_hash_table_new_full (cache_key_hash, cache_key_equal,
cache_key_destroy, cogl_handle_unref);
self->priv->thumbnails = gnome_thumbnail_factory_new (GNOME_THUMBNAIL_SIZE_NORMAL);
}
static void
@ -77,6 +89,10 @@ shell_texture_cache_dispose (GObject *object)
g_hash_table_destroy (self->priv->gicon_cache);
self->priv->gicon_cache = NULL;
if (self->priv->thumbnails)
g_object_unref (self->priv->thumbnails);
self->priv->thumbnails = NULL;
G_OBJECT_CLASS (shell_texture_cache_parent_class)->dispose (object);
}
@ -94,7 +110,11 @@ shell_texture_cache_new ()
}
typedef struct {
ShellTextureCache *cache;
char *uri;
char *mimetype;
GdkPixbuf *fallback_pixbuf;
gboolean thumbnail;
GIcon *icon;
GtkIconInfo *icon_info;
gint width;
@ -102,7 +122,6 @@ typedef struct {
gpointer user_data;
} AsyncIconLookupData;
static gboolean
compute_pixbuf_scale (gint width,
gint height,
@ -200,6 +219,10 @@ icon_lookup_data_destroy (gpointer p)
}
else if (data->uri)
g_free (data->uri);
if (data->mimetype)
g_free (data->mimetype);
if (data->fallback_pixbuf)
g_object_unref (data->fallback_pixbuf);
g_free (data);
}
@ -328,6 +351,56 @@ out:
return rotated_pixbuf;
}
static GdkPixbuf *
impl_load_thumbnail (ShellTextureCache *cache,
const char *uri,
const char *mime_type,
guint size,
GError **error)
{
GnomeThumbnailFactory *thumbnail_factory;
GdkPixbuf *pixbuf = NULL;
GFile *file;
GFileInfo *file_info;
GTimeVal mtime_g;
time_t mtime = 0;
char *existing_thumbnail;
file = g_file_new_for_uri (uri);
file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_unref (file);
if (file_info)
{
g_file_info_get_modification_time (file_info, &mtime_g);
g_object_unref (file_info);
mtime = (time_t) mtime_g.tv_sec;
}
thumbnail_factory = cache->priv->thumbnails;
existing_thumbnail = gnome_thumbnail_factory_lookup (thumbnail_factory, uri, mtime);
if (existing_thumbnail != NULL)
pixbuf = gdk_pixbuf_new_from_file_at_size (existing_thumbnail, size, size, error);
else if (gnome_thumbnail_factory_has_valid_failed_thumbnail (thumbnail_factory, uri, mtime))
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Has failed thumbnail");
else if (gnome_thumbnail_factory_can_thumbnail (thumbnail_factory, uri, mime_type, mtime))
{
pixbuf = gnome_thumbnail_factory_generate_thumbnail (thumbnail_factory, uri, mime_type);
if (pixbuf)
{
// we need to save the thumbnail so that we don't need to generate it again in the future
gnome_thumbnail_factory_save_thumbnail (thumbnail_factory, pixbuf, uri, mtime);
}
else
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to generate thumbnail");
gnome_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, uri, mtime);
}
}
return pixbuf;
}
static void
load_pixbuf_thread (GSimpleAsyncResult *result,
GObject *object,
@ -337,9 +410,19 @@ load_pixbuf_thread (GSimpleAsyncResult *result,
AsyncIconLookupData *data;
GError *error = NULL;
data = g_object_get_data (G_OBJECT (result), "load_icon_pixbuf_async");
data = g_object_get_data (G_OBJECT (result), "load_pixbuf_async");
g_assert (data != NULL);
if (data->uri)
if (data->thumbnail)
{
pixbuf = impl_load_thumbnail (data->cache, data->uri, data->mimetype, data->width, &error);
if (!pixbuf && data->fallback_pixbuf)
{
g_clear_error (&error);
pixbuf = g_object_ref (data->fallback_pixbuf);
}
}
else if (data->uri)
pixbuf = impl_load_pixbuf_file (data->uri, data->width, data->height, &error);
else if (data->icon)
pixbuf = impl_load_pixbuf_gicon (data->icon, data->icon_info, data->width, &error);
@ -375,6 +458,7 @@ load_icon_pixbuf_async (ShellTextureCache *cache,
AsyncIconLookupData *data;
data = g_new0 (AsyncIconLookupData, 1);
data->cache = cache;
data->icon = g_object_ref (icon);
data->icon_info = gtk_icon_info_copy (icon_info);
data->width = data->height = size;
@ -382,7 +466,7 @@ load_icon_pixbuf_async (ShellTextureCache *cache,
result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_icon_pixbuf_async);
g_object_set_data_full (G_OBJECT (result), "load_icon_pixbuf_async", data, icon_lookup_data_destroy);
g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy);
g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
g_object_unref (result);
@ -401,6 +485,7 @@ load_uri_pixbuf_async (ShellTextureCache *cache,
AsyncIconLookupData *data;
data = g_new0 (AsyncIconLookupData, 1);
data->cache = cache;
data->uri = g_strdup (uri);
data->width = width;
data->height = height;
@ -408,7 +493,38 @@ load_uri_pixbuf_async (ShellTextureCache *cache,
result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_uri_pixbuf_async);
g_object_set_data_full (G_OBJECT (result), "load_uri_pixbuf_async", data, icon_lookup_data_destroy);
g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy);
g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
g_object_unref (result);
}
static void
load_thumbnail_async (ShellTextureCache *cache,
const char *uri,
const char *mimetype,
guint size,
GdkPixbuf *fallback,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
AsyncIconLookupData *data;
data = g_new0 (AsyncIconLookupData, 1);
data->cache = cache;
data->uri = g_strdup (uri);
data->mimetype = g_strdup (mimetype);
data->fallback_pixbuf = g_object_ref (fallback);
data->thumbnail = TRUE;
data->width = size;
data->height = size;
data->user_data = user_data;
result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_thumbnail_async);
g_object_set_data_full (G_OBJECT (result), "load_pixbuf_async", data, icon_lookup_data_destroy);
g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
g_object_unref (result);
@ -425,6 +541,8 @@ load_pixbuf_async_finish (ShellTextureCache *cache, GAsyncResult *result, GError
typedef struct {
char *uri;
gboolean thumbnail;
char *mimetype;
GIcon *icon;
GtkIconInfo *icon_info;
guint width;
@ -469,12 +587,15 @@ on_pixbuf_loaded (GObject *source,
g_object_unref (pixbuf);
if (data->icon)
if (data->icon || data->thumbnail)
{
gpointer orig_key, value;
key = g_new0 (CacheKey, 1);
key->icon = g_object_ref (data->icon);
if (data->icon)
key->icon = g_object_ref (data->icon);
else
key->thumbnail_uri = g_strdup (data->uri);
key->size = data->width;
if (!g_hash_table_lookup_extended (cache->priv->gicon_cache, key,
@ -495,10 +616,15 @@ out:
}
else if (data->uri)
g_free (data->uri);
if (data->mimetype)
g_free (data->mimetype);
/* Alternatively we could weakref and just do nothing if the texture
is destroyed */
g_object_unref (data->texture);
g_clear_error (&error);
g_free (data);
}
@ -522,6 +648,7 @@ shell_texture_cache_load_gicon (ShellTextureCache *cache,
texture = CLUTTER_TEXTURE (clutter_texture_new ());
clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
memset (&key, 0, sizeof(key));
key.icon = icon;
key.size = size;
texdata = g_hash_table_lookup (cache->priv->gicon_cache, &key);
@ -630,6 +757,88 @@ shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
return CLUTTER_ACTOR (texture);
}
/**
* shell_texture_cache_load_thumbnail:
* @cache:
* @size: Size in pixels to use for thumbnail
* @uri: Source URI
* @mimetype: Source mime type
* @fallback: (allow-none): If no thumbnail is cached, use this pixbuf
*
* Asynchronously load a thumbnail image of a URI into a texture. The
* returned texture object will be a new instance; however, its texture data
* may be shared with other objects. This implies the texture data is cached.
*
* The current caching policy is permanent; to uncache, you must explicitly
* call shell_texture_cache_unref_thumbnail().
*
* A fallback pixbuf may be provided in the case where thumbnailing is
* not applicable, or fails.
*
* Returns: (transfer none): A new #ClutterActor
*/
ClutterActor *
shell_texture_cache_load_thumbnail (ShellTextureCache *cache,
int size,
const char *uri,
const char *mimetype,
GdkPixbuf *fallback)
{
ClutterTexture *texture;
AsyncTextureLoadData *data;
CacheKey key;
CoglHandle texdata;
texture = CLUTTER_TEXTURE (clutter_texture_new ());
clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
memset (&key, 0, sizeof(key));
key.size = size;
key.thumbnail_uri = (char*)uri;
texdata = g_hash_table_lookup (cache->priv->gicon_cache, &key);
if (!texdata)
{
data = g_new0 (AsyncTextureLoadData, 1);
data->uri = g_strdup (uri);
data->mimetype = g_strdup (mimetype);
data->thumbnail = TRUE;
data->width = size;
data->height = size;
data->texture = g_object_ref (texture);
load_thumbnail_async (cache, uri, mimetype, size, fallback, NULL, on_pixbuf_loaded, data);
}
else
{
clutter_texture_set_cogl_texture (texture, texdata);
}
return CLUTTER_ACTOR (texture);
}
/**
* shell_texture_cache_unref_thumbnail:
* @cache:
* @size: Size in pixels
* @uri: Source URI
*
* Removes the reference the shell_texture_cache_load_thumbnail() function
* created for a thumbnail.
*/
void
shell_texture_cache_unref_thumbnail (ShellTextureCache *cache,
int size,
const char *uri)
{
CacheKey key;
memset (&key, 0, sizeof(key));
key.size = size;
key.thumbnail_uri = (char*)uri;
g_hash_table_remove (cache->priv->gicon_cache, &key);
}
static ShellTextureCache *instance = NULL;
/**