changeset 6846:8ab95f4c9800

[gaim-migrate @ 7391] Added new buddy icon caching code. Each GaimBuddy has its own icon, and the complete list of all icons is now stored in a set of hashtables for quick retrieval. Buddy icons now live much happier in the core, with the magma and tooth fairies (that's where they really live). committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Mon, 15 Sep 2003 07:35:49 +0000
parents 5de4d9a4e0e2
children 7de1b559cbbb
files src/Makefile.am src/blist.c src/blist.h src/buddyicon.c src/buddyicon.h src/conversation.c src/conversation.h src/core.c src/dialogs.c src/gtkconv.c src/gtkutils.c src/protocols/oscar/oscar.c src/prpl.c src/prpl.h
diffstat 14 files changed, 668 insertions(+), 207 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile.am	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/Makefile.am	Mon Sep 15 07:35:49 2003 +0000
@@ -52,6 +52,8 @@
 	accountopt.h \
 	blist.c \
 	blist.h \
+	buddyicon.c \
+	buddyicon.h \
 	connection.c \
 	connection.h \
 	conversation.c \
--- a/src/blist.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/blist.c	Mon Sep 15 07:35:49 2003 +0000
@@ -37,6 +37,7 @@
 struct gaim_buddy_list *gaimbuddylist = NULL;
 static struct gaim_blist_ui_ops *blist_ui_ops = NULL;
 
+
 /*****************************************************************************
  * Private Utility functions                                                 *
  *****************************************************************************/
@@ -494,6 +495,84 @@
 	return b;
 }
 
