diff libpurple/blist.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 6d8728fd3dda
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/blist.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,2755 @@
+/*
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 "blist.h"
+#include "conversation.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "notify.h"
+#include "prefs.h"
+#include "privacy.h"
+#include "prpl.h"
+#include "server.h"
+#include "signals.h"
+#include "util.h"
+#include "value.h"
+#include "xmlnode.h"
+
+#define PATHSIZE 1024
+
+static GaimBlistUiOps *blist_ui_ops = NULL;
+
+static GaimBuddyList *gaimbuddylist = NULL;
+static guint          save_timer = 0;
+static gboolean       blist_loaded = FALSE;
+
+
+/*********************************************************************
+ * Private utility functions                                         *
+ *********************************************************************/
+
+static GaimBlistNode *gaim_blist_get_last_sibling(GaimBlistNode *node)
+{
+	GaimBlistNode *n = node;
+	if (!n)
+		return NULL;
+	while (n->next)
+		n = n->next;
+	return n;
+}
+
+static GaimBlistNode *gaim_blist_get_last_child(GaimBlistNode *node)
+{
+	if (!node)
+		return NULL;
+	return gaim_blist_get_last_sibling(node->child);
+}
+
+struct _list_account_buddies {
+	GSList *list;
+	GaimAccount *account;
+};
+
+struct _gaim_hbuddy {
+	char *name;
+	GaimAccount *account;
+	GaimBlistNode *group;
+};
+
+static guint _gaim_blist_hbuddy_hash(struct _gaim_hbuddy *hb)
+{
+	return g_str_hash(hb->name);
+}
+
+static guint _gaim_blist_hbuddy_equal(struct _gaim_hbuddy *hb1, struct _gaim_hbuddy *hb2)
+{
+	return ((!strcmp(hb1->name, hb2->name)) && hb1->account == hb2->account && hb1->group == hb2->group);
+}
+
+static void _gaim_blist_hbuddy_free_key(struct _gaim_hbuddy *hb)
+{
+	g_free(hb->name);
+	g_free(hb);
+}
+
+
+/*********************************************************************
+ * Writing to disk                                                   *
+ *********************************************************************/
+
+static void
+value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
+{
+	const char *name;
+	GaimValue *value;
+	xmlnode *node, *child;
+	char buf[20];
+
+	name    = (const char *)key;
+	value   = (GaimValue *)hvalue;
+	node    = (xmlnode *)user_data;
+
+	g_return_if_fail(value != NULL);
+
+	child = xmlnode_new_child(node, "setting");
+	xmlnode_set_attrib(child, "name", name);
+
+	if (gaim_value_get_type(value) == GAIM_TYPE_INT) {
+		xmlnode_set_attrib(child, "type", "int");
+		snprintf(buf, sizeof(buf), "%d", gaim_value_get_int(value));
+		xmlnode_insert_data(child, buf, -1);
+	}
+	else if (gaim_value_get_type(value) == GAIM_TYPE_STRING) {
+		xmlnode_set_attrib(child, "type", "string");
+		xmlnode_insert_data(child, gaim_value_get_string(value), -1);
+	}
+	else if (gaim_value_get_type(value) == GAIM_TYPE_BOOLEAN) {
+		xmlnode_set_attrib(child, "type", "bool");
+		snprintf(buf, sizeof(buf), "%d", gaim_value_get_boolean(value));
+		xmlnode_insert_data(child, buf, -1);
+	}
+}
+
+static void
+chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
+{
+	const char *name;
+	const char *data;
+	xmlnode *node, *child;
+
+	name = (const char *)key;
+	data = (const char *)value;
+	node = (xmlnode *)user_data;
+
+	g_return_if_fail(data != NULL);
+
+	child = xmlnode_new_child(node, "component");
+	xmlnode_set_attrib(child, "name", name);
+	xmlnode_insert_data(child, data, -1);
+}
+
+static xmlnode *
+buddy_to_xmlnode(GaimBlistNode *bnode)
+{
+	xmlnode *node, *child;
+	GaimBuddy *buddy;
+
+	buddy = (GaimBuddy *)bnode;
+
+	node = xmlnode_new("buddy");
+	xmlnode_set_attrib(node, "account", gaim_account_get_username(buddy->account));
+	xmlnode_set_attrib(node, "proto", gaim_account_get_protocol_id(buddy->account));
+
+	child = xmlnode_new_child(node, "name");
+	xmlnode_insert_data(child, buddy->name, -1);
+
+	if (buddy->alias != NULL)
+	{
+		child = xmlnode_new_child(node, "alias");
+		xmlnode_insert_data(child, buddy->alias, -1);
+	}
+
+	/* Write buddy settings */
+	g_hash_table_foreach(buddy->node.settings, value_to_xmlnode, node);
+
+	return node;
+}
+
+static xmlnode *
+contact_to_xmlnode(GaimBlistNode *cnode)
+{
+	xmlnode *node, *child;
+	GaimContact *contact;
+	GaimBlistNode *bnode;
+
+	contact = (GaimContact *)cnode;
+
+	node = xmlnode_new("contact");
+
+	if (contact->alias != NULL)
+	{
+		xmlnode_set_attrib(node, "alias", contact->alias);
+	}
+
+	/* Write buddies */
+	for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+	{
+		if (!GAIM_BLIST_NODE_SHOULD_SAVE(bnode))
+			continue;
+		if (GAIM_BLIST_NODE_IS_BUDDY(bnode))
+		{
+			child = buddy_to_xmlnode(bnode);
+			xmlnode_insert_child(node, child);
+		}
+	}
+
+	/* Write contact settings */
+	g_hash_table_foreach(cnode->settings, value_to_xmlnode, node);
+
+	return node;
+}
+
+static xmlnode *
+chat_to_xmlnode(GaimBlistNode *cnode)
+{
+	xmlnode *node, *child;
+	GaimChat *chat;
+
+	chat = (GaimChat *)cnode;
+
+	node = xmlnode_new("chat");
+	xmlnode_set_attrib(node, "proto", gaim_account_get_protocol_id(chat->account));
+	xmlnode_set_attrib(node, "account", gaim_account_get_username(chat->account));
+
+	if (chat->alias != NULL)
+	{
+		child = xmlnode_new_child(node, "alias");
+		xmlnode_insert_data(child, chat->alias, -1);
+	}
+
+	/* Write chat components */
+	g_hash_table_foreach(chat->components, chat_component_to_xmlnode, node);
+
+	/* Write chat settings */
+	g_hash_table_foreach(chat->node.settings, value_to_xmlnode, node);
+
+	return node;
+}
+
+static xmlnode *
+group_to_xmlnode(GaimBlistNode *gnode)
+{
+	xmlnode *node, *child;
+	GaimGroup *group;
+	GaimBlistNode *cnode;
+
+	group = (GaimGroup *)gnode;
+
+	node = xmlnode_new("group");
+	xmlnode_set_attrib(node, "name", group->name);
+
+	/* Write settings */
+	g_hash_table_foreach(group->node.settings, value_to_xmlnode, node);
+
+	/* Write contacts and chats */
+	for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
+	{
+		if (!GAIM_BLIST_NODE_SHOULD_SAVE(cnode))
+			continue;
+		if (GAIM_BLIST_NODE_IS_CONTACT(cnode))
+		{
+			child = contact_to_xmlnode(cnode);
+			xmlnode_insert_child(node, child);
+		}
+		else if (GAIM_BLIST_NODE_IS_CHAT(cnode))
+		{
+			child = chat_to_xmlnode(cnode);
+			xmlnode_insert_child(node, child);
+		}
+	}
+
+	return node;
+}
+
+static xmlnode *
+accountprivacy_to_xmlnode(GaimAccount *account)
+{
+	xmlnode *node, *child;
+	GSList *cur;
+	char buf[10];
+
+	node = xmlnode_new("account");
+	xmlnode_set_attrib(node, "proto", gaim_account_get_protocol_id(account));
+	xmlnode_set_attrib(node, "name", gaim_account_get_username(account));
+	snprintf(buf, sizeof(buf), "%d", account->perm_deny);
+	xmlnode_set_attrib(node, "mode", buf);
+
+	for (cur = account->permit; cur; cur = cur->next)
+	{
+		child = xmlnode_new_child(node, "permit");
+		xmlnode_insert_data(child, cur->data, -1);
+	}
+
+	for (cur = account->deny; cur; cur = cur->next)
+	{
+		child = xmlnode_new_child(node, "block");
+		xmlnode_insert_data(child, cur->data, -1);
+	}
+
+	return node;
+}
+
+static xmlnode *
+blist_to_xmlnode()
+{
+	xmlnode *node, *child, *grandchild;
+	GaimBlistNode *gnode;
+	GList *cur;
+
+	node = xmlnode_new("gaim");
+	xmlnode_set_attrib(node, "version", "1.0");
+
+	/* Write groups */
+	child = xmlnode_new_child(node, "blist");
+	for (gnode = gaimbuddylist->root; gnode != NULL; gnode = gnode->next)
+	{
+		if (!GAIM_BLIST_NODE_SHOULD_SAVE(gnode))
+			continue;
+		if (GAIM_BLIST_NODE_IS_GROUP(gnode))
+		{
+			grandchild = group_to_xmlnode(gnode);
+			xmlnode_insert_child(child, grandchild);
+		}
+	}
+
+	/* Write privacy settings */
+	child = xmlnode_new_child(node, "privacy");
+	for (cur = gaim_accounts_get_all(); cur != NULL; cur = cur->next)
+	{
+		grandchild = accountprivacy_to_xmlnode(cur->data);
+		xmlnode_insert_child(child, grandchild);
+	}
+
+	return node;
+}
+
+static void
+gaim_blist_sync()
+{
+	xmlnode *node;
+	char *data;
+
+	if (!blist_loaded)
+	{
+		gaim_debug_error("blist", "Attempted to save buddy list before it "
+						 "was read!\n");
+		return;
+	}
+
+	node = blist_to_xmlnode();
+	data = xmlnode_to_formatted_str(node, NULL);
+	gaim_util_write_data_to_file("blist.xml", data, -1);
+	g_free(data);
+	xmlnode_free(node);
+}
+
+static gboolean
+save_cb(gpointer data)
+{
+	gaim_blist_sync();
+	save_timer = 0;
+	return FALSE;
+}
+
+void
+gaim_blist_schedule_save()
+{
+	if (save_timer == 0)
+		save_timer = gaim_timeout_add(5000, save_cb, NULL);
+}
+
+
+/*********************************************************************
+ * Reading from disk                                                 *
+ *********************************************************************/
+
+static void
+parse_setting(GaimBlistNode *node, xmlnode *setting)
+{
+	const char *name = xmlnode_get_attrib(setting, "name");
+	const char *type = xmlnode_get_attrib(setting, "type");
+	char *value = xmlnode_get_data(setting);
+
+	if (!value)
+		return;
+
+	if (!type || !strcmp(type, "string"))
+		gaim_blist_node_set_string(node, name, value);
+	else if (!strcmp(type, "bool"))
+		gaim_blist_node_set_bool(node, name, atoi(value));
+	else if (!strcmp(type, "int"))
+		gaim_blist_node_set_int(node, name, atoi(value));
+
+	g_free(value);
+}
+
+static void
+parse_buddy(GaimGroup *group, GaimContact *contact, xmlnode *bnode)
+{
+	GaimAccount *account;
+	GaimBuddy *buddy;
+	char *name = NULL, *alias = NULL;
+	const char *acct_name, *proto, *protocol;
+	xmlnode *x;
+
+	acct_name = xmlnode_get_attrib(bnode, "account");
+	protocol = xmlnode_get_attrib(bnode, "protocol");
+	proto = xmlnode_get_attrib(bnode, "proto");
+
+	if (!acct_name || (!proto && !protocol))
+		return;
+
+	account = gaim_accounts_find(acct_name, proto ? proto : protocol);
+
+	if (!account)
+		return;
+
+	if ((x = xmlnode_get_child(bnode, "name")))
+		name = xmlnode_get_data(x);
+
+	if (!name)
+		return;
+
+	if ((x = xmlnode_get_child(bnode, "alias")))
+		alias = xmlnode_get_data(x);
+
+	buddy = gaim_buddy_new(account, name, alias);
+	gaim_blist_add_buddy(buddy, contact, group,
+			gaim_blist_get_last_child((GaimBlistNode*)contact));
+
+	for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
+		parse_setting((GaimBlistNode*)buddy, x);
+	}
+
+	g_free(name);
+	g_free(alias);
+}
+
+static void
+parse_contact(GaimGroup *group, xmlnode *cnode)
+{
+	GaimContact *contact = gaim_contact_new();
+	xmlnode *x;
+	const char *alias;
+
+	gaim_blist_add_contact(contact, group,
+			gaim_blist_get_last_child((GaimBlistNode*)group));
+
+	if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
+		gaim_contact_set_alias(contact, alias);
+	}
+
+	for (x = cnode->child; x; x = x->next) {
+		if (x->type != XMLNODE_TYPE_TAG)
+			continue;
+		if (!strcmp(x->name, "buddy"))
+			parse_buddy(group, contact, x);
+		else if (!strcmp(x->name, "setting"))
+			parse_setting((GaimBlistNode*)contact, x);
+	}
+
+	/* if the contact is empty, don't keep it around.  it causes problems */
+	if (!((GaimBlistNode*)contact)->child)
+		gaim_blist_remove_contact(contact);
+}
+
+static void
+parse_chat(GaimGroup *group, xmlnode *cnode)
+{
+	GaimChat *chat;
+	GaimAccount *account;
+	const char *acct_name, *proto, *protocol;
+	xmlnode *x;
+	char *alias = NULL;
+	GHashTable *components;
+
+	acct_name = xmlnode_get_attrib(cnode, "account");
+	protocol = xmlnode_get_attrib(cnode, "protocol");
+	proto = xmlnode_get_attrib(cnode, "proto");
+
+	if (!acct_name || (!proto && !protocol))
+		return;
+
+	account = gaim_accounts_find(acct_name, proto ? proto : protocol);
+
+	if (!account)
+		return;
+
+	if ((x = xmlnode_get_child(cnode, "alias")))
+		alias = xmlnode_get_data(x);
+
+	components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+	for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) {
+		const char *name;
+		char *value;
+
+		name = xmlnode_get_attrib(x, "name");
+		value = xmlnode_get_data(x);
+		g_hash_table_replace(components, g_strdup(name), value);
+	}
+
+	chat = gaim_chat_new(account, alias, components);
+	gaim_blist_add_chat(chat, group,
+			gaim_blist_get_last_child((GaimBlistNode*)group));
+
+	for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
+		parse_setting((GaimBlistNode*)chat, x);
+	}
+
+	g_free(alias);
+}
+
+static void
+parse_group(xmlnode *groupnode)
+{
+	const char *name = xmlnode_get_attrib(groupnode, "name");
+	GaimGroup *group;
+	xmlnode *cnode;
+
+	if (!name)
+		name = _("Buddies");
+
+	group = gaim_group_new(name);
+	gaim_blist_add_group(group,
+			gaim_blist_get_last_sibling(gaimbuddylist->root));
+
+	for (cnode = groupnode->child; cnode; cnode = cnode->next) {
+		if (cnode->type != XMLNODE_TYPE_TAG)
+			continue;
+		if (!strcmp(cnode->name, "setting"))
+			parse_setting((GaimBlistNode*)group, cnode);
+		else if (!strcmp(cnode->name, "contact") ||
+				!strcmp(cnode->name, "person"))
+			parse_contact(group, cnode);
+		else if (!strcmp(cnode->name, "chat"))
+			parse_chat(group, cnode);
+	}
+}
+
+/* TODO: Make static and rename to load_blist */
+void
+gaim_blist_load()
+{
+	xmlnode *gaim, *blist, *privacy;
+
+	blist_loaded = TRUE;
+
+	gaim = gaim_util_read_xml_from_file("blist.xml", _("buddy list"));
+
+	if (gaim == NULL)
+		return;
+
+	blist = xmlnode_get_child(gaim, "blist");
+	if (blist) {
+		xmlnode *groupnode;
+		for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL;
+				groupnode = xmlnode_get_next_twin(groupnode)) {
+			parse_group(groupnode);
+		}
+	}
+
+	privacy = xmlnode_get_child(gaim, "privacy");
+	if (privacy) {
+		xmlnode *anode;
+		for (anode = privacy->child; anode; anode = anode->next) {
+			xmlnode *x;
+			GaimAccount *account;
+			int imode;
+			const char *acct_name, *proto, *mode, *protocol;
+
+			acct_name = xmlnode_get_attrib(anode, "name");
+			protocol = xmlnode_get_attrib(anode, "protocol");
+			proto = xmlnode_get_attrib(anode, "proto");
+			mode = xmlnode_get_attrib(anode, "mode");
+
+			if (!acct_name || (!proto && !protocol) || !mode)
+				continue;
+
+			account = gaim_accounts_find(acct_name, proto ? proto : protocol);
+
+			if (!account)
+				continue;
+
+			imode = atoi(mode);
+			account->perm_deny = (imode != 0 ? imode : GAIM_PRIVACY_ALLOW_ALL);
+
+			for (x = anode->child; x; x = x->next) {
+				char *name;
+				if (x->type != XMLNODE_TYPE_TAG)
+					continue;
+
+				if (!strcmp(x->name, "permit")) {
+					name = xmlnode_get_data(x);
+					gaim_privacy_permit_add(account, name, TRUE);
+					g_free(name);
+				} else if (!strcmp(x->name, "block")) {
+					name = xmlnode_get_data(x);
+					gaim_privacy_deny_add(account, name, TRUE);
+					g_free(name);
+				}
+			}
+		}
+	}
+
+	xmlnode_free(gaim);
+}
+
+
+/*********************************************************************
+ * Stuff                                                             *
+ *********************************************************************/
+
+static void
+gaim_contact_compute_priority_buddy(GaimContact *contact)
+{
+	GaimBlistNode *bnode;
+	GaimBuddy *new_priority = NULL;
+
+	g_return_if_fail(contact != NULL);
+
+	contact->priority = NULL;
+	for (bnode = ((GaimBlistNode*)contact)->child;
+			bnode != NULL;
+			bnode = bnode->next)
+	{
+		GaimBuddy *buddy;
+
+		if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+			continue;
+
+		buddy = (GaimBuddy*)bnode;
+
+		if (!gaim_account_is_connected(buddy->account))
+			continue;
+		if (new_priority == NULL)
+			new_priority = buddy;
+		else
+		{
+			int cmp;
+
+			cmp = gaim_presence_compare(gaim_buddy_get_presence(new_priority),
+			                            gaim_buddy_get_presence(buddy));
+
+			if (cmp > 0 || (cmp == 0 &&
+			                gaim_prefs_get_bool("/core/contact/last_match")))
+			{
+				new_priority = buddy;
+			}
+		}
+	}
+
+	contact->priority = new_priority;
+	contact->priority_valid = TRUE;
+}
+
+
+/*****************************************************************************
+ * Public API functions                                                      *
+ *****************************************************************************/
+
+GaimBuddyList *gaim_blist_new()
+{
+	GaimBlistUiOps *ui_ops;
+	GaimBuddyList *gbl = g_new0(GaimBuddyList, 1);
+	GAIM_DBUS_REGISTER_POINTER(gbl, GaimBuddyList);
+
+	ui_ops = gaim_blist_get_ui_ops();
+
+	gbl->buddies = g_hash_table_new_full((GHashFunc)_gaim_blist_hbuddy_hash,
+					 (GEqualFunc)_gaim_blist_hbuddy_equal,
+					 (GDestroyNotify)_gaim_blist_hbuddy_free_key, NULL);
+
+	if (ui_ops != NULL && ui_ops->new_list != NULL)
+		ui_ops->new_list(gbl);
+
+	return gbl;
+}
+
+void
+gaim_set_blist(GaimBuddyList *list)
+{
+	gaimbuddylist = list;
+}
+
+GaimBuddyList *
+gaim_get_blist()
+{
+	return gaimbuddylist;
+}
+
+GaimBlistNode *
+gaim_blist_get_root()
+{
+	return gaimbuddylist ? gaimbuddylist->root : NULL;
+}
+
+void gaim_blist_show()
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	if (ops && ops->show)
+		ops->show(gaimbuddylist);
+}
+
+void gaim_blist_destroy()
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	gaim_debug(GAIM_DEBUG_INFO, "blist", "Destroying\n");
+
+	if (ops && ops->destroy)
+		ops->destroy(gaimbuddylist);
+}
+
+void gaim_blist_set_visible(gboolean show)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	if (ops && ops->set_visible)
+		ops->set_visible(gaimbuddylist, show);
+}
+
+static GaimBlistNode *get_next_node(GaimBlistNode *node, gboolean godeep)
+{
+	if (node == NULL)
+		return NULL;
+
+	if (godeep && node->child)
+		return node->child;
+
+	if (node->next)
+		return node->next;
+
+	return get_next_node(node->parent, FALSE);
+}
+
+GaimBlistNode *gaim_blist_node_next(GaimBlistNode *node, gboolean offline)
+{
+	GaimBlistNode *ret = node;
+
+	if (offline)
+		return get_next_node(ret, TRUE);
+	do
+	{
+		ret = get_next_node(ret, TRUE);
+	} while (ret && GAIM_BLIST_NODE_IS_BUDDY(ret) &&
+			!gaim_account_is_connected(gaim_buddy_get_account((GaimBuddy *)ret)));
+
+	return ret;
+}
+
+void
+gaim_blist_update_buddy_status(GaimBuddy *buddy, GaimStatus *old_status)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimPresence *presence;
+	GaimStatus *status;
+
+	g_return_if_fail(buddy != NULL);
+
+	presence = gaim_buddy_get_presence(buddy);
+	status = gaim_presence_get_active_status(presence);
+
+	gaim_debug_info("blist", "Updating buddy status for %s (%s)\n",
+			buddy->name, gaim_account_get_protocol_name(buddy->account));
+
+	if (gaim_status_is_online(status) &&
+		!gaim_status_is_online(old_status)) {
+
+		gaim_signal_emit(gaim_blist_get_handle(), "buddy-signed-on", buddy);
+
+		((GaimContact*)((GaimBlistNode*)buddy)->parent)->online++;
+		if (((GaimContact*)((GaimBlistNode*)buddy)->parent)->online == 1)
+			((GaimGroup *)((GaimBlistNode *)buddy)->parent->parent)->online++;
+	} else if (!gaim_status_is_online(status) &&
+				gaim_status_is_online(old_status)) {
+		gaim_blist_node_set_int(&buddy->node, "last_seen", time(NULL));
+		gaim_signal_emit(gaim_blist_get_handle(), "buddy-signed-off", buddy);
+		((GaimContact*)((GaimBlistNode*)buddy)->parent)->online--;
+		if (((GaimContact*)((GaimBlistNode*)buddy)->parent)->online == 0)
+			((GaimGroup *)((GaimBlistNode *)buddy)->parent->parent)->online--;
+	} else {
+		gaim_signal_emit(gaim_blist_get_handle(),
+		                 "buddy-status-changed", buddy, old_status,
+		                 status);
+	}
+
+	/*
+	 * This function used to only call the following two functions if one of
+	 * the above signals had been triggered, but that's not good, because
+	 * if someone's away message changes and they don't go from away to back
+	 * to away then no signal is triggered.
+	 *
+	 * It's a safe assumption that SOMETHING called this function.  PROBABLY
+	 * because something, somewhere changed.  Calling the stuff below
+	 * certainly won't hurt anything.  Unless you're on a K6-2 300.
+	 */
+	gaim_contact_invalidate_priority_buddy(gaim_buddy_get_contact(buddy));
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)buddy);
+}
+
+void gaim_blist_update_buddy_icon(GaimBuddy *buddy)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	g_return_if_fail(buddy != NULL);
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)buddy);
+}
+
+/*
+ * TODO: Maybe remove the call to this from server.c and call it
+ * from oscar.c and toc.c instead?
+ */
+void gaim_blist_rename_buddy(GaimBuddy *buddy, const char *name)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	struct _gaim_hbuddy *hb;
+
+	g_return_if_fail(buddy != NULL);
+
+	hb = g_new(struct _gaim_hbuddy, 1);
+	hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name));
+	hb->account = buddy->account;
+	hb->group = ((GaimBlistNode *)buddy)->parent->parent;
+	g_hash_table_remove(gaimbuddylist->buddies, hb);
+
+	g_free(hb->name);
+	hb->name = g_strdup(gaim_normalize(buddy->account, name));
+	g_hash_table_replace(gaimbuddylist->buddies, hb, buddy);
+
+	g_free(buddy->name);
+	buddy->name = g_strdup(name);
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)buddy);
+}
+
+void gaim_blist_alias_contact(GaimContact *contact, const char *alias)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimConversation *conv;
+	GaimBlistNode *bnode;
+	char *old_alias;
+
+	g_return_if_fail(contact != NULL);
+
+	old_alias = contact->alias;
+
+	if ((alias != NULL) && (*alias != '\0'))
+		contact->alias = g_strdup(alias);
+	else
+		contact->alias = NULL;
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)contact);
+
+	for(bnode = ((GaimBlistNode *)contact)->child; bnode != NULL; bnode = bnode->next)
+	{
+		GaimBuddy *buddy = (GaimBuddy *)bnode;
+
+		conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, buddy->name,
+												   buddy->account);
+		if (conv)
+			gaim_conversation_autoset_title(conv);
+	}
+
+	gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased",
+					 contact, old_alias);
+	g_free(old_alias);
+}
+
+void gaim_blist_alias_chat(GaimChat *chat, const char *alias)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	char *old_alias;
+
+	g_return_if_fail(chat != NULL);
+
+	old_alias = chat->alias;
+
+	if ((alias != NULL) && (*alias != '\0'))
+		chat->alias = g_strdup(alias);
+	else
+		chat->alias = NULL;
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)chat);
+
+	gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased",
+					 chat, old_alias);
+	g_free(old_alias);
+}
+
+void gaim_blist_alias_buddy(GaimBuddy *buddy, const char *alias)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimConversation *conv;
+	char *old_alias;
+
+	g_return_if_fail(buddy != NULL);
+
+	old_alias = buddy->alias;
+
+	if ((alias != NULL) && (*alias != '\0'))
+		buddy->alias = g_strdup(alias);
+	else
+		buddy->alias = NULL;
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)buddy);
+
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, buddy->name,
+											   buddy->account);
+	if (conv)
+		gaim_conversation_autoset_title(conv);
+
+	gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased",
+					 buddy, old_alias);
+	g_free(old_alias);
+}
+
+void gaim_blist_server_alias_buddy(GaimBuddy *buddy, const char *alias)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimConversation *conv;
+	char *old_alias;
+
+	g_return_if_fail(buddy != NULL);
+
+	old_alias = buddy->server_alias;
+
+	if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
+		buddy->server_alias = g_strdup(alias);
+	else
+		buddy->server_alias = NULL;
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)buddy);
+
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, buddy->name,
+											   buddy->account);
+	if (conv)
+		gaim_conversation_autoset_title(conv);
+
+	gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased",
+					 buddy, old_alias);
+	g_free(old_alias);
+}
+
+/*
+ * TODO: If merging, prompt the user if they want to merge.
+ */
+void gaim_blist_rename_group(GaimGroup *source, const char *new_name)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimGroup *dest;
+	gchar *old_name;
+	GList *moved_buddies = NULL;
+	GSList *accts;
+
+	g_return_if_fail(source != NULL);
+	g_return_if_fail(new_name != NULL);
+
+	if (*new_name == '\0' || !strcmp(new_name, source->name))
+		return;
+
+	dest = gaim_find_group(new_name);
+	if (dest != NULL) {
+		/* We're merging two groups */
+		GaimBlistNode *prev, *child, *next;
+
+		prev = gaim_blist_get_last_child((GaimBlistNode*)dest);
+		child = ((GaimBlistNode*)source)->child;
+
+		/*
+		 * TODO: This seems like a dumb way to do this... why not just
+		 * append all children from the old group to the end of the new
+		 * one?  PRPLs might be expecting to receive an add_buddy() for
+		 * each moved buddy...
+		 */
+		while (child)
+		{
+			next = child->next;
+			if (GAIM_BLIST_NODE_IS_CONTACT(child)) {
+				GaimBlistNode *bnode;
+				gaim_blist_add_contact((GaimContact *)child, dest, prev);
+				for (bnode = child->child; bnode != NULL; bnode = bnode->next) {
+					gaim_blist_add_buddy((GaimBuddy *)bnode, (GaimContact *)child,
+							NULL, bnode->prev);
+					moved_buddies = g_list_append(moved_buddies, bnode);
+				}
+				prev = child;
+			} else if (GAIM_BLIST_NODE_IS_CHAT(child)) {
+				gaim_blist_add_chat((GaimChat *)child, dest, prev);
+				prev = child;
+			} else {
+				gaim_debug(GAIM_DEBUG_ERROR, "blist",
+						"Unknown child type in group %s\n", source->name);
+			}
+			child = next;
+		}
+
+		/* Make a copy of the old group name and then delete the old group */
+		old_name = g_strdup(source->name);
+		gaim_blist_remove_group(source);
+		source = dest;
+	} else {
+		/* A simple rename */
+		GaimBlistNode *cnode, *bnode;
+
+		/* Build a GList of all buddies in this group */
+		for (cnode = ((GaimBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) {
+			if (GAIM_BLIST_NODE_IS_CONTACT(cnode))
+				for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+					moved_buddies = g_list_append(moved_buddies, bnode);
+		}
+
+		old_name = source->name;
+		source->name = g_strdup(new_name);
+	}
+
+	/* Save our changes */
+	gaim_blist_schedule_save();
+
+	/* Update the UI */
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode*)source);
+
+	/* Notify all PRPLs */
+	/* TODO: Is this condition needed?  Seems like it would always be TRUE */
+	if(old_name && source && strcmp(source->name, old_name)) {
+		for (accts = gaim_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) {
+			GaimAccount *account = accts->data;
+			GaimPluginProtocolInfo *prpl_info = NULL;
+			GList *l = NULL, *buddies = NULL;
+
+			if(account->gc && account->gc->prpl)
+				prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
+
+			if(!prpl_info)
+				continue;
+
+			for(l = moved_buddies; l; l = l->next) {
+				GaimBuddy *buddy = (GaimBuddy *)l->data;
+
+				if(buddy && buddy->account == account)
+					buddies = g_list_append(buddies, (GaimBlistNode *)buddy);
+			}
+
+			if(prpl_info->rename_group) {
+				prpl_info->rename_group(account->gc, old_name, source, buddies);
+			} else {
+				GList *cur, *groups = NULL;
+
+				/* Make a list of what the groups each buddy is in */
+				for(cur = buddies; cur; cur = cur->next) {
+					GaimBlistNode *node = (GaimBlistNode *)cur->data;
+					groups = g_list_prepend(groups, node->parent->parent);
+				}
+
+				gaim_account_remove_buddies(account, buddies, groups);
+				g_list_free(groups);
+				gaim_account_add_buddies(account, buddies);
+			}
+
+			g_list_free(buddies);
+		}
+	}
+	g_list_free(moved_buddies);
+	g_free(old_name);
+}
+
+static void gaim_blist_node_initialize_settings(GaimBlistNode *node);
+
+GaimChat *gaim_chat_new(GaimAccount *account, const char *alias, GHashTable *components)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimChat *chat;
+
+	g_return_val_if_fail(account != NULL, FALSE);
+	g_return_val_if_fail(components != NULL, FALSE);
+
+	chat = g_new0(GaimChat, 1);
+	chat->account = account;
+	if ((alias != NULL) && (*alias != '\0'))
+		chat->alias = g_strdup(alias);
+	chat->components = components;
+	gaim_blist_node_initialize_settings((GaimBlistNode *)chat);
+	((GaimBlistNode *)chat)->type = GAIM_BLIST_CHAT_NODE;
+
+	if (ops != NULL && ops->new_node != NULL)
+		ops->new_node((GaimBlistNode *)chat);
+
+	GAIM_DBUS_REGISTER_POINTER(chat, GaimChat);
+	return chat;
+}
+
+GaimBuddy *gaim_buddy_new(GaimAccount *account, const char *screenname, const char *alias)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBuddy *buddy;
+
+	g_return_val_if_fail(account != NULL, FALSE);
+	g_return_val_if_fail(screenname != NULL, FALSE);
+
+	buddy = g_new0(GaimBuddy, 1);
+	buddy->account  = account;
+	buddy->name     = g_strdup(screenname);
+	buddy->alias    = g_strdup(alias);
+	buddy->presence = gaim_presence_new_for_buddy(buddy);
+	((GaimBlistNode *)buddy)->type = GAIM_BLIST_BUDDY_NODE;
+
+	gaim_presence_set_status_active(buddy->presence, "offline", TRUE);
+
+	gaim_blist_node_initialize_settings((GaimBlistNode *)buddy);
+
+	if (ops && ops->new_node)
+		ops->new_node((GaimBlistNode *)buddy);
+
+	GAIM_DBUS_REGISTER_POINTER(buddy, GaimBuddy);
+	return buddy;
+}
+
+void
+gaim_buddy_set_icon(GaimBuddy *buddy, GaimBuddyIcon *icon)
+{
+	g_return_if_fail(buddy != NULL);
+
+	if (buddy->icon != icon) {
+		if (buddy->icon != NULL)
+			gaim_buddy_icon_unref(buddy->icon);
+		
+		buddy->icon = (icon != NULL ? gaim_buddy_icon_ref(icon) : NULL);
+	}
+
+	if (buddy->icon)
+		gaim_buddy_icon_cache(icon, buddy);
+	else
+		gaim_buddy_icon_uncache(buddy);
+
+	gaim_blist_schedule_save();
+
+	gaim_signal_emit(gaim_blist_get_handle(), "buddy-icon-changed", buddy);
+
+	gaim_blist_update_buddy_icon(buddy);
+}
+
+GaimAccount *
+gaim_buddy_get_account(const GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	return buddy->account;
+}
+
+const char *
+gaim_buddy_get_name(const GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	return buddy->name;
+}
+
+GaimBuddyIcon *
+gaim_buddy_get_icon(const GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	return buddy->icon;
+}
+
+void gaim_blist_add_chat(GaimChat *chat, GaimGroup *group, GaimBlistNode *node)
+{
+	GaimBlistNode *cnode = (GaimBlistNode*)chat;
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	g_return_if_fail(chat != NULL);
+	g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT((GaimBlistNode *)chat));
+
+	if (node == NULL) {
+		if (group == NULL) {
+			group = gaim_group_new(_("Chats"));
+			gaim_blist_add_group(group,
+					gaim_blist_get_last_sibling(gaimbuddylist->root));
+		}
+	} else {
+		group = (GaimGroup*)node->parent;
+	}
+
+	/* if we're moving to overtop of ourselves, do nothing */
+	if (cnode == node)
+		return;
+
+	if (cnode->parent) {
+		/* This chat was already in the list and is
+		 * being moved.
+		 */
+		((GaimGroup *)cnode->parent)->totalsize--;
+		if (gaim_account_is_connected(chat->account)) {
+			((GaimGroup *)cnode->parent)->online--;
+			((GaimGroup *)cnode->parent)->currentsize--;
+		}
+		if (cnode->next)
+			cnode->next->prev = cnode->prev;
+		if (cnode->prev)
+			cnode->prev->next = cnode->next;
+		if (cnode->parent->child == cnode)
+			cnode->parent->child = cnode->next;
+
+		if (ops && ops->remove)
+			ops->remove(gaimbuddylist, cnode);
+		/* ops->remove() cleaned up the cnode's ui_data, so we need to
+		 * reinitialize it */
+		if (ops && ops->new_node)
+			ops->new_node(cnode);
+
+		gaim_blist_schedule_save();
+	}
+
+	if (node != NULL) {
+		if (node->next)
+			node->next->prev = cnode;
+		cnode->next = node->next;
+		cnode->prev = node;
+		cnode->parent = node->parent;
+		node->next = cnode;
+		((GaimGroup *)node->parent)->totalsize++;
+		if (gaim_account_is_connected(chat->account)) {
+			((GaimGroup *)node->parent)->online++;
+			((GaimGroup *)node->parent)->currentsize++;
+		}
+	} else {
+		if (((GaimBlistNode *)group)->child)
+			((GaimBlistNode *)group)->child->prev = cnode;
+		cnode->next = ((GaimBlistNode *)group)->child;
+		cnode->prev = NULL;
+		((GaimBlistNode *)group)->child = cnode;
+		cnode->parent = (GaimBlistNode *)group;
+		group->totalsize++;
+		if (gaim_account_is_connected(chat->account)) {
+			group->online++;
+			group->currentsize++;
+		}
+	}
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode *)cnode);
+}
+
+void gaim_blist_add_buddy(GaimBuddy *buddy, GaimContact *contact, GaimGroup *group, GaimBlistNode *node)
+{
+	GaimBlistNode *cnode, *bnode;
+	GaimGroup *g;
+	GaimContact *c;
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	struct _gaim_hbuddy *hb;
+
+	g_return_if_fail(buddy != NULL);
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY((GaimBlistNode*)buddy));
+
+	bnode = (GaimBlistNode *)buddy;
+
+	/* if we're moving to overtop of ourselves, do nothing */
+	if (bnode == node || (!node && bnode->parent &&
+				contact && bnode->parent == (GaimBlistNode*)contact
+				&& bnode == bnode->parent->child))
+		return;
+
+	if (node && GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		c = (GaimContact*)node->parent;
+		g = (GaimGroup*)node->parent->parent;
+	} else if (contact) {
+		c = contact;
+		g = (GaimGroup *)((GaimBlistNode *)c)->parent;
+	} else {
+		if (group) {
+			g = group;
+		} else {
+			g = gaim_group_new(_("Buddies"));
+			gaim_blist_add_group(g,
+					gaim_blist_get_last_sibling(gaimbuddylist->root));
+		}
+		c = gaim_contact_new();
+		gaim_blist_add_contact(c, g,
+				gaim_blist_get_last_child((GaimBlistNode*)g));
+	}
+
+	cnode = (GaimBlistNode *)c;
+
+	if (bnode->parent) {
+		if (GAIM_BUDDY_IS_ONLINE(buddy)) {
+			((GaimContact*)bnode->parent)->online--;
+			if (((GaimContact*)bnode->parent)->online == 0)
+				((GaimGroup*)bnode->parent->parent)->online--;
+		}
+		if (gaim_account_is_connected(buddy->account)) {
+			((GaimContact*)bnode->parent)->currentsize--;
+			if (((GaimContact*)bnode->parent)->currentsize == 0)
+				((GaimGroup*)bnode->parent->parent)->currentsize--;
+		}
+		((GaimContact*)bnode->parent)->totalsize--;
+		/* the group totalsize will be taken care of by remove_contact below */
+
+		if (bnode->parent->parent != (GaimBlistNode*)g)
+			serv_move_buddy(buddy, (GaimGroup *)bnode->parent->parent, g);
+
+		if (bnode->next)
+			bnode->next->prev = bnode->prev;
+		if (bnode->prev)
+			bnode->prev->next = bnode->next;
+		if (bnode->parent->child == bnode)
+			bnode->parent->child = bnode->next;
+
+		if (ops && ops->remove)
+			ops->remove(gaimbuddylist, bnode);
+
+		gaim_blist_schedule_save();
+
+		if (bnode->parent->parent != (GaimBlistNode*)g) {
+			hb = g_new(struct _gaim_hbuddy, 1);
+			hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name));
+			hb->account = buddy->account;
+			hb->group = bnode->parent->parent;
+			g_hash_table_remove(gaimbuddylist->buddies, hb);
+			g_free(hb->name);
+			g_free(hb);
+		}
+
+		if (!bnode->parent->child) {
+			gaim_blist_remove_contact((GaimContact*)bnode->parent);
+		} else {
+			gaim_contact_invalidate_priority_buddy((GaimContact*)bnode->parent);
+			if (ops && ops->update)
+				ops->update(gaimbuddylist, bnode->parent);
+		}
+	}
+
+	if (node && GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		if (node->next)
+			node->next->prev = bnode;
+		bnode->next = node->next;
+		bnode->prev = node;
+		bnode->parent = node->parent;
+		node->next = bnode;
+	} else {
+		if (cnode->child)
+			cnode->child->prev = bnode;
+		bnode->prev = NULL;
+		bnode->next = cnode->child;
+		cnode->child = bnode;
+		bnode->parent = cnode;
+	}
+
+	if (GAIM_BUDDY_IS_ONLINE(buddy)) {
+		((GaimContact*)bnode->parent)->online++;
+		if (((GaimContact*)bnode->parent)->online == 1)
+			((GaimGroup*)bnode->parent->parent)->online++;
+	}
+	if (gaim_account_is_connected(buddy->account)) {
+		((GaimContact*)bnode->parent)->currentsize++;
+		if (((GaimContact*)bnode->parent)->currentsize == 1)
+			((GaimGroup*)bnode->parent->parent)->currentsize++;
+	}
+	((GaimContact*)bnode->parent)->totalsize++;
+
+	hb = g_new(struct _gaim_hbuddy, 1);
+	hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name));
+	hb->account = buddy->account;
+	hb->group = ((GaimBlistNode*)buddy)->parent->parent;
+
+	g_hash_table_replace(gaimbuddylist->buddies, hb, buddy);
+
+	gaim_contact_invalidate_priority_buddy(gaim_buddy_get_contact(buddy));
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode*)buddy);
+
+	/* Signal that the buddy has been added */
+	gaim_signal_emit(gaim_blist_get_handle(), "buddy-added", buddy);
+}
+
+GaimContact *gaim_contact_new()
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	GaimContact *contact = g_new0(GaimContact, 1);
+	contact->totalsize = 0;
+	contact->currentsize = 0;
+	contact->online = 0;
+	gaim_blist_node_initialize_settings((GaimBlistNode *)contact);
+	((GaimBlistNode *)contact)->type = GAIM_BLIST_CONTACT_NODE;
+
+	if (ops && ops->new_node)
+		ops->new_node((GaimBlistNode *)contact);
+
+	GAIM_DBUS_REGISTER_POINTER(contact, GaimContact);
+	return contact;
+}
+
+void gaim_contact_set_alias(GaimContact *contact, const char *alias)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	char *old_alias;
+
+	g_return_if_fail(contact != NULL);
+
+	old_alias = contact->alias;
+
+	if ((alias != NULL) && (*alias != '\0'))
+		contact->alias = g_strdup(alias);
+	else
+		contact->alias = NULL;
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+		ops->update(gaimbuddylist, (GaimBlistNode*)contact);
+
+	gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased",
+					 contact, old_alias);
+	g_free(old_alias);
+}
+
+const char *gaim_contact_get_alias(GaimContact* contact)
+{
+	g_return_val_if_fail(contact != NULL, NULL);
+
+	if (contact->alias)
+		return contact->alias;
+
+	return gaim_buddy_get_alias(gaim_contact_get_priority_buddy(contact));
+}
+
+gboolean gaim_contact_on_account(GaimContact *c, GaimAccount *account)
+{
+	GaimBlistNode *bnode, *cnode = (GaimBlistNode *) c;
+
+	g_return_val_if_fail(c != NULL, FALSE);
+	g_return_val_if_fail(account != NULL, FALSE);
+
+	for (bnode = cnode->child; bnode; bnode = bnode->next) {
+		GaimBuddy *buddy;
+
+		if (! GAIM_BLIST_NODE_IS_BUDDY(bnode))
+			continue;
+
+		buddy = (GaimBuddy *)bnode;
+		if (buddy->account == account)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+void gaim_contact_invalidate_priority_buddy(GaimContact *contact)
+{
+	g_return_if_fail(contact != NULL);
+
+	contact->priority_valid = FALSE;
+}
+
+GaimGroup *gaim_group_new(const char *name)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimGroup *group;
+
+	g_return_val_if_fail(name  != NULL, NULL);
+	g_return_val_if_fail(*name != '\0', NULL);
+
+	group = gaim_find_group(name);
+	if (group != NULL)
+		return group;
+
+	group = g_new0(GaimGroup, 1);
+	group->name = g_strdup(name);
+	group->totalsize = 0;
+	group->currentsize = 0;
+	group->online = 0;
+	gaim_blist_node_initialize_settings((GaimBlistNode *)group);
+	((GaimBlistNode *)group)->type = GAIM_BLIST_GROUP_NODE;
+
+	if (ops && ops->new_node)
+		ops->new_node((GaimBlistNode *)group);
+
+	GAIM_DBUS_REGISTER_POINTER(group, GaimGroup);
+	return group;
+}
+
+void gaim_blist_add_contact(GaimContact *contact, GaimGroup *group, GaimBlistNode *node)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimGroup *g;
+	GaimBlistNode *gnode, *cnode, *bnode;
+
+	g_return_if_fail(contact != NULL);
+	g_return_if_fail(GAIM_BLIST_NODE_IS_CONTACT((GaimBlistNode*)contact));
+
+	if ((GaimBlistNode*)contact == node)
+		return;
+
+	if (node && (GAIM_BLIST_NODE_IS_CONTACT(node) ||
+				GAIM_BLIST_NODE_IS_CHAT(node)))
+		g = (GaimGroup*)node->parent;
+	else if (group)
+		g = group;
+	else {
+		g = gaim_group_new(_("Buddies"));
+		gaim_blist_add_group(g,
+				gaim_blist_get_last_sibling(gaimbuddylist->root));
+	}
+
+	gnode = (GaimBlistNode*)g;
+	cnode = (GaimBlistNode*)contact;
+
+	if (cnode->parent) {
+		if (cnode->parent->child == cnode)
+			cnode->parent->child = cnode->next;
+		if (cnode->prev)
+			cnode->prev->next = cnode->next;
+		if (cnode->next)
+			cnode->next->prev = cnode->prev;
+
+		if (cnode->parent != gnode) {
+			bnode = cnode->child;
+			while (bnode) {
+				GaimBlistNode *next_bnode = bnode->next;
+				GaimBuddy *b = (GaimBuddy*)bnode;
+
+				struct _gaim_hbuddy *hb = g_new(struct _gaim_hbuddy, 1);
+				hb->name = g_strdup(gaim_normalize(b->account, b->name));
+				hb->account = b->account;
+				hb->group = cnode->parent;
+
+				g_hash_table_remove(gaimbuddylist->buddies, hb);
+
+				if (!gaim_find_buddy_in_group(b->account, b->name, g)) {
+					hb->group = gnode;
+					g_hash_table_replace(gaimbuddylist->buddies, hb, b);
+
+					if (b->account->gc)
+						serv_move_buddy(b, (GaimGroup *)cnode->parent, g);
+				} else {
+					gboolean empty_contact = FALSE;
+
+					/* this buddy already exists in the group, so we're
+					 * gonna delete it instead */
+					g_free(hb->name);
+					g_free(hb);
+					if (b->account->gc)
+						gaim_account_remove_buddy(b->account, b, (GaimGroup *)cnode->parent);
+
+					if (!cnode->child->next)
+						empty_contact = TRUE;
+					gaim_blist_remove_buddy(b);
+
+					/** in gaim_blist_remove_buddy(), if the last buddy in a
+					 * contact is removed, the contact is cleaned up and
+					 * g_free'd, so we mustn't try to reference bnode->next */
+					if (empty_contact)
+						return;
+				}
+				bnode = next_bnode;
+			}
+		}
+
+		if (contact->online > 0)
+			((GaimGroup*)cnode->parent)->online--;
+		if (contact->currentsize > 0)
+			((GaimGroup*)cnode->parent)->currentsize--;
+		((GaimGroup*)cnode->parent)->totalsize--;
+
+		if (ops && ops->remove)
+			ops->remove(gaimbuddylist, cnode);
+
+		gaim_blist_schedule_save();
+	}
+
+	if (node && (GAIM_BLIST_NODE_IS_CONTACT(node) ||
+				GAIM_BLIST_NODE_IS_CHAT(node))) {
+		if (node->next)
+			node->next->prev = cnode;
+		cnode->next = node->next;
+		cnode->prev = node;
+		cnode->parent = node->parent;
+		node->next = cnode;
+	} else {
+		if (gnode->child)
+			gnode->child->prev = cnode;
+		cnode->prev = NULL;
+		cnode->next = gnode->child;
+		gnode->child = cnode;
+		cnode->parent = gnode;
+	}
+
+	if (contact->online > 0)
+		g->online++;
+	if (contact->currentsize > 0)
+		g->currentsize++;
+	g->totalsize++;
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update)
+	{
+		if (cnode->child)
+			ops->update(gaimbuddylist, cnode);
+
+		for (bnode = cnode->child; bnode; bnode = bnode->next)
+			ops->update(gaimbuddylist, bnode);
+	}
+}
+
+void gaim_blist_merge_contact(GaimContact *source, GaimBlistNode *node)
+{
+	GaimBlistNode *sourcenode = (GaimBlistNode*)source;
+	GaimBlistNode *targetnode;
+	GaimBlistNode *prev, *cur, *next;
+	GaimContact *target;
+
+	g_return_if_fail(source != NULL);
+	g_return_if_fail(node != NULL);
+
+	if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
+		target = (GaimContact *)node;
+		prev = gaim_blist_get_last_child(node);
+	} else if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		target = (GaimContact *)node->parent;
+		prev = node;
+	} else {
+		return;
+	}
+
+	if (source == target || !target)
+		return;
+
+	targetnode = (GaimBlistNode *)target;
+	next = sourcenode->child;
+
+	while (next) {
+		cur = next;
+		next = cur->next;
+		if (GAIM_BLIST_NODE_IS_BUDDY(cur)) {
+			gaim_blist_add_buddy((GaimBuddy *)cur, target, NULL, prev);
+			prev = cur;
+		}
+	}
+}
+
+void gaim_blist_add_group(GaimGroup *group, GaimBlistNode *node)
+{
+	GaimBlistUiOps *ops;
+	GaimBlistNode *gnode = (GaimBlistNode*)group;
+
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(GAIM_BLIST_NODE_IS_GROUP((GaimBlistNode *)group));
+
+	ops = gaim_blist_get_ui_ops();
+
+	if (!gaimbuddylist->root) {
+		gaimbuddylist->root = gnode;
+		return;
+	}
+
+	/* if we're moving to overtop of ourselves, do nothing */
+	if (gnode == node)
+		return;
+
+	if (gaim_find_group(group->name)) {
+		/* This is just being moved */
+
+		if (ops && ops->remove)
+			ops->remove(gaimbuddylist, (GaimBlistNode *)group);
+
+		if (gnode == gaimbuddylist->root)
+			gaimbuddylist->root = gnode->next;
+		if (gnode->prev)
+			gnode->prev->next = gnode->next;
+		if (gnode->next)
+			gnode->next->prev = gnode->prev;
+	}
+
+	if (node && GAIM_BLIST_NODE_IS_GROUP(node)) {
+		gnode->next = node->next;
+		gnode->prev = node;
+		if (node->next)
+			node->next->prev = gnode;
+		node->next = gnode;
+	} else {
+		if (gaimbuddylist->root)
+			gaimbuddylist->root->prev = gnode;
+		gnode->next = gaimbuddylist->root;
+		gnode->prev = NULL;
+		gaimbuddylist->root = gnode;
+	}
+
+	gaim_blist_schedule_save();
+
+	if (ops && ops->update) {
+		ops->update(gaimbuddylist, gnode);
+		for (node = gnode->child; node; node = node->next)
+			ops->update(gaimbuddylist, node);
+	}
+}
+
+void gaim_blist_remove_contact(GaimContact *contact)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBlistNode *node, *gnode;
+
+	g_return_if_fail(contact != NULL);
+
+	node = (GaimBlistNode *)contact;
+	gnode = node->parent;
+
+	if (node->child) {
+		/*
+		 * If this contact has children then remove them.  When the last
+		 * buddy is removed from the contact, the contact is automatically
+		 * deleted.
+		 */
+		while (node->child->next) {
+			gaim_blist_remove_buddy((GaimBuddy*)node->child);
+		}
+		/*
+		 * Remove the last buddy and trigger the deletion of the contact.
+		 * It would probably be cleaner if contact-deletion was done after
+		 * a timeout?  Or if it had to be done manually, like below?
+		 */
+		gaim_blist_remove_buddy((GaimBuddy*)node->child);
+	} else {
+		/* Remove the node from its parent */
+		if (gnode->child == node)
+			gnode->child = node->next;
+		if (node->prev)
+			node->prev->next = node->next;
+		if (node->next)
+			node->next->prev = node->prev;
+
+		gaim_blist_schedule_save();
+
+		/* Update the UI */
+		if (ops && ops->remove)
+			ops->remove(gaimbuddylist, node);
+
+		/* Delete the node */
+		g_hash_table_destroy(contact->node.settings);
+		GAIM_DBUS_UNREGISTER_POINTER(contact);
+		g_free(contact);
+	}
+}
+
+void gaim_blist_remove_buddy(GaimBuddy *buddy)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBlistNode *node, *cnode, *gnode;
+	GaimContact *contact;
+	GaimGroup *group;
+	struct _gaim_hbuddy hb;
+
+	g_return_if_fail(buddy != NULL);
+
+	node = (GaimBlistNode *)buddy;
+	cnode = node->parent;
+	gnode = cnode->parent;
+	contact = (GaimContact *)cnode;
+	group = (GaimGroup *)gnode;
+
+	/* Delete any buddy icon. */
+	gaim_buddy_icon_uncache(buddy);
+
+	/* Remove the node from its parent */
+	if (node->prev)
+		node->prev->next = node->next;
+	if (node->next)
+		node->next->prev = node->prev;
+	if (cnode->child == node)
+		cnode->child = node->next;
+
+	/* Adjust size counts */
+	if (GAIM_BUDDY_IS_ONLINE(buddy)) {
+		contact->online--;
+		if (contact->online == 0)
+			group->online--;
+	}
+	if (gaim_account_is_connected(buddy->account)) {
+		contact->currentsize--;
+		if (contact->currentsize == 0)
+			group->currentsize--;
+	}
+	contact->totalsize--;
+
+	gaim_blist_schedule_save();
+
+	/* Re-sort the contact */
+	if (cnode->child && contact->priority == buddy) {
+		gaim_contact_invalidate_priority_buddy(contact);
+		if (ops && ops->update)
+			ops->update(gaimbuddylist, cnode);
+	}
+
+	/* Remove this buddy from the buddies hash table */
+	hb.name = g_strdup(gaim_normalize(buddy->account, buddy->name));
+	hb.account = buddy->account;
+	hb.group = ((GaimBlistNode*)buddy)->parent->parent;
+	g_hash_table_remove(gaimbuddylist->buddies, &hb);
+	g_free(hb.name);
+
+	/* Update the UI */
+	if (ops && ops->remove)
+		ops->remove(gaimbuddylist, node);
+
+	/* Signal that the buddy has been removed before freeing the memory for it */
+	gaim_signal_emit(gaim_blist_get_handle(), "buddy-removed", buddy);
+
+	/* Delete the node */
+	if (buddy->icon != NULL)
+		gaim_buddy_icon_unref(buddy->icon);
+	g_hash_table_destroy(buddy->node.settings);
+	gaim_presence_remove_buddy(buddy->presence, buddy);
+	gaim_presence_destroy(buddy->presence);
+	g_free(buddy->name);
+	g_free(buddy->alias);
+	g_free(buddy->server_alias);
+
+	GAIM_DBUS_UNREGISTER_POINTER(buddy);
+	g_free(buddy);
+
+	/* FIXME: Once GaimBuddy is a GObject, timeout callbacks can
+	 * g_object_ref() it when connecting the callback and
+	 * g_object_unref() it in the handler.  That way, it won't
+	 * get freed while the timeout is pending and this line can
+	 * be removed. */
+	while (g_source_remove_by_user_data((gpointer *)buddy));
+
+	/* If the contact is empty then remove it */
+	if (!cnode->child)
+		gaim_blist_remove_contact(contact);
+}
+
+void gaim_blist_remove_chat(GaimChat *chat)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBlistNode *node, *gnode;
+	GaimGroup *group;
+
+	g_return_if_fail(chat != NULL);
+
+	node = (GaimBlistNode *)chat;
+	gnode = node->parent;
+	group = (GaimGroup *)gnode;
+
+	if (gnode != NULL)
+	{
+		/* Remove the node from its parent */
+		if (gnode->child == node)
+			gnode->child = node->next;
+		if (node->prev)
+			node->prev->next = node->next;
+		if (node->next)
+			node->next->prev = node->prev;
+
+		/* Adjust size counts */
+		if (gaim_account_is_connected(chat->account)) {
+			group->online--;
+			group->currentsize--;
+		}
+		group->totalsize--;
+
+		gaim_blist_schedule_save();
+	}
+
+	/* Update the UI */
+	if (ops && ops->remove)
+		ops->remove(gaimbuddylist, node);
+
+	/* Delete the node */
+	g_hash_table_destroy(chat->components);
+	g_hash_table_destroy(chat->node.settings);
+	g_free(chat->alias);
+	GAIM_DBUS_UNREGISTER_POINTER(chat);
+	g_free(chat);
+}
+
+void gaim_blist_remove_group(GaimGroup *group)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBlistNode *node;
+	GList *l;
+
+	g_return_if_fail(group != NULL);
+
+	node = (GaimBlistNode *)group;
+
+	/* Make sure the group is empty */
+	if (node->child) {
+		char *buf;
+		int count = 0;
+		GaimBlistNode *child;
+
+		for (child = node->child; child != NULL; child = child->next)
+			count++;
+
+		buf = g_strdup_printf(ngettext("%d buddy from group %s was not removed "
+									   "because it belongs to an account which is "
+									   "disabled or offline.  This buddy and the "
+									   "group were not removed.\n",
+									   "%d buddies from group %s were not "
+									   "removed because they belong to accounts "
+									   "which are currently disabled or offline.  "
+									   "These buddies and the group were not "
+									   "removed.\n", count),
+							  count, group->name);
+		gaim_notify_error(NULL, NULL, _("Group not removed"), buf);
+		g_free(buf);
+		return;
+	}
+
+	/* Remove the node from its parent */
+	if (gaimbuddylist->root == node)
+		gaimbuddylist->root = node->next;
+	if (node->prev)
+		node->prev->next = node->next;
+	if (node->next)
+		node->next->prev = node->prev;
+
+	gaim_blist_schedule_save();
+
+	/* Update the UI */
+	if (ops && ops->remove)
+		ops->remove(gaimbuddylist, node);
+
+	/* Remove the group from all accounts that are online */
+	for (l = gaim_connections_get_all(); l != NULL; l = l->next)
+	{
+		GaimConnection *gc = (GaimConnection *)l->data;
+
+		if (gaim_connection_get_state(gc) == GAIM_CONNECTED)
+			gaim_account_remove_group(gaim_connection_get_account(gc), group);
+	}
+
+	/* Delete the node */
+	g_hash_table_destroy(group->node.settings);
+	g_free(group->name);
+	GAIM_DBUS_UNREGISTER_POINTER(group);
+	g_free(group);
+}
+
+GaimBuddy *gaim_contact_get_priority_buddy(GaimContact *contact)
+{
+	g_return_val_if_fail(contact != NULL, NULL);
+
+	if (!contact->priority_valid)
+		gaim_contact_compute_priority_buddy(contact);
+
+	return contact->priority;
+}
+
+const char *gaim_buddy_get_alias_only(GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	if ((buddy->alias != NULL) && (*buddy->alias != '\0')) {
+		return buddy->alias;
+	} else if ((buddy->server_alias != NULL) &&
+		   (*buddy->server_alias != '\0')) {
+
+		return buddy->server_alias;
+	}
+
+	return NULL;
+}
+
+
+const char *gaim_buddy_get_contact_alias(GaimBuddy *buddy)
+{
+	GaimContact *c;
+
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	/* Search for an alias for the buddy. In order of precedence: */
+	/* The buddy alias */
+	if (buddy->alias != NULL)
+		return buddy->alias;
+
+	/* The contact alias */
+	c = gaim_buddy_get_contact(buddy);
+	if ((c != NULL) && (c->alias != NULL))
+		return c->alias;
+
+	/* The server alias */
+	if ((buddy->server_alias) && (*buddy->server_alias))
+		return buddy->server_alias;
+
+	/* The buddy's user name (i.e. no alias) */
+	return buddy->name;
+}
+
+
+const char *gaim_buddy_get_alias(GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	/* Search for an alias for the buddy. In order of precedence: */
+	/* The buddy alias */
+	if (buddy->alias != NULL)
+		return buddy->alias;
+
+	/* The server alias */
+	if ((buddy->server_alias) && (*buddy->server_alias))
+		return buddy->server_alias;
+
+	/* The buddy's user name (i.e. no alias) */
+	return buddy->name;
+}
+
+const char *gaim_buddy_get_server_alias(GaimBuddy *buddy)
+{
+        g_return_val_if_fail(buddy != NULL, NULL);
+
+	if ((buddy->server_alias) && (*buddy->server_alias))
+	    return buddy->server_alias;
+
+	return NULL;
+}
+
+const char *gaim_buddy_get_local_alias(GaimBuddy *buddy)
+{
+	GaimContact *c;
+
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	/* Search for an alias for the buddy. In order of precedence: */
+	/* The buddy alias */
+	if (buddy->alias != NULL)
+		return buddy->alias;
+
+	/* The contact alias */
+	c = gaim_buddy_get_contact(buddy);
+	if ((c != NULL) && (c->alias != NULL))
+		return c->alias;
+
+	/* The buddy's user name (i.e. no alias) */
+	return buddy->name;
+}
+
+const char *gaim_chat_get_name(GaimChat *chat)
+{
+	struct proto_chat_entry *pce;
+	GList *parts;
+	char *ret;
+
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	if ((chat->alias != NULL) && (*chat->alias != '\0'))
+		return chat->alias;
+
+	parts = GAIM_PLUGIN_PROTOCOL_INFO(chat->account->gc->prpl)->chat_info(chat->account->gc);
+	pce = parts->data;
+	ret = g_hash_table_lookup(chat->components, pce->identifier);
+	g_list_foreach(parts, (GFunc)g_free, NULL);
+	g_list_free(parts);
+
+	return ret;
+}
+
+GaimBuddy *gaim_find_buddy(GaimAccount *account, const char *name)
+{
+	GaimBuddy *buddy;
+	struct _gaim_hbuddy hb;
+	GaimBlistNode *group;
+
+	g_return_val_if_fail(gaimbuddylist != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
+
+	hb.account = account;
+	hb.name = g_strdup(gaim_normalize(account, name));
+
+	for (group = gaimbuddylist->root; group; group = group->next) {
+		hb.group = group;
+		if ((buddy = g_hash_table_lookup(gaimbuddylist->buddies, &hb))) {
+			g_free(hb.name);
+			return buddy;
+		}
+	}
+	g_free(hb.name);
+
+	return NULL;
+}
+
+GaimBuddy *gaim_find_buddy_in_group(GaimAccount *account, const char *name,
+		GaimGroup *group)
+{
+	struct _gaim_hbuddy hb;
+	GaimBuddy *ret;
+
+	g_return_val_if_fail(gaimbuddylist != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
+
+	hb.name = g_strdup(gaim_normalize(account, name));
+	hb.account = account;
+	hb.group = (GaimBlistNode*)group;
+
+	ret = g_hash_table_lookup(gaimbuddylist->buddies, &hb);
+	g_free(hb.name);
+
+	return ret;
+}
+
+static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
+{
+	struct _gaim_hbuddy *hb = key;
+	GaimBuddy *buddy = value;
+	struct _list_account_buddies *ab = data;
+
+	if (hb->account == ab->account) {
+		ab->list = g_slist_prepend(ab->list, buddy);
+	}
+}
+
+GSList *gaim_find_buddies(GaimAccount *account, const char *name)
+{
+	GaimBuddy *buddy;
+	GaimBlistNode *node;
+	GSList *ret = NULL;
+
+	g_return_val_if_fail(gaimbuddylist != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+
+
+	if ((name != NULL) && (*name != '\0')) {
+		struct _gaim_hbuddy hb;
+
+		hb.name = g_strdup(gaim_normalize(account, name));
+		hb.account = account;
+
+		for (node = gaimbuddylist->root; node != NULL; node = node->next) {
+			hb.group = node;
+			if ((buddy = g_hash_table_lookup(gaimbuddylist->buddies, &hb)) != NULL)
+				ret = g_slist_prepend(ret, buddy);
+		}
+		g_free(hb.name);
+	} else {
+		struct _list_account_buddies *ab = g_new0(struct _list_account_buddies, 1);
+		ab->account = account;
+		g_hash_table_foreach(gaimbuddylist->buddies, find_acct_buddies, ab);
+		ret = ab->list;
+		g_free(ab);
+	}
+
+	return ret;
+}
+
+GaimGroup *gaim_find_group(const char *name)
+{
+	GaimBlistNode *node;
+
+	g_return_val_if_fail(gaimbuddylist != NULL, NULL);
+	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
+
+	for (node = gaimbuddylist->root; node != NULL; node = node->next) {
+		if (!strcmp(((GaimGroup *)node)->name, name))
+			return (GaimGroup *)node;
+	}
+
+	return NULL;
+}
+
+GaimChat *
+gaim_blist_find_chat(GaimAccount *account, const char *name)
+{
+	char *chat_name;
+	GaimChat *chat;
+	GaimPlugin *prpl;
+	GaimPluginProtocolInfo *prpl_info = NULL;
+	struct proto_chat_entry *pce;
+	GaimBlistNode *node, *group;
+	GList *parts;
+
+	g_return_val_if_fail(gaimbuddylist != NULL, NULL);
+	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
+
+	if (!gaim_account_is_connected(account))
+		return NULL;
+
+	prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
+	prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
+
+	if (prpl_info->find_blist_chat != NULL)
+		return prpl_info->find_blist_chat(account, name);
+
+	for (group = gaimbuddylist->root; group != NULL; group = group->next) {
+		for (node = group->child; node != NULL; node = node->next) {
+			if (GAIM_BLIST_NODE_IS_CHAT(node)) {
+
+				chat = (GaimChat*)node;
+
+				if (account != chat->account)
+					continue;
+
+				parts = prpl_info->chat_info(
+					gaim_account_get_connection(chat->account));
+
+				pce = parts->data;
+				chat_name = g_hash_table_lookup(chat->components,
+												pce->identifier);
+
+				if (chat->account == account && chat_name != NULL &&
+					name != NULL && !strcmp(chat_name, name)) {
+
+					return chat;
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+GaimGroup *
+gaim_chat_get_group(GaimChat *chat)
+{
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	return (GaimGroup *)(((GaimBlistNode *)chat)->parent);
+}
+
+GaimContact *gaim_buddy_get_contact(GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	return (GaimContact*)((GaimBlistNode*)buddy)->parent;
+}
+
+GaimPresence *gaim_buddy_get_presence(const GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+	return buddy->presence;
+}
+
+GaimGroup *gaim_buddy_get_group(GaimBuddy *buddy)
+{
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	if (((GaimBlistNode *)buddy)->parent == NULL)
+		return NULL;
+
+	return (GaimGroup *)(((GaimBlistNode*)buddy)->parent->parent);
+}
+
+GSList *gaim_group_get_accounts(GaimGroup *group)
+{
+	GSList *l = NULL;
+	GaimBlistNode *gnode, *cnode, *bnode;
+
+	gnode = (GaimBlistNode *)group;
+
+	for (cnode = gnode->child;  cnode; cnode = cnode->next) {
+		if (GAIM_BLIST_NODE_IS_CHAT(cnode)) {
+			if (!g_slist_find(l, ((GaimChat *)cnode)->account))
+				l = g_slist_append(l, ((GaimChat *)cnode)->account);
+		} else if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
+			for (bnode = cnode->child; bnode; bnode = bnode->next) {
+				if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
+					if (!g_slist_find(l, ((GaimBuddy *)bnode)->account))
+						l = g_slist_append(l, ((GaimBuddy *)bnode)->account);
+				}
+			}
+		}
+	}
+
+	return l;
+}
+
+void gaim_blist_add_account(GaimAccount *account)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBlistNode *gnode, *cnode, *bnode;
+
+	g_return_if_fail(gaimbuddylist != NULL);
+
+	if (!ops || !ops->update)
+		return;
+
+	for (gnode = gaimbuddylist->root; gnode; gnode = gnode->next) {
+		if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+			continue;
+		for (cnode = gnode->child; cnode; cnode = cnode->next) {
+			if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
+				gboolean recompute = FALSE;
+					for (bnode = cnode->child; bnode; bnode = bnode->next) {
+						if (GAIM_BLIST_NODE_IS_BUDDY(bnode) &&
+								((GaimBuddy*)bnode)->account == account) {
+							recompute = TRUE;
+							((GaimContact*)cnode)->currentsize++;
+							if (((GaimContact*)cnode)->currentsize == 1)
+								((GaimGroup*)gnode)->currentsize++;
+							ops->update(gaimbuddylist, bnode);
+						}
+					}
+					if (recompute ||
+							gaim_blist_node_get_bool(cnode, "show_offline")) {
+						gaim_contact_invalidate_priority_buddy((GaimContact*)cnode);
+						ops->update(gaimbuddylist, cnode);
+					}
+			} else if (GAIM_BLIST_NODE_IS_CHAT(cnode) &&
+					((GaimChat*)cnode)->account == account) {
+				((GaimGroup *)gnode)->online++;
+				((GaimGroup *)gnode)->currentsize++;
+				ops->update(gaimbuddylist, cnode);
+			}
+		}
+		ops->update(gaimbuddylist, gnode);
+	}
+}
+
+void gaim_blist_remove_account(GaimAccount *account)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+	GaimBlistNode *gnode, *cnode, *bnode;
+	GaimBuddy *buddy;
+	GaimChat *chat;
+	GaimContact *contact;
+	GaimGroup *group;
+	GList *list = NULL, *iter = NULL;
+
+	g_return_if_fail(gaimbuddylist != NULL);
+
+	for (gnode = gaimbuddylist->root; gnode; gnode = gnode->next) {
+		if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+			continue;
+
+		group = (GaimGroup *)gnode;
+
+		for (cnode = gnode->child; cnode; cnode = cnode->next) {
+			if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
+				gboolean recompute = FALSE;
+				contact = (GaimContact *)cnode;
+
+				for (bnode = cnode->child; bnode; bnode = bnode->next) {
+					if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+						continue;
+
+					buddy = (GaimBuddy *)bnode;
+					if (account == buddy->account) {
+						GaimPresence *presence;
+						recompute = TRUE;
+
+						presence = gaim_buddy_get_presence(buddy);
+
+						if(gaim_presence_is_online(presence)) {
+							contact->online--;
+							if (contact->online == 0)
+								group->online--;
+
+							gaim_blist_node_set_int(&buddy->node,
+													"last_seen", time(NULL));
+						}
+
+						contact->currentsize--;
+						if (contact->currentsize == 0)
+							group->currentsize--;
+
+						if (!g_list_find(list, presence))
+							list = g_list_prepend(list, presence);
+
+						if (ops && ops->remove)
+							ops->remove(gaimbuddylist, bnode);
+					}
+				}
+				if (recompute) {
+					gaim_contact_invalidate_priority_buddy(contact);
+					if (ops && ops->update)
+						ops->update(gaimbuddylist, cnode);
+				}
+			} else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) {
+				chat = (GaimChat *)cnode;
+
+				if(chat->account == account) {
+					group->currentsize--;
+					group->online--;
+
+					if (ops && ops->remove)
+						ops->remove(gaimbuddylist, cnode);
+				}
+			}
+		}
+	}
+
+	for (iter = list; iter; iter = iter->next)
+	{
+		gaim_presence_set_status_active(iter->data, "offline", TRUE);
+	}
+	g_list_free(list);
+}
+
+gboolean gaim_group_on_account(GaimGroup *g, GaimAccount *account)
+{
+	GaimBlistNode *cnode;
+	for (cnode = ((GaimBlistNode *)g)->child; cnode; cnode = cnode->next) {
+		if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
+			if(gaim_contact_on_account((GaimContact *) cnode, account))
+				return TRUE;
+		} else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) {
+			GaimChat *chat = (GaimChat *)cnode;
+			if ((!account && gaim_account_is_connected(chat->account))
+					|| chat->account == account)
+				return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+void
+gaim_blist_request_add_buddy(GaimAccount *account, const char *username,
+							 const char *group, const char *alias)
+{
+	GaimBlistUiOps *ui_ops;
+
+	ui_ops = gaim_blist_get_ui_ops();
+
+	if (ui_ops != NULL && ui_ops->request_add_buddy != NULL)
+		ui_ops->request_add_buddy(account, username, group, alias);
+}
+
+void
+gaim_blist_request_add_chat(GaimAccount *account, GaimGroup *group,
+							const char *alias, const char *name)
+{
+	GaimBlistUiOps *ui_ops;
+
+	ui_ops = gaim_blist_get_ui_ops();
+
+	if (ui_ops != NULL && ui_ops->request_add_chat != NULL)
+		ui_ops->request_add_chat(account, group, alias, name);
+}
+
+void
+gaim_blist_request_add_group(void)
+{
+	GaimBlistUiOps *ui_ops;
+
+	ui_ops = gaim_blist_get_ui_ops();
+
+	if (ui_ops != NULL && ui_ops->request_add_group != NULL)
+		ui_ops->request_add_group();
+}
+
+static void
+gaim_blist_node_setting_free(gpointer data)
+{
+	GaimValue *value;
+
+	value = (GaimValue *)data;
+
+	gaim_value_destroy(value);
+}
+
+static void gaim_blist_node_initialize_settings(GaimBlistNode *node)
+{
+	if (node->settings)
+		return;
+
+	node->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+			(GDestroyNotify)gaim_blist_node_setting_free);
+}
+
+void gaim_blist_node_remove_setting(GaimBlistNode *node, const char *key)
+{
+	g_return_if_fail(node != NULL);
+	g_return_if_fail(node->settings != NULL);
+	g_return_if_fail(key != NULL);
+
+	g_hash_table_remove(node->settings, key);
+
+	gaim_blist_schedule_save();
+}
+
+void
+gaim_blist_node_set_flags(GaimBlistNode *node, GaimBlistNodeFlags flags)
+{
+	g_return_if_fail(node != NULL);
+
+	node->flags = flags;
+}
+
+GaimBlistNodeFlags
+gaim_blist_node_get_flags(GaimBlistNode *node)
+{
+	g_return_val_if_fail(node != NULL, 0);
+
+	return node->flags;
+}
+
+void
+gaim_blist_node_set_bool(GaimBlistNode* node, const char *key, gboolean data)
+{
+	GaimValue *value;
+
+	g_return_if_fail(node != NULL);
+	g_return_if_fail(node->settings != NULL);
+	g_return_if_fail(key != NULL);
+
+	value = gaim_value_new(GAIM_TYPE_BOOLEAN);
+	gaim_value_set_boolean(value, data);
+
+	g_hash_table_replace(node->settings, g_strdup(key), value);
+
+	gaim_blist_schedule_save();
+}
+
+gboolean
+gaim_blist_node_get_bool(GaimBlistNode* node, const char *key)
+{
+	GaimValue *value;
+
+	g_return_val_if_fail(node != NULL, FALSE);
+	g_return_val_if_fail(node->settings != NULL, FALSE);
+	g_return_val_if_fail(key != NULL, FALSE);
+
+	value = g_hash_table_lookup(node->settings, key);
+
+	if (value == NULL)
+		return FALSE;
+
+	g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_BOOLEAN, FALSE);
+
+	return gaim_value_get_boolean(value);
+}
+
+void
+gaim_blist_node_set_int(GaimBlistNode* node, const char *key, int data)
+{
+	GaimValue *value;
+
+	g_return_if_fail(node != NULL);
+	g_return_if_fail(node->settings != NULL);
+	g_return_if_fail(key != NULL);
+
+	value = gaim_value_new(GAIM_TYPE_INT);
+	gaim_value_set_int(value, data);
+
+	g_hash_table_replace(node->settings, g_strdup(key), value);
+
+	gaim_blist_schedule_save();
+}
+
+int
+gaim_blist_node_get_int(GaimBlistNode* node, const char *key)
+{
+	GaimValue *value;
+
+	g_return_val_if_fail(node != NULL, 0);
+	g_return_val_if_fail(node->settings != NULL, 0);
+	g_return_val_if_fail(key != NULL, 0);
+
+	value = g_hash_table_lookup(node->settings, key);
+
+	if (value == NULL)
+		return 0;
+
+	g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_INT, 0);
+
+	return gaim_value_get_int(value);
+}
+
+void
+gaim_blist_node_set_string(GaimBlistNode* node, const char *key, const char *data)
+{
+	GaimValue *value;
+
+	g_return_if_fail(node != NULL);
+	g_return_if_fail(node->settings != NULL);
+	g_return_if_fail(key != NULL);
+
+	value = gaim_value_new(GAIM_TYPE_STRING);
+	gaim_value_set_string(value, data);
+
+	g_hash_table_replace(node->settings, g_strdup(key), value);
+
+	gaim_blist_schedule_save();
+}
+
+const char *
+gaim_blist_node_get_string(GaimBlistNode* node, const char *key)
+{
+	GaimValue *value;
+
+	g_return_val_if_fail(node != NULL, NULL);
+	g_return_val_if_fail(node->settings != NULL, NULL);
+	g_return_val_if_fail(key != NULL, NULL);
+
+	value = g_hash_table_lookup(node->settings, key);
+
+	if (value == NULL)
+		return NULL;
+
+	g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_STRING, NULL);
+
+	return gaim_value_get_string(value);
+}
+
+GList *
+gaim_blist_node_get_extended_menu(GaimBlistNode *n)
+{
+	GList *menu = NULL;
+
+	g_return_val_if_fail(n != NULL, NULL);
+
+	gaim_signal_emit(gaim_blist_get_handle(),
+			"blist-node-extended-menu",
+			n, &menu);
+	return menu;
+}
+
+int gaim_blist_get_group_size(GaimGroup *group, gboolean offline)
+{
+	if (!group)
+		return 0;
+
+	return offline ? group->totalsize : group->currentsize;
+}
+
+int gaim_blist_get_group_online_count(GaimGroup *group)
+{
+	if (!group)
+		return 0;
+
+	return group->online;
+}
+
+void
+gaim_blist_set_ui_ops(GaimBlistUiOps *ops)
+{
+	blist_ui_ops = ops;
+}
+
+GaimBlistUiOps *
+gaim_blist_get_ui_ops(void)
+{
+	return blist_ui_ops;
+}
+
+
+void *
+gaim_blist_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
+
+void
+gaim_blist_init(void)
+{
+	void *handle = gaim_blist_get_handle();
+
+	gaim_signal_register(handle, "buddy-status-changed",
+	                     gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL,
+	                     3,
+	                     gaim_value_new(GAIM_TYPE_SUBTYPE,
+	                                    GAIM_SUBTYPE_BLIST_BUDDY),
+	                     gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_STATUS),
+	                     gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_STATUS));
+	gaim_signal_register(handle, "buddy-privacy-changed",
+	                     gaim_marshal_VOID__POINTER, NULL,
+	                     1,
+	                     gaim_value_new(GAIM_TYPE_SUBTYPE,
+	                                    GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "buddy-idle-changed",
+	                     gaim_marshal_VOID__POINTER_INT_INT, NULL,
+	                     3,
+	                     gaim_value_new(GAIM_TYPE_SUBTYPE,
+	                                    GAIM_SUBTYPE_BLIST_BUDDY),
+	                     gaim_value_new(GAIM_TYPE_INT),
+	                     gaim_value_new(GAIM_TYPE_INT));
+
+
+	gaim_signal_register(handle, "buddy-signed-on",
+						 gaim_marshal_VOID__POINTER, NULL, 1,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "buddy-signed-off",
+						 gaim_marshal_VOID__POINTER, NULL, 1,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "buddy-got-login-time",
+						 gaim_marshal_VOID__POINTER, NULL, 1,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "buddy-added",
+						 gaim_marshal_VOID__POINTER, NULL, 1,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "buddy-removed",
+						 gaim_marshal_VOID__POINTER, NULL, 1,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "buddy-icon-changed",
+						 gaim_marshal_VOID__POINTER, NULL, 1,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_BUDDY));
+
+	gaim_signal_register(handle, "update-idle", gaim_marshal_VOID, NULL, 0);
+
+	gaim_signal_register(handle, "blist-node-extended-menu",
+			     gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+			     gaim_value_new(GAIM_TYPE_SUBTYPE,
+					    GAIM_SUBTYPE_BLIST_NODE),
+			     gaim_value_new(GAIM_TYPE_BOXED, "GList **"));
+
+	gaim_signal_register(handle, "blist-node-aliased",
+						 gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+						 gaim_value_new(GAIM_TYPE_SUBTYPE,
+										GAIM_SUBTYPE_BLIST_NODE),
+						 gaim_value_new(GAIM_TYPE_STRING));
+}
+
+void
+gaim_blist_uninit(void)
+{
+	if (save_timer != 0)
+	{
+		gaim_timeout_remove(save_timer);
+		save_timer = 0;
+		gaim_blist_sync();
+	}
+
+	gaim_signals_unregister_by_instance(gaim_blist_get_handle());
+}