changeset 16387:414b9c9405fd

propagate from branch 'im.pidgin.pidgin' (head efad0dfb8f3cfbe4b837192b16689470c201d03a) to branch 'im.pidgin.rlaager.gaim_migration' (head bfd4aafa5fc4e55af7283d4dd1b7d825cd6e9f62)
author Richard Laager <rlaager@wiktel.com>
date Wed, 25 Apr 2007 00:57:53 +0000
parents e0c9a46b459f (diff) 7f8fd78bf62b (current diff)
children d4c79390d424
files pidgin/gtkimhtmltoolbar.c
diffstat 38 files changed, 1767 insertions(+), 877 deletions(-) [+]
line wrap: on
line diff
--- a/finch/finch.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/finch/finch.c	Wed Apr 25 00:57:53 2007 +0000
@@ -272,9 +272,32 @@
 	 * Fire up this baby.
 	 */
 
-	/* Because we don't want debug-messages to show up and corrup the display */
+	/* We don't want debug-messages to show up and corrupt the display */
 	purple_debug_set_enabled(debug_enabled);
 
+	/* If we're using a custom configuration directory, we
+	 * do NOT want to migrate, or weird things will happen. */
+	if (opt_config_dir_arg == NULL)
+	{
+		if (!purple_core_migrate())
+		{
+			char *old = g_strconcat(purple_home_dir(),
+			                        G_DIR_SEPARATOR_S ".gaim", NULL);
+			char *text = g_strdup_printf(_(
+				"%s encountered errors migrating your settings "
+				"from %s to %s. Please investigate and complete the "
+				"migration by hand."), _("Finch"),
+				old, purple_user_dir());
+
+			g_free(old);
+
+			purple_print_utf8_to_console(stderr, text);
+			g_free(text);
+
+			return 0;
+		}
+	}
+
 	purple_core_set_ui_ops(gnt_core_get_ui_ops());
 	purple_eventloop_set_ui_ops(gnt_eventloop_get_ui_ops());
 	purple_idle_set_ui_ops(finch_idle_get_ui_ops());
--- a/libpurple/blist.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/blist.c	Wed Apr 25 00:57:53 2007 +0000
@@ -605,6 +605,9 @@
 	}
 
 	xmlnode_free(purple);
+
+	/* This tells the buddy icon code to do its thing. */
+	purple_buddy_icons_blist_loaded_cb();
 }
 
 
@@ -1141,20 +1144,12 @@
 {
 	g_return_if_fail(buddy != NULL);
 
-	if (buddy->icon != icon) {
-		if (buddy->icon != NULL)
-			purple_buddy_icon_unref(buddy->icon);
-		
+	if (buddy->icon != icon)
+	{
+		purple_buddy_icon_unref(buddy->icon);
 		buddy->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL);
 	}
 
-	if (buddy->icon)
-		purple_buddy_icon_cache(icon, buddy);
-	else
-		purple_buddy_icon_uncache(buddy);
-
-	purple_blist_schedule_save();
-
 	purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
 
 	purple_blist_update_buddy_icon(buddy);
@@ -1783,9 +1778,6 @@
 	contact = (PurpleContact *)cnode;
 	group = (PurpleGroup *)gnode;
 
-	/* Delete any buddy icon. */
-	purple_buddy_icon_uncache(buddy);
-
 	/* Remove the node from its parent */
 	if (node->prev)
 		node->prev->next = node->next;
@@ -1831,8 +1823,7 @@
 	purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy);
 
 	/* Delete the node */
-	if (buddy->icon != NULL)
-		purple_buddy_icon_unref(buddy->icon);
+	purple_buddy_icon_unref(buddy->icon);
 	g_hash_table_destroy(buddy->node.settings);
 	purple_presence_remove_buddy(buddy->presence, buddy);
 	purple_presence_destroy(buddy->presence);
--- a/libpurple/buddyicon.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/buddyicon.c	Wed Apr 25 00:57:53 2007 +0000
@@ -24,26 +24,249 @@
  */
 #include "internal.h"
 #include "buddyicon.h"
+#include "cipher.h"
 #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.          */
+	PurpleStoredImage *img;    /**< The id of the stored image with the
+	                                the icon data.                       */
+	char *username;            /**< The username the icon belongs to.    */
+	char *checksum;            /**< The protocol checksum.               */
+	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;
 
+/* For ~/.gaim to ~/.purple migration. */
+static char *old_icons_dir = NULL;
+
+static void
+ref_filename(const char *filename)
+{
+	int refs;
+
+	g_return_if_fail(filename != NULL);
+
+	refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
+
+	g_hash_table_insert(icon_file_cache, g_strdup(filename),
+	                    GINT_TO_POINTER(refs + 1));
+}
+
+static void
+unref_filename(const char *filename)
+{
+	int refs;
+
+	if (filename == NULL)
+		return;
+
+	refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
+
+	if (refs == 1)
+	{
+		g_hash_table_remove(icon_file_cache, filename);
+	}
+	else
+	{
+		g_hash_table_insert(icon_file_cache, g_strdup(filename),
+		                    GINT_TO_POINTER(refs - 1));
+	}
+}
+
+static char *
+purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len)
+{
+	PurpleCipherContext *context;
+	gchar digest[41];
+
+	context = purple_cipher_context_new_by_name("sha1", NULL);
+	if (context == NULL)
+	{
+		purple_debug_error("buddyicon", "Could not find sha1 cipher\n");
+		g_return_val_if_reached(NULL);
+	}
+
+	/* Hash the icon data */
+	purple_cipher_context_append(context, icon_data, icon_len);
+	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
+	{
+		purple_debug_error("buddyicon", "Failed to get SHA-1 digest.\n");
+		g_return_val_if_reached(NULL);
+	}
+	purple_cipher_context_destroy(context);
+
+	/* Return the filename */
+	return g_strdup_printf("%s.%s", digest,
+	                       purple_util_get_image_extension(icon_data, icon_len));
+}
+
+static void
+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, purple_imgstore_get_filename(img), NULL);
+
+	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
+	{
+		purple_debug_info("buddyicon", "Creating icon cache directory.\n");
+
+		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
+		{
+			purple_debug_error("buddyicon",
+			                   "Unable to create directory %s: %s\n",
+			                   dirname, strerror(errno));
+		}
+	}
+
+	if ((file = g_fopen(path, "wb")) != NULL)
+	{
+		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));
+		}
+		else
+			purple_debug_info("buddyicon", "Wrote cache file: %s\n", path);
+
+		fclose(file);
+	}
+	else
+	{
+		purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
+		                   path, strerror(errno));
+		g_free(path);
+		return;
+	}
+	g_free(path);
+}
+
+static void
+purple_buddy_icon_data_uncache_file(const char *filename)
+{
+	const char *dirname;
+	char *path;
+
+	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 (GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename)))
+		return;
+
+	dirname  = purple_buddy_icons_get_cache_dir();
+	path = g_build_filename(dirname, filename, NULL);
+
+	if (g_file_test(path, G_FILE_TEST_EXISTS))
+	{
+		if (g_unlink(path))
+		{
+			purple_debug_error("buddyicon", "Failed to delete %s: %s\n",
+			                   path, strerror(errno));
+		}
+		else
+		{
+			purple_debug_info("buddyicon", "Deleted cache file: %s\n", path);
+		}
+	}
+
+	g_free(path);
+}
+
+static gboolean
+value_equals(gpointer key, gpointer value, gpointer user_data)
+{
+	return (value == user_data);
+}
+
+static void
+image_deleting_cb(PurpleStoredImage *img, gpointer data)
+{
+	const char *filename = purple_imgstore_get_filename(img);
+
+	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);
+
+		/* We could make this O(1) by using another hash table, but
+		 * this is probably good enough. */
+		g_hash_table_foreach_remove(custom_icon_cache, value_equals, img);
+	}
+}
+
+static PurpleStoredImage *
+purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len, const char *filename)
+{
+	char *file;
+	PurpleStoredImage *img;
+
+	g_return_val_if_fail(icon_data != NULL, NULL);
+	g_return_val_if_fail(icon_len  > 0,     NULL);
+
+	if (filename == NULL)
+	{
+		file = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		if (file == NULL)
+			return NULL;
+	}
+	else
+		file = g_strdup(filename);
+
+	if ((img = g_hash_table_lookup(icon_data_cache, file)))
+	{
+		g_free(file);
+		return purple_imgstore_ref(img);
+	}
+
+	img = purple_imgstore_add(icon_data, icon_len, file);
+
+	/* This will take ownership of file and g_free it either now or later. */
+	g_hash_table_insert(icon_data_cache, file, img);
+
+	purple_buddy_icon_data_cache(img);
+
+	return img;
+}
+
 static PurpleBuddyIcon *
 purple_buddy_icon_create(PurpleAccount *account, const char *username)
 {
 	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);
 
-	purple_buddy_icon_set_account(icon,  account);
-	purple_buddy_icon_set_username(icon, username);
+	icon->account = account;
+	icon->username = g_strdup(username);
+	icon->checksum = NULL;
 
 	icon_cache = g_hash_table_lookup(account_cache, account);
 
@@ -55,13 +278,14 @@
 	}
 
 	g_hash_table_insert(icon_cache,
-	                   (char *)purple_buddy_icon_get_username(icon), icon);
+	                    (char *)purple_buddy_icon_get_username(icon), icon);
 	return icon;
 }
 
 PurpleBuddyIcon *
 purple_buddy_icon_new(PurpleAccount *account, const char *username,
-					void *icon_data, size_t icon_len)
+                      void *icon_data, size_t icon_len,
+                      const char *checksum)
 {
 	PurpleBuddyIcon *icon;
 
@@ -70,76 +294,25 @@
 	g_return_val_if_fail(icon_data != NULL, NULL);
 	g_return_val_if_fail(icon_len  > 0,    NULL);
 
+	/* purple_buddy_icons_find() does allocation, so be
+	 * sure to update it as well when members are added. */
 	icon = purple_buddy_icons_find(account, username);
 
+	/* purple_buddy_icon_create() sets account & username */
 	if (icon == NULL)
 		icon = purple_buddy_icon_create(account, username);
 
-	purple_buddy_icon_ref(icon);
-	purple_buddy_icon_set_data(icon, icon_data, icon_len);
-	purple_buddy_icon_set_path(icon, NULL);
+	/* Take a reference for the caller of this function. */
+	icon->ref_count = 1;
 
-	/* purple_buddy_icon_set_data() makes blist.c or
-	 * conversation.c, or both, take a reference.
-	 *
-	 * Plus, we leave one for the caller of this function.
-	 */
+	/* purple_buddy_icon_set_data() sets img, but it
+	 * references img first, so we need to initialize it */
+	icon->img = NULL;
+	purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
 
 	return icon;
 }
 
-void
-purple_buddy_icon_destroy(PurpleBuddyIcon *icon)
-{
-	PurpleConversation *conv;
-	PurpleAccount *account;
-	GHashTable *icon_cache;
-	const char *username;
-	GSList *sl, *list;
-
-	g_return_if_fail(icon != NULL);
-
-	if (icon->ref_count > 0)
-	{
-		/* If the ref count is greater than 0, then we weren't called from
-		 * purple_buddy_icon_unref(). So we go through and ask everyone to
-		 * unref us. Then we return, since we know somewhere along the
-		 * line we got called recursively by one of the unrefs, and the
-		 * icon is already destroyed.
-		 */
-		account  = purple_buddy_icon_get_account(icon);
-		username = purple_buddy_icon_get_username(icon);
-
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account);
-		if (conv != NULL)
-			purple_conv_im_set_icon(PURPLE_CONV_IM(conv), NULL);
-
-		for (list = sl = purple_find_buddies(account, username); sl != NULL;
-			 sl = sl->next)
-		{
-			PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
-
-			purple_buddy_set_icon(buddy, NULL);
-		}
-
-		g_slist_free(list);
-
-		return;
-	}
-
-	icon_cache = g_hash_table_lookup(account_cache,
-									 purple_buddy_icon_get_account(icon));
-
-	if (icon_cache != NULL)
-		g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
-
-	g_free(icon->username);
-	g_free(icon->data);
-	g_free(icon->path);
-	PURPLE_DBUS_UNREGISTER_POINTER(icon);
-	g_free(icon);
-}
-
 PurpleBuddyIcon *
 purple_buddy_icon_ref(PurpleBuddyIcon *icon)
 {
@@ -153,14 +326,26 @@
 PurpleBuddyIcon *
 purple_buddy_icon_unref(PurpleBuddyIcon *icon)
 {
-	g_return_val_if_fail(icon != NULL, NULL);
+	if (icon == NULL)
+		return NULL;
+
 	g_return_val_if_fail(icon->ref_count > 0, NULL);
 
 	icon->ref_count--;
 
 	if (icon->ref_count == 0)
 	{
-		purple_buddy_icon_destroy(icon);
+		GHashTable *icon_cache = g_hash_table_lookup(account_cache, purple_buddy_icon_get_account(icon));
+
+		if (icon_cache != NULL)
+			g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
+
+		g_free(icon->username);
+		g_free(icon->checksum);
+		purple_imgstore_unref(icon->img);
+
+		PURPLE_DBUS_UNREGISTER_POINTER(icon);
+		g_slice_free(PurpleBuddyIcon, icon);
 
 		return NULL;
 	}
@@ -174,6 +359,7 @@
 	PurpleConversation *conv;
 	PurpleAccount *account;
 	const char *username;
+	PurpleBuddyIcon *icon_to_set;
 	GSList *sl, *list;
 
 	g_return_if_fail(icon != NULL);
@@ -181,12 +367,52 @@
 	account  = purple_buddy_icon_get_account(icon);
 	username = purple_buddy_icon_get_username(icon);
 
-	for (list = sl = purple_find_buddies(account, username); sl != NULL;
-		 sl = sl->next)
+	/* 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;
+		char *old_icon;
 
-		purple_buddy_set_icon(buddy, icon);
+		purple_buddy_set_icon(buddy, icon_to_set);
+
+
+		old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy,
+		                                                 "buddy_icon"));
+		if (icon->img && purple_buddy_icons_is_caching())
+		{
+			const char *filename = purple_imgstore_get_filename(icon->img);
+			purple_blist_node_set_string((PurpleBlistNode *)buddy,
+			                             "buddy_icon",
+			                             filename);
+
+			if (icon->checksum && *icon->checksum)
+			{
+				purple_blist_node_set_string((PurpleBlistNode *)buddy,
+				                             "icon_checksum",
+				                             icon->checksum);
+			}
+			else
+			{
+				purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
+				                                 "icon_checksum");
+			}
+			ref_filename(filename);
+		}
+		else
+		{
+			purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
+			purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum");
+		}
+		unref_filename(old_icon);
+		g_free(old_icon);
 	}
 
 	g_slist_free(list);
@@ -194,160 +420,28 @@
 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account);
 
 	if (conv != NULL)
-		purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon);
-}
-
-static void
-delete_icon_cache_file(const char *dirname, const char *old_icon)
-{
-	struct stat st;
-
-	g_return_if_fail(dirname != NULL);
-	g_return_if_fail(old_icon != NULL);
-
-	if (g_stat(old_icon, &st) == 0)
-		g_unlink(old_icon);
-	else
-	{
-		char *filename = g_build_filename(dirname, old_icon, NULL);
-		if (g_stat(filename, &st) == 0)
-			g_unlink(filename);
-		g_free(filename);
-	}
-	purple_debug_info("buddyicon", "Uncached file %s\n", old_icon);
+		purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon_to_set);
 }
 
 void
-purple_buddy_icon_cache(PurpleBuddyIcon *icon, PurpleBuddy *buddy)
+purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data,
+                           size_t len, const char *checksum)
 {
-	const guchar *data;
-	const char *dirname;
-	char *random;
-	char *filename;
-	const char *old_icon;
-	size_t len = 0;
-	FILE *file = NULL;
-
-	g_return_if_fail(icon  != NULL);
-	g_return_if_fail(buddy != NULL);
-
-	if (!purple_buddy_icons_is_caching())
-		return;
-
-	data = purple_buddy_icon_get_data(icon, &len);
-
-	random   = g_strdup_printf("%x", g_random_int());
-	dirname  = purple_buddy_icons_get_cache_dir();
-	filename = g_build_filename(dirname, random, NULL);
-	old_icon = purple_blist_node_get_string((PurpleBlistNode*)buddy, "buddy_icon");
-
-	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
-	{
-		purple_debug_info("buddyicon", "Creating icon cache directory.\n");
-
-		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
-		{
-			purple_debug_error("buddyicon",
-							 "Unable to create directory %s: %s\n",
-							 dirname, strerror(errno));
-		}
-	}
-
-	if ((file = g_fopen(filename, "wb")) != NULL)
-	{
-		fwrite(data, 1, len, file);
-		fclose(file);
-		purple_debug_info("buddyicon", "Wrote file %s\n", filename);
-	}
-	else
-	{
-		purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
-						 filename, strerror(errno));
-		g_free(filename);
-		g_free(random);
-		return;
-	}
-
-	g_free(filename);
+	PurpleStoredImage *old_img;
 
-	if (old_icon != NULL)
-		delete_icon_cache_file(dirname, old_icon);
-
-	purple_blist_node_set_string((PurpleBlistNode *)buddy, "buddy_icon", random);
-
-	g_free(random);
-}
-
-void
-purple_buddy_icon_uncache(PurpleBuddy *buddy)
-{
-	const char *old_icon;
-
-	g_return_if_fail(buddy != NULL);
-
-	old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon");
-
-	if (old_icon != NULL)
-		delete_icon_cache_file(purple_buddy_icons_get_cache_dir(), old_icon);
-
-	purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
-
-	/* Unset the icon in case this function is called from
-	 * something other than purple_buddy_set_icon(). */
-	if (buddy->icon != NULL)
-	{
-		purple_buddy_icon_unref(buddy->icon);
-		buddy->icon = NULL;
-	}
-}
-
-void
-purple_buddy_icon_set_account(PurpleBuddyIcon *icon, PurpleAccount *account)
-{
-	g_return_if_fail(icon    != NULL);
-	g_return_if_fail(account != NULL);
-
-	icon->account = account;
-}
-
-void
-purple_buddy_icon_set_username(PurpleBuddyIcon *icon, const char *username)
-{
-	g_return_if_fail(icon     != NULL);
-	g_return_if_fail(username != NULL);
-
-	g_free(icon->username);
-	icon->username = g_strdup(username);
-}
-
-void
-purple_buddy_icon_set_data(PurpleBuddyIcon *icon, void *data, size_t len)
-{
 	g_return_if_fail(icon != NULL);
 
-	g_free(icon->data);
+	old_img = icon->img;
+	icon->img = NULL;
 
 	if (data != NULL && len > 0)
-	{
-		icon->data = g_memdup(data, len);
-		icon->len  = len;
-	}
-	else
-	{
-		icon->data = NULL;
-		icon->len  = 0;
-	}
+		icon->img = purple_buddy_icon_data_new(data, len, NULL);
+
+	icon->checksum = g_strdup(checksum);
 
 	purple_buddy_icon_update(icon);
-}
 
-void 
-purple_buddy_icon_set_path(PurpleBuddyIcon *icon, const gchar *path)
-{
-	g_return_if_fail(icon != NULL);
-	
-	g_free(icon->path);
-	icon->path = g_strdup(path);
+	purple_imgstore_unref(old_img);
 }
 
 PurpleAccount *
@@ -366,121 +460,460 @@
 	return icon->username;
 }
 
-const guchar *
+const char *
+purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon)
+{
+	g_return_val_if_fail(icon != NULL, NULL);
+
+	return icon->checksum;
+}
+
+gconstpointer
 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
 {
 	g_return_val_if_fail(icon != NULL, NULL);
 
-	if (len != NULL)
-		*len = icon->len;
-
-	return icon->data;
-}
+	if (icon->img)
+	{
+		if (len != NULL)
+			*len = purple_imgstore_get_size(icon->img);
 
-const char *
-purple_buddy_icon_get_path(PurpleBuddyIcon *icon)
-{
-	g_return_val_if_fail(icon != NULL, NULL);
+		return purple_imgstore_get_data(icon->img);
+	}
 
-	return icon->path;
+	return NULL;
 }
 
 const char *
-purple_buddy_icon_get_type(const PurpleBuddyIcon *icon)
+purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon)
 {
-	const void *data;
-	size_t len;
-
-	g_return_val_if_fail(icon != NULL, NULL);
-
-	data = purple_buddy_icon_get_data(icon, &len);
-
-	/* TODO: Find a way to do this with GDK */
-	if (len >= 4)
-	{
-		if (!strncmp(data, "BM", 2))
-			return "bmp";
-		else if (!strncmp(data, "GIF8", 4))
-			return "gif";
-		else if (!strncmp(data, "\xff\xd8\xff\xe0", 4))
-			return "jpg";
-		else if (!strncmp(data, "\x89PNG", 4))
-			return "png";
-	}
+	if (icon->img != NULL)
+		return purple_imgstore_get_extension(icon->img);
 
 	return NULL;
 }
 
 void
 purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
-							void *icon_data, size_t icon_len)
+                                void *icon_data, size_t icon_len,
+                                const char *checksum)
 {
 	g_return_if_fail(account  != NULL);
 	g_return_if_fail(username != NULL);
 
-	if (icon_data == NULL || icon_len == 0)
+	if (icon_data != NULL && icon_len > 0)
 	{
-		PurpleBuddyIcon *buddy_icon;
+		PurpleBuddyIcon *icon;
 
-		buddy_icon = purple_buddy_icons_find(account, username);
+		icon = purple_buddy_icons_find(account, username);
 
-		if (buddy_icon != NULL)
-			purple_buddy_icon_destroy(buddy_icon);
+		if (icon != NULL)
+			purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
 	}
 	else
 	{
-		PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len);
+		PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, checksum);
 		purple_buddy_icon_unref(icon);
 	}
 }
 
+const char *
+purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy)
+{
+	return purple_blist_node_get_string((PurpleBlistNode*)buddy,
+	                                    "icon_checksum");
+}
+
+static gboolean
+read_icon_file(const char *path, guchar **data, size_t *len)
+{
+	GError *err = NULL;
+
+	if (!g_file_get_contents(path, (gchar **)data, len, &err))
+	{
+		purple_debug_error("buddyicon", "Error reading %s: %s\n",
+		                   path, err->message);
+		g_error_free(err);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 PurpleBuddyIcon *
 purple_buddy_icons_find(PurpleAccount *account, const char *username)
 {
 	GHashTable *icon_cache;
-	PurpleBuddyIcon *ret = NULL;
-	char *filename = NULL;
+	PurpleBuddyIcon *icon = NULL;
 
 	g_return_val_if_fail(account  != NULL, NULL);
 	g_return_val_if_fail(username != NULL, NULL);
 
 	icon_cache = g_hash_table_lookup(account_cache, account);
 
-	if ((icon_cache == NULL) || ((ret = g_hash_table_lookup(icon_cache, username)) == NULL)) {
-		const char *file;
-		struct stat st;
+	if ((icon_cache == NULL) || ((icon = g_hash_table_lookup(icon_cache, username)) == NULL))
+	{
 		PurpleBuddy *b = purple_find_buddy(account, username);
+		const char *protocol_icon_file;
+		const char *dirname;
+		gboolean caching;
+		guchar *data;
+		size_t len;
 
 		if (!b)
 			return NULL;
 
-		if ((file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon")) == NULL)
+		protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
+
+		if (protocol_icon_file == NULL)
 			return NULL;
 
-		if (!g_stat(file, &st))
-			filename = g_strdup(file);
-		else
-			filename = g_build_filename(purple_buddy_icons_get_cache_dir(), file, NULL);
+		dirname = purple_buddy_icons_get_cache_dir();
+
+		caching = purple_buddy_icons_is_caching();
+		/* By disabling caching temporarily, we avoid a loop
+		 * and don't have to add special code through several
+		 * functions. */
+		purple_buddy_icons_set_caching(FALSE);
+
+		if (protocol_icon_file != NULL)
+		{
+			char *path = g_build_filename(dirname, protocol_icon_file, NULL);
+			if (read_icon_file(path, &data, &len))
+			{
+				const char *checksum;
+
+				if (icon == NULL)
+					icon = purple_buddy_icon_create(account, username);
+				icon->ref_count = 0;
+				icon->img = NULL;
+				checksum = g_strdup(purple_blist_node_get_string((PurpleBlistNode*)b, "icon_checksum"));
+				purple_buddy_icon_set_data(icon, data, len, checksum);
+				g_free(data);
+			}
+			g_free(path);
+		}
+
+		purple_buddy_icons_set_caching(caching);
+	}
+
+	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_free(data);
+		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;
+	PurpleBlistNode *child;
+
+	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 && purple_buddy_icons_is_caching())
+	{
+		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_hash_table_insert(custom_icon_cache, contact, img);
+
+	for (child = contact->node.child ; child ; child = child->next)
+	{
+		PurpleBuddy *buddy;
+		PurpleConversation *conv;
+
+		if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
+			continue;
 
-		if (!g_stat(filename, &st)) {
-			FILE *f = g_fopen(filename, "rb");
-			if (f) {
-				char *data = g_malloc(st.st_size);
-				fread(data, 1, st.st_size, f);
-				fclose(f);
-				ret = purple_buddy_icon_create(account, username);
-				purple_buddy_icon_ref(ret);
-				purple_buddy_icon_set_data(ret, data, st.st_size);
-				purple_buddy_icon_unref(ret);
-				g_free(data);
-				g_free(filename);
-				return ret;
+		buddy = (PurpleBuddy *)child;
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+		                                             purple_buddy_get_name(buddy),
+		                                             purple_buddy_get_account(buddy));
+		if (conv)
+			purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
+
+		purple_blist_update_buddy_icon(buddy);
+	}
+
+	if (old_img)
+		purple_imgstore_unref(old_img);
+	else
+	{
+		/* The old icon may not have been loaded into memory.  In that
+		 * case, we'll need to uncache the filename.  The filenames
+		 * are ref-counted, so this is safe. */
+		purple_buddy_icon_data_uncache_file(old_icon);
+	}
+	g_free(old_icon);
+}
+
+void
+purple_buddy_icon_set_old_icons_dir(const char *dirname)
+{
+	old_icons_dir = g_strdup(dirname);
+}
+
+static void
+migrate_buddy_icon(PurpleBlistNode *node, const char *setting_name,
+                   const char *dirname, const char *filename)
+{
+	char *path;
+
+	if (filename[0] != '/')
+	{
+		path = g_build_filename(dirname, filename, NULL);
+		if (g_file_test(path, G_FILE_TEST_EXISTS))
+		{
+			g_free(path);
+			return;
+		}
+		g_free(path);
+
+		path = g_build_filename(old_icons_dir, filename, NULL);
+	}
+	else
+		path = g_strdup(filename);
+
+	if (g_file_test(path, G_FILE_TEST_EXISTS))
+	{
+		guchar *icon_data;
+		size_t icon_len;
+		FILE *file;
+		char *new_filename;
+
+		if (!read_icon_file(path, &icon_data, &icon_len))
+		{
+			g_free(path);
+			return;
+		}
+
+		g_free(path);
+
+		new_filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		if (new_filename == NULL)
+		{
+			return;
+		}
+
+		path = g_build_filename(dirname, new_filename, NULL);
+		if ((file = g_fopen(path, "wb")) != NULL)
+		{
+			if (!fwrite(icon_data, icon_len, 1, file))
+			{
+				purple_debug_error("buddyicon", "Error writing %s: %s\n",
+				                   path, strerror(errno));
+			}
+			else
+				purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path);
+
+			fclose(file);
+		}
+		else
+		{
+			purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
+			                   path, strerror(errno));
+			g_free(new_filename);
+			g_free(path);
+			return;
+		}
+		g_free(path);
+
+		purple_blist_node_set_string(node,
+		                             setting_name,
+		                             new_filename);
+		ref_filename(new_filename);
+
+		g_free(new_filename);
+
+		if (!strcmp(setting_name, "buddy_icon"))
+		{
+			const char *hash;
+
+			hash = purple_blist_node_get_string(node, "avatar_hash");
+			if (hash != NULL)
+			{
+				purple_blist_node_set_string(node, "icon_checksum", hash);
+				purple_blist_node_remove_setting(node, "avatar_hash");
+			}
+			else
+			{
+				int checksum = purple_blist_node_get_int(node, "icon_checksum");
+				if (checksum != 0)
+				{
+					char *checksum_str = g_strdup_printf("%i", checksum);
+					purple_blist_node_remove_setting(node, "icon_checksum");
+					purple_blist_node_set_string(node, "icon_checksum", checksum_str);
+					g_free(checksum_str);
+				}
 			}
 		}
-		g_free(filename);
+	}
+	else
+	{
+		/* If the icon is gone, drop the setting... */
+		purple_blist_node_remove_setting(node,
+		                                 setting_name);
+
+		if (!strcmp(setting_name, "buddy_icon"))
+		{
+			purple_blist_node_remove_setting(node,
+			                                 "avatar_hash");
+			purple_blist_node_remove_setting(node,
+			                                 "icon_checksum");
+		}
+		g_free(path);
+	}
+}
+
+void
+purple_buddy_icons_blist_loaded_cb()
+{
+	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)
+	{
+		if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
+		{
+			purple_debug_info("buddyicon", "Creating icon cache directory.\n");
+
+			if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
+			{
+				purple_debug_error("buddyicon",
+				                   "Unable to create directory %s: %s\n",
+				                   dirname, strerror(errno));
+			}
+		}
 	}
 
-	return ret;
+        while (node != NULL)
+	{
+		if (PURPLE_BLIST_NODE_IS_BUDDY(node))
+		{
+			const char *filename;
+
+			filename = purple_blist_node_get_string(node, "buddy_icon");
+			if (filename != NULL)
+			{
+				if (old_icons_dir != NULL)
+				{
+					migrate_buddy_icon(node,
+					                   "buddy_icon",
+					                   dirname, filename);
+				}
+				else
+				{
+					char *path = g_build_filename(dirname, filename, NULL);
+					if (!g_file_test(filename, G_FILE_TEST_EXISTS))
+					{
+						purple_blist_node_remove_setting(node,
+						                                 "buddy_icon");
+						purple_blist_node_remove_setting(node,
+						                                 "icon_checksum");
+					}
+					else
+						ref_filename(filename);
+					g_free(path);
+				}
+			}
+		}
+		else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
+		{
+			const char *filename;
+
+			filename = purple_blist_node_get_string(node, "custom_buddy_icon");
+			if (filename != NULL)
+			{
+				if (old_icons_dir != NULL)
+				{
+					migrate_buddy_icon(node,
+					                   "custom_buddy_icon",
+					                    dirname, filename);
+				}
+				else
+				{
+					char *path = g_build_filename(dirname, filename, NULL);
+					if (!g_file_test(path, G_FILE_TEST_EXISTS))
+					{
+						purple_blist_node_remove_setting(node,
+						                                 "custom_buddy_icon");
+					}
+					else
+						ref_filename(filename);
+					g_free(path);
+				}
+			}
+		}
+		node = purple_blist_node_next(node, TRUE);
+        }
 }
 
 void
@@ -510,6 +943,7 @@
 	return cache_dir;
 }
 
+// TODO: Deal with this
 char *purple_buddy_icons_get_full_path(const char *icon) {
 	if (icon == NULL)
 		return NULL;
@@ -535,13 +969,28 @@
 		g_direct_hash, g_direct_equal,
 		NULL, (GFreeFunc)g_hash_table_destroy);
 
+	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);
 }
 
 void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height)
@@ -572,4 +1021,3 @@
 	*width = new_width;
 	*height = new_height;
 }
-
--- a/libpurple/buddyicon.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/buddyicon.h	Wed Apr 25 00:57:53 2007 +0000
@@ -29,51 +29,38 @@
 
 #include "account.h"
 #include "blist.h"
+#include "imgstore.h"
 #include "prpl.h"
 
-struct _PurpleBuddyIcon
-{
-	PurpleAccount *account;  /**< The account the user is on.        */
-	char *username;        /**< The username the icon belongs to.  */
-
-	void  *data;           /**< The buddy icon data.               */
-	size_t len;            /**< The length of the buddy icon data. */
-	char *path;	       /**< The buddy icon's non-cached path.  */
-
-	int ref_count;         /**< The buddy icon reference count.    */
-};
-
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+// TODO: Deal with this.
+char *purple_buddy_icons_get_full_path(const char *icon);
+
 /**************************************************************************/
 /** @name Buddy Icon API                                                  */
 /**************************************************************************/
 /*@{*/
 
 /**
- * Creates a new buddy icon structure.
+ * 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 icon_data The buddy icon data.
  * @param icon_len  The buddy icon length.
+ * @param checksum  A protocol checksum from the prpl or @c NULL.
  *
  * @return The buddy icon structure.
  */
 PurpleBuddyIcon *purple_buddy_icon_new(PurpleAccount *account, const char *username,
-								void *icon_data, size_t icon_len);
-
-/**
- * Destroys a buddy icon structure.
- *
- * If the buddy icon's reference count is greater than 1, this will
- * just decrease the reference count and return.
- *
- * @param icon The buddy icon structure to destroy.
- */
-void purple_buddy_icon_destroy(PurpleBuddyIcon *icon);
+                                       void *icon_data, size_t icon_len,
+                                       const char *checksum);
 
 /**
  * Increments the reference count on a buddy icon.
@@ -103,52 +90,16 @@
 void purple_buddy_icon_update(PurpleBuddyIcon *icon);
 
 /**
- * Caches a buddy icon associated with a specific buddy to disk.
- *
- * @param icon  The buddy icon.
- * @param buddy The buddy that this icon belongs to.
- */
-void purple_buddy_icon_cache(PurpleBuddyIcon *icon, PurpleBuddy *buddy);
-
-/**
- * Removes cached buddy icon for a specific buddy.
- *
- * @param buddy The buddy for which to remove the cached icon.
- */
-void purple_buddy_icon_uncache(PurpleBuddy *buddy);
-
-/**
- * Sets the buddy icon's account.
- *
- * @param icon    The buddy icon.
- * @param account The account.
- */
-void purple_buddy_icon_set_account(PurpleBuddyIcon *icon, PurpleAccount *account);
-
-/**
- * Sets the buddy icon's username.
- *
- * @param icon     The buddy icon.
- * @param username The username.
- */
-void purple_buddy_icon_set_username(PurpleBuddyIcon *icon, const char *username);
-
-/**
- * Sets the buddy icon's icon data.
+ * Sets the buddy icon's data that was received over the wire.
  *
  * @param icon The buddy icon.
- * @param data The buddy icon data.
- * @param len  The length of the icon data.
+ * @param data The buddy icon data received over the wire.
+ * @param len  The length of the data in @a data.
+ * @param checksum  A protocol checksum from the prpl or @c NULL.
  */
-void purple_buddy_icon_set_data(PurpleBuddyIcon *icon, void *data, size_t len);
-
-/**
- * Sets the buddy icon's path.
- *
- * @param icon The buddy icon.
- * @param path The buddy icon's non-cached path.
- */
-void purple_buddy_icon_set_path(PurpleBuddyIcon *icon, const gchar *path);
+void
+purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data,
+                           size_t len, const char *checksum);
 
 /**
  * Returns the buddy icon's account.
@@ -169,32 +120,36 @@
 const char *purple_buddy_icon_get_username(const PurpleBuddyIcon *icon);
 
 /**
+ * Returns the buddy icon's checksum.
+ *
+ * This function is really only for prpl use.
+ *
+ * @param icon The buddy icon.
+ *
+ * @return The checksum.
+ */
+const char *purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon);
+
+/**
  * Returns the buddy icon's data.
  *
  * @param icon The buddy icon.
- * @param len  The returned icon length.
+ * @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);
-
-/**
- * Returns the buddy icon's path.
- *
- * @param icon The buddy icon.
- *
- * @return The buddy icon's non-cached path.
- */
-const gchar *purple_buddy_icon_get_path(PurpleBuddyIcon *icon);
+gconstpointer purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len);
 
 /**
  * Returns an extension corresponding to the buddy icon's file type.
  *
  * @param icon The buddy icon.
  *
- * @return The icon's extension.
+ * @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);
 
 /*@}*/
 
@@ -210,11 +165,27 @@
  * @param username  The username of the user.
  * @param icon_data The icon data.
  * @param icon_len  The length of the icon data.
+ * @param checksum  A protocol checksum from the prpl or @c NULL.
  *
  * @return The buddy icon set, or NULL if no icon was set.
  */
-void purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
-									void *icon_data, size_t icon_len);
+void
+purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
+                                void *icon_data, size_t icon_len,
+                                const char *checksum);
+
+/**
+ * Returns the checksum for the buddy icon of a specified buddy.
+ *
+ * This avoids loading the icon image data from the cache if it's
+ * not already loaded for some other reason.
+ *
+ * @param buddy The buddy
+ *
+ * @return The checksum.
+ */
+const char *
+purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy);
 
 /**
  * Returns the buddy icon information for a user.
@@ -224,8 +195,46 @@
  *
  * @return The icon data if found, or @c NULL if not found.
  */
-PurpleBuddyIcon *purple_buddy_icons_find(PurpleAccount *account,
-									 const char *username);
+PurpleBuddyIcon *
+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.
@@ -263,18 +272,6 @@
 const char *purple_buddy_icons_get_cache_dir(void);
 
 /**
- * Takes a buddy icon and returns a full path.
- *
- * If @a icon is a full path to an existing file, a copy of
- * @a icon is returned. Otherwise, a newly allocated string
- * consiting of purple_buddy_icons_get_cache_dir() + @a icon is
- * returned.
- *
- * @return The full path for an icon.
- */
-char *purple_buddy_icons_get_full_path(const char *icon);
-
-/**
  * Returns the buddy icon subsystem handle.
  *
  * @return The subsystem handle.
--- a/libpurple/conversation.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/conversation.c	Wed Apr 25 00:57:53 2007 +0000
@@ -425,8 +425,7 @@
 		purple_conv_im_stop_typing_timeout(conv->u.im);
 		purple_conv_im_stop_send_typed_timeout(conv->u.im);
 
-		if (conv->u.im->icon != NULL)
-			purple_buddy_icon_unref(conv->u.im->icon);
+		purple_buddy_icon_unref(conv->u.im->icon);
 		conv->u.im->icon = NULL;
 
 		PURPLE_DBUS_UNREGISTER_POINTER(conv->u.im);
@@ -941,8 +940,7 @@
 
 	if (im->icon != icon)
 	{
-		if (im->icon != NULL)
-			purple_buddy_icon_unref(im->icon);
+		purple_buddy_icon_unref(im->icon);
 
 		im->icon = (icon == NULL ? NULL : purple_buddy_icon_ref(icon));
 	}
--- a/libpurple/core.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/core.c	Wed Apr 25 00:57:53 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"
@@ -44,6 +45,7 @@
 #include "sslconn.h"
 #include "status.h"
 #include "stun.h"
+#include "util.h"
 
 #ifdef HAVE_DBUS
 #  include "dbus-server.h"
@@ -123,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();
@@ -189,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();
@@ -268,3 +274,376 @@
 {
 	return _ops;
 }
+
+static gboolean
+move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative)
+{
+	char *new_name = g_build_filename(new_base, basename, NULL);
+#ifndef _WIN32
+	char *old_name;
+#endif
+	if (g_rename(path, new_name))
+	{
+		purple_debug_error("core", "Error renaming %s to %s: %s\n",
+		                   path, new_name, strerror(errno));
+		g_free(new_name);
+		return FALSE;
+	}
+	g_free(new_name);
+
+#ifndef _WIN32
+	/* NOTE: This new_name is relative. */
+	new_name = g_build_filename(relative, basename, NULL);
+	old_name = g_build_filename(old_base, basename, NULL);
+	if (symlink(new_name, old_name))
+	{
+		purple_debug_warning("core", "Error symlinking %s to %s: %s\n",
+		                     old_name, new_name, strerror(errno));
+	}
+	g_free(old_name);
+	g_free(new_name);
+#endif
+
+	return TRUE;
+}
+
+gboolean
+purple_core_migrate(void)
+{
+	const char *user_dir = purple_user_dir();
+	char *old_user_dir = g_strconcat(purple_home_dir(),
+	                                 G_DIR_SEPARATOR_S ".gaim", NULL);
+	char *status_file;
+	FILE *fp;
+	GDir *dir;
+	GError *err;
+	const char *entry;
+#ifndef _WIN32
+	char *logs_dir;
+#endif
+	char *old_icons_dir;
+
+	if (!g_file_test(old_user_dir, G_FILE_TEST_EXISTS))
+	{
+		/* ~/.gaim doesn't exist, so there's nothing to migrate. */
+		g_free(old_user_dir);
+		return TRUE;
+	}
+
+	status_file = g_strconcat(user_dir, G_DIR_SEPARATOR_S "migrating", NULL);
+
+	if (g_file_test(user_dir, G_FILE_TEST_EXISTS))
+	{
+		/* If we're here, we have both ~/.gaim and .purple. */
+
+		if (!g_file_test(status_file, G_FILE_TEST_EXISTS))
+		{
+			/* There's no "migrating" status file,
+			 * so ~/.purple is all up to date. */
+			g_free(status_file);
+			g_free(old_user_dir);
+			return TRUE;
+		}
+	}
+
+	/* If we're here, it's time to migrate from ~/.gaim to ~/.purple. */
+
+        /* Ensure the user directory exists */
+	if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
+	{
+		if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+		{
+			purple_debug_error("core", "Error creating directory %s: %s\n",
+			                   user_dir, strerror(errno));
+			g_free(status_file);
+			g_free(old_user_dir);
+			return FALSE;
+		}
+	}
+
+	/* This writes ~/.purple/migrating, which allows us to detect
+	 * incomplete migrations and properly retry. */
+	if (!(fp = g_fopen(status_file, "w")))
+	{
+		purple_debug_error("core", "Error opening file %s for writing: %s\n",
+		                   status_file, strerror(errno));
+		g_free(status_file);
+		g_free(old_user_dir);
+		return FALSE;
+	}
+	fclose(fp);
+
+	/* Open ~/.gaim so we can loop over its contents. */
+	err = NULL;
+	if (!(dir = g_dir_open(old_user_dir, 0, &err)))
+	{
+		purple_debug_error("core", "Error opening directory %s: %s\n",
+		                   status_file,
+		                   (err ? err->message : "Unknown error"));
+		if (err)
+			g_error_free(err);
+		g_free(status_file);
+		g_free(old_user_dir);
+		return FALSE;
+	}
+
+	/* Loop over the contents of ~/.gaim */
+	while ((entry = g_dir_read_name(dir)))
+	{
+		char *name = g_build_filename(old_user_dir, entry, NULL);
+
+#ifndef _WIN32
+		/* Deal with symlinks... */
+		if (g_file_test(name, G_FILE_TEST_IS_SYMLINK))
+		{
+			/* We're only going to duplicate a logs symlink. */
+			if (!strcmp(entry, "logs"))
+			{
+				char buf[MAXPATHLEN];
+
+				if (readlink(name, buf, sizeof(buf) - 1) == -1)
+				{
+					purple_debug_error("core", "Error reading symlink %s: %s\n",
+					                   name, strerror(errno));
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+				buf[sizeof(buf) - 1] = '\0';
+
+				logs_dir = g_strconcat(user_dir, G_DIR_SEPARATOR_S "logs", NULL);
+
+				if (!strcmp(buf, "../.purple/logs") || !strcmp(buf, logs_dir))
+				{
+					/* If the symlink points to the new directory, we're
+					 * likely just trying again after a failed migration,
+					 * so there's no need to fail here. */
+					g_free(logs_dir);
+					continue;
+				}
+
+				/* In case we are trying again after a failed migration, we need
+				 * to unlink any existing symlink.  If it's a directory, this
+				 * will fail, and so will the symlink below, which is good
+				 * because the user should sort things out. */
+				g_unlink(logs_dir);
+
+				/* Relative links will most likely still be
+				 * valid from ~/.purple, though not it's not
+				 * guaranteed.  Oh well. */
+				if (symlink(buf, logs_dir))
+				{
+					purple_debug_error("core", "Error symlinking %s to %s: %s\n",
+					                   logs_dir, buf, strerror(errno));
+					g_free(name);
+					g_free(logs_dir);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+
+				g_free(logs_dir);
+				continue;
+			}
+
+			/* Ignore all other symlinks. */
+			continue;
+		}
+#endif
+
+		/* Deal with directories... */
+		if (g_file_test(name, G_FILE_TEST_IS_DIR))
+		{
+			if (!strcmp(entry, "icons"))
+			{
+				/* This is a special case for the Album plugin, which
+				 * stores data in the icons folder.  We're not copying
+				 * the icons directory over because previous bugs
+				 * meant that it filled up with junk for many users.
+				 * This is a great time to purge it. */
+
+				GDir *icons_dir;
+				char *new_icons_dir;
+				const char *icons_entry;
+
+				err = NULL;
+				if (!(icons_dir = g_dir_open(name, 0, &err)))
+				{
+					purple_debug_error("core", "Error opening directory %s: %s\n",
+					                   name,
+					                   (err ? err->message : "Unknown error"));
+					if (err)
+						g_error_free(err);
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+
+				new_icons_dir = g_build_filename(user_dir, "icons", NULL);
+			        /* Ensure the new icon directory exists */
+				if (!g_file_test(new_icons_dir, G_FILE_TEST_IS_DIR))
+				{
+					if (g_mkdir(new_icons_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+					{
+						purple_debug_error("core", "Error creating directory %s: %s\n",
+						                   new_icons_dir, strerror(errno));
+						g_free(new_icons_dir);
+						g_dir_close(icons_dir);
+						g_free(name);
+						g_dir_close(dir);
+						g_free(status_file);
+						g_free(old_user_dir);
+						return FALSE;
+					}
+				}
+
+				while ((icons_entry = g_dir_read_name(icons_dir)))
+				{
+					char *icons_name = g_build_filename(name, icons_entry, NULL);
+
+					if (g_file_test(icons_name, G_FILE_TEST_IS_DIR))
+					{
+						if (!move_and_symlink_dir(icons_name, icons_entry,
+						                          name, new_icons_dir, "../../.purple/icons"))
+						{
+							g_free(icons_name);
+							g_free(new_icons_dir);
+							g_dir_close(icons_dir);
+							g_free(name);
+							g_dir_close(dir);
+							g_free(status_file);
+							g_free(old_user_dir);
+							return FALSE;
+						}
+					}
+					g_free(icons_name);
+				}
+
+				g_dir_close(icons_dir);
+			}
+			else if (!strcmp(entry, "plugins"))
+			{
+				/* Do nothing, because we broke plugin compatibility.
+				 * This means that the plugins directory gets left behind. */
+			}
+			else
+			{
+				/* All other directories are moved and symlinked. */
+				if (!move_and_symlink_dir(name, entry, old_user_dir, user_dir, "../.purple"))
+				{
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+			}
+		}
+		else if (g_file_test(name, G_FILE_TEST_IS_REGULAR))
+		{
+			/* Regular files are copied. */
+
+			char *new_name;
+			FILE *new_file;
+
+			if (!(fp = g_fopen(name, "rb")))
+			{
+				purple_debug_error("core", "Error opening file %s for reading: %s\n",
+				                   name, strerror(errno));
+				g_free(name);
+				g_dir_close(dir);
+				g_free(status_file);
+				g_free(old_user_dir);
+				return FALSE;
+			}
+
+			new_name = g_build_filename(user_dir, entry, NULL);
+			if (!(new_file = g_fopen(new_name, "wb")))
+			{
+				purple_debug_error("core", "Error opening file %s for writing: %s\n",
+				                   new_name, strerror(errno));
+				fclose(fp);
+				g_free(new_name);
+				g_free(name);
+				g_dir_close(dir);
+				g_free(status_file);
+				g_free(old_user_dir);
+				return FALSE;
+			}
+
+			while (!feof(fp))
+			{
+				unsigned char buf[256];
+				size_t size;
+
+				size = fread(buf, 1, sizeof(buf), fp);
+				if (size != sizeof(buf) && !feof(fp))
+				{
+					purple_debug_error("core", "Error reading %s: %s\n",
+					                   name, strerror(errno));
+					fclose(new_file);
+					fclose(fp);
+					g_free(new_name);
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+
+				if (!fwrite(buf, size, 1, new_file))
+				{
+					purple_debug_error("core", "Error writing %s: %s\n",
+					                   new_name, strerror(errno));
+					fclose(new_file);
+					fclose(fp);
+					g_free(new_name);
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+			}
+
+			if (fclose(new_file))
+			{
+				purple_debug_error("core", "Error writing: %s: %s\n",
+				                   new_name, strerror(errno));
+			}
+			if (fclose(fp))
+			{
+				purple_debug_warning("core", "Error closing %s: %s\n",
+				                     name, strerror(errno));
+			}
+			g_free(new_name);
+		}
+		else
+			purple_debug_warning("core", "Not a regular file or directory: %s\n", name);
+
+		g_free(name);
+	}
+
+	/* The migration was successful, so delete the status file. */
+	if (g_unlink(status_file))
+	{
+		purple_debug_error("core", "Error unlinking file %s: %s\n",
+		                   status_file, strerror(errno));
+		g_free(status_file);
+		return FALSE;
+	}
+
+	old_icons_dir = g_build_filename(old_user_dir, "icons", NULL);
+	purple_buddy_icon_set_old_icons_dir(old_icons_dir);
+	g_free(old_icons_dir);
+
+	g_free(old_user_dir);
+
+	g_free(status_file);
+	return TRUE;
+}
--- a/libpurple/core.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/core.h	Wed Apr 25 00:57:53 2007 +0000
@@ -106,6 +106,17 @@
  */
 PurpleCoreUiOps *purple_core_get_ui_ops(void);
 
+/**
+ * Migrates from .gaim to .purple.
+ *
+ * UIs MUST NOT call this if they have been told to use a custom
+ * user directory.
+ *
+ * @return A boolean indicating success or migration failure. On failure,
+ *         the application must display an error to the user and then exit.
+ */
+gboolean purple_core_migrate(void);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/gaim-compat.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/gaim-compat.h	Wed Apr 25 00:57:53 2007 +0000
@@ -343,19 +343,15 @@
 #define gaim_buddy_icon_ref      purple_buddy_icon_ref
 #define gaim_buddy_icon_unref    purple_buddy_icon_unref
 #define gaim_buddy_icon_update   purple_buddy_icon_update
-#define gaim_buddy_icon_cache    purple_buddy_icon_cache
-#define gaim_buddy_icon_uncache  purple_buddy_icon_uncache
 
 #define gaim_buddy_icon_set_account   purple_buddy_icon_set_account
 #define gaim_buddy_icon_set_username  purple_buddy_icon_set_username
-#define gaim_buddy_icon_set_data      purple_buddy_icon_set_data
-#define gaim_buddy_icon_set_path      purple_buddy_icon_set_path
+#define gaim_buddy_icon_set_data      purple_buddy_icon_set_protocol_data
 
 #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_path      purple_buddy_icon_get_path
-#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
@@ -363,7 +359,6 @@
 #define gaim_buddy_icons_is_caching     purple_buddy_icons_is_caching
 #define gaim_buddy_icons_set_cache_dir  purple_buddy_icons_set_cache_dir
 #define gaim_buddy_icons_get_cache_dir  purple_buddy_icons_get_cache_dir
-#define gaim_buddy_icons_get_full_path  purple_buddy_icons_get_full_path
 #define gaim_buddy_icons_get_handle     purple_buddy_icons_get_handle
 
 #define gaim_buddy_icons_init    purple_buddy_icons_init
@@ -967,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 */
@@ -2237,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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/imgstore.c	Wed Apr 25 00:57:53 2007 +0000
@@ -27,140 +27,162 @@
 #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 */
+	g_return_val_if_fail(data != NULL, 0);
+	g_return_val_if_fail(size > 0, 0);
 
-static PurpleStoredImagePriv *purple_imgstore_get_priv(int id) {
-	GSList *tmp = imgstore;
-	PurpleStoredImagePriv *priv = NULL;
+	img = g_new(PurpleStoredImage, 1);
+	img->data = g_memdup(data, size);
+	img->size = size;
+	img->filename = g_strdup(filename);
+	img->refcount = 1;
+	img->id = 0;
+
+	return img;
+}
 
-	g_return_val_if_fail(id > 0, NULL);
+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;
 
-	while (tmp && !priv) {
-		PurpleStoredImagePriv *tmp_priv = tmp->data;
+	g_hash_table_insert(imgstore, GINT_TO_POINTER(img->id), img);
 
-		if (tmp_priv->id == id)
-			priv = tmp_priv;
+	return img->id;
+}
+
+PurpleStoredImage *purple_imgstore_find_by_id(int id) {
+	PurpleStoredImage *img = g_hash_table_lookup(imgstore, GINT_TO_POINTER(id));
 
-		tmp = tmp->next;
-	}
+	if (img != NULL)
+		purple_debug_misc("imgstore", "retrieved image id %d\n", img->id);
+
+	return img;
+}
 
-	if (!priv)
-		purple_debug(PURPLE_DEBUG_ERROR, "imgstore", "failed to find image id %d\n", id);
+gconstpointer purple_imgstore_get_data(PurpleStoredImage *img) {
+	return img->data;
+}
 
-	return priv;
+size_t purple_imgstore_get_size(PurpleStoredImage *img)
+{
+	return img->size;
 }
 
-static void purple_imgstore_free_priv(PurpleStoredImagePriv *priv) {
-	PurpleStoredImage *img = NULL;
+const char *purple_imgstore_get_filename(PurpleStoredImage *img)
+{
+	return img->filename;
+}
+
+const char *purple_imgstore_get_extension(PurpleStoredImage *img)
+{
+	return purple_util_get_image_extension(img->data, img->size);
+}
+
+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);
+}
 
-	g_return_if_fail(priv != NULL);
+PurpleStoredImage *
+purple_imgstore_ref(PurpleStoredImage *img)
+{
+	g_return_val_if_fail(img != NULL, NULL);
+
+	img->refcount++;
+
+	return img;
+}
 
-	img = priv->img;
-	if (img) {
+PurpleStoredImage *
+purple_imgstore_unref(PurpleStoredImage *img)
+{
+	if (img == NULL)
+		return NULL;
+
+	g_return_val_if_fail(img->refcount > 0, NULL);
+
+	img->refcount--;
+
+	if (img->refcount == 0)
+	{
+		purple_signal_emit(purple_imgstore_get_handle(),
+		                   "image-deleting", img);
+		if (img->id)
+			g_hash_table_remove(imgstore, GINT_TO_POINTER(img->id));
+
 		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);
+	return img;
 }
 
-/* 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);
+void *
+purple_imgstore_get_handle()
+{
+	static int handle;
 
-	img = g_new0(PurpleStoredImage, 1);
-	img->data = g_memdup(data, size);
-	img->size = size;
-	img->filename = g_strdup(filename);
-
-	priv = g_new0(PurpleStoredImagePriv, 1);
-	priv->id = ++nextid;
-	priv->refcount = 1;
-	priv->img = img;
-
-	imgstore = g_slist_append(imgstore, priv);
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "added image id %d\n", priv->id);
-
-	return priv->id;
+	return &handle;
 }
 
-PurpleStoredImage *purple_imgstore_get(int id) {
-	PurpleStoredImagePriv *priv = purple_imgstore_get_priv(id);
-
-	g_return_val_if_fail(priv != NULL, NULL);
-
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "retrieved image id %d\n", priv->id);
-
-	return priv->img;
-}
+void
+purple_imgstore_init()
+{
+	void *handle = purple_imgstore_get_handle();
 
-gpointer purple_imgstore_get_data(PurpleStoredImage *i) {
-	return i->data;
-}
+	purple_signal_register(handle, "image-deleting",
+	                       purple_marshal_VOID__POINTER, NULL,
+	                       1,
+	                       purple_value_new(PURPLE_TYPE_SUBTYPE,
+	                                        PURPLE_SUBTYPE_STORED_IMAGE));
 
-size_t purple_imgstore_get_size(PurpleStoredImage *i) {
-	return i->size;
-}
-
-const char *purple_imgstore_get_filename(PurpleStoredImage *i) {
-	return i->filename;
+	imgstore = g_hash_table_new(g_int_hash, g_int_equal);
 }
 
-void purple_imgstore_ref(int id) {
-	PurpleStoredImagePriv *priv = purple_imgstore_get_priv(id);
-
-	g_return_if_fail(priv != NULL);
-
-	(priv->refcount)++;
-
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "referenced image id %d (count now %d)\n", priv->id, priv->refcount);
-}
+void
+purple_imgstore_uninit()
+{
+	g_hash_table_destroy(imgstore);
 
-void purple_imgstore_unref(int id) {
-	PurpleStoredImagePriv *priv = purple_imgstore_get_priv(id);
-
-	g_return_if_fail(priv != NULL);
-	g_return_if_fail(priv->refcount > 0);
-
-	(priv->refcount)--;
-
-	purple_debug(PURPLE_DEBUG_INFO, "imgstore", "unreferenced image id %d (count now %d)\n", priv->id, priv->refcount);
-
-	if (priv->refcount == 0)
-		purple_imgstore_free_priv(priv);
+	purple_signals_unregister_by_instance(purple_blist_get_handle());
 }
--- a/libpurple/imgstore.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/imgstore.h	Wed Apr 25 00:57:53 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/internal.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/internal.h	Wed Apr 25 00:57:53 2007 +0000
@@ -184,4 +184,15 @@
 
 #define PURPLE_WEBSITE "http://pidgin.im/"
 
+/* This is for the buddy list to notify the buddy icon code that
+ * it's done loading.  We may want to replace this with a signal. */
+void
+purple_buddy_icons_blist_loaded_cb(void);
+
+/* This is for the purple_core_migrate() code to tell the buddy
+ * icon subsystem about the old icons directory so it can
+ * migrate any icons in use. */
+void
+purple_buddy_icon_set_old_icons_dir(const char *dirname);
+
 #endif /* _PURPLE_INTERNAL_H_ */
--- a/libpurple/plugins/perl/common/BuddyIcon.xs	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/plugins/perl/common/BuddyIcon.xs	Wed Apr 25 00:57:53 2007 +0000
@@ -3,10 +3,6 @@
 MODULE = Purple::Buddy::Icon PACKAGE = Purple::Buddy::Icon   PREFIX = purple_buddy_icon_
 PROTOTYPES: ENABLE
 
-void
-purple_buddy_icon_destroy(icon)
-	Purple::Buddy::Icon icon
-
 Purple::Buddy::Icon
 purple_buddy_icon_ref(icon)
 	Purple::Buddy::Icon icon
@@ -20,22 +16,13 @@
 	Purple::Buddy::Icon icon
 
 void
-purple_buddy_icon_cache(icon, buddy)
+purple_buddy_icon_set_custom_data(icon, data, len)
 	Purple::Buddy::Icon icon
-	Purple::BuddyList::Buddy buddy
+	void * data
+	size_t len
 
 void
-purple_buddy_icon_set_account(icon, account)
-	Purple::Buddy::Icon icon
-	Purple::Account account
-
-void
-purple_buddy_icon_set_username(icon, username)
-	Purple::Buddy::Icon icon
-	const char * username
-
-void
-purple_buddy_icon_set_data(icon, data, len)
+purple_buddy_icon_set_protocol_data(icon, data, len)
 	Purple::Buddy::Icon icon
 	void * data
 	size_t len
--- a/libpurple/protocols/jabber/buddy.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Apr 25 00:57:53 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,21 +959,20 @@
 
 					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"),
 							GPOINTER_TO_INT(jbi->vcard_imgids->data));
 
-					purple_buddy_icons_set_for_user(js->gc->account, bare_jid,
-							data, size);
-
 					purple_cipher_digest_region("sha1", (guchar *)data, size,
 							sizeof(hashval), hashval, NULL);
 					p = hash;
 					for(i=0; i<20; i++, p+=2)
 						snprintf(p, 3, "%02x", hashval[i]);
-					purple_blist_node_set_string((PurpleBlistNode*)b, "avatar_hash", hash);
+
+					purple_buddy_icons_set_for_user(js->gc->account, bare_jid,
+							data, size, hash);
 
 					g_free(data);
 					g_free(bintext);
--- a/libpurple/protocols/jabber/presence.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed Apr 25 00:57:53 2007 +0000
@@ -201,7 +201,6 @@
 static void jabber_vcard_parse_avatar(JabberStream *js, xmlnode *packet, gpointer blah)
 {
 	JabberBuddy *jb = NULL;
-	PurpleBuddy *b = NULL;
 	xmlnode *vcard, *photo, *binval;
 	char *text;
 	guchar *data;
@@ -221,21 +220,20 @@
 				(( (binval = xmlnode_get_child(photo, "BINVAL")) &&
 				(text = xmlnode_get_data(binval))) ||
 				(text = xmlnode_get_data(photo)))) {
+			unsigned char hashval[20];
+			char hash[41], *p;
+			int i;
+
 			data = purple_base64_decode(text, &size);
 
-			purple_buddy_icons_set_for_user(js->gc->account, from, data, size);
-			if((b = purple_find_buddy(js->gc->account, from))) {
-				unsigned char hashval[20];
-				char hash[41], *p;
-				int i;
+			purple_cipher_digest_region("sha1", data, size,
+					sizeof(hashval), hashval, NULL);
+			p = hash;
+			for(i=0; i<20; i++, p+=2)
+				snprintf(p, 3, "%02x", hashval[i]);
 
-				purple_cipher_digest_region("sha1", data, size,
-						sizeof(hashval), hashval, NULL);
-				p = hash;
-				for(i=0; i<20; i++, p+=2)
-					snprintf(p, 3, "%02x", hashval[i]);
-				purple_blist_node_set_string((PurpleBlistNode*)b, "avatar_hash", hash);
-			}
+			purple_buddy_icons_set_for_user(js->gc->account, from, data, size, hash);
+
 			g_free(data);
 			g_free(text);
 		}
@@ -520,7 +518,7 @@
 		}
 
 		if(avatar_hash) {
-			const char *avatar_hash2 = purple_blist_node_get_string((PurpleBlistNode*)b, "avatar_hash");
+			const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
 			if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) {
 				JabberIq *iq;
 				xmlnode *vcard;
--- a/libpurple/protocols/msn/msn.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Wed Apr 25 00:57:53 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/msn/slp.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/msn/slp.c	Wed Apr 25 00:57:53 2007 +0000
@@ -870,7 +870,7 @@
 	if (buddy == NULL)
 		return FALSE;
 
-	old = purple_blist_node_get_string((PurpleBlistNode *)buddy, "icon_checksum");
+	old = purple_buddy_icons_get_checksum_for_user(buddy);
 	new = msn_object_get_sha1(obj);
 
 	if (new == NULL)
@@ -956,22 +956,7 @@
 
 	if (obj == NULL)
 	{
-		/* It seems the user has not set a msnobject */
-		GSList *sl, *list;
-
-		list = purple_find_buddies(account, user->passport);
-
-		for (sl = list; sl != NULL; sl = sl->next)
-		{
-			PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
-			if (buddy->icon)
-				purple_blist_node_remove_setting((PurpleBlistNode*)buddy, "icon_checksum");
-		}
-		g_slist_free(list);
-
-		/* TODO: I think we need better buddy icon core functions. */
-		purple_buddy_icons_set_for_user(account, user->passport, NULL, 0);
-
+		purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL);
 		return;
 	}
 
@@ -1001,7 +986,6 @@
 	MsnUserList *userlist;
 	const char *info;
 	PurpleAccount *account;
-	GSList *sl, *list;
 
 	g_return_if_fail(slpcall != NULL);
 
@@ -1013,18 +997,8 @@
 	userlist = slpcall->slplink->session->userlist;
 	account = slpcall->slplink->session->account;
 
-	/* TODO: I think we need better buddy icon core functions. */
 	purple_buddy_icons_set_for_user(account, slpcall->slplink->remote_user,
-								  (void *)data, size);
-
-	list = purple_find_buddies(account, slpcall->slplink->remote_user);
-
-	for (sl = list; sl != NULL; sl = sl->next)
-	{
-		PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
-		purple_blist_node_set_string((PurpleBlistNode*)buddy, "icon_checksum", info);
-	}
-	g_slist_free(list);
+								  (void *)data, size, info);
 
 #if 0
 	/* Free one window slot */
@@ -1103,7 +1077,6 @@
 		MsnObject *my_obj = NULL;
 		gchar *data = NULL;
 		gsize len = 0;
-		GSList *sl, *list;
 
 #ifdef MSN_DEBUG_UD
 		purple_debug_info("msn", "Requesting our own user display\n");
@@ -1120,18 +1093,9 @@
 		}
 
 		/* TODO: I think we need better buddy icon core functions. */
-		purple_buddy_icons_set_for_user(account, user->passport, (void *)data, len);
+		purple_buddy_icons_set_for_user(account, user->passport, (void *)data, len, info);
 		g_free(data);
 
-		list = purple_find_buddies(account, user->passport);
-
-		for (sl = list; sl != NULL; sl = sl->next)
-		{
-			PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
-			purple_blist_node_set_string((PurpleBlistNode*)buddy, "icon_checksum", info);
-		}
-		g_slist_free(list);
-
 		/* Free one window slot */
 		session->userlist->buddy_icon_window++;
 
--- a/libpurple/protocols/oscar/odc.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/oscar/odc.c	Wed Apr 25 00:57:53 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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Wed Apr 25 00:57:53 2007 +0000
@@ -1841,34 +1841,14 @@
 	bi->ipaddr = info->icqinfo.ipaddr;
 
 	if (info->iconcsumlen) {
-		const char *filename, *saved_b16 = NULL;
-		char *b16 = NULL, *filepath = NULL;
+		const char *saved_b16 = NULL;
+		char *b16 = NULL;
 		PurpleBuddy *b = NULL;
 
 		b16 = purple_base16_encode(info->iconcsum, info->iconcsumlen);
 		b = purple_find_buddy(account, info->sn);
-		/*
-		 * If for some reason the checksum is valid, but cached file is not..
-		 * we want to know.
-		 */
 		if (b != NULL)
-			filename = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
-		else
-			filename = NULL;
-		if (filename != NULL) {
-			if (g_file_test(filename, G_FILE_TEST_EXISTS))
-				saved_b16 = purple_blist_node_get_string((PurpleBlistNode*)b,
-						"icon_checksum");
-			else {
-				filepath = g_build_filename(purple_buddy_icons_get_cache_dir(),
-											filename, NULL);
-				if (g_file_test(filepath, G_FILE_TEST_EXISTS))
-					saved_b16 = purple_blist_node_get_string((PurpleBlistNode*)b,
-															"icon_checksum");
-				g_free(filepath);
-			}
-		} else
-			saved_b16 = NULL;
+			saved_b16 = purple_buddy_icons_get_checksum_for_user(b);
 
 		if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) {
 			GSList *cur = od->requesticon;
@@ -2177,7 +2157,8 @@
 	{
 		purple_buddy_icons_set_for_user(account, userinfo->sn,
 									  args->info.icon.icon,
-									  args->info.icon.length);
+									  args->info.icon.length,
+									  NULL);
 	}
 
 	else if (args->type & OSCAR_CAPABILITY_ICQSERVERRELAY)
@@ -3273,16 +3254,10 @@
 	 * no icon is set.  Ignore these.
 	 */
 	if ((iconlen > 0) && (iconlen != 90)) {
-		char *b16;
-		PurpleBuddy *b;
+		char *b16 = purple_base16_encode(iconcsum, iconcsumlen);
 		purple_buddy_icons_set_for_user(purple_connection_get_account(gc),
-									  sn, icon, iconlen);
-		b16 = purple_base16_encode(iconcsum, iconcsumlen);
-		b = purple_find_buddy(gc->account, sn);
-		if ((b16 != NULL) && (b != NULL)) {
-			purple_blist_node_set_string((PurpleBlistNode*)b, "icon_checksum", b16);
-			g_free(b16);
-		}
+									  sn, icon, iconlen, b16);
+		g_free(b16);
 	}
 
 	cur = od->requesticon;
@@ -4140,11 +4115,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/qq/buddy_info.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Wed Apr 25 00:57:53 2007 +0000
@@ -531,22 +531,17 @@
 	qq_send_packet_get_info(gc, qd->uid, FALSE);
 }
 
-void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *iconfile)
+void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile)
 {
-	FILE *file;
-	struct stat st;
+	gchar *data;
+	gsize len;
 
-	g_return_if_fail(g_stat(iconfile, &st) == 0);
-	file = g_fopen(iconfile, "rb");
-	if (file) {
-		PurpleBuddyIcon *icon;
-		size_t data_len;
-		gchar *data = g_new(gchar, st.st_size + 1);
-		data_len = fread(data, 1, st.st_size, file);
-		fclose(file);
-		purple_buddy_icons_set_for_user(account, who, data, data_len);
-		icon = purple_buddy_icons_find(account, who);
-		purple_buddy_icon_set_path(icon, iconfile);
+	if (!g_file_get_contents(iconfile, &data, &len, NULL))
+		g_return_if_reached();
+	else
+	{
+		purple_buddy_icons_set_for_user(account, who, data, len, icon_num);
+		g_free(data);
 	}
 }
 
@@ -600,25 +595,29 @@
 	/* tell server my icon changed */
 	_qq_send_packet_modify_face(gc, icon_num);
 	/* display in blist */
-	qq_set_buddy_icon_for_user(account, account->username, icon_path);
+	qq_set_buddy_icon_for_user(account, account->username, icon, icon_path);
 }
 
 
 static void _qq_update_buddy_icon(PurpleAccount *account, const gchar *name, gint face)
 {
-	gchar *icon_path;
 	PurpleBuddyIcon *icon = purple_buddy_icons_find(account, name);
 	gchar *icon_num_str = face_to_icon_str(face);
-	const gchar *old_path = purple_buddy_icon_get_path(icon);
-	const gchar *buddy_icon_dir = qq_buddy_icon_dir();
+	const gchar *old_icon_num = purple_buddy_icon_get_checksum(icon);
+
+	if (icon == NULL || old_icon_num == NULL ||
+	    strcmp(icon_num_str, old_icon_num))
+	{
+		gchar *icon_path;
 
-	icon_path = g_strconcat(buddy_icon_dir, G_DIR_SEPARATOR_S, QQ_ICON_PREFIX, 
-			icon_num_str, QQ_ICON_SUFFIX, NULL);
-	if (icon == NULL || old_path == NULL 
-		|| g_ascii_strcasecmp(icon_path, old_path) != 0)
-		qq_set_buddy_icon_for_user(account, name, icon_path);
+		icon_path = g_strconcat(qq_buddy_icon_dir(), G_DIR_SEPARATOR_S,
+		                        QQ_ICON_PREFIX, icon_num_str,
+		                        QQ_ICON_SUFFIX, NULL);
+
+		qq_set_buddy_icon_for_user(account, name, icon_num_str, icon_path);
+		g_free(icon_path);
+	}
 	g_free(icon_num_str);
-	g_free(icon_path);
 }
 
 /* after getting info or modify myself, refresh the buddy list accordingly */
--- a/libpurple/protocols/qq/buddy_info.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/qq/buddy_info.h	Wed Apr 25 00:57:53 2007 +0000
@@ -87,7 +87,7 @@
 void qq_refresh_buddy_and_myself(contact_info *info, PurpleConnection *gc);
 void qq_send_packet_get_info(PurpleConnection *gc, guint32 uid, gboolean show_window);
 void qq_set_my_buddy_icon(PurpleConnection *gc, const gchar *iconfile);
-void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *iconfile);
+void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile);
 void qq_prepare_modify_info(PurpleConnection *gc);
 void qq_process_modify_info_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
 void qq_process_get_info_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
--- a/libpurple/protocols/sametime/sametime.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Wed Apr 25 00:57:53 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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/silc/buddy.c	Wed Apr 25 00:57:53 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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/silc/ops.c	Wed Apr 25 00:57:53 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.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Wed Apr 25 00:57:53 2007 +0000
@@ -342,6 +342,8 @@
 		}
 		case 192: /* Pictures, aka Buddy Icons, checksum */
 		{
+			/* FIXME: Please, if you know this protocol,
+			 * FIXME: fix up the strtol() stuff if possible. */
 			int cksum = strtol(pair->value, NULL, 10);
 			PurpleBuddy *b;
 
@@ -353,9 +355,7 @@
 			if (!cksum || (cksum == -1)) {
 				if (f)
 					yahoo_friend_set_buddy_icon_need_request(f, TRUE);
-				purple_buddy_icons_set_for_user(gc->account, name, NULL, 0);
-				if (b)
-					purple_blist_node_remove_setting((PurpleBlistNode *)b, YAHOO_ICON_CHECKSUM_KEY);
+				purple_buddy_icons_set_for_user(gc->account, name, NULL, 0, NULL);
 				break;
 			}
 
@@ -363,7 +363,7 @@
 				break;
 
 			yahoo_friend_set_buddy_icon_need_request(f, FALSE);
-			if (b && cksum != purple_blist_node_get_int((PurpleBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY))
+			if (b && cksum != strtol(purple_buddy_icons_get_checksum_for_user(b), NULL, 10))
 				yahoo_send_picture_request(gc, name);
 
 			break;
--- a/libpurple/protocols/yahoo/yahoo.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Wed Apr 25 00:57:53 2007 +0000
@@ -48,7 +48,6 @@
 
 #define WEBMESSENGER_URL "http://login.yahoo.com/config/login?.src=pg"
 
-#define YAHOO_ICON_CHECKSUM_KEY "icon_checksum"
 #define YAHOO_PICURL_SETTING "picture_url"
 #define YAHOO_PICCKSUM_SETTING "picture_checksum"
 #define YAHOO_PICEXPIRE_SETTING "picture_expire"
--- a/libpurple/protocols/yahoo/yahoo_picture.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Wed Apr 25 00:57:53 2007 +0000
@@ -49,7 +49,6 @@
 {
 	struct yahoo_fetch_picture_data *d;
 	struct yahoo_data *yd;
-	PurpleBuddy *b;
 
 	d = user_data;
 	yd = d->gc->proto_data;
@@ -60,10 +59,9 @@
 	} else if (len == 0) {
 		purple_debug_error("yahoo", "Fetched an icon with length 0.  Strange.\n");
 	} else {
-		purple_buddy_icons_set_for_user(purple_connection_get_account(d->gc), d->who, (void *)pic_data, len);
-		b = purple_find_buddy(purple_connection_get_account(d->gc), d->who);
-		if (b)
-			purple_blist_node_set_int((PurpleBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY, d->checksum);
+		char *checksum = g_strdup_printf("%i", d->checksum);
+		purple_buddy_icons_set_for_user(purple_connection_get_account(d->gc), d->who, (void *)pic_data, len, checksum);
+		g_free(checksum);
 	}
 
 	g_free(d->who);
@@ -117,7 +115,9 @@
 		PurpleUtilFetchUrlData *url_data;
 		struct yahoo_fetch_picture_data *data;
 		PurpleBuddy *b = purple_find_buddy(gc->account, who);
-		if (b && (checksum == purple_blist_node_get_int((PurpleBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY)))
+
+		/* FIXME: Cleanup this strtol() stuff if possible. */
+		if (b && (checksum == strtol(purple_buddy_icons_get_checksum_for_user(b), NULL, 10)))
 			return;
 
 		data = g_new0(struct yahoo_fetch_picture_data, 1);
@@ -166,11 +166,8 @@
 		if (icon == 2)
 			yahoo_send_picture_request(gc, who);
 		else if ((icon == 0) || (icon == 1)) {
-			PurpleBuddy *b = purple_find_buddy(gc->account, who);
 			YahooFriend *f;
-			purple_buddy_icons_set_for_user(gc->account, who, NULL, 0);
-			if (b)
-				purple_blist_node_remove_setting((PurpleBlistNode *)b, YAHOO_ICON_CHECKSUM_KEY);
+			purple_buddy_icons_set_for_user(gc->account, who, NULL, 0, NULL);
 			if ((f = yahoo_friend_find(gc, who)))
 				yahoo_friend_set_buddy_icon_need_request(f, TRUE);
 			purple_debug_misc("yahoo", "Setting user %s's icon to NULL.\n", who);
@@ -203,7 +200,9 @@
 
 	if (who) {
 		PurpleBuddy *b = purple_find_buddy(gc->account, who);
-		if (b && (checksum != purple_blist_node_get_int((PurpleBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY)))
+
+		/* FIXME: Cleanup this strtol() stuff if possible. */
+		if (b && (checksum != strtol(purple_buddy_icons_get_checksum_for_user(b), NULL, 10)))
 			yahoo_send_picture_request(gc, who);
 	}
 }
@@ -276,11 +275,8 @@
 		if (avatar == 2)
 			yahoo_send_picture_request(gc, who);
 		else if ((avatar == 0) || (avatar == 1)) {
-			PurpleBuddy *b = purple_find_buddy(gc->account, who);
 			YahooFriend *f;
-			purple_buddy_icons_set_for_user(gc->account, who, NULL, 0);
-			if (b)
-				purple_blist_node_remove_setting((PurpleBlistNode *)b, YAHOO_ICON_CHECKSUM_KEY);
+			purple_buddy_icons_set_for_user(gc->account, who, NULL, 0, NULL);
 			if ((f = yahoo_friend_find(gc, who)))
 				yahoo_friend_set_buddy_icon_need_request(f, TRUE);
 			purple_debug_misc("yahoo", "Setting user %s's icon to NULL.\n", who);
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Wed Apr 25 00:57:53 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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/util.c	Wed Apr 25 00:57:53 2007 +0000
@@ -65,7 +65,7 @@
 };
 
 static char custom_home_dir[MAXPATHLEN];
-static char home_dir[MAXPATHLEN];
+static char home_dir[MAXPATHLEN] = "";
 
 PurpleMenuAction *
 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
@@ -2245,25 +2245,17 @@
 #endif
 }
 
-/* Returns the argument passed to -c IFF it was present, or ~/.gaim IFF it
- * exists, else ~/.purple. */
+/* Returns the argument passed to -c IFF it was present, or ~/.purple. */
 const char *
 purple_user_dir(void)
 {
-	if (custom_home_dir != NULL && strlen(custom_home_dir) > 0) {
+	if (custom_home_dir != NULL && *custom_home_dir) {
 		strcpy ((char*) &home_dir, (char*) &custom_home_dir);
-	} else {
+	} else if (!*home_dir) {
 		const gchar *hd = purple_home_dir();
 
 		if (hd) {
 			g_strlcpy((char*) &home_dir, hd, sizeof(home_dir));
-			g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".gaim",
-					sizeof(home_dir));
-
-			if (g_file_test(home_dir, G_FILE_TEST_EXISTS))
-				return home_dir;
-
-			g_strlcpy((char*) &home_dir, hd, sizeof(home_dir));
 			g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".purple",
 					sizeof(home_dir));
 		}
@@ -2577,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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/util.h	Wed Apr 25 00:57:53 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	Wed Apr 25 00:48:59 2007 +0000
+++ b/libpurple/value.h	Wed Apr 25 00:57:53 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/gtkaccount.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkaccount.c	Wed Apr 25 00:57:53 2007 +0000
@@ -302,8 +302,10 @@
 
 	dialog = data;
 
+#if 0
 	if (filename != NULL)
 		set_dialog_icon(dialog, pidgin_convert_buddy_icon(dialog->plugin, filename), g_strdup(filename));
+#endif
 
 	dialog->icon_filesel = NULL;
 }
@@ -342,7 +344,9 @@
 			}
 			if ((rtmp = strchr(tmp, '\r')) || (rtmp = strchr(tmp, '\n')))
 				*rtmp = '\0';
+#if 0
 			set_dialog_icon(dialog, pidgin_convert_buddy_icon(dialog->plugin, tmp), g_strdup(tmp));
+#endif
 			g_free(tmp);
 		}
 		gtk_drag_finish(dc, TRUE, FALSE, t);
@@ -1159,10 +1163,12 @@
 		else if (purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon") && icon_change)
 		{
 			const char *filename = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");
+#if 0
 			char *icon = pidgin_convert_buddy_icon(dialog->plugin, filename);
 			purple_account_set_buddy_icon_path(account, filename);
 			purple_account_set_buddy_icon(account, icon);
 			g_free(icon);
+#endif
 		}
 	}
 
--- a/pidgin/gtkblist.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkblist.c	Wed Apr 25 00:57:53 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) {
@@ -2211,8 +2204,9 @@
 				if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name))) /* Not sure I like this...*/
 					return NULL;
 			data = purple_buddy_icon_get_data(icon, &len);
+			if (data == NULL)
+				return NULL;
 		}
-		custom = FALSE;  /* We are not using the custom icon */
 	}
 
 	if(data == NULL)
@@ -2221,13 +2215,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 +2330,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 +4888,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 +5073,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	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkconv.c	Wed Apr 25 00:57:53 2007 +0000
@@ -2482,23 +2482,6 @@
 	fclose(fp);
 }
 
-static const char *
-custom_icon_pref_name(PidginConversation *gtkconv)
-{
-	PurpleConversation *conv;
-	PurpleAccount *account;
-	PurpleBuddy *buddy;
-
-	conv = gtkconv->active_conv;
-	account = purple_conversation_get_account(conv);
-	buddy = purple_find_buddy(account, purple_conversation_get_name(conv));
-	if (buddy) {
-		PurpleContact *contact = purple_buddy_get_contact(buddy);
-		return purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon");
-	}
-	return NULL;
-}
-
 static void
 custom_icon_sel_cb(const char *filename, gpointer data)
 {
@@ -2538,9 +2521,7 @@
 
 	g_return_if_fail(conv != NULL);
 
-	ext = purple_buddy_icon_get_type(purple_conv_im_get_icon(PURPLE_CONV_IM(conv)));
-	if (ext == NULL)
-		ext = "icon";
+	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);
 
@@ -2576,7 +2557,8 @@
 icon_menu(GtkObject *obj, GdkEventButton *e, PidginConversation *gtkconv)
 {
 	static GtkWidget *menu = NULL;
-	const char *pref;
+	PurpleConversation *conv;
+	PurpleBuddy *buddy;
 
 	if (e->button != 3 || e->type != GDK_BUTTON_PRESS)
 		return FALSE;
@@ -2610,11 +2592,18 @@
 							 0, 0, NULL);
 
 	/* Is there a custom icon for this person? */
-	pref = custom_icon_pref_name(gtkconv);
-	if (pref && *pref) {
-		pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
-							G_CALLBACK(remove_custom_icon_cb), gtkconv,
-							0, 0, NULL);
+	conv = gtkconv->active_conv;
+	buddy = purple_find_buddy(purple_conversation_get_account(conv),
+	                          purple_conversation_get_name(conv));
+	if (buddy)
+	{
+		PurpleContact *contact = purple_buddy_get_contact(buddy);
+		if (contact && purple_buddy_icons_has_custom_icon(contact))
+		{
+			pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
+			                           G_CALLBACK(remove_custom_icon_cb), gtkconv,
+			                           0, 0, NULL);
+		}
 	}
 
 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, e->button, e->time);
@@ -6177,12 +6166,14 @@
 	PidginConversation *gtkconv;
 	PidginWindow *win;
 
+	PurpleBuddy *buddy;
+
 	GdkPixbufLoader *loader;
 	GdkPixbufAnimation *anim;
 	GError *err = NULL;
 
-	const char *custom = NULL;
-	const void *data = NULL;
+	PurpleStoredImage *custom_img = NULL;
+	gconstpointer data = NULL;
 	size_t len;
 
 	GdkPixbuf *buf;
@@ -6239,17 +6230,18 @@
 	if (purple_conversation_get_gc(conv) == NULL)
 		return;
 
-	custom = custom_icon_pref_name(gtkconv);
-	if (custom) {
-		/* There is a custom icon for this user */
-		char *contents = NULL;
-		if (!g_file_get_contents(custom, &contents, &len, &err)) {
-			purple_debug_warning("custom icon", "could not load custom icon %s for %s\n",
-						custom, purple_conversation_get_name(conv));
-			g_error_free(err);
-			err = NULL;
-		} else
-			data = contents;
+	buddy = purple_find_buddy(account, purple_conversation_get_name(conv));
+	if (buddy)
+	{
+		PurpleContact *contact = purple_buddy_get_contact(buddy);
+		if (contact) {
+			custom_img = purple_buddy_icons_find_custom_icon(contact);
+			if (custom_img) {
+				/* There is a custom icon for this user */
+				data = purple_imgstore_get_data(custom_img);
+				len = purple_imgstore_get_size(custom_img);
+			}
+		}
 	}
 
 	if (data == NULL) {
@@ -6259,20 +6251,22 @@
 			return;
 
 		data = purple_buddy_icon_get_data(icon, &len);
-		custom = NULL;
+
+		if (data == NULL)
+			return;
 	}
 
 	loader = gdk_pixbuf_loader_new();
 	gdk_pixbuf_loader_write(loader, data, len, NULL);
 	gdk_pixbuf_loader_close(loader, &err);
+
+	purple_imgstore_unref(custom_img);
+
 	anim = gdk_pixbuf_loader_get_animation(loader);
 	if (anim)
 		g_object_ref(G_OBJECT(anim));
 	g_object_unref(loader);
 
-	if (custom)
-		g_free((void*)data);
-
 	if (!anim)
 		return;
 	gtkconv->u.im->anim = anim;
@@ -6283,9 +6277,6 @@
 		g_error_free(err);
 	}
 
-	if (!gtkconv->u.im->anim)
-		return;
-
 	if (gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim)) {
 		gtkconv->u.im->iter = NULL;
 		buf = gdk_pixbuf_animation_get_static_image(gtkconv->u.im->anim);
--- a/pidgin/gtkimhtmltoolbar.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Wed Apr 25 00:57:53 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	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkmain.c	Wed Apr 25 00:57:53 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.
  *
@@ -446,6 +446,7 @@
 	int opt;
 	gboolean gui_check;
 	gboolean debug_enabled;
+	gboolean migration_failed = FALSE;
 
 	struct option long_options[] = {
 		{"config",   required_argument, NULL, 'c'},
@@ -639,6 +640,15 @@
 
 	purple_debug_set_enabled(debug_enabled);
 
+	/* If we're using a custom configuration directory, we
+	 * do NOT want to migrate, or weird things will happen. */
+	if (opt_config_dir_arg == NULL)
+	{
+		if (!purple_core_migrate())
+		{
+			migration_failed = TRUE;
+		}
+	}
 
 	search_path = g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL);
 	gtk_rc_add_default_file(search_path);
@@ -663,6 +673,37 @@
 	winpidgin_init(hint);
 #endif
 
+	if (migration_failed)
+	{
+		char *old = g_strconcat(purple_home_dir(),
+		                        G_DIR_SEPARATOR_S ".gaim", NULL);
+		const char *text = _(
+			"%s encountered errors migrating your settings "
+			"from %s to %s. Please investigate and complete the "
+			"migration by hand.");
+		GtkWidget *dialog;
+
+		dialog = gtk_message_dialog_new(NULL,
+		                                0,
+		                                GTK_MESSAGE_ERROR,
+		                                GTK_BUTTONS_CLOSE,
+		                                text, PIDGIN_NAME,
+		                                old, purple_user_dir());
+		g_free(old);
+
+		g_signal_connect_swapped(dialog, "response",
+		                         G_CALLBACK(gtk_main_quit), NULL);
+
+		gtk_widget_show_all(dialog);
+
+		gtk_main();
+
+#ifdef HAVE_SIGNAL_H
+		g_free(segfault_message);
+#endif
+		return 0;
+	}
+
 	purple_core_set_ui_ops(pidgin_core_get_ui_ops());
 	purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops());
 
--- a/pidgin/gtkstatusbox.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkstatusbox.c	Wed Apr 25 00:57:53 2007 +0000
@@ -1439,8 +1439,10 @@
 			PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
 			if (prplinfo && prplinfo->icon_spec.format) {
 				char *icon = NULL;
+#if 0
 				if (filename)
 					icon = pidgin_convert_buddy_icon(plug, filename);
+#endif
 				purple_account_set_bool(box->account, "use-global-buddyicon", (filename != NULL));
 				purple_account_set_ui_string(box->account, PIDGIN_UI, "non-global-buddyicon-cached-path", icon);
 				purple_account_set_buddy_icon_path(box->account, filename);
@@ -1459,8 +1461,10 @@
 				    purple_account_get_bool(account, "use-global-buddyicon", TRUE) &&
 				    prplinfo->icon_spec.format) {
 					char *icon = NULL;
+#if 0
 					if (filename)
 						icon = pidgin_convert_buddy_icon(plug, filename);
+#endif
 					purple_account_set_buddy_icon_path(account, filename);
 					purple_account_set_buddy_icon(account, icon);
 					g_free(icon);
--- a/pidgin/gtkutils.c	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkutils.c	Wed Apr 25 00:57:53 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;
 	}
@@ -2409,15 +2409,14 @@
 }
 #endif
 
-char *
-pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path)
+gpointer
+pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
 {
 	PurplePluginProtocolInfo *prpl_info;
 #if GTK_CHECK_VERSION(2,2,0)
 	char **prpl_formats;
 	int width, height;
 	char **pixbuf_formats = NULL;
-	struct stat st;
 	GdkPixbufFormat *format;
 	GdkPixbuf *pixbuf;
 #if !GTK_CHECK_VERSION(2,4,0)
@@ -2426,28 +2425,11 @@
 #endif
 	gchar *contents;
 	gsize length;
-	const char *dirname;
-	char *random;
-	char *filename;
 
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
 
 	g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL);
 
-	dirname = purple_buddy_icons_get_cache_dir();
-	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
-		purple_debug_info("buddyicon", "Creating icon cache directory.\n");
-
-		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
-			purple_debug_error("buddyicon",
-							 "Unable to create directory %s: %s\n",
-							 dirname, strerror(errno));
-			return NULL;
-		}
-	}
-
-	random = g_strdup_printf("%x", g_random_int());
-	filename = g_build_filename(dirname, random, NULL);
 
 #if GTK_CHECK_VERSION(2,2,0)
 #if GTK_CHECK_VERSION(2,4,0)
@@ -2478,47 +2460,21 @@
 		   prpl_info->icon_spec.max_height >= height)))                   /* The icon is the correct size */
 #endif
 	{
-		FILE *image;
-
 #if GTK_CHECK_VERSION(2,2,0)
 		g_strfreev(prpl_formats);
 		g_strfreev(pixbuf_formats);
 #endif
-
-		/* We don't need to scale the image, so copy it to the cache folder verbatim */
+		/* We don't need to scale the image. */
 
 		contents = NULL;
-		if (!g_file_get_contents(path, &contents, &length, NULL) ||
-		    (image = g_fopen(filename, "wb")) == NULL)
+		if (!g_file_get_contents(path, &contents, &length, NULL))
 		{
-			g_free(random);
-			g_free(filename);
 			g_free(contents);
 #if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
-			g_object_unref(G_OBJECT(pixbuf));
+		g_object_unref(G_OBJECT(pixbuf));
 #endif
 			return NULL;
 		}
-
-		if (fwrite(contents, 1, length, image) != length)
-		{
-			fclose(image);
-			g_unlink(filename);
-
-			g_free(random);
-			g_free(filename);
-			g_free(contents);
-#if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
-			g_object_unref(G_OBJECT(pixbuf));
-#endif
-			return NULL;
-		}
-		fclose(image);
-		g_free(contents);
-
-#if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
-		g_object_unref(G_OBJECT(pixbuf));
-#endif
 	}
 #if GTK_CHECK_VERSION(2,2,0)
 	else
@@ -2527,6 +2483,7 @@
 		GError *error = NULL;
 		GdkPixbuf *scale;
 		gboolean success = FALSE;
+		char *filename = NULL;
 
 		g_strfreev(pixbuf_formats);
 
@@ -2534,8 +2491,6 @@
 		if (error) {
 			purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
 			g_error_free(error);
-			g_free(random);
-			g_free(filename);
 			g_strfreev(prpl_formats);
 			return NULL;
 		}
@@ -2558,6 +2513,17 @@
 		}
 
 		for (i = 0; prpl_formats[i]; i++) {
+			FILE *fp;
+
+			g_free(filename);
+			fp = purple_mkstemp(&filename, TRUE);
+			if (!fp)
+			{
+				g_free(filename);
+				return NULL;
+			}
+			fclose(fp);
+
 			purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats[i], filename);
 			/* The "compression" param wasn't supported until gdk-pixbuf 2.8.
 			 * Using it in previous versions causes the save to fail (and an assert message).  */
@@ -2586,29 +2552,33 @@
 		g_object_unref(G_OBJECT(pixbuf));
 		if (!success) {
 			purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
-			g_free(random);
+			return NULL;
+		}
+
+		contents = NULL;
+		if (!g_file_get_contents(filename, &contents, &length, NULL))
+		{
+			purple_debug_error("buddyicon",
+					"Could not read '%s', which we just wrote to disk.\n",
+					filename);
+
+			g_free(contents);
 			g_free(filename);
 			return NULL;
 		}
+
+		g_unlink(filename);
+		g_free(filename);
 	}
 
-	if (g_stat(filename, &st) != 0) {
-		purple_debug_error("buddyicon",
-				"Could not stat '%s', which we just wrote to disk: %s\n",
-				filename, strerror(errno));
-		g_free(random);
-		g_free(filename);
-		return NULL;
-	}
-
-	/* Check the file size */
+	/* Check the image size */
 	/*
 	 * TODO: If the file is too big, it would be cool if we checked if
 	 *       the prpl supported jpeg, and then we could convert to that
 	 *       and use a lower quality setting.
 	 */
 	if ((prpl_info->icon_spec.max_filesize != 0) &&
-		(st.st_size > prpl_info->icon_spec.max_filesize))
+	    (length > prpl_info->icon_spec.max_filesize))
 	{
 		gchar *tmp;
 		tmp = g_strdup_printf(_("The file '%s' is too large for %s.  Please try a smaller image.\n"),
@@ -2618,16 +2588,15 @@
 		purple_debug_info("buddyicon",
 				"'%s' was converted to an image which is %" G_GSIZE_FORMAT
 				" bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
-				" bytes\n", path, st.st_size, plugin->info->name,
+				" bytes\n", path, length, plugin->info->name,
 				prpl_info->icon_spec.max_filesize);
 		g_free(tmp);
-		g_free(random);
-		g_free(filename);
 		return NULL;
 	}
 
-	g_free(filename);
-	return random;
+	if (len)
+		*len = length;
+	return contents;
 #else
 	/*
 	 * The chosen icon wasn't the right size, and we're using
@@ -2780,10 +2749,10 @@
 
 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
 {
-	PurpleConversation *conv;
 	PurpleBuddy *buddy;
-	PurpleBlistNode *node;
-	char *path = NULL;
+	PurpleContact *contact;
+	gpointer data = NULL;
+	size_t len = 0;
 
 	buddy = purple_find_buddy(account, who);
 	if (!buddy) {
@@ -2791,35 +2760,20 @@
 		return;
 	}
 
-	node = (PurpleBlistNode*)purple_buddy_get_contact(buddy);
-	path = (char*)purple_blist_node_get_string(node, "custom_buddy_icon");
-	if (path) {
-		struct stat st;
-		if (g_stat(path, &st) == 0)
-			g_unlink(path);
-		path = NULL;
-	}
+	contact = purple_buddy_get_contact(buddy);
 
 	if (filename) {
-		char *newfile;
-
-		newfile = pidgin_convert_buddy_icon(purple_find_prpl(purple_account_get_protocol_id(account)),
-						filename);
-		path = purple_buddy_icons_get_full_path(newfile);
-		g_free(newfile);
+		const char *prpl_id = purple_account_get_protocol_id(account);
+		PurplePlugin *prpl = purple_find_prpl(prpl_id);
+
+		data = pidgin_convert_buddy_icon(prpl, filename, &len);
+
+		/* We don't want to delete the old icon if the new one didn't load. */
+		if (data == NULL)
+			return;
 	}
 
-	purple_blist_node_set_string(node, "custom_buddy_icon", path);
-	g_free(path);
-
-	/* Update the conversation */
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account);
-	if (conv)
-		purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
-
-	/* Update the buddylist */
-	if (buddy)
-		purple_blist_update_buddy_icon(buddy);
+	purple_buddy_icons_set_custom_icon(contact, data, len);
 }
 
 char *pidgin_make_pretty_arrows(const char *str)
--- a/pidgin/gtkutils.h	Wed Apr 25 00:48:59 2007 +0000
+++ b/pidgin/gtkutils.h	Wed Apr 25 00:57:53 2007 +0000
@@ -445,11 +445,13 @@
 /**
  * Converts a buddy icon to the required size and format
  * 
- * @param plugin     The prpl to conver the icon
- * @param path       The path of a buddy icon to convert
- * @return           The name of a new buddy icon
+ * @param plugin     The prpl to convert the icon
+ * @param path       The path of a file to convert
+ * @param len        If not @c NULL, the length of the returned data will be set here.
+ *
+ * @return           The converted image data, or @c NULL if an error occurred.
  */
-char* pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path);
+gpointer pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len);
 
 #if !GTK_CHECK_VERSION(2,6,0)
 /**