changeset 16375:391a79778f89

Rework the buddy icon subsystem to use the imgstore subsystem, and modify the imgstore subsystem to not require IDs for everything.
author Richard Laager <rlaager@wiktel.com>
date Tue, 24 Apr 2007 03:57:07 +0000
parents 2a19bbc743ed
children dd47fa8ba3e4
files libpurple/buddyicon.c libpurple/buddyicon.h libpurple/core.c libpurple/gaim-compat.h libpurple/imgstore.c libpurple/imgstore.h libpurple/protocols/jabber/buddy.c libpurple/protocols/msn/msn.c libpurple/protocols/oscar/odc.c libpurple/protocols/oscar/oscar.c libpurple/protocols/sametime/sametime.c libpurple/protocols/silc/buddy.c libpurple/protocols/silc/ops.c libpurple/protocols/yahoo/yahoo_profile.c libpurple/util.c libpurple/util.h libpurple/value.h pidgin/gtkblist.c pidgin/gtkconv.c pidgin/gtkimhtmltoolbar.c pidgin/gtkmain.c pidgin/gtkutils.c
diffstat 22 files changed, 545 insertions(+), 407 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/buddyicon.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/buddyicon.c	Tue Apr 24 03:57:07 2007 +0000
@@ -28,30 +28,26 @@
 #include "conversation.h"
 #include "dbus-maybe.h"
 #include "debug.h"
+#include "imgstore.h"
 #include "util.h"
 
 typedef struct _PurpleBuddyIconData PurpleBuddyIconData;
 
+/* NOTE: Instances of this struct are allocated without zeroing the memory, so
+ * NOTE: be sure to update purple_buddy_icon_new() if you add members. */
 struct _PurpleBuddyIcon
 {
-	PurpleAccount *account;             /**< The account the user is on.          */
-	char *username;                     /**< The username the icon belongs to.    */
-	PurpleBuddyIconData *protocol_icon; /**< The icon data.                       */
-	PurpleBuddyIconData *custom_icon;   /**< The data for a user-set custom icon. */
-	int ref_count;                      /**< The buddy icon reference count.      */
-};
-
-struct _PurpleBuddyIconData
-{
-	guchar *image_data;    /**< The buddy icon data.               */
-	size_t len;            /**< The length of the buddy icon data. */
-	char *filename;        /**< The filename of the cache file.    */
-	int ref_count;         /**< The buddy icon reference count.    */
+	PurpleAccount *account;    /**< The account the user is on.          */
+	char *username;            /**< The username the icon belongs to.    */
+	PurpleStoredImage *img;    /**< The id of the stored image with the
+	                                the icon data.                       */
+	int ref_count;             /**< The buddy icon reference count.      */
 };
 
 static GHashTable *account_cache = NULL;
 static GHashTable *icon_data_cache = NULL;
 static GHashTable *icon_file_cache = NULL;
+static GHashTable *custom_icon_cache = NULL;
 static char       *cache_dir     = NULL;
 static gboolean    icon_caching  = TRUE;
 
@@ -92,33 +88,6 @@
 	}
 }
 
-static const char *
-get_icon_type(guchar *icon_data, size_t icon_len)
-{
-	g_return_val_if_fail(icon_data != NULL, NULL);
-	g_return_val_if_fail(icon_len > 0, NULL);
-
-	if (icon_len >= 4)
-	{
-		if (!strncmp((char *)icon_data, "BM", 2))
-			return "bmp";
-		else if (!strncmp((char *)icon_data, "GIF8", 4))
-			return "gif";
-		else if (!strncmp((char *)icon_data, "\xff\xd8\xff\xe0", 4))
-			return "jpg";
-		else if (!strncmp((char *)icon_data, "\x89PNG", 4))
-			return "png";
-	}
-
-	return "icon";
-}
-
-static const char *
-purple_buddy_icon_data_get_type(PurpleBuddyIconData *data)
-{
-	return get_icon_type(data->image_data, data->len);
-}
-
 static char *
 purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len)
 {
@@ -143,21 +112,23 @@
 
 	/* Return the filename */
 	return g_strdup_printf("%s.%s", digest,
-	                       get_icon_type(icon_data, icon_len));
+	                       purple_util_get_image_extension(icon_data, icon_len));
 }
 
 static void
