changeset 16374:2a19bbc743ed

propagate from branch 'im.pidgin.pidgin' (head 6096404018084d5ea24f916c8e757d64e237dbd7) to branch 'im.pidgin.rlaager.gaim_migration' (head 9163bb7f709cd6472c0fae9555e2d357b4d2d58e)
author Richard Laager <rlaager@wiktel.com>
date Tue, 24 Apr 2007 03:56:16 +0000
parents bf353c85959a (current diff) c9b4ff420140 (diff)
children 391a79778f89
files
diffstat 13 files changed, 1111 insertions(+), 418 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/blist.c	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/blist.c	Tue Apr 24 03:56:16 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	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/buddyicon.c	Tue Apr 24 03:56:16 2007 +0000
@@ -24,15 +24,270 @@
  */
 #include "internal.h"
 #include "buddyicon.h"
+#include "cipher.h"
 #include "conversation.h"
 #include "dbus-maybe.h"
 #include "debug.h"
 #include "util.h"
 
+typedef struct _PurpleBuddyIconData PurpleBuddyIconData;
+
+struct _PurpleBuddyIcon
+{
+	PurpleAccount *account;             /**< The account the user is on.          */
+	char *username;                     /**< The username the icon belongs to.    */
+	PurpleBuddyIconData *protocol_icon; /**< The icon data.                       */
+	PurpleBuddyIconData *custom_icon;   /**< The data for a user-set custom icon. */
+	int ref_count;                      /**< The buddy icon reference count.      */
+};
+
+struct _PurpleBuddyIconData
+{
+	guchar *image_data;    /**< The buddy icon data.               */
+	size_t len;            /**< The length of the buddy icon data. */
+	char *filename;        /**< The filename of the cache file.    */
+	int ref_count;         /**< The buddy icon reference count.    */
+};
+
 static GHashTable *account_cache = NULL;