+static void
+write_buddy_icon(GaimBuddy *buddy, GaimBuddyIcon *icon)
+{
+	const void *data;
+	size_t len;
+	char *random;
+	char *filename;
+	char *dirname;
+	char *old_icon;
+	FILE *file = NULL;
+
+	data = gaim_buddy_icon_get_data(icon, &len);
+
+	random   = g_strdup_printf("%x", g_random_int());
+	filename = g_build_filename(gaim_user_dir(), "icons", random, NULL);
+	dirname  = g_build_filename(gaim_user_dir(), "icons", NULL);
+	old_icon = gaim_buddy_get_setting(buddy, "buddy_icon");
+
+	g_free(random);
+
+	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
+	{
+		gaim_debug_info("buddy icons", "Creating icon cache directory.\n");
+
+		if (mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
+		{
+			gaim_debug_error("buddy icons",
+							 "Unable to create directory %s: %s\n",
+							 dirname, strerror(errno));
+		}
+	}
+
+	g_free(dirname);
+
+	if ((file = fopen(filename, "wb")) != NULL)
+	{
+		fwrite(data, 1, len, file);
+		fclose(file);
+	}
+
+	if (old_icon != NULL)
+	{
+		unlink(old_icon);
+		g_free(old_icon);
+	}
+
+	gaim_buddy_set_setting(buddy, "buddy_icon", filename);
+	gaim_blist_save();
+
+	g_free(filename);
+}
+
+void
+gaim_buddy_set_icon(GaimBuddy *buddy, GaimBuddyIcon *icon)
+{
+	g_return_if_fail(buddy != NULL);
+
+	if (buddy->icon == icon)
+		return;
+
+	if (buddy->icon != NULL)
+		gaim_buddy_icon_unref(buddy->icon);
+
+	buddy->icon = (icon == NULL ? NULL : gaim_buddy_icon_ref(icon));
+
+	write_buddy_icon(buddy, icon);
+
+	gaim_blist_update_buddy_icon(buddy);
+}
+
+GaimBuddyIcon *
+gaim_buddy_get_icon(const GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	return buddy->icon;
+}
+
 void gaim_blist_add_chat(GaimBlistChat *chat, GaimGroup *group, GaimBlistNode *node)
 {
 	GaimBlistNode *n = node, *cnode = (GaimBlistNode*)chat;
@@ -977,6 +1056,9 @@
 	if(buddy->timer > 0)
 		g_source_remove(buddy->timer);
 
+	if (buddy->icon != NULL)
+		gaim_buddy_icon_unref(buddy->icon);
+
 	ops->remove(gaimbuddylist, node);
 	g_hash_table_destroy(buddy->settings);
 	g_free(buddy->name);
--- a/src/blist.h	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/blist.h	Mon Sep 15 07:35:49 2003 +0000
@@ -36,6 +36,7 @@
 typedef struct _GaimBuddy GaimBuddy;
 
 #include "account.h"
+#include "buddyicon.h"
 
 /**************************************************************************/
 /* Enumerations                                                           */
@@ -96,6 +97,7 @@
 	int idle;                               /**< The time the buddy has been idle in minutes. */
 	int uc;                                 /**< This is a cryptic bitmask that makes sense only to the prpl.  This will get changed */
 	void *proto_data;                       /**< This allows the prpl to associate whatever data it wants with a buddy */
+	GaimBuddyIcon *icon;                    /**< The buddy icon. */
 	GaimAccount *account;           /**< the account this buddy belongs to */
 	GHashTable *settings;                   /**< per-buddy settings from the XML buddy list, set by plugins and the likes. */
 	guint timer;							/**< The timer handle. */
@@ -262,8 +264,6 @@
  */
 void gaim_blist_update_buddy_icon(GaimBuddy *buddy);
 
-
-
 /**
  * Renames a buddy in the buddy list.
  *
@@ -347,6 +347,28 @@
 GaimBuddy *gaim_buddy_new(GaimAccount *account, const char *screenname, const char *alias);
 
 /**
+ * Sets a buddy's icon.
+ *
+ * This should only be called from within Gaim. You probably want to
+ * call gaim_buddy_icon_set_data().
+ *
+ * @param buddy The buddy.
+ * @param icon  The buddy icon.
+ *
+ * @see gaim_buddy_icon_set_data()
+ */
+void gaim_buddy_set_icon(GaimBuddy *buddy, GaimBuddyIcon *icon);
+
+/**
+ * Returns a buddy's icon.
+ *
+ * @param buddy The buddy.
+ *
+ * @return The buddy icon.
+ */
+GaimBuddyIcon *gaim_buddy_get_icon(const GaimBuddy *buddy);
+
+/**
  * Adds a new buddy to the buddy list.
  *
  * The buddy will be inserted right after node or prepended to the
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/buddyicon.c	Mon Sep 15 07:35:49 2003 +0000
@@ -0,0 +1,272 @@
+/**
+ * @file icon.c Buddy Icon API
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "internal.h"
+#include "buddyicon.h"
+#include "conversation.h"
+
+static GHashTable *account_cache = NULL;
+
+GaimBuddyIcon *
+gaim_buddy_icon_new(GaimAccount *account, const char *username,
+					void *icon_data, size_t icon_len)
+{
+	GaimBuddyIcon *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);
+
+	icon = gaim_buddy_icons_find(account, username);
+
+	if (icon == NULL)
+	{
+		GHashTable *icon_cache;
+
+		icon = g_new0(GaimBuddyIcon, 1);
+
+		gaim_buddy_icon_set_account(icon,  account);
+		gaim_buddy_icon_set_username(icon, username);
+
+		icon_cache = g_hash_table_lookup(account_cache, account);
+
+		if (icon_cache == NULL)
+		{
+			icon_cache = g_hash_table_new(g_str_hash, g_str_equal);
+
+			g_hash_table_insert(account_cache, account, icon_cache);
+		}
+
+		g_hash_table_insert(icon_cache,
+							(char *)gaim_buddy_icon_get_username(icon), icon);
+	}
+
+	gaim_buddy_icon_set_data(icon, icon_data, icon_len);
+
+	gaim_buddy_icon_ref(icon);
+
+	return icon;
+}
+
+void
+gaim_buddy_icon_destroy(GaimBuddyIcon *icon)
+{
+	GHashTable *icon_cache;
+
+	g_return_if_fail(icon != NULL);
+
+	if (icon->ref_count > 0)
+	{
+		gaim_buddy_icon_unref(icon);
+
+		return;
+	}
+
+	icon_cache = g_hash_table_lookup(account_cache,
+									 gaim_buddy_icon_get_account(icon));
+
+	if (icon_cache != NULL)
+		g_hash_table_remove(icon_cache, gaim_buddy_icon_get_username(icon));
+
+	if (icon->username != NULL)
+		g_free(icon->username);
+
+	if (icon->data != NULL)
+		g_free(icon->data);
+
+	g_free(icon);
+}
+
+GaimBuddyIcon *
+gaim_buddy_icon_ref(GaimBuddyIcon *icon)
+{
+	g_return_val_if_fail(icon != NULL, NULL);
+
+	icon->ref_count++;
+
+	return icon;
+}
+
+GaimBuddyIcon *
+gaim_buddy_icon_unref(GaimBuddyIcon *icon)
+{
+	g_return_val_if_fail(icon != NULL, NULL);
+
+	if (icon->ref_count <= 0)
+		return NULL;
+
+	icon->ref_count--;
+
+	if (icon->ref_count == 0)
+	{
+		gaim_buddy_icon_destroy(icon);
+
+		return NULL;
+	}
+
+	return icon;
+}
+
+void
+gaim_buddy_icon_update(GaimBuddyIcon *icon)
+{
+	GaimConversation *conv;
+	GaimAccount *account;
+	const char *username;
+	GSList *sl;
+
+	g_return_if_fail(icon != NULL);
+
+	account  = gaim_buddy_icon_get_account(icon);
+	username = gaim_buddy_icon_get_username(icon);
+
+	for (sl = gaim_find_buddies(account, username); sl != NULL; sl = sl->next)
+	{
+		GaimBuddy *buddy = (GaimBuddy *)sl->data;
+
+		gaim_buddy_set_icon(buddy, icon);
+	}
+
+	conv = gaim_find_conversation_with_account(username, account);
+
+	if (conv != NULL && gaim_conversation_get_type(conv) == GAIM_CONV_IM)
+		gaim_im_set_icon(GAIM_IM(conv), icon);
+}
+
+void
+gaim_buddy_icon_set_account(GaimBuddyIcon *icon, GaimAccount *account)
+{
+	g_return_if_fail(icon    != NULL);
+	g_return_if_fail(account != NULL);
+
+	icon->account = account;
+}
+
+void
+gaim_buddy_icon_set_username(GaimBuddyIcon *icon, const char *username)
+{
+	g_return_if_fail(icon     != NULL);
+	g_return_if_fail(username != NULL);
+
+	if (icon->username != NULL)
+		g_free(icon->username);
+
+	icon->username = g_strdup(username);
+}
+
+void
+gaim_buddy_icon_set_data(GaimBuddyIcon *icon, void *data, size_t len)
+{
+	g_return_if_fail(icon != NULL);
+
+	if (icon->data != NULL)
+		g_free(icon->data);
+
+	if (data != NULL && len > 0)
+	{
+		icon->data = g_memdup(data, len);
+		icon->len  = len;
+	}
+	else
+	{
+		icon->data = NULL;
+		icon->len  = 0;
+	}
+
+	gaim_buddy_icon_update(icon);
+}
+
+GaimAccount *
+gaim_buddy_icon_get_account(const GaimBuddyIcon *icon)
+{
+	g_return_val_if_fail(icon != NULL, NULL);
+
+	return icon->account;
+}
+
+const char *
+gaim_buddy_icon_get_username(const GaimBuddyIcon *icon)
+{
+	g_return_val_if_fail(icon != NULL, NULL);
+
+	return icon->username;
+}
+
+const void *
+gaim_buddy_icon_get_data(const GaimBuddyIcon *icon, size_t *len)
+{
+	g_return_val_if_fail(icon != NULL, NULL);
+
+	if (len != NULL)
+		*len = icon->len;
+
+	return icon->data;
+}
+
+void
+gaim_buddy_icons_set_for_user(GaimAccount *account, const char *username,
+							  void *icon_data, size_t icon_len)
+{
+	g_return_if_fail(account  != NULL);
+	g_return_if_fail(username != NULL);
+
+	gaim_buddy_icon_new(account, username, icon_data, icon_len);
+}
+
+GaimBuddyIcon *
+gaim_buddy_icons_find(const GaimAccount *account, const char *username)
+{
+	GHashTable *icon_cache;
+
+	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)
+		return NULL;
+
+	return g_hash_table_lookup(icon_cache, username);
+}
+
+void *
+gaim_buddy_icons_get_handle()
+{
+	static int handle;
+
+	return &handle;
+}
+
+void
+gaim_buddy_icons_init()
+{
+	account_cache = g_hash_table_new_full(
+		g_direct_hash, g_direct_equal,
+		NULL, (GFreeFunc)g_hash_table_destroy);
+}
+
+void
+gaim_buddy_icons_uninit()
+{
+	g_hash_table_destroy(account_cache);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/buddyicon.h	Mon Sep 15 07:35:49 2003 +0000
@@ -0,0 +1,197 @@
+/**
+ * @file icon.h Buddy Icon API
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _GAIM_ICON_H_
+#define _GAIM_ICON_H_
+
+typedef struct _GaimBuddyIcon GaimBuddyIcon;
+
+#include "account.h"
+
+struct _GaimBuddyIcon
+{
+	GaimAccount *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. */
+
+	int ref_count;         /**< The buddy icon reference count.    */
+};
+
+/**************************************************************************/
+/** @name Buddy Icon API                                                  */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new buddy icon structure.
+ *
+ * @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.
+ */
+GaimBuddyIcon *gaim_buddy_icon_new(GaimAccount *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 gaim_buddy_icon_destroy(GaimBuddyIcon *icon);
+
+/**
+ * Increments the reference count on a buddy icon.
+ *
+ * @param icon The buddy icon.
+ *
+ * @return @a icon.
+ */
+GaimBuddyIcon *gaim_buddy_icon_ref(GaimBuddyIcon *icon);
+
+/**
+ * Decrements the reference count on a buddy icon.
+ *
+ * If the reference count reaches 0, the icon will be destroyed.
+ *
+ * @param icon The buddy icon.
+ *
+ * @return @a icon, or @c NULL if the reference count reached 0.
+ */
+GaimBuddyIcon *gaim_buddy_icon_unref(GaimBuddyIcon *icon);
+
+/**
+ * Updates every instance of this icon.
+ *
+ * @param icon The buddy icon.
+ */
+void gaim_buddy_icon_update(GaimBuddyIcon *icon);
+
+/**
+ * Sets the buddy icon's account.
+ *
+ * @param icon    The buddy icon.
+ * @param account The account.
+ */
+void gaim_buddy_icon_set_account(GaimBuddyIcon *icon, GaimAccount *account);
+
+/**
+ * Sets the buddy icon's username.
+ *
+ * @param icon     The buddy icon.
+ * @param username The username.
+ */
+void gaim_buddy_icon_set_username(GaimBuddyIcon *icon, const char *username);
+
+/**
+ * Sets the buddy icon's icon data.
+ *
+ * @param icon The buddy icon.
+ * @param data The buddy icon data.
+ * @param len  The length of the icon data.
+ */
+void gaim_buddy_icon_set_data(GaimBuddyIcon *icon, void *data, size_t len);
+
+/**
+ * Returns the buddy icon's account.
+ *
+ * @param icon The buddy icon.
+ *
+ * @return The account.
+ */
+GaimAccount *gaim_buddy_icon_get_account(const GaimBuddyIcon *icon);
+
+/**
+ * Returns the buddy icon's username.
+ *
+ * @param icon The buddy icon.
+ *
+ * @return The username.
+ */
+const char *gaim_buddy_icon_get_username(const GaimBuddyIcon *icon);
+
+/**
+ * Returns the buddy icon's data.
+ *
+ * @param icon The buddy icon.
+ * @param len  The returned icon length.
+ *
+ * @return The icon data.
+ */
+const void *gaim_buddy_icon_get_data(const GaimBuddyIcon *icon, size_t *len);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Buddy Icon Subsystem API                                        */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Sets a buddy icon for a user.
+ *
+ * @param account   The account the user is on.
+ * @param username  The username of the user.
+ * @param icon_data The icon data.
+ * @param icon_len  The length of the icon data.
+ */
+void gaim_buddy_icons_set_for_user(GaimAccount *account, const char *username,
+								   void *icon_data, size_t icon_len);
+
+/**
+ * Returns the buddy icon information for a user.
+ *
+ * @param account  The account the user is on.
+ * @param username The username of the user.
+ *
+ * @return The icon data if found, or @c NULL if not found.
+ */
+GaimBuddyIcon *gaim_buddy_icons_find(const GaimAccount *account,
+									 const char *username);
+
+/**
+ * Returns the buddy icon subsystem handle.
+ *
+ * @return The subsystem handle.
+ */
+void *gaim_buddy_icons_get_handle();
+
+/**
+ * Initializes the buddy icon subsystem.
+ */
+void gaim_buddy_icons_init();
+
+/**
+ * Uninitializes the buddy icon subsystem.
+ */
+void gaim_buddy_icons_uninit();
+
+/*@}*/
+
+#endif /* _GAIM_ICON_H_ */
--- a/src/conversation.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/conversation.c	Mon Sep 15 07:35:49 2003 +0000
@@ -980,7 +980,7 @@
 	gc   = gaim_conversation_get_gc(conv);
 	name = gaim_conversation_get_name(conv);
 
-	if (gc) {
+	if (gc != NULL) {
 		/* Still connected */
 		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
 
@@ -1052,6 +1052,9 @@
 
 		g_slist_free(conv->u.im->images);
 
+		if (conv->u.im->icon != NULL)
+			gaim_buddy_icon_unref(conv->u.im->icon);
+
 		g_free(conv->u.im);
 
 		ims = g_list_remove(ims, conv);
@@ -1573,6 +1576,31 @@
 }
 
 void
+gaim_im_set_icon(GaimIm *im, GaimBuddyIcon *icon)
+{
+	g_return_if_fail(im != NULL);
+
+	if (im->icon == icon)
+		return;
+
+	if (im->icon != NULL)
+		gaim_buddy_icon_unref(im->icon);
+
+	im->icon = (icon == NULL ? NULL : gaim_buddy_icon_ref(icon));
+
+	gaim_conversation_update(gaim_im_get_conversation(im),
+							 GAIM_CONV_UPDATE_ICON);
+}
+
+GaimBuddyIcon *
+gaim_im_get_icon(const GaimIm *im)
+{
+	g_return_val_if_fail(im != NULL, NULL);
+
+	return im->icon;
+}
+
+void
 gaim_im_set_typing_state(GaimIm *im, int state)
 {
 	g_return_if_fail(im != NULL);
@@ -2587,7 +2615,9 @@
 {
 	void *handle = gaim_conversations_get_handle();
 
-	/* Register preferences */
+	/**********************************************************************
+	 * Register preferences
+	 **********************************************************************/
 
 	/* Conversations */
 	gaim_prefs_add_none("/core/conversations");
@@ -2617,7 +2647,9 @@
 			update_titles_pref_cb, NULL);
 
 
-	/* Register signals */
+	/**********************************************************************
+	 * Register signals
+	 **********************************************************************/
 	gaim_signal_register(handle, "displaying-im-msg",
 						 gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER,
 						 gaim_value_new(GAIM_TYPE_BOOLEAN), 3,
--- a/src/conversation.h	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/conversation.h	Mon Sep 15 07:35:49 2003 +0000
@@ -82,7 +82,8 @@
 	 */
 	GAIM_CONV_ACCOUNT_ONLINE,  /**< One of the user's accounts went online.  */
 	GAIM_CONV_ACCOUNT_OFFLINE, /**< One of the user's accounts went offline. */
-	GAIM_CONV_UPDATE_AWAY      /**< The other user went away.                */
+	GAIM_CONV_UPDATE_AWAY,     /**< The other user went away.                */
+	GAIM_CONV_UPDATE_ICON      /**< The other user's buddy icon changed.     */
 
 } GaimConvUpdateType;
 
@@ -110,9 +111,11 @@
 	GAIM_MESSAGE_NICK      = 0x0020, /**< Contains your nick.   */
 	GAIM_MESSAGE_NO_LOG    = 0x0040, /**< Do not log.           */
 	GAIM_MESSAGE_WHISPER   = 0x0080  /**< Whispered message.    */
+
 } GaimMessageFlags;
 
 #include "account.h"
+#include "buddyicon.h"
 #include "server.h"
 
 /**
@@ -198,6 +201,8 @@
 	guint  type_again_timeout;         /**< The type again timer handle. */
 
 	GSList *images;                    /**< A list of images in the IM.  */
+
+	GaimBuddyIcon *icon;               /**< The buddy icon.              */
 };
 
 /**
@@ -804,6 +809,28 @@
 GaimConversation *gaim_im_get_conversation(const GaimIm *im);
 
 /**
+ * Sets the IM's buddy icon.
+ *
+ * This should only be called from within Gaim. You probably want to
+ * call gaim_buddy_icon_set_data().
+ *
+ * @param im   The IM.
+ * @param icon The buddy icon.
+ *
+ * @see gaim_buddy_icon_set_data()
+ */
+void gaim_im_set_icon(GaimIm *im, GaimBuddyIcon *icon);
+
+/**
+ * Returns the IM's buddy icon.
+ *
+ * @param im The IM.
+ *
+ * @return The buddy icon.
+ */
+GaimBuddyIcon *gaim_im_get_icon(const GaimIm *im);
+
+/**
  * Sets the IM's typing state.
  *
  * @param im    The IM.
--- a/src/core.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/core.c	Mon Sep 15 07:35:49 2003 +0000
@@ -84,6 +84,7 @@
 	gaim_connections_init();
 	gaim_conversations_init();
 	gaim_blist_init();
+	gaim_buddy_icons_init();
 	gaim_privacy_init();
 	gaim_pounces_init();
 	gaim_proxy_init();
@@ -128,6 +129,7 @@
 	gaim_blist_uninit();
 	gaim_conversations_uninit();
 	gaim_connections_uninit();
+	gaim_buddy_icons_uninit();
 	gaim_accounts_uninit();
 
 	gaim_signals_uninit();
--- a/src/dialogs.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/dialogs.c	Mon Sep 15 07:35:49 2003 +0000
@@ -832,9 +832,6 @@
 	GaimConversation *c;
 	GaimBuddy *b;
 	GaimGroup *g;
-	void *icon_data;
-	void *icon_data2;
-	int icon_len;
 
 	if (resp == GTK_RESPONSE_OK) {
 
@@ -852,15 +849,7 @@
 		serv_add_buddy(a->gc, who, g);
 
 		if (c != NULL)
-			gaim_conversation_update(c, GAIM_CONV_UPDATE_ADD);
-
-		icon_data = get_icon_data(a->gc, normalize(who), &icon_len);
-
-		if(icon_data) {
-			icon_data2 = g_memdup(icon_data, icon_len);
-			set_icon_data(a->gc, who, icon_data2, icon_len);
-			g_free(icon_data2);
-		}
+			gaim_buddy_icon_update(gaim_im_get_icon(GAIM_IM(c)));
 
 		gaim_blist_save();
 	}
--- a/src/gtkconv.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/gtkconv.c	Mon Sep 15 07:35:49 2003 +0000
@@ -5019,11 +5019,15 @@
 		if (gaim_prefs_get_bool("/gaim/gtk/conversations/icons_on_tabs"))
 			update_tab_icon(conv);
 	}
-	else if(type == GAIM_CONV_UPDATE_ADD ||
-			type == GAIM_CONV_UPDATE_REMOVE) {
+	else if (type == GAIM_CONV_UPDATE_ADD ||
+			 type == GAIM_CONV_UPDATE_REMOVE) {
 
 		update_convo_add_button(conv);
 	}
+	else if (type == GAIM_CONV_UPDATE_ICON)
+	{
+		gaim_gtkconv_update_buddy_icon(conv);
+	}
 }
 
 static GaimConversationUiOps conversation_ui_ops =
@@ -5226,8 +5230,9 @@
 
 	GaimBuddy *buddy;
 
-	void *data;
-	int len, delay;
+	const void *data;
+	size_t len;
+	int delay;
 
 	GdkPixbuf *buf;
 
@@ -5265,10 +5270,12 @@
 			gtkconv->u.im->anim = gdk_pixbuf_animation_new_from_file(file, &err);
 			g_free(file);
 		}
-	} else {
-		data = get_icon_data(gaim_conversation_get_gc(conv),
-				normalize(gaim_conversation_get_name(conv)),
-				&len);
+	}
+	else
+	{
+		GaimBuddyIcon *icon = gaim_im_get_icon(GAIM_IM(conv));
+
+		data = gaim_buddy_icon_get_data(icon, &len);
 
 		if (!data)
 			return;
--- a/src/gtkutils.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/gtkutils.c	Mon Sep 15 07:35:49 2003 +0000
@@ -266,7 +266,7 @@
 	GaimGtkConversation *gtkconv;
 	FILE *file;
 	const char *f;
-	
+
 	gtkconv = GAIM_GTK_CONVERSATION(c);
 
 	f = gtk_file_selection_get_filename(
@@ -276,10 +276,9 @@
 		return;
 
 	if ((file = fopen(f, "w")) != NULL) {
-		int len;
-		void *data = get_icon_data(gaim_conversation_get_gc(c),
-								   normalize(gaim_conversation_get_name(c)),
-								   &len);
+		GaimBuddyIcon *icon = gaim_im_get_icon(GAIM_IM(c));
+		size_t len;
+		const void *data = gaim_buddy_icon_get_data(icon, &len);
 
 		if (data)
 			fwrite(data, 1, len, file);
--- a/src/protocols/oscar/oscar.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/protocols/oscar/oscar.c	Mon Sep 15 07:35:49 2003 +0000
@@ -28,6 +28,7 @@
 
 #include "account.h"
 #include "accountopt.h"
+#include "buddyicon.h"
 #include "conversation.h"
 #include "debug.h"
 #include "ft.h"
@@ -2387,8 +2388,9 @@
 	} else if (args->reqclass & AIM_CAPS_GETFILE) {
 	} else if (args->reqclass & AIM_CAPS_VOICE) {
 	} else if (args->reqclass & AIM_CAPS_BUDDYICON) {
-		set_icon_data(gc, userinfo->sn, args->info.icon.icon,
-				args->info.icon.length);
+		gaim_buddy_icons_set_for_user(gaim_connection_get_account(gc),
+									  userinfo->sn, args->info.icon.icon,
+									  args->info.icon.length);
 	} else if (args->reqclass & AIM_CAPS_IMIMAGE) {
 		struct ask_direct *d = g_new0(struct ask_direct, 1);
 		char buf[256];
@@ -3512,7 +3514,8 @@
 	if (iconlen > 0) {
 		char *b16;
 		GaimBuddy *b = gaim_find_buddy(gc->account, sn);
-		set_icon_data(gc, sn, icon, iconlen);
+		gaim_buddy_icons_set_for_user(gaim_connection_get_account(gc),
+									  sn, icon, iconlen);
 		b16 = tobase16(iconcsum, iconcsumlen);
 		if (b16) {
 			gaim_buddy_set_setting(b, "icon_checksum", b16);
--- a/src/prpl.c	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/prpl.c	Mon Sep 15 07:35:49 2003 +0000
@@ -102,158 +102,6 @@
 	return NULL;
 }
 
-struct icon_data {
-	GaimConnection *gc;
-	char *who;
-	void *data;
-	int len;
-};
-
-static GList *icons = NULL;
-
-static gint find_icon_data(gconstpointer a, gconstpointer b)
-{
-	const struct icon_data *x = a;
-	const struct icon_data *y = b;
-
-	return ((x->gc != y->gc) || gaim_utf8_strcasecmp(x->who, y->who));
-}
-
-void set_icon_data(GaimConnection *gc, const char *who, void *data, int len)
-{
-	GaimConversation *conv;
-	struct icon_data tmp;
-	GList *l;
-	struct icon_data *id;
-	GaimBuddy *b;
-	/* i'm going to vent here a little bit about normalize().  normalize()
-	 * uses a static buffer, so when we call functions that use normalize() from
-	 * functions that use normalize(), whose parameters are the result of running
-	 * normalize(), bad things happen.  To prevent some of this, we're going
-	 * to make a copy of what we get from normalize(), so we know nothing else
-	 * touches it, and buddy icons don't go to the wrong person.  Some day I
-	 * will kill normalize(), and dance on its grave.  That will be a very happy
-	 * day for everyone.
-	 *                                    --ndw
-	 */
-	char *realwho = g_strdup(normalize(who));
-	tmp.gc = gc;
-	tmp.who = realwho;
-	tmp.data=NULL;
-	tmp.len = 0;
-	l = g_list_find_custom(icons, &tmp, find_icon_data);
-	id = l ? l->data : NULL;
-
-	if (id) {
-		g_free(id->data);
-		if (!data) {
-			icons = g_list_remove(icons, id);
-			g_free(id->who);
-			g_free(id);
-			g_free(realwho);
-			return;
-		}
-	} else if (data) {
-		id = g_new0(struct icon_data, 1);
-		icons = g_list_append(icons, id);
-		id->gc = gc;
-		id->who = g_strdup(realwho);
-	} else {
-		g_free(realwho);
-		return;
-	}
-
-	gaim_debug(GAIM_DEBUG_MISC, "prpl", "Got icon for %s (length %d)\n",
-			   realwho, len);
-
-	id->data = g_memdup(data, len);
-	id->len = len;
-
-	/* Update the buddy icon for this user. */
-	conv = gaim_find_conversation_with_account(realwho, gc->account);
-
-	/* XXX Buddy Icon should probalby be part of struct buddy instead of this weird global
-	 * linked list stuff. */
-
-	if ((b = gaim_find_buddy(gc->account, realwho)) != NULL) {
-		char *random = g_strdup_printf("%x", g_random_int());
-		char *filename = g_build_filename(gaim_user_dir(), "icons", random,
-				NULL);
-		char *dirname = g_build_filename(gaim_user_dir(), "icons", NULL);
-		char *old_icon = gaim_buddy_get_setting(b, "buddy_icon");
-		FILE *file = NULL;
-
-		g_free(random);
-
-		if(!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
-			gaim_debug(GAIM_DEBUG_INFO, "buddy icons",
-					   "Creating icon cache directory.\n");
-
-			if(mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
-				gaim_debug(GAIM_DEBUG_ERROR, "buddy icons",
-						   "Unable to create directory %s: %s\n",
-						   dirname, strerror(errno));
-		}
-
-		g_free(dirname);
-
-		file = fopen(filename, "wb");
-		if (file) {
-			fwrite(data, 1, len, file);
-			fclose(file);
-		}
-
-		if(old_icon) {
-			unlink(old_icon);
-			g_free(old_icon);
-		}
-
-		gaim_buddy_set_setting(b, "buddy_icon", filename);
-		gaim_blist_save();
-
-		g_free(filename);
-
-		gaim_blist_update_buddy_icon(b);
-	}
-
-	if (conv != NULL && gaim_conversation_get_gc(conv) == gc)
-		gaim_gtkconv_update_buddy_icon(conv);
-
-	g_free(realwho);
-}
-
-void remove_icon_data(GaimConnection *gc)
-{
-	GList *list = icons;
-	struct icon_data *id;
-
-	while (list) {
-		id = list->data;
-		if (id->gc == gc) {
-			g_free(id->data);
-			g_free(id->who);
-			list = icons = g_list_remove(icons, id);
-			g_free(id);
-		} else
-			list = list->next;
-	}
-}
-
-void *get_icon_data(GaimConnection *gc, const char *who, int *len)
-{
-	struct icon_data tmp = { gc, normalize(who), NULL, 0 };
-	GList *l = g_list_find_custom(icons, &tmp, find_icon_data);
-	struct icon_data *id = l ? l->data : NULL;
-
-	if (id) {
-		*len = id->len;
-		return id->data;
-	}
-
-	*len = 0;
-	return NULL;
-}
-
 struct got_add {
 	GaimConnection *gc;
 	char *who;
--- a/src/prpl.h	Mon Sep 15 02:23:58 2003 +0000
+++ b/src/prpl.h	Mon Sep 15 07:35:49 2003 +0000
@@ -360,27 +360,6 @@
 void show_got_added(GaimConnection *gc, const char *id,
 					const char *who, const char *alias, const char *msg);
 
-/**
- * Retrieves and sets the new buddy icon for a user.
- *
- * @param gc   The gaim connection.
- * @param who  The user.
- * @param data The icon data.
- * @param len  The length of @a data.
- */
-void set_icon_data(GaimConnection *gc, const char *who, void *data, int len);
-
-/**
- * Retrieves the buddy icon data for a user.
- *
- * @param gc  The gaim connection.
- * @param who The user.
- * @param len The returned length of the data.
- *
- * @return The buddy icon data.
- */
-void *get_icon_data(GaimConnection *gc, const char *who, int *len);
-
 #ifdef __cplusplus
 }
 #endif