-purple_buddy_icon_data_cache(PurpleBuddyIconData *data)
+purple_buddy_icon_data_cache(PurpleStoredImage *img)
 {
 	const char *dirname;
 	char *path;
 	FILE *file = NULL;
 
+	g_return_if_fail(img != NULL);
+
 	if (!purple_buddy_icons_is_caching())
 		return;
 
 	dirname  = purple_buddy_icons_get_cache_dir();
-	path = g_build_filename(dirname, data->filename, NULL);
+	path = g_build_filename(dirname, purple_imgstore_get_filename(img), NULL);
 
 	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
 	{
@@ -173,7 +144,7 @@
 
 	if ((file = g_fopen(path, "wb")) != NULL)
 	{
-		if (!fwrite(data->image_data, data->len, 1, file))
+		if (!fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file))
 		{
 			purple_debug_error("buddyicon", "Error writing %s: %s\n",
 			                   path, strerror(errno));
@@ -194,20 +165,20 @@
 }
 
 static void
-purple_buddy_icon_data_uncache(PurpleBuddyIconData *data)
+purple_buddy_icon_data_uncache_file(const char *filename)
 {
 	const char *dirname;
 	char *path;
 
-	g_return_if_fail(data != NULL);
+	g_return_if_fail(filename != NULL);
 
 	/* It's possible that there are other references to this icon
 	 * cache file that are not currently loaded into memory. */
-	if (g_hash_table_lookup(icon_file_cache, data->filename))
+	if (g_hash_table_lookup(icon_file_cache, filename))
 		return;
 
 	dirname  = purple_buddy_icons_get_cache_dir();
-	path = g_build_filename(dirname, data->filename, NULL);
+	path = g_build_filename(dirname, filename, NULL);
 
 	if (g_file_test(path, G_FILE_TEST_EXISTS))
 	{
@@ -223,69 +194,50 @@
 	g_free(path);
 }
 
-static PurpleBuddyIconData *
-purple_buddy_icon_data_ref(PurpleBuddyIconData *data)
+static void
+image_deleting_cb(PurpleStoredImage *img, gpointer data)
 {
-	g_return_val_if_fail(data != NULL, NULL);
+	const char *filename = purple_imgstore_get_filename(img);
 
-	data->ref_count++;
-
-	return data;
+	if (img == g_hash_table_lookup(icon_data_cache, filename))
+	{
+		purple_buddy_icon_data_uncache_file(filename);
+		g_hash_table_remove(icon_data_cache, filename);
+	}
 }
 
-static PurpleBuddyIconData *
-purple_buddy_icon_data_unref(PurpleBuddyIconData *data)
+static PurpleStoredImage *
+purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len, const char *filename)
 {
-	if (data == NULL)
-		return NULL;
-
-	g_return_val_if_fail(data->ref_count > 0, NULL);
-
-	data->ref_count--;
-
-	if (data->ref_count == 0)
-	{
-		g_hash_table_remove(icon_data_cache, data->filename);
-
-		purple_buddy_icon_data_uncache(data);
-
-		g_free(data->image_data);
-		g_free(data->filename);
-		g_free(data);
-
-		return NULL;
-	}
-
-	return data;
-}
-
-static PurpleBuddyIconData *
-purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len)
-{
-	PurpleBuddyIconData *data;
-	char *filename;
+	char *file;
+	PurpleStoredImage *img;
 
 	g_return_val_if_fail(icon_data != NULL, NULL);
 	g_return_val_if_fail(icon_len  > 0,     NULL);
 
-	filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
 	if (filename == NULL)
-		return NULL;
+	{
+		file = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		if (file == NULL)
+			return NULL;
+	}
+	else
+		file = g_strdup(filename);
 
-	if ((data = g_hash_table_lookup(icon_data_cache, filename)))
+	if ((img = g_hash_table_lookup(icon_data_cache, file)))
 	{
-		g_free(filename);
-		return purple_buddy_icon_data_ref(data);
+		g_free(file);
+		return purple_imgstore_ref(img);
 	}
 
-	data = g_new0(PurpleBuddyIconData, 1);
-	data->image_data = g_memdup(icon_data, icon_len);
-	data->len = icon_len;
-	data->filename = filename;
+	img = purple_imgstore_add(icon_data, icon_len, file);
 
-	purple_buddy_icon_data_cache(data);
+	/* This will take ownership of file and g_free it either now or later. */
+	g_hash_table_insert(icon_data_cache, file, img);
 
-	return data;
+	purple_buddy_icon_data_cache(img);
+
+	return img;
 }
 
 static PurpleBuddyIcon *
@@ -294,7 +246,9 @@
 	PurpleBuddyIcon *icon;
 	GHashTable *icon_cache;
 
-	icon = g_new0(PurpleBuddyIcon, 1);
+	/* This does not zero.  See purple_buddy_icon_new() for
+	 * information on which function allocates which member. */
+	icon = g_slice_new(PurpleBuddyIcon);
 	PURPLE_DBUS_REGISTER_POINTER(icon, PurpleBuddyIcon);
 
 	icon->account = account;
@@ -316,29 +270,26 @@
 
 PurpleBuddyIcon *
 purple_buddy_icon_new(PurpleAccount *account, const char *username,
-                      void *protocol_icon_data, size_t protocol_icon_len,
-                      void *custom_icon_data, size_t custom_icon_len)
+                      void *icon_data, size_t icon_len)
 {
 	PurpleBuddyIcon *icon;
 
 	g_return_val_if_fail(account   != NULL, NULL);
 	g_return_val_if_fail(username  != NULL, NULL);
-	g_return_val_if_fail((protocol_icon_data != NULL && protocol_icon_len > 0) ||
-	                     (custom_icon_data != NULL && custom_icon_len > 0), NULL);
+	g_return_val_if_fail(icon_data != NULL, NULL);
+	g_return_val_if_fail(icon_len  > 0,    NULL);
 
 	icon = purple_buddy_icons_find(account, username);
 
+	/* purple_buddy_icon_create() sets account & username */
 	if (icon == NULL)
 		icon = purple_buddy_icon_create(account, username);
 
 	/* Take a reference for the caller of this function. */
-	purple_buddy_icon_ref(icon);
+	icon->ref_count = 1;
 
-	if (protocol_icon_data != NULL && protocol_icon_len > 0)
-		purple_buddy_icon_set_protocol_data(icon, protocol_icon_data, protocol_icon_len);
-
-	if (custom_icon_data != NULL && custom_icon_len > 0)
-		purple_buddy_icon_set_custom_data(icon, custom_icon_data, custom_icon_len);
+	/* purple_buddy_icon_set_data() sets img */
+	purple_buddy_icon_set_data(icon, icon_data, icon_len);
 
 	return icon;
 }
@@ -371,11 +322,10 @@
 			g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
 
 		g_free(icon->username);
-		purple_buddy_icon_data_unref(icon->protocol_icon);
-		purple_buddy_icon_data_unref(icon->custom_icon);
+		purple_imgstore_unref(icon->img);
 
 		PURPLE_DBUS_UNREGISTER_POINTER(icon);
-		g_free(icon);
+		g_slice_free(PurpleBuddyIcon, icon);
 
 		return NULL;
 	}
@@ -397,58 +347,39 @@
 	account  = purple_buddy_icon_get_account(icon);
 	username = purple_buddy_icon_get_username(icon);
 
-	/* If neither type of data exists, then call the functions below with
-	 * NULL to unset the icon.  They will then unref the icon and it
-	 * should be destroyed.  The only way it wouldn't be destroyed is if
-	 * someone else is holding a reference to it, in which case they can
-	 * kill the icon when they realize it has no data any more. */
-	icon_to_set = (icon->protocol_icon || icon->custom_icon) ? icon : NULL;
+	/* If no data exists, then call the functions below with NULL to
+	 * unset the icon.  They will then unref the icon and it should be
+	 * destroyed.  The only way it wouldn't be destroyed is if someone
+	 * else is holding a reference to it, in which case they can kill
+	 * the icon when they realize it has no data. */
+	icon_to_set = icon->img ? icon : NULL;
 
 	for (list = sl = purple_find_buddies(account, username);
 	     sl != NULL;
 	     sl = sl->next)
 	{
 		PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
-		const char *old_icon;
+		char *old_icon;
 
 		purple_buddy_set_icon(buddy, icon_to_set);
 
 
-		old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
-		                                        "buddy_icon");
-		if (icon->protocol_icon)
+		old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy,
+		                                                 "buddy_icon"));
+		if (icon->img)
 		{
-			old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
-			                                        "buddy_icon");
+			const char *filename = purple_imgstore_get_filename(icon->img);
 			purple_blist_node_set_string((PurpleBlistNode *)buddy,
 			                             "buddy_icon",
-			                             icon->protocol_icon->filename);
-			ref_filename(icon->protocol_icon->filename);
+			                             filename);
+			ref_filename(filename);
 		}
 		else
 		{
 			purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
 		}
 		unref_filename(old_icon);
-
-
-		old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
-		                                        "custom_buddy_icon");
-		if (icon->custom_icon)
-		{
-			old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
-			                                        "custom_buddy_icon");
-			purple_blist_node_set_string((PurpleBlistNode *)buddy,
-			                             "custom_buddy_icon",
-			                             icon->custom_icon->filename);
-			ref_filename(icon->custom_icon->filename);
-		}
-		else
-		{
-			purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
-			                                 "custom_buddy_icon");
-		}
-		unref_filename(old_icon);
+		g_free(old_icon);
 	}
 
 	g_slist_free(list);
@@ -460,39 +391,21 @@
 }
 
 void
-purple_buddy_icon_set_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
+purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
 {
-	PurpleBuddyIconData *old_data;
+	PurpleStoredImage *old_img;
 
 	g_return_if_fail(icon != NULL);
 
-	old_data = icon->custom_icon;
-	icon->custom_icon = NULL;
+	old_img = icon->img;
+	icon->img = NULL;
 
 	if (data != NULL && len > 0)
-		icon->custom_icon = purple_buddy_icon_data_new(data, len);
+		icon->img = purple_buddy_icon_data_new(data, len, NULL);
 
 	purple_buddy_icon_update(icon);
 
-	purple_buddy_icon_data_unref(icon->custom_icon);
-}
-
-void
-purple_buddy_icon_set_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
-{
-	PurpleBuddyIconData *old_data;
-
-	g_return_if_fail(icon != NULL);
-
-	old_data = icon->protocol_icon;
-	icon->protocol_icon = NULL;
-
-	if (data != NULL && len > 0)
-		icon->protocol_icon = purple_buddy_icon_data_new(data, len);
-
-	purple_buddy_icon_update(icon);
-
-	purple_buddy_icon_data_unref(old_data);
+	purple_imgstore_unref(old_img);
 }
 
 PurpleAccount *
@@ -511,38 +424,27 @@
 	return icon->username;
 }
 