+static GHashTable *icon_data_cache = NULL;
+static GHashTable *icon_file_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 const char *
+get_icon_type(guchar *icon_data, size_t icon_len)
+{
+	g_return_val_if_fail(icon_data != NULL, NULL);
+	g_return_val_if_fail(icon_len > 0, NULL);
+
+	if (icon_len >= 4)
+	{
+		if (!strncmp((char *)icon_data, "BM", 2))
+			return "bmp";
+		else if (!strncmp((char *)icon_data, "GIF8", 4))
+			return "gif";
+		else if (!strncmp((char *)icon_data, "\xff\xd8\xff\xe0", 4))
+			return "jpg";
+		else if (!strncmp((char *)icon_data, "\x89PNG", 4))
+			return "png";
+	}
+
+	return "icon";
+}
+
+static const char *
+purple_buddy_icon_data_get_type(PurpleBuddyIconData *data)
+{
+	return get_icon_type(data->image_data, data->len);
+}
+
+static char *
+purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len)
+{
+	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,
+	                       get_icon_type(icon_data, icon_len));
+}
+
+static void
+purple_buddy_icon_data_cache(PurpleBuddyIconData *data)
+{
+	const char *dirname;
+	char *path;
+	FILE *file = NULL;
+
+	if (!purple_buddy_icons_is_caching())
+		return;
+
+	dirname  = purple_buddy_icons_get_cache_dir();
+	path = g_build_filename(dirname, data->filename, 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(data->image_data, data->len, 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(PurpleBuddyIconData *data)
+{
+	const char *dirname;
+	char *path;
+
+	g_return_if_fail(data != NULL);
+
+	/* It's possible that there are other references to this icon
+	 * cache file that are not currently loaded into memory. */
+	if (g_hash_table_lookup(icon_file_cache, data->filename))
+		return;
+
+	dirname  = purple_buddy_icons_get_cache_dir();
+	path = g_build_filename(dirname, data->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 PurpleBuddyIconData *
+purple_buddy_icon_data_ref(PurpleBuddyIconData *data)
+{
+	g_return_val_if_fail(data != NULL, NULL);
+
+	data->ref_count++;
+
+	return data;
+}
+
+static PurpleBuddyIconData *
+purple_buddy_icon_data_unref(PurpleBuddyIconData *data)
+{
+	if (data == NULL)
+		return NULL;
+
+	g_return_val_if_fail(data->ref_count > 0, NULL);
+
+	data->ref_count--;
+
+	if (data->ref_count == 0)
+	{
+		g_hash_table_remove(icon_data_cache, data->filename);
+
+		purple_buddy_icon_data_uncache(data);
+
+		g_free(data->image_data);
+		g_free(data->filename);
+		g_free(data);
+
+		return NULL;
+	}
+
+	return data;
+}
+
+static PurpleBuddyIconData *
+purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len)
+{
+	PurpleBuddyIconData *data;
+	char *filename;
+
+	g_return_val_if_fail(icon_data != NULL, NULL);
+	g_return_val_if_fail(icon_len  > 0,     NULL);
+
+	filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+	if (filename == NULL)
+		return NULL;
+
+	if ((data = g_hash_table_lookup(icon_data_cache, filename)))
+	{
+		g_free(filename);
+		return purple_buddy_icon_data_ref(data);
+	}
+
+	data = g_new0(PurpleBuddyIconData, 1);
+	data->image_data = g_memdup(icon_data, icon_len);
+	data->len = icon_len;
+	data->filename = filename;
+
+	purple_buddy_icon_data_cache(data);
+
+	return data;
+}
+
 static PurpleBuddyIcon *
 purple_buddy_icon_create(PurpleAccount *account, const char *username)
 {
@@ -42,8 +297,8 @@
 	icon = g_new0(PurpleBuddyIcon, 1);
 	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_cache = g_hash_table_lookup(account_cache, account);
 
@@ -55,91 +310,39 @@
 	}
 
 	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 *protocol_icon_data, size_t protocol_icon_len,
+                      void *custom_icon_data, size_t custom_icon_len)
 {
 	PurpleBuddyIcon *icon;
 
 	g_return_val_if_fail(account   != NULL, NULL);
 	g_return_val_if_fail(username  != NULL, NULL);
-	g_return_val_if_fail(icon_data != NULL, NULL);
-	g_return_val_if_fail(icon_len  > 0,    NULL);
+	g_return_val_if_fail((protocol_icon_data != NULL && protocol_icon_len > 0) ||
+	                     (custom_icon_data != NULL && custom_icon_len > 0), NULL);
 
 	icon = purple_buddy_icons_find(account, username);
 
 	if (icon == NULL)
 		icon = purple_buddy_icon_create(account, username);
 
+	/* Take a reference for the caller of this function. */
 	purple_buddy_icon_ref(icon);
-	purple_buddy_icon_set_data(icon, icon_data, icon_len);
-	purple_buddy_icon_set_path(icon, NULL);
 
-	/* 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.
-	 */
+	if (protocol_icon_data != NULL && protocol_icon_len > 0)
+		purple_buddy_icon_set_protocol_data(icon, protocol_icon_data, protocol_icon_len);
+
+	if (custom_icon_data != NULL && custom_icon_len > 0)
+		purple_buddy_icon_set_custom_data(icon, custom_icon_data, custom_icon_len);
 
 	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 +356,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);
+		purple_buddy_icon_data_unref(icon->protocol_icon);
+		purple_buddy_icon_data_unref(icon->custom_icon);
+
+		PURPLE_DBUS_UNREGISTER_POINTER(icon);
+		g_free(icon);
 
 		return NULL;
 	}
@@ -174,6 +389,7 @@
 	PurpleConversation *conv;
 	PurpleAccount *account;
 	const char *username;
+	PurpleBuddyIcon *icon_to_set;
 	GSList *sl, *list;
 
 	g_return_if_fail(icon != NULL);
@@ -181,12 +397,58 @@
 	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 neither type of data exists, then call the functions below with
+	 * NULL to unset the icon.  They will then unref the icon and it
+	 * should be destroyed.  The only way it wouldn't be destroyed is if
+	 * someone else is holding a reference to it, in which case they can
+	 * kill the icon when they realize it has no data any more. */
+	icon_to_set = (icon->protocol_icon || icon->custom_icon) ? icon : NULL;
+
+	for (list = sl = purple_find_buddies(account, username);
+	     sl != NULL;
+	     sl = sl->next)
 	{
 		PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
+		const char *old_icon;
 
-		purple_buddy_set_icon(buddy, icon);
+		purple_buddy_set_icon(buddy, icon_to_set);
+
+
+		old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
+		                                        "buddy_icon");
+		if (icon->protocol_icon)
+		{
+			old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
+			                                        "buddy_icon");
+			purple_blist_node_set_string((PurpleBlistNode *)buddy,
+			                             "buddy_icon",
+			                             icon->protocol_icon->filename);
+			ref_filename(icon->protocol_icon->filename);
+		}
+		else
+		{
+			purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
+		}
+		unref_filename(old_icon);
+
+
+		old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
+		                                        "custom_buddy_icon");
+		if (icon->custom_icon)
+		{
+			old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
+			                                        "custom_buddy_icon");
+			purple_blist_node_set_string((PurpleBlistNode *)buddy,
+			                             "custom_buddy_icon",
+			                             icon->custom_icon->filename);
+			ref_filename(icon->custom_icon->filename);
+		}
+		else
+		{
+			purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
+			                                 "custom_buddy_icon");
+		}
+		unref_filename(old_icon);
 	}
 
 	g_slist_free(list);
