Rework the buddy icon subsystem to use the imgstore subsystem, and modify the rlaager.gaim_migration

Tue, 24 Apr 2007 03:57:07 +0000

author
Richard Laager <rlaager@pidgin.im>
date
Tue, 24 Apr 2007 03:57:07 +0000
branch
rlaager.gaim_migration
changeset 16437
7ff7c3405ea2
parent 16436
a2852f053d3f
child 16438
64e892ac6180

Rework the buddy icon subsystem to use the imgstore subsystem, and modify the
imgstore subsystem to not require IDs for everything.

libpurple/buddyicon.c file | annotate | diff | comparison | revisions
libpurple/buddyicon.h file | annotate | diff | comparison | revisions
libpurple/core.c file | annotate | diff | comparison | revisions
libpurple/gaim-compat.h file | annotate | diff | comparison | revisions
libpurple/imgstore.c file | annotate | diff | comparison | revisions
libpurple/imgstore.h file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/buddy.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/msn.c file | annotate | diff | comparison | revisions
libpurple/protocols/oscar/odc.c file | annotate | diff | comparison | revisions
libpurple/protocols/oscar/oscar.c file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/sametime.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc/buddy.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc/ops.c file | annotate | diff | comparison | revisions
libpurple/protocols/yahoo/yahoo_profile.c file | annotate | diff | comparison | revisions
libpurple/util.c file | annotate | diff | comparison | revisions
libpurple/util.h file | annotate | diff | comparison | revisions
libpurple/value.h file | annotate | diff | comparison | revisions
pidgin/gtkblist.c file | annotate | diff | comparison | revisions
pidgin/gtkconv.c file | annotate | diff | comparison | revisions
pidgin/gtkimhtmltoolbar.c file | annotate | diff | comparison | revisions
pidgin/gtkmain.c file | annotate | diff | comparison | revisions
pidgin/gtkutils.c file | annotate | diff | comparison | revisions
--- 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;
 	}

mercurial