-const guchar *
+gconstpointer
 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
 {
 	g_return_val_if_fail(icon != NULL, NULL);
 
-	if (icon->custom_icon)
+	if (icon->img)
 	{
 		if (len != NULL)
-			*len = icon->custom_icon->len;
-
-		return icon->custom_icon->image_data;
-	}
+			*len = purple_imgstore_get_size(icon->img);
 
-	if (icon->protocol_icon)
-	{
-		if (len != NULL)
-			*len = icon->protocol_icon->len;
-
-		return icon->protocol_icon->image_data;
+		return purple_imgstore_get_data(icon->img);
 	}
 
 	return NULL;
 }
 
 const char *
-purple_buddy_icon_get_type(const PurpleBuddyIcon *icon)
+purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon)
 {
-	if (icon->custom_icon != NULL)
-		return purple_buddy_icon_data_get_type(icon->custom_icon);
-
-	if (icon->protocol_icon != NULL)
-		return purple_buddy_icon_data_get_type(icon->protocol_icon);
+	if (icon->img != NULL)
+		return purple_imgstore_get_extension(icon->img);
 
 	return NULL;
 }
@@ -561,11 +463,11 @@
 		icon = purple_buddy_icons_find(account, username);
 
 		if (icon != NULL)
-			purple_buddy_icon_set_protocol_data(icon, icon_data, icon_len);
+			purple_buddy_icon_set_data(icon, icon_data, icon_len);
 	}
 	else
 	{
-		PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, NULL, 0);
+		PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len);
 		purple_buddy_icon_unref(icon);
 	}
 }
@@ -617,7 +519,6 @@
 	{
 		PurpleBuddy *b = purple_find_buddy(account, username);
 		const char *protocol_icon_file;
-		const char *custom_icon_file;
 		const char *dirname;
 		gboolean caching;
 		guchar *data;
@@ -627,9 +528,8 @@
 			return NULL;
 
 		protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
-		custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "custom_buddy_icon");
 
-		if (protocol_icon_file == NULL && custom_icon_file == NULL)
+		if (protocol_icon_file == NULL)
 			return NULL;
 
 		dirname = purple_buddy_icons_get_cache_dir();
@@ -640,17 +540,6 @@
 		 * functions. */
 		purple_buddy_icons_set_caching(FALSE);
 
-		if (custom_icon_file != NULL)
-		{
-			char *path = g_build_filename(dirname, custom_icon_file, NULL);
-			if (read_icon_file(path, &data, &len))
-			{
-				icon = purple_buddy_icon_create(account, username);
-				purple_buddy_icon_set_custom_data(icon, data, len);
-			}
-			g_free(path);
-		}
-
 		if (protocol_icon_file != NULL)
 		{
 			char *path = g_build_filename(dirname, protocol_icon_file, NULL);
@@ -658,7 +547,7 @@
 			{
 				if (icon == NULL)
 					icon = purple_buddy_icon_create(account, username);
-				purple_buddy_icon_set_protocol_data(icon, data, len);
+				purple_buddy_icon_set_data(icon, data, len);
 			}
 			g_free(path);
 		}
@@ -669,6 +558,87 @@
 	return icon;
 }
 
+gboolean
+purple_buddy_icons_has_custom_icon(PurpleContact *contact)
+{
+	g_return_val_if_fail(contact != NULL, FALSE);
+
+	return (purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon") != NULL);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_find_custom_icon(PurpleContact *contact)
+{
+	PurpleStoredImage *img;
+	const char *custom_icon_file;
+	const char *dirname;
+	char *path;
+	guchar *data;
+	size_t len;
+
+	g_return_val_if_fail(contact != NULL, NULL);
+
+	if ((img = g_hash_table_lookup(custom_icon_cache, contact)))
+	{
+		return purple_imgstore_ref(img);
+	}
+
+	custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon");
+
+	if (custom_icon_file == NULL)
+		return NULL;
+
+	dirname = purple_buddy_icons_get_cache_dir();
+	path = g_build_filename(dirname, custom_icon_file, NULL);
+
+	if (read_icon_file(path, &data, &len))
+	{
+		g_free(path);
+		img = purple_buddy_icon_data_new(data, len, custom_icon_file);
+		g_hash_table_insert(custom_icon_cache, contact, img);
+		return img;
+	}
+	g_free(path);
+
+	return NULL;
+}
+
+void
+purple_buddy_icons_set_custom_icon(PurpleContact *contact,
+                                   guchar *icon_data, size_t icon_len)
+{
+	PurpleStoredImage *old_img;
+	PurpleStoredImage *img = NULL;
+	char *old_icon;
+
+	old_img = g_hash_table_lookup(custom_icon_cache, contact);
+
+	if (icon_data != NULL && icon_len > 0)
+		img = purple_buddy_icon_data_new(icon_data, icon_len, NULL);
+
+	old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)contact,
+	                                                 "custom_buddy_icon"));
+	if (img)
+	{
+		const char *filename = purple_imgstore_get_filename(img);
+		purple_blist_node_set_string((PurpleBlistNode *)contact,
+		                             "custom_buddy_icon",
+		                             filename);
+		ref_filename(filename);
+	}
+	else
+	{
+		purple_blist_node_remove_setting((PurpleBlistNode *)contact,
+		                                 "custom_buddy_icon");
+	}
+	unref_filename(old_icon);
+	g_free(old_icon);
+
+
+	g_hash_table_insert(custom_icon_cache, contact, img);
+	purple_imgstore_unref(old_img);
+}
+
 void
 purple_buddy_icon_set_old_icons_dir(const char *dirname)
 {
@@ -762,6 +732,9 @@
 	PurpleBlistNode *node = purple_blist_get_root();
 	const char *dirname = purple_buddy_icons_get_cache_dir();
 
+	// TODO: TEMP
+	old_icons_dir = g_strdup("/home/rlaager/.gaim/icons");
+
 	/* Doing this once here saves having to check it inside a loop. */
 	if (old_icons_dir != NULL)
 	{
@@ -795,10 +768,18 @@
 				}
 				else
 				{
-					// TODO: If filename doesn't exist, drop the setting.
+					if (!g_file_test(filename, G_FILE_TEST_EXISTS))
+					{
+						purple_blist_node_remove_setting(node,
+						                                 "buddy_icon");
+					}
 					ref_filename(filename);
 				}
 			}
+		}
+		else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
+		{
+			const char *filename;
 
 			filename = purple_blist_node_get_string(node, "custom_buddy_icon");
 			if (filename != NULL)
@@ -811,7 +792,11 @@
 				}
 				else
 				{
-					// TODO: If filename doesn't exist, drop the setting.
+					if (!g_file_test(filename, G_FILE_TEST_EXISTS))
+					{
+						purple_blist_node_remove_setting(node,
+						                                 "custom_buddy_icon");
+					}
 					ref_filename(filename);
 				}
 			}
@@ -876,16 +861,24 @@
 	icon_data_cache = g_hash_table_new(g_str_hash, g_str_equal);
 	icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
 	                                        g_free, NULL);
+	custom_icon_cache = g_hash_table_new(g_direct_hash, g_direct_equal);
 
 	cache_dir = g_build_filename(purple_user_dir(), "icons", NULL);
+
+	purple_signal_connect(purple_imgstore_get_handle(), "image-deleting",
+	                      purple_buddy_icons_get_handle(),
+	                      G_CALLBACK(image_deleting_cb), NULL);
 }
 
 void
 purple_buddy_icons_uninit()
 {
+	purple_signals_disconnect_by_handle(purple_buddy_icons_get_handle());
+
 	g_hash_table_destroy(account_cache);
 	g_hash_table_destroy(icon_data_cache);
 	g_hash_table_destroy(icon_file_cache);
+	g_hash_table_destroy(custom_icon_cache);
 	g_free(old_icons_dir);
 }
 
--- a/libpurple/buddyicon.h	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/buddyicon.h	Tue Apr 24 03:57:07 2007 +0000
@@ -29,6 +29,7 @@
 
 #include "account.h"
 #include "blist.h"
+#include "imgstore.h"
 #include "prpl.h"
 
 #ifdef __cplusplus
@@ -44,25 +45,20 @@
 /*@{*/
 
 /**
- * Create a buddy icon structure and populate it.
+ * Creates a new buddy icon structure and populate it.
  *
  * If the buddy icon already exists, you'll get a reference to that structure,
  * which will have been updated with the data supplied.
  *
  * @param account   The account the user is on.
  * @param username  The username the icon belongs to.
- * @param protocol_icon_data The buddy icon data received over the wire.
- * @param protocol_icon_len  The length of the data in @a protocol_icon_data.
- * @param custom_icon_data   The data for a custom buddy icon set by the user.
- * @param custom_icon_len    The length of the data in @a custom_icon-data.
- * @return The buddy icon structure, referenced for you. If you don't
- *         want the reference, then call purple_buddy_icon_unref().  However,
- *         you may want to use purple_buddy_icon_set_for_user() instead.
+ * @param icon_data The buddy icon data.
+ * @param icon_len  The buddy icon length.
+ *
+ * @return The buddy icon structure.
  */
-PurpleBuddyIcon *
-purple_buddy_icon_new(PurpleAccount *account, const char *username,
-                      void *protocol_icon_data, size_t protocol_icon_len,
-                      void *custom_icon_data, size_t custom_icon_len);
+PurpleBuddyIcon *purple_buddy_icon_new(PurpleAccount *account, const char *username,
+								void *icon_data, size_t icon_len);
 
 /**
  * Increments the reference count on a buddy icon.
@@ -92,22 +88,13 @@
 void purple_buddy_icon_update(PurpleBuddyIcon *icon);
 
 /**
- * Sets the buddy icon's data for a custom icon set by the user.
- *
- * @param icon The buddy icon.
- * @param data The buddy icon data for the custom icon set by the user.
- * @param len  The length of the data in @a data.
- */
-void purple_buddy_icon_set_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len);
-
-/**
  * Sets the buddy icon's data that was received over the wire.
  *
  * @param icon The buddy icon.
  * @param data The buddy icon data received over the wire.
  * @param len  The length of the data in @a data.
  */
-void purple_buddy_icon_set_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len);
+void purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data, size_t len);
 
 /**
  * Returns the buddy icon's account.
@@ -130,34 +117,23 @@
 /**
  * Returns the buddy icon's data.
  *
- * This will return the data for a custom icon, if one has been set by the
- * user.  Otherwise, it will return the protocol data, if available.  If
- * no data is available (which can happen if you're holding on to a
- * reference to an icon and the protocol and/or custom icon(s) are deleted
- * under you), it will return @c NULL.
- *
  * @param icon The buddy icon.
  * @param len  If not @c NULL, the length of the icon data returned will be
  *             set in the location pointed to by this.
  *
- * @return The icon data.
+ * @return A pointer to the icon data.
  */
-const guchar *purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len);
+gconstpointer purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len);
 
 /**
  * Returns an extension corresponding to the buddy icon's file type.
  *
- * This will return the type of a custom icon, if one has been set by the
- * user.  Otherwise, it will return the type of the protocol icon, if
- * available.  If no data is available (which can happen if you're holding on
- * to a reference to an icon and the protocol and/or custom icon(s) are deleted
- * under you), it will return @c NULL.
- *
  * @param icon The buddy icon.
  *
- * @return The icon's extension, or "icon" if unknown.
+ * @return The icon's extension, "icon" if unknown, or @c NULL if
+ *         the image data has disappeared.
  */
-const char *purple_buddy_icon_get_type(const PurpleBuddyIcon *icon);
+const char *purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon);
 
 /*@}*/
 
@@ -192,6 +168,44 @@
 purple_buddy_icons_find(PurpleAccount *account, const char *username);
 
 /**
+ * Returns a boolean indicating if a given contact has a custom buddy icon.
+ *
+ * @param contact The contact
+ *
+ * @return A boolean indicating if @a contact has a custom buddy icon.
+ */
+gboolean
+purple_buddy_icons_has_custom_icon(PurpleContact *contact);
+
+/**
+ * Returns the custom buddy icon image for a contact.
+ *
+ * This function deals with loading the icon from the cache, if
+ * needed, so it should be called in any case where you want the
+ * appropriate icon.
+ *
+ * @param contact The contact
+ *
+ * @return The custom buddy icon image.
+ */
+PurpleStoredImage *
+purple_buddy_icons_find_custom_icon(PurpleContact *contact);
+
+/**
+ * Sets a custom buddy icon for a user.
+ *
+ * This function will deal with saving a record of the icon,
+ * caching the data, etc.
+ *
+ * @param contact   The contact for which to set a custom icon.
+ * @param icon_data The image data of the icon.
+ * @param icon_len  The length of the data in @a icon_data.
+ */
+void
+purple_buddy_icons_set_custom_icon(PurpleContact *contact,
+                                   guchar *icon_data, size_t icon_len);
+
+/**
  * Sets whether or not buddy icon caching is enabled.
  *
  * @param caching TRUE of buddy icon caching should be enabled, or
--- a/libpurple/core.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/core.c	Tue Apr 24 03:57:07 2007 +0000
@@ -31,6 +31,7 @@
 #include "dnsquery.h"
 #include "ft.h"
 #include "idle.h"
+#include "imgstore.h"
 #include "network.h"
 #include "notify.h"
 #include "plugin.h"
@@ -124,6 +125,9 @@
 	purple_plugins_init();
 	purple_plugins_probe(G_MODULE_SUFFIX);
 
+	/* The buddy icon code uses the imgstore, so init it early. */
+	purple_imgstore_init();
+
 	/* Accounts use status and buddy icons, so initialize these before accounts */
 	purple_status_init();
 	purple_buddy_icons_init();
@@ -190,6 +194,7 @@
 	purple_xfers_uninit();
 	purple_proxy_uninit();
 	purple_dnsquery_uninit();
+	purple_imgstore_uninit();
 
 	purple_debug_info("main", "Unloading all plugins\n");
 	purple_plugins_destroy_all();
--- a/libpurple/gaim-compat.h	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/gaim-compat.h	Tue Apr 24 03:57:07 2007 +0000
@@ -351,7 +351,7 @@
 #define gaim_buddy_icon_get_account   purple_buddy_icon_get_account
 #define gaim_buddy_icon_get_username  purple_buddy_icon_get_username
 #define gaim_buddy_icon_get_data      purple_buddy_icon_get_data
-#define gaim_buddy_icon_get_type      purple_buddy_icon_get_type
+#define gaim_buddy_icon_get_type      purple_buddy_icon_get_extension
 
 #define gaim_buddy_icons_set_for_user   purple_buddy_icons_set_for_user
 #define gaim_buddy_icons_find           purple_buddy_icons_find
@@ -962,13 +962,13 @@
 
 #define GaimStoredImage  PurpleStoredImage
 
-#define gaim_imgstore_add           purple_imgstore_add
-#define gaim_imgstore_get           purple_imgstore_get
+#define gaim_imgstore_add           purple_imgstore_add_with_id
+#define gaim_imgstore_get           purple_imgstore_find_by_id
 #define gaim_imgstore_get_data      purple_imgstore_get_data
 #define gaim_imgstore_get_size      purple_imgstore_get_size
 #define gaim_imgstore_get_filename  purple_imgstore_get_filename
-#define gaim_imgstore_ref           purple_imgstore_ref
-#define gaim_imgstore_unref         purple_imgstore_unref
+#define gaim_imgstore_ref           purple_imgstore_ref_by_id
+#define gaim_imgstore_unref         purple_imgstore_unref_by_id
 
 
 /* from log.h */
@@ -2232,7 +2232,7 @@
 #define gaim_value_new_outgoing       purple_value_new_outgoing
 #define gaim_value_destroy            purple_value_destroy
 #define gaim_value_dup                purple_value_dup
-#define gaim_value_get_type           purple_value_get_type
+#define gaim_value_purple_buddy_icon_get_extensionget_type           purple_value_get_type
 #define gaim_value_get_subtype        purple_value_get_subtype
 #define gaim_value_get_specific_type  purple_value_get_specific_type
 #define gaim_value_is_outgoing        purple_value_is_outgoing
--- a/libpurple/imgstore.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/imgstore.c	Tue Apr 24 03:57:07 2007 +0000
@@ -27,140 +27,160 @@
 #include <glib.h>
 #include "debug.h"
 #include "imgstore.h"
+#include "util.h"
 
-static GSList *imgstore = NULL;
+static GHashTable *imgstore;
 static int nextid = 0;
 
 /**
  * Stored image
  *
- * Represents a single IM image awaiting display and/or transmission.
- * Now that this type is basicly private too, these two structs could
- * probably be combined.
+ * NOTE: purple_imgstore_add() creates these without zeroing the memory, so
+ * NOTE: make sure to update that function when adding members.
  */
 struct _PurpleStoredImage
 {
-	char *data;		/**< The image data.		*/
+	int id;
+	guint8 refcount;
 	size_t size;		/**< The image data's size.	*/
 	char *filename;		/**< The filename (for the UI)	*/
+	gpointer data;		/**< The image data.		*/
 };
 
-typedef struct
+PurpleStoredImage *
+purple_imgstore_add(gconstpointer data, size_t size, const char *filename)
 {
-	int id;
-	int refcount;
-	PurpleStoredImage *img;
-} PurpleStoredImagePriv;
-
-/* private functions */
-
-static PurpleStoredImagePriv *purple_imgstore_get_priv(int id) {
-	GSList *tmp = imgstore;
-	PurpleStoredImagePriv *priv = NULL;
-
-	g_return_val_if_fail(id > 0, NULL);
-
-	while (tmp && !priv) {
-		PurpleStoredImagePriv *tmp_priv = tmp->data;
-
-		if (tmp_priv->id == id)
-			priv = tmp_priv;
-
-		tmp = tmp->next;
-	}
-
-	if (!priv)
-		purple_debug(PURPLE_DEBUG_ERROR, "imgstore", "failed to find image id %d\n", id);
-
-	return priv;
-}
-
-static void purple_imgstore_free_priv(PurpleStoredImagePriv *priv) {
-	PurpleStoredImage *img = NULL;
-
-	g_return_if_fail(priv != NULL);
-
-	img = priv->img;
-	if (img) {
-		g_free(img->data);
-		g_free(img->filename);
-		g_free(img);
-	}
-
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "freed image id %d\n", priv->id);
-
-	g_free(priv);
-
-	imgstore = g_slist_remove(imgstore, priv);
-}
-
-/* public functions */
-
-int purple_imgstore_add(const void *data, size_t size, const char *filename) {
-	PurpleStoredImagePriv *priv;
 	PurpleStoredImage *img;
 
 	g_return_val_if_fail(data != NULL, 0);
 	g_return_val_if_fail(size > 0, 0);
 
-	img = g_new0(PurpleStoredImage, 1);
+	img = g_slice_new(PurpleStoredImage);
 	img->data = g_memdup(data, size);
 	img->size = size;
 	img->filename = g_strdup(filename);
+	img->refcount = 1;
+	img->id = 0;
 
-	priv = g_new0(PurpleStoredImagePriv, 1);
-	priv->id = ++nextid;
-	priv->refcount = 1;
-	priv->img = img;
+	return img;
+}
+
+int
+purple_imgstore_add_with_id(gconstpointer data, size_t size, const char *filename)
+{
+	PurpleStoredImage *img = purple_imgstore_add(data, size, filename);
+	img->id = ++nextid;
+
+	g_hash_table_insert(imgstore, GINT_TO_POINTER(img->id), img);
 
-	imgstore = g_slist_append(imgstore, priv);
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "added image id %d\n", priv->id);
+	return img->id;
+}
+
+PurpleStoredImage *purple_imgstore_find_by_id(int id) {
+	PurpleStoredImage *img = g_hash_table_lookup(imgstore, GINT_TO_POINTER(id));
 
-	return priv->id;
+	if (img != NULL)
+		purple_debug_misc("imgstore", "retrieved image id %d\n", img->id);
+
+	return img;
+}
+
+gconstpointer purple_imgstore_get_data(PurpleStoredImage *img) {
+	return img->data;
 }
 
-PurpleStoredImage *purple_imgstore_get(int id) {
-	PurpleStoredImagePriv *priv = purple_imgstore_get_priv(id);
+size_t purple_imgstore_get_size(PurpleStoredImage *img)
+{
+	return img->size;
+}
 
-	g_return_val_if_fail(priv != NULL, NULL);
+const char *purple_imgstore_get_filename(PurpleStoredImage *img)
+{
+	return img->filename;
+}
 
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "retrieved image id %d\n", priv->id);
-
-	return priv->img;
+const char *purple_imgstore_get_extension(PurpleStoredImage *img)
+{
+	return purple_util_get_image_extension(img->data, img->size);
 }
 
-gpointer purple_imgstore_get_data(PurpleStoredImage *i) {
-	return i->data;
+void purple_imgstore_ref_by_id(int id)
+{
+	PurpleStoredImage *img = purple_imgstore_find_by_id(id);
+
+	g_return_if_fail(img != NULL);
+
+	purple_imgstore_ref(img);
+}
+
+void purple_imgstore_unref_by_id(int id)
+{
+	PurpleStoredImage *img = purple_imgstore_find_by_id(id);
+
+	g_return_if_fail(img != NULL);
+
+	purple_imgstore_unref(img);
 }
 
-size_t purple_imgstore_get_size(PurpleStoredImage *i) {
-	return i->size;
-}
+PurpleStoredImage *
+purple_imgstore_ref(PurpleStoredImage *img)
+{
+	g_return_val_if_fail(img != NULL, NULL);
 
-const char *purple_imgstore_get_filename(PurpleStoredImage *i) {
-	return i->filename;
+	img->refcount++;
+
+	return img;
 }
 
-void purple_imgstore_ref(int id) {
-	PurpleStoredImagePriv *priv = purple_imgstore_get_priv(id);
+PurpleStoredImage *
+purple_imgstore_unref(PurpleStoredImage *img)
+{
+	if (img == NULL)
+		return NULL;
+
+	g_return_val_if_fail(img->refcount > 0, NULL);
+
+	img->refcount--;
 
-	g_return_if_fail(priv != NULL);
+	if (img->refcount == 0)
+	{
+		purple_signal_emit(purple_blist_get_handle(),
+		                   "image-deleting", img);
 
-	(priv->refcount)++;
+		if (img->id)
+			g_hash_table_remove(imgstore, GINT_TO_POINTER(img->id));
+		g_slice_free(PurpleStoredImage, img);
+	}
 
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "referenced image id %d (count now %d)\n", priv->id, priv->refcount);
+	return img;
 }
 
-void purple_imgstore_unref(int id) {
-	PurpleStoredImagePriv *priv = purple_imgstore_get_priv(id);
+void *
+purple_imgstore_get_handle()
+{
+	static int handle;
 
-	g_return_if_fail(priv != NULL);
-	g_return_if_fail(priv->refcount > 0);
+	return &handle;
+}
+
+void
+purple_imgstore_init()
+{
+	void *handle = purple_imgstore_get_handle();
 
-	(priv->refcount)--;
-
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "unreferenced image id %d (count now %d)\n", priv->id, priv->refcount);
+	purple_signal_register(handle, "image-deleting",
+	                       purple_marshal_VOID__POINTER, NULL,
+	                       1,
+	                       purple_value_new(PURPLE_TYPE_SUBTYPE,
+	                                        PURPLE_SUBTYPE_STORED_IMAGE));
 
-	if (priv->refcount == 0)
-		purple_imgstore_free_priv(priv);
+	imgstore = g_hash_table_new(g_int_hash, g_int_equal);
 }
+
+void
+purple_imgstore_uninit()
+{
+	g_hash_table_destroy(imgstore);
+
+	purple_signals_unregister_by_instance(purple_blist_get_handle());
+}
--- a/libpurple/imgstore.h	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/imgstore.h	Tue Apr 24 03:57:07 2007 +0000
@@ -34,9 +34,29 @@
 #endif
 
 /**
- * Add an image to the store. The caller owns a reference
- * to the image in the store, and must dereference the image
- * with purple_imgstore_unref for it to be freed.
+ * Add an image to the store.
+ *
+ * The caller owns a reference to the image in the store, and must dereference
+ * the image with purple_imgstore_unref() for it to be freed.
+ *
+ * No ID is allocated when using this function.  If you need to reference the
+ * image by an ID, use purple_imgstore_add_with_id() instead.
+ *
+ * @param data		Pointer to the image data.
+ * @param size		Image data's size.
+ * @param filename	Filename associated with image.
+ *
+ * @return The stored image.
+ */
+PurpleStoredImage *
+purple_imgstore_add(gconstpointer data, size_t size, const char *filename);
+
+/**
+ * Add an image to the store, allocating an ID.
+ *
+ * The caller owns a reference to the image in the store, and must dereference
+ * the image with purple_imgstore_unref_by_id() or purple_imgstore_unref()
+ * for it to be freed.
  *
  * @param data		Pointer to the image data.
  * @param size		Image data's size.
@@ -44,7 +64,7 @@
 
  * @return ID for the image.
  */
-int purple_imgstore_add(const void *data, size_t size, const char *filename);
+int purple_imgstore_add_with_id(gconstpointer data, size_t size, const char *filename);
 
 /**
  * Retrieve an image from the store. The caller does not own a
@@ -54,22 +74,22 @@
  *
  * @return A pointer to the requested image, or NULL if it was not found.
  */
-PurpleStoredImage *purple_imgstore_get(int id);
+PurpleStoredImage *purple_imgstore_find_by_id(int id);
 
 /**
  * Retrieves a pointer to the image's data.
  *
- * @param i	The Image
+ * @param img	The Image
  *
  * @return A pointer to the data, which must not
  *         be freed or modified.
  */
-gpointer purple_imgstore_get_data(PurpleStoredImage *i);
+gconstpointer purple_imgstore_get_data(PurpleStoredImage *img);
 
 /**
  * Retrieves the length of the image's data.
  *
- * @param i	The Image
+ * @param img	The Image
  *
  * @return The size of the data that the pointer returned by
  *         purple_imgstore_get_data points to.
@@ -79,30 +99,82 @@
 /**
  * Retrieves a pointer to the image's filename.
  *
- * @param i	The Image
+ * @param img	The image
  *
  * @return A pointer to the filename, which must not
  *         be freed or modified.
  */
-const char *purple_imgstore_get_filename(PurpleStoredImage *i);
+const char *purple_imgstore_get_filename(PurpleStoredImage *img);
+
+/**
+ * Returns an extension corresponding to the image's file type.
+ *
+ * @param img  The image.
+ *
+ * @return The icon's extension or "icon" if unknown.
+ */
+const char *purple_imgstore_get_extension(PurpleStoredImage *img);
 
 /**
- * Increment the reference count for an image in the store. The
- * image will be removed from the store when the reference count
- * is zero.
+ * Increment the reference count.
+ *
+ * @param img The image.
+ *
+ * @return @a img
+ */
+PurpleStoredImage *
+purple_imgstore_ref(PurpleStoredImage *img);
+
+/**
+ * Decrement the reference count.
+ *
+ * If the reference count reaches zero, the image will be freed.
+ *
+ * @param img The image.
+ *
+ * @return @a img or @c NULL if the reference count reached zero.
+ */
+PurpleStoredImage *
+purple_imgstore_unref(PurpleStoredImage *img);
+
+/**
+ * Increment the reference count using an ID.
+ *
+ * This is a convience wrapper for purple_imgstore_find_by_id() and
+ * purple_imgstore_ref(), so if you have a PurpleStoredImage, it'll
+ * be more efficient to call purple_imgstore_ref() directly.
  *
  * @param id		The ID for the image.
  */
-void purple_imgstore_ref(int id);
+void purple_imgstore_ref_by_id(int id);
 
 /**
- * Decrement the reference count for an image in the store. The
- * image will be removed from the store when the reference count
- * is zero.
+ * Decrement the reference count using an ID.
+ *
+ * This is a convience wrapper for purple_imgstore_find_by_id() and
+ * purple_imgstore_unref(), so if you have a PurpleStoredImage, it'll
+ * be more efficient to call purple_imgstore_unref() directly.
  *
  * @param id		The ID for the image.
  */
-void purple_imgstore_unref(int id);
+void purple_imgstore_unref_by_id(int id);
+
+/**
+ * Returns the image store subsystem handle.
+ *
+ * @return The subsystem handle.
+ */
+void *purple_imgstore_get_handle(void);
+
+/**
+ * Initializes the image store subsystem.
+ */
+void purple_imgstore_init(void);
+
+/**
+ * Uninitializes the image store subsystem.
+ */
+void purple_imgstore_uninit(void);
 
 #ifdef __cplusplus
 }
--- a/libpurple/protocols/jabber/buddy.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Tue Apr 24 03:57:07 2007 +0000
@@ -714,7 +714,7 @@
 	purple_notify_user_info_destroy(user_info);
 
 	while(jbi->vcard_imgids) {
-		purple_imgstore_unref(GPOINTER_TO_INT(jbi->vcard_imgids->data));
+		purple_imgstore_unref_by_id(GPOINTER_TO_INT(jbi->vcard_imgids->data));
 		jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids);
 	}
 
@@ -959,7 +959,7 @@
 
 					data = purple_base64_decode(bintext, &size);
 
-					jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(purple_imgstore_add(data, size, "logo.png")));
+					jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(purple_imgstore_add_with_id(data, size, "logo.png")));
 					g_string_append_printf(info_text,
 							"<b>%s:</b> <img id='%d'><br/>",
 							photo ? _("Photo") : _("Logo"),
--- a/libpurple/protocols/msn/msn.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Tue Apr 24 03:57:07 2007 +0000
@@ -1869,7 +1869,7 @@
 		{
 			char buf[1024];
 			purple_debug_info("msn", "%s is %d bytes\n", photo_url_text, len);
-			id = purple_imgstore_add(url_text, len, NULL);
+			id = purple_imgstore_add_with_id(url_text, len, NULL);
 			g_snprintf(buf, sizeof(buf), "<img id=\"%d\"><br>", id);
 			purple_notify_user_info_prepend_pair(user_info, NULL, buf);
 		}
@@ -1888,7 +1888,7 @@
 	g_free(photo_url_text);
 	g_free(info2_data);
 	if (id != -1)
-		purple_imgstore_unref(id);
+		purple_imgstore_unref_by_id(id);
 #endif
 }
 
--- a/libpurple/protocols/oscar/odc.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/oscar/odc.c	Tue Apr 24 03:57:07 2007 +0000
@@ -353,7 +353,7 @@
 
 			if ((embedded_data != NULL) && (embedded_data->size == size))
 			{
-				imgid = purple_imgstore_add(embedded_data->data, size, src);
+				imgid = purple_imgstore_add_with_id(embedded_data->data, size, src);
 
 				/* Record the image number */
 				images = g_slist_append(images, GINT_TO_POINTER(imgid));
@@ -406,7 +406,7 @@
 	{
 		GSList *l;
 		for (l = images; l != NULL; l = l->next)
-			purple_imgstore_unref(GPOINTER_TO_INT(l->data));
+			purple_imgstore_unref_by_id(GPOINTER_TO_INT(l->data));
 		g_slist_free(images);
 	}
 
--- a/libpurple/protocols/oscar/oscar.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Tue Apr 24 03:57:07 2007 +0000
@@ -4140,11 +4140,11 @@
 		id = g_datalist_get_data(&attribs, "id");
 
 		/* ... if it refers to a valid purple image ... */
-		if (id && (image = purple_imgstore_get(atoi(id)))) {
+		if (id && (image = purple_imgstore_find_by_id(atoi(id)))) {
 			/* ... append the message from start to the tag ... */
 			unsigned long size = purple_imgstore_get_size(image);
 			const char *filename = purple_imgstore_get_filename(image);
-			gpointer imgdata = purple_imgstore_get_data(image);
+			gconstpointer imgdata = purple_imgstore_get_data(image);
 
 			oscar_id++;
 
--- a/libpurple/protocols/sametime/sametime.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Tue Apr 24 03:57:07 2007 +0000
@@ -2698,7 +2698,7 @@
       cid = make_cid(cid);
 
       /* add image to the purple image store */
-      img = purple_imgstore_add(d_dat, d_len, cid);
+      img = purple_imgstore_add_with_id(d_dat, d_len, cid);
       g_free(d_dat);
 
       /* map the cid to the image store identifier */
@@ -2772,7 +2772,7 @@
 
   /* dereference all the imgages */
   while(images) {
-    purple_imgstore_unref(GPOINTER_TO_INT(images->data));
+    purple_imgstore_unref_by_id(GPOINTER_TO_INT(images->data));
     images = g_list_delete_link(images, images);
   }
 }
@@ -3856,7 +3856,7 @@
     /* find the imgstore data by the id tag */
     id = g_datalist_get_data(&attr, "id");
     if(id && *id)
-      img = purple_imgstore_get(atoi(id));
+      img = purple_imgstore_find_by_id(atoi(id));
 
     if(img) {
       char *cid;
@@ -3882,9 +3882,8 @@
 
       /* obtain and base64 encode the image data, and put it in the
 	 mime part */
-      data = purple_imgstore_get_data(img);
       size = purple_imgstore_get_size(img);
-      data = purple_base64_encode(data, (gsize) size);
+      data = purple_base64_encode(purple_imgstore_get_data(img), (gsize) size);
       purple_mime_part_set_data(part, data);
       g_free(data);
 
--- a/libpurple/protocols/silc/buddy.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/silc/buddy.c	Tue Apr 24 03:57:07 2007 +0000
@@ -1716,7 +1716,7 @@
 	}
 
 	t = purple_buddy_icon_get_type((const PurpleBuddyIcon *)&ic);
-	if (!t) {
+	if (!t || !strcmp(t, "icon")) {
 		g_free(ic.data);
 		silc_mime_free(mime);
 		return;
--- a/libpurple/protocols/silc/ops.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/silc/ops.c	Tue Apr 24 03:57:07 2007 +0000
@@ -161,7 +161,7 @@
 		if (channel && !convo)
 			goto out;
 
-		imgid = purple_imgstore_add(data, data_len, "");
+		imgid = purple_imgstore_add_with_id(data, data_len, "");
 		if (imgid) {
 			cflags |= PURPLE_MESSAGE_IMAGES | PURPLE_MESSAGE_RECV;
 			g_snprintf(tmp, sizeof(tmp), "<IMG ID=\"%d\">", imgid);
@@ -177,7 +177,7 @@
 					    sender->nickname : "<unknown>",
 					    tmp, cflags, time(NULL));
 
-			purple_imgstore_unref(imgid);
+			purple_imgstore_unref_by_id(imgid);
 			cflags = 0;
 		}
 		goto out;
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Tue Apr 24 03:57:07 2007 +0000
@@ -1022,7 +1022,7 @@
 					photo_url_text, url_text);
 		} else {
 			purple_debug_info("yahoo", "%s is %d bytes\n", photo_url_text, len);
-			id = purple_imgstore_add(url_text, len, NULL);
+			id = purple_imgstore_add_with_id(url_text, len, NULL);
 			
 			tmp = g_strdup_printf("<img id=\"%d\"><br>", id);
 			purple_notify_user_info_add_pair(user_info, NULL, tmp);
@@ -1234,7 +1234,7 @@
 	g_free(photo_url_text);
 	g_free(info2_data);
 	if (id != -1)
-		purple_imgstore_unref(id);
+		purple_imgstore_unref_by_id(id);
 #endif
 }
 
--- a/libpurple/util.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/util.c	Tue Apr 24 03:57:07 2007 +0000
@@ -2569,6 +2569,27 @@
 	return fp;
 }
 
+const char *
+purple_util_get_image_extension(gpointer data, size_t len)
+{
+	g_return_val_if_fail(data != NULL, NULL);
+	g_return_val_if_fail(len   > 0,    NULL);
+
+	if (len >= 4)
+	{
+		if (!strncmp((char *)data, "BM", 2))
+			return "bmp";
+		else if (!strncmp((char *)data, "GIF8", 4))
+			return "gif";
+		else if (!strncmp((char *)data, "\xff\xd8\xff\xe0", 4))
+			return "jpg";
+		else if (!strncmp((char *)data, "\x89PNG", 4))
+			return "png";
+	}
+
+	return "icon";
+}
+
 gboolean
 purple_program_is_valid(const char *program)
 {
--- a/libpurple/util.h	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/util.h	Tue Apr 24 03:57:07 2007 +0000
@@ -598,6 +598,25 @@
 FILE *purple_mkstemp(char **path, gboolean binary);
 
 /**
+ * Returns an extension corresponding to the image data's file type.
+ *
+ * @param data A pointer to the image data
+ * @param len  The length of the image data
+ *
+ * @return The appropriate extension, or "icon" if unknown.
+ */
+const char *
+purple_util_get_image_extension(gpointer data, size_t len);
+
+/*@}*/
+
+
+/**************************************************************************/
+/** @name Environment Detection Functions                                 */
+/**************************************************************************/
+/*@{*/
+
+/**
  * Checks if the given program name is valid and executable.
  *
  * @param program The file name of the application.
@@ -1118,6 +1137,7 @@
  * inherit the handlers of the parent.
  */
 void purple_restore_default_signal_handlers(void);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/value.h	Tue Apr 24 03:56:16 2007 +0000
+++ b/libpurple/value.h	Tue Apr 24 03:57:07 2007 +0000
@@ -76,7 +76,8 @@
 	PURPLE_SUBTYPE_XFER,
 	PURPLE_SUBTYPE_SAVEDSTATUS,
 	PURPLE_SUBTYPE_XMLNODE,
-	PURPLE_SUBTYPE_USERINFO
+	PURPLE_SUBTYPE_USERINFO,
+	PURPLE_SUBTYPE_STORED_IMAGE
 } PurpleSubType;
 
 /**
--- a/pidgin/gtkblist.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/pidgin/gtkblist.c	Tue Apr 24 03:57:07 2007 +0000
@@ -2155,7 +2155,7 @@
 
 
 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
-		gboolean scaled, gboolean greyed, gboolean custom)
+		gboolean scaled, gboolean greyed)
 {
 	GdkPixbuf *buf, *ret = NULL;
 	GdkPixbufLoader *loader;
@@ -2166,6 +2166,7 @@
 	PurpleChat *chat = NULL;
 	PurpleAccount *account = NULL;
 	PurplePluginProtocolInfo *prpl_info = NULL;
+	PurpleStoredImage *custom_img;
 
 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
@@ -2190,19 +2191,11 @@
 		return NULL;
 #endif
 
-	if (custom) {
-		const char *file = purple_blist_node_get_string((PurpleBlistNode*)purple_buddy_get_contact(buddy),
-							"custom_buddy_icon");
-		if (file && *file) {
-			char *contents;
-			GError *err  = NULL;
-			if (!g_file_get_contents(file, &contents, &len, &err)) {
-				purple_debug_info("custom -icon", "Could not open custom-icon %s for %s\n",
-							file, purple_buddy_get_name(buddy), err->message);
-				g_error_free(err);
-			} else
-				data = (const guchar*)contents;
-		}
+	custom_img = purple_buddy_icons_find_custom_icon(purple_buddy_get_contact(buddy));
+	if (custom_img)
+	{
+		data = purple_imgstore_get_data(custom_img);
+		len = purple_imgstore_get_size(custom_img);
 	}
 
 	if (data == NULL) {
@@ -2212,7 +2205,6 @@
 					return NULL;
 			data = purple_buddy_icon_get_data(icon, &len);
 		}
-		custom = FALSE;  /* We are not using the custom icon */
 	}
 
 	if(data == NULL)
@@ -2221,13 +2213,14 @@
 	loader = gdk_pixbuf_loader_new();
 	gdk_pixbuf_loader_write(loader, data, len, NULL);
 	gdk_pixbuf_loader_close(loader, NULL);
+
+	purple_imgstore_unref(custom_img);
+
 	buf = gdk_pixbuf_loader_get_pixbuf(loader);
 	if (buf)
 		g_object_ref(G_OBJECT(buf));
 	g_object_unref(G_OBJECT(loader));
 
-	if (custom)
-		g_free((void*)data);
 	if (buf) {
 		int orig_width, orig_height;
 		int scale_width, scale_height;
@@ -2335,7 +2328,7 @@
 	}
 
 	td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
-	td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE, TRUE);
+	td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
 	td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
 	tooltip_text = pidgin_get_tooltip_text(node, full);
 	td->layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
@@ -4893,7 +4886,7 @@
 	status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
 						PIDGIN_STATUS_ICON_SMALL);
 
-	avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE, TRUE);
+	avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
 	if (!avatar) {
 		g_object_ref(G_OBJECT(gtkblist->empty_avatar));
 		avatar = gtkblist->empty_avatar;
@@ -5078,7 +5071,7 @@
 		status = pidgin_blist_get_status_icon(node,
 				 PIDGIN_STATUS_ICON_SMALL);
 		emblem = pidgin_blist_get_emblem(node);
-		avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE, TRUE);
+		avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
 
 		mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
 
--- a/pidgin/gtkconv.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/pidgin/gtkconv.c	Tue Apr 24 03:57:07 2007 +0000
@@ -2538,7 +2538,7 @@
 
 	g_return_if_fail(conv != NULL);
 
-	ext = purple_buddy_icon_get_type(purple_conv_im_get_icon(PURPLE_CONV_IM(conv)));
+	ext = purple_buddy_icon_get_extension(purple_conv_im_get_icon(PURPLE_CONV_IM(conv)));
 
 	buf = g_strdup_printf("%s.%s", purple_normalize(conv->account, conv->name), ext);
 
--- a/pidgin/gtkimhtmltoolbar.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Tue Apr 24 03:57:07 2007 +0000
@@ -480,7 +480,7 @@
 
 	name = strrchr(filename, G_DIR_SEPARATOR) + 1;
 
-	id = purple_imgstore_add(filedata, size, name);
+	id = purple_imgstore_add_with_id(filedata, size, name);
 	g_free(filedata);
 
 	if (id == 0) {
@@ -499,7 +499,7 @@
 	gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)),
 									 &iter, ins);
 	gtk_imhtml_insert_image_at_iter(GTK_IMHTML(toolbar->imhtml), id, &iter);
-	purple_imgstore_unref(id);
+	purple_imgstore_unref_by_id(id);
 }
 
 
--- a/pidgin/gtkmain.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/pidgin/gtkmain.c	Tue Apr 24 03:57:07 2007 +0000
@@ -145,7 +145,7 @@
 static void sighandler(int sig);
 
 /**
- * Reap all our dead children.  Sometimes Purple forks off a separate
+ * Reap all our dead children.  Sometimes libpurple forks off a separate
  * process to do some stuff.  When that process exits we are
  * informed about it so that we can call waitpid() and let it
  * stop being a zombie.
@@ -160,7 +160,7 @@
  * it continues with the initialization process.  This means that
  * we have a race condition where GStreamer is waitpid()ing for its
  * child to die and we're catching the SIGCHLD signal.  If GStreamer
- * is awarded the zombied process then everything is ok.  But if Purple
+ * is awarded the zombied process then everything is ok.  But if libpurple
  * reaps the zombie process then the GStreamer initialization sequence
  * fails.
  *
@@ -677,7 +677,7 @@
 	{
 		char *old = g_strconcat(purple_home_dir(),
 		                        G_DIR_SEPARATOR_S ".gaim", NULL);
-		char *text = _(
+		const char *text = _(
 			"Pidgin encountered errors migrating your settings "
 			"from %s to %s. Please investigate and complete the "
 			"migration by hand.");
--- a/pidgin/gtkutils.c	Tue Apr 24 03:56:16 2007 +0000
+++ b/pidgin/gtkutils.c	Tue Apr 24 03:57:07 2007 +0000
@@ -78,12 +78,12 @@
 }
 
 static GtkIMHtmlFuncs gtkimhtml_cbs = {
-	(GtkIMHtmlGetImageFunc)purple_imgstore_get,
+	(GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id,
 	(GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data,
 	(GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size,
 	(GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename,
-	purple_imgstore_ref,
-	purple_imgstore_unref,
+	purple_imgstore_ref_by_id,
+	purple_imgstore_unref_by_id,
 };
 
 void
@@ -1350,13 +1350,13 @@
 
 			return;
 		}
-		id = purple_imgstore_add(filedata, size, data->filename);
+		id = purple_imgstore_add_with_id(filedata, size, data->filename);
 		g_free(filedata);
 
 		gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
 						 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
 		gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
-		purple_imgstore_unref(id);
+		purple_imgstore_unref_by_id(id);
 
 		break;
 	}