@@ -194,160 +456,43 @@
 	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_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
 {
-	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);
+	PurpleBuddyIconData *old_data;
 
-	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");
+	g_return_if_fail(icon != NULL);
 
-		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));
-		}
-	}
+	old_data = icon->custom_icon;
+	icon->custom_icon = NULL;
 
-	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;
-	}
+	if (data != NULL && len > 0)
+		icon->custom_icon = purple_buddy_icon_data_new(data, len);
 
-	g_free(filename);
+	purple_buddy_icon_update(icon);
 
-	if (old_icon != NULL)
-		delete_icon_cache_file(dirname, old_icon);
-
-	purple_blist_node_set_string((PurpleBlistNode *)buddy, "buddy_icon", random);
-
-	g_free(random);
+	purple_buddy_icon_data_unref(icon->custom_icon);
 }
 
 void
-purple_buddy_icon_uncache(PurpleBuddy *buddy)
+purple_buddy_icon_set_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
 {
-	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;
-	}
-}
+	PurpleBuddyIconData *old_data;
 
-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_data = icon->protocol_icon;
+	icon->protocol_icon = NULL;
 
 	if (data != NULL && len > 0)
-	{
-		icon->data = g_memdup(data, len);
-		icon->len  = len;
-	}
-	else
-	{
-		icon->data = NULL;
-		icon->len  = 0;
-	}
+		icon->protocol_icon = purple_buddy_icon_data_new(data, len);
 
 	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_buddy_icon_data_unref(old_data);
 }
 
 PurpleAccount *
@@ -371,116 +516,308 @@
 {
 	g_return_val_if_fail(icon != NULL, NULL);
 
-	if (len != NULL)
-		*len = icon->len;
+	if (icon->custom_icon)
+	{
+		if (len != NULL)
+			*len = icon->custom_icon->len;
 
-	return icon->data;
-}
+		return icon->custom_icon->image_data;
+	}
 
-const char *
-purple_buddy_icon_get_path(PurpleBuddyIcon *icon)
-{
-	g_return_val_if_fail(icon != NULL, NULL);
+	if (icon->protocol_icon)
+	{
+		if (len != NULL)
+			*len = icon->protocol_icon->len;
 
-	return icon->path;
+		return icon->protocol_icon->image_data;
+	}
+
+	return NULL;
 }
 
 const char *
 purple_buddy_icon_get_type(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);
+	if (icon->custom_icon != NULL)
+		return purple_buddy_icon_data_get_type(icon->custom_icon);
 
-	/* 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->protocol_icon != NULL)
+		return purple_buddy_icon_data_get_type(icon->protocol_icon);
 
 	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)
 {
 	g_return_if_fail(account  != NULL);
 	g_return_if_fail(username != NULL);
 
 	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_protocol_data(icon, icon_data, icon_len);
 	}
 	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, NULL, 0);
 		purple_buddy_icon_unref(icon);
 	}
 }
 
+static gboolean
+read_icon_file(const char *path, guchar **data, size_t *len)
+{
+	struct stat st;
+
+	if (!g_stat(path, &st))
+	{
+		FILE *f = g_fopen(path, "rb");
+		if (f)
+		{
+			*data = g_malloc(st.st_size);
+			if (!fread(*data, st.st_size, 1, f))
+			{
+				purple_debug_error("buddyicon", "Error reading %s: %s\n",
+				                   path, strerror(errno));
+				g_free(*data);
+				return FALSE;
+			}
+			fclose(f);
+
+			*len = st.st_size;
+			return TRUE;
+		}
+		else
+		{
+			purple_debug_error("buddyicon", "Unable to open file %s for reading: %s\n",
+			                   path, strerror(errno));
+		}
+	}
+	return FALSE;
+}
+
 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 *custom_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");
+		custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "custom_buddy_icon");
+
+		if (protocol_icon_file == NULL && custom_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 (custom_icon_file != NULL)
+		{
+			char *path = g_build_filename(dirname, custom_icon_file, NULL);
+			if (read_icon_file(path, &data, &len))
+			{
+				icon = purple_buddy_icon_create(account, username);
+				purple_buddy_icon_set_custom_data(icon, data, len);
+			}
+			g_free(path);
+		}
+
+		if (protocol_icon_file != NULL)
+		{
+			char *path = g_build_filename(dirname, protocol_icon_file, NULL);
+			if (read_icon_file(path, &data, &len))
+			{
+				if (icon == NULL)
+					icon = purple_buddy_icon_create(account, username);
+				purple_buddy_icon_set_protocol_data(icon, data, len);
+			}
+			g_free(path);
+		}
+
+		purple_buddy_icons_set_caching(caching);
+	}
+
+	return 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 (!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;
+		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);
+	}
+	else
+	{
+		/* If the icon is gone, drop the setting... */
+		purple_blist_node_remove_setting(node,
+		                                 setting_name);
+		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();
+
+	/* 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));
 			}
 		}
-		g_free(filename);
 	}
 
-	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
+				{
+					// TODO: If filename doesn't exist, drop the setting.
+					ref_filename(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
+				{
+					// TODO: If filename doesn't exist, drop the setting.
+					ref_filename(filename);
+				}
+			}
+		}
+		node = purple_blist_node_next(node, TRUE);
+        }
 }
 
 void
@@ -510,6 +847,7 @@
 	return cache_dir;
 }
 
+// TODO: Deal with this
 char *purple_buddy_icons_get_full_path(const char *icon) {
 	if (icon == NULL)
 		return NULL;
@@ -535,6 +873,10 @@
 		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);
+
 	cache_dir = g_build_filename(purple_user_dir(), "icons", NULL);
 }
 
@@ -542,6 +884,9 @@
 purple_buddy_icons_uninit()
 {
 	g_hash_table_destroy(account_cache);
+	g_hash_table_destroy(icon_data_cache);
+	g_hash_table_destroy(icon_file_cache);
+	g_free(old_icons_dir);
 }
 
 void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height)
@@ -572,4 +917,3 @@
 	*width = new_width;
 	*height = new_height;
 }
-
--- a/libpurple/buddyicon.h	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/buddyicon.h	Tue Apr 24 03:56:16 2007 +0000
@@ -31,49 +31,38 @@
 #include "blist.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.
+ * Create a 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.
- *
- * @return The buddy icon structure.
+ * @param protocol_icon_data The buddy icon data received over the wire.
+ * @param protocol_icon_len  The length of the data in @a protocol_icon_data.
+ * @param custom_icon_data   The data for a custom buddy icon set by the user.
+ * @param custom_icon_len    The length of the data in @a custom_icon-data.
+ * @return The buddy icon structure, referenced for you. If you don't
+ *         want the reference, then call purple_buddy_icon_unref().  However,
+ *         you may want to use purple_buddy_icon_set_for_user() instead.
  */
-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);
+PurpleBuddyIcon *
+purple_buddy_icon_new(PurpleAccount *account, const char *username,
+                      void *protocol_icon_data, size_t protocol_icon_len,
+                      void *custom_icon_data, size_t custom_icon_len);
 
 /**
  * Increments the reference count on a buddy icon.
@@ -103,52 +92,22 @@
 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.
+ * Sets the buddy icon's data for a custom icon set by the user.
  *
- * @param buddy The buddy for which to remove the cached icon.
+ * @param icon The buddy icon.
+ * @param data The buddy icon data for the custom icon set by the user.
+ * @param len  The length of the data in @a data.
  */
-void purple_buddy_icon_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);
+void purple_buddy_icon_set_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len);
 
 /**
- * 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.
  */
-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_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len);
 
 /**
  * Returns the buddy icon's account.
@@ -171,28 +130,32 @@
 /**
  * Returns the buddy icon's data.
  *
+ * This will return the data for a custom icon, if one has been set by the
+ * user.  Otherwise, it will return the protocol data, if available.  If
+ * no data is available (which can happen if you're holding on to a
+ * reference to an icon and the protocol and/or custom icon(s) are deleted
+ * under you), it will return @c NULL.
+ *
  * @param icon The buddy icon.
- * @param len  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.
  */
 const guchar *purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len);
 
 /**
- * Returns the buddy icon's path.
+ * Returns an extension corresponding to the buddy icon's file type.
+ *
+ * This will return the type of a custom icon, if one has been set by the
+ * user.  Otherwise, it will return the type of the protocol icon, if
+ * available.  If no data is available (which can happen if you're holding on
+ * to a reference to an icon and the protocol and/or custom icon(s) are deleted
+ * under you), it will return @c NULL.
  *
  * @param icon The buddy icon.
  *
- * @return The buddy icon's non-cached path.
- */
-const gchar *purple_buddy_icon_get_path(PurpleBuddyIcon *icon);
-
-/**
- * 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, or "icon" if unknown.
  */
 const char *purple_buddy_icon_get_type(const PurpleBuddyIcon *icon);
 
@@ -213,8 +176,9 @@
  *
  * @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);
 
 /**
  * Returns the buddy icon information for a user.
@@ -224,8 +188,8 @@
  *
  * @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);
 
 /**
  * Sets whether or not buddy icon caching is enabled.
@@ -263,18 +227,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	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/conversation.c	Tue Apr 24 03:56:16 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	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/core.c	Tue Apr 24 03:56:16 2007 +0000
@@ -44,6 +44,7 @@
 #include "sslconn.h"
 #include "status.h"
 #include "stun.h"
+#include "util.h"
 
 #ifdef HAVE_DBUS
 #  include "dbus-server.h"
@@ -268,3 +269,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	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/core.h	Tue Apr 24 03:56:16 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	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/gaim-compat.h	Tue Apr 24 03:56:16 2007 +0000
@@ -343,18 +343,14 @@
 #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_icons_set_for_user   purple_buddy_icons_set_for_user
@@ -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
--- a/libpurple/internal.h	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/internal.h	Tue Apr 24 03:56:16 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	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/plugins/perl/common/BuddyIcon.xs	Tue Apr 24 03:56:16 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/qq/buddy_info.c	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Tue Apr 24 03:56:16 2007 +0000
@@ -545,8 +545,7 @@
 		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);
+		// TODO: Set a blist setting or something
 	}
 }
 
@@ -609,7 +608,8 @@
 	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);
+	// TODO: This needs to use a blist setting or something.
+	const gchar *old_path = NULL;
 	const gchar *buddy_icon_dir = qq_buddy_icon_dir();
 
 	icon_path = g_strconcat(buddy_icon_dir, G_DIR_SEPARATOR_S, QQ_ICON_PREFIX, 
--- a/libpurple/util.c	Tue Apr 24 03:56:03 2007 +0000
+++ b/libpurple/util.c	Tue Apr 24 03:56:16 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));
 		}
--- a/pidgin/gtkconv.c	Tue Apr 24 03:56:03 2007 +0000
+++ b/pidgin/gtkconv.c	Tue Apr 24 03:56:16 2007 +0000
@@ -2539,8 +2539,6 @@
 	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";
 
 	buf = g_strdup_printf("%s.%s", purple_normalize(conv->account, conv->name), ext);
 
--- a/pidgin/gtkmain.c	Tue Apr 24 03:56:03 2007 +0000
+++ b/pidgin/gtkmain.c	Tue Apr 24 03:56:16 2007 +0000
@@ -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,36 @@
 	winpidgin_init(hint);
 #endif
 
+	if (migration_failed)
+	{
+		char *old = g_strconcat(purple_home_dir(),
+		                        G_DIR_SEPARATOR_S ".gaim", NULL);
+		char *text = _(
+			"Pidgin 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, 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());