view libpurple/protocols/msn/contact.c @ 23797:35f7b7c62995

Remove calls to g_markup_escape_text. Not only does this help with old GLib compatibility, but some of these calls were wrong, too. It just never showed up because the code doesn't run yet.
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Fri, 15 Aug 2008 04:27:42 +0000
parents d572fb194e61
children 16734635febf f54e7398f733
line wrap: on
line source

/**
 * @file contact.c 
 * 	get MSN contacts via SOAP request
 *	created by MaYuan<mayuan2006@gmail.com>
 *
 * purple
 *
 * Purple 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 "msn.h"
#include "contact.h"
#include "xmlnode.h"
#include "group.h"
#include "soap.h"
#include "nexus.h"

const char *MsnSoapPartnerScenarioText[] =
{
	"Initial",
	"ContactSave",
	"MessengerPendingList",
	"ContactMsgrAPI",
	"BlockUnblock"
};

const char *MsnMemberRole[] =
{
	"Forward",
	"Allow",
	"Block",
	"Reverse",
	"Pending"
};

typedef struct {
	MsnSession *session;
	MsnSoapPartnerScenario which;
} GetContactListCbData;

MsnCallbackState *
msn_callback_state_new(MsnSession *session)
{
	MsnCallbackState *state = g_new0(MsnCallbackState, 1);

	state->session = session;

	return state;
}

MsnCallbackState *
msn_callback_state_dup(MsnCallbackState *state)
{
	MsnCallbackState *new_state = g_new0(MsnCallbackState, 1);

	new_state->session = state->session;
	new_state->who = g_strdup(state->who);
	new_state->uid = g_strdup(state->uid);
	new_state->old_group_name = g_strdup(state->old_group_name);
	new_state->new_group_name = g_strdup(state->new_group_name);
	new_state->guid = g_strdup(state->guid);
	/* The rest should be made new */

	return new_state;
}

void
msn_callback_state_free(MsnCallbackState *state)
{
	if (state == NULL)
		return;

	g_free(state->who);
	g_free(state->uid);
	g_free(state->old_group_name);
	g_free(state->new_group_name);
	g_free(state->guid);
	xmlnode_free(state->body);

	g_free(state);
}

void
msn_callback_state_set_who(MsnCallbackState *state, const gchar *who)
{
	g_return_if_fail(state != NULL);

	g_free(state->who);
	state->who = g_strdup(who);
}

void
msn_callback_state_set_uid(MsnCallbackState *state, const gchar *uid)
{
	g_return_if_fail(state != NULL);

	g_free(state->uid);
	state->uid = g_strdup(uid);
}

void
msn_callback_state_set_old_group_name(MsnCallbackState *state, const gchar *old_group_name)
{
	g_return_if_fail(state != NULL);

	g_free(state->old_group_name);
	state->old_group_name = g_strdup(old_group_name);
}

void
msn_callback_state_set_new_group_name(MsnCallbackState *state, const gchar *new_group_name)
{
	g_return_if_fail(state != NULL);

	g_free(state->new_group_name);
	state->new_group_name = g_strdup(new_group_name);
}

void
msn_callback_state_set_guid(MsnCallbackState *state, const gchar *guid)
{
	g_return_if_fail(state != NULL);

	g_free(state->guid);
	state->guid = g_strdup(guid);
}


void
msn_callback_state_set_list_id(MsnCallbackState *state, MsnListId list_id)
{
	g_return_if_fail(state != NULL);

	state->list_id = list_id;
}

void
msn_callback_state_set_action(MsnCallbackState *state, MsnCallbackAction action)
{
	g_return_if_fail(state != NULL);

	state->action |= action;
}

/*get MSN member role utility*/
static MsnListId
msn_get_memberrole(const char *role)
{
	g_return_val_if_fail(role != NULL, 0);

	if (!strcmp(role,"Allow")) {
		return MSN_LIST_AL;
	} else if (!strcmp(role,"Block")) {
		return MSN_LIST_BL;
	} else if (!strcmp(role,"Reverse")) {
		return MSN_LIST_RL;
	} else if (!strcmp(role,"Pending")) {
		return MSN_LIST_PL;
	}
	return 0;
}

/* get Network */
/* QuLogic: These names don't really refer to the MsnNetwork,
 *          but I haven't yet written the code to properly use them.
 */
static MsnNetwork
msn_get_network(char *type)
{
	g_return_val_if_fail(type != NULL, 0);

	if (!strcmp(type,"Regular")) {
		return MSN_NETWORK_PASSPORT;
	}
	if (!strcmp(type,"Live")) {
		return MSN_NETWORK_PASSPORT;
	}
	if (!strcmp(type,"LivePending")) {
		return MSN_NETWORK_PASSPORT;
	}

	return MSN_NETWORK_UNKNOWN;
}

/* Create the AddressBook in the server, if we don't have one */
static void
msn_create_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
{
	if (resp && xmlnode_get_child(resp->xml, "Body/Fault") == NULL) {
		purple_debug_info("msn", "Address Book successfully created!\n");
		msn_get_address_book((MsnSession *)data, MSN_PS_INITIAL, NULL, NULL);
	} else {
		purple_debug_info("msn", "Address Book creation failed!\n");
	}
}

static void
msn_create_address_book(MsnSession *session)
{
	gchar *body;
	gchar *token_str;

	g_return_if_fail(session != NULL);
	g_return_if_fail(session->user != NULL);
	g_return_if_fail(session->user->passport != NULL);
	
	purple_debug_info("msn", "Creating an Address Book.\n");

	token_str = g_markup_escape_text(
		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS), -1);
	body = g_strdup_printf(MSN_ADD_ADDRESSBOOK_TEMPLATE,
		token_str, session->user->passport);
	g_free(token_str);

	msn_soap_message_send(session,
		msn_soap_message_new(MSN_ADD_ADDRESSBOOK_SOAP_ACTION,
			xmlnode_from_str(body, -1)),
		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, FALSE,
		msn_create_address_cb, session);

	g_free(body);
}

static void
msn_parse_each_member(MsnSession *session, xmlnode *member, const char *node,
	MsnListId list)
{
	char *passport = xmlnode_get_data(xmlnode_get_child(member, node));
	char *type = xmlnode_get_data(xmlnode_get_child(member, "Type"));
	char *member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId"));
	MsnUser *user = msn_userlist_find_add_user(session->userlist, passport, NULL);

	purple_debug_info("msn", "CL: %s name: %s, Type: %s, MembershipID: %s\n",
		node, passport, type, member_id == NULL ? "(null)" : member_id);

	if (member_id) {
		user->membership_id[list] = atoi(member_id);
	}

	msn_got_lst_user(session, user, 1 << list, NULL);

	g_free(passport);
	g_free(type);
	g_free(member_id);
}

static void
msn_parse_each_service(MsnSession *session, xmlnode *service)
{
	xmlnode *type;

	if ((type = xmlnode_get_child(service, "Info/Handle/Type"))) {
		char *type_str = xmlnode_get_data(type);

		if (g_str_equal(type_str, "Profile")) {
			/* Process Windows Live 'Messenger Roaming Identity' */
		} else if (g_str_equal(type_str, "Messenger")) {
			xmlnode *lastchange = xmlnode_get_child(service, "LastChange");
			char *lastchange_str = xmlnode_get_data(lastchange);
			xmlnode *membership;

			purple_debug_info("msn", "CL last change: %s\n", lastchange_str);
			purple_account_set_string(session->account,	"CLLastChange",
				lastchange_str);

			for (membership = xmlnode_get_child(service,
					"Memberships/Membership");
				 membership; membership = xmlnode_get_next_twin(membership)) {

				xmlnode *role = xmlnode_get_child(membership, "MemberRole");
				char *role_str = xmlnode_get_data(role);
				MsnListId list = msn_get_memberrole(role_str);
				xmlnode *member;

				purple_debug_info("msn", "CL MemberRole role: %s, list: %d\n",
					role_str, list);

				for (member = xmlnode_get_child(membership, "Members/Member");
					 member; member = xmlnode_get_next_twin(member)) {
					const char *member_type = xmlnode_get_attrib(member, "type");
					if (g_str_equal(member_type, "PassportMember")) {
						msn_parse_each_member(session, member, "PassportName",
							list);
					} else if (g_str_equal(member_type, "PhoneMember")) {

					} else if (g_str_equal(member_type, "EmailMember")) {
						msn_parse_each_member(session, member, "Email",	list);
					}
				}

				g_free(role_str);
			}

			g_free(lastchange_str);
		}

		g_free(type_str);
	}
}

/*parse contact list*/
static void
msn_parse_contact_list(MsnSession *session, xmlnode *node)
{
	xmlnode *fault, *faultnode;

	/* we may get a response if our cache data is too old:
	 *
	 * <faultstring>Need to do full sync. Can't sync deltas Client
	 * has too old a copy for us to do a delta sync</faultstring>
	 *
	 * this is not handled yet
	 */
	if ((fault = xmlnode_get_child(node, "Body/Fault"))) {
		if ((faultnode = xmlnode_get_child(fault, "faultstring"))) {
			char *faultstring = xmlnode_get_data(faultnode);
			purple_debug_info("msn", "Retrieving contact list failed: %s\n",
				faultstring);
			g_free(faultstring);
		}
		if ((faultnode = xmlnode_get_child(fault, "detail/errorcode"))) {
			char *errorcode = xmlnode_get_data(faultnode);

			if (g_str_equal(errorcode, "ABDoesNotExist")) {
				msn_create_address_book(session);
				g_free(errorcode);
				return;
			}

			g_free(errorcode);
		}

		msn_get_contact_list(session, MSN_PS_INITIAL, NULL);
	} else {
		xmlnode *service;

		for (service = xmlnode_get_child(node, "Body/FindMembershipResponse/"
				"FindMembershipResult/Services/Service");
			 service; service = xmlnode_get_next_twin(service)) {
			msn_parse_each_service(session, service);
		}
	}
}

static void
msn_get_contact_list_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	GetContactListCbData *cb_data = data;
	MsnSession *session = cb_data->session;

	g_return_if_fail(session != NULL);

	if (resp != NULL) {
		const char *abLastChange;
		const char *dynamicItemLastChange;

		purple_debug_misc("msn", "Got the contact list!\n");

		msn_parse_contact_list(session, resp->xml);
		abLastChange = purple_account_get_string(session->account,
			"ablastChange", NULL);
		dynamicItemLastChange = purple_account_get_string(session->account,
			"dynamicItemLastChange", NULL);

		if (cb_data->which == MSN_PS_INITIAL) {
#ifdef MSN_PARTIAL_LISTS
			/* XXX: this should be enabled when we can correctly do partial
			   syncs with the server. Currently we need to retrieve the whole
			   list to detect sync issues */
			msn_get_address_book(session, MSN_PS_INITIAL, abLastChange, dynamicItemLastChange);
#else
			msn_get_address_book(session, MSN_PS_INITIAL, NULL, NULL);
#endif
		}
	}

	g_free(cb_data);
}

/*SOAP  get contact list*/
void
msn_get_contact_list(MsnSession *session,
	const MsnSoapPartnerScenario partner_scenario, const char *update_time)
{
	gchar *body = NULL;
	gchar *update_str = NULL;
	gchar *token_str;
	GetContactListCbData cb_data = { session, partner_scenario };
	const gchar *partner_scenario_str = MsnSoapPartnerScenarioText[partner_scenario];

	purple_debug_misc("msn", "Getting Contact List.\n");

	if (update_time != NULL) {
		purple_debug_info("msn", "CL Last update time: %s\n", update_time);
		update_str = g_strdup_printf(MSN_GET_CONTACT_UPDATE_XML, update_time);
	}

	token_str = g_markup_escape_text(
		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS), -1);
	body = g_strdup_printf(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str,
		token_str, update_str ? update_str : "");
	g_free(token_str);

	msn_soap_message_send(session,
		msn_soap_message_new(MSN_GET_CONTACT_SOAP_ACTION,
			xmlnode_from_str(body, -1)),
		MSN_CONTACT_SERVER, MSN_GET_CONTACT_POST_URL, FALSE,
		msn_get_contact_list_cb, g_memdup(&cb_data, sizeof(cb_data)));

	g_free(update_str);
	g_free(body);
}

static void
msn_parse_addressbook_groups(MsnSession *session, xmlnode *node)
{
	xmlnode *group;

	purple_debug_info("msn", "msn_parse_addressbook_groups()\n");

	for(group = xmlnode_get_child(node, "Group"); group;
					group = xmlnode_get_next_twin(group)){
		xmlnode *groupId, *groupInfo, *groupname;
		char *group_id = NULL, *group_name = NULL;

		if ((groupId = xmlnode_get_child(group, "groupId")))
			group_id = xmlnode_get_data(groupId);
		if ((groupInfo = xmlnode_get_child(group, "groupInfo")) && (groupname = xmlnode_get_child(groupInfo, "name")))
			group_name = xmlnode_get_data(groupname);

		msn_group_new(session->userlist, group_id, group_name);

		if (group_id == NULL){
			/* Group of ungroupped buddies */
			g_free(group_name);
			continue;
		}

		purple_debug_info("msn", "AB group_id: %s, name: %s\n", group_id, group_name ? group_name : "(null)");
		if ((purple_find_group(group_name)) == NULL){
			PurpleGroup *g = purple_group_new(group_name);
			purple_blist_add_group(g, NULL);
		}
		g_free(group_id);
		g_free(group_name);
	}
}

static gboolean
msn_parse_addressbook_mobile(xmlnode *contactInfo, char **inout_mobile_number)
{
	xmlnode *phones;
	char *mobile_number = NULL;
	gboolean mobile = FALSE;

	*inout_mobile_number = NULL;

	if ((phones = xmlnode_get_child(contactInfo, "phones"))) {
		xmlnode *contact_phone;
		char *phone_type = NULL;

		for (contact_phone = xmlnode_get_child(phones, "ContactPhone");
			 contact_phone;
			 contact_phone = xmlnode_get_next_twin(contact_phone)) {
			xmlnode *contact_phone_type;

			if (!(contact_phone_type =
					xmlnode_get_child(contact_phone, "contactPhoneType")))
				continue;

			phone_type = xmlnode_get_data(contact_phone_type);

			if (phone_type && !strcmp(phone_type, "ContactPhoneMobile")) {
				xmlnode *number;

				if ((number = xmlnode_get_child(contact_phone, "number"))) {
					xmlnode *messenger_enabled;
					char *is_messenger_enabled = NULL;

					g_free(mobile_number);
					mobile_number = xmlnode_get_data(number);

					if (mobile_number &&
							(messenger_enabled = xmlnode_get_child(contact_phone, "isMessengerEnabled"))
							&& (is_messenger_enabled = xmlnode_get_data(messenger_enabled))
							&& !strcmp(is_messenger_enabled, "true"))
						mobile = TRUE;

					g_free(is_messenger_enabled);
				}
			}

			g_free(phone_type);
		}
	}

	*inout_mobile_number = mobile_number;
	return mobile;
}

static void
msn_parse_addressbook_contacts(MsnSession *session, xmlnode *node)
{
	xmlnode *contactNode;
	char *passport = NULL, *Name = NULL, *uid = NULL, *type = NULL, *mobile_number = NULL, *alias = NULL;
	gboolean mobile = FALSE;
	PurpleConnection *pc = purple_account_get_connection(session->account);

	for(contactNode = xmlnode_get_child(node, "Contact"); contactNode;
				contactNode = xmlnode_get_next_twin(contactNode)) {
		xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds, *messenger_user;
		xmlnode *annotation;
		MsnUser *user;
		MsnNetwork networkId;

		if (!(contactId = xmlnode_get_child(contactNode,"contactId"))
				|| !(contactInfo = xmlnode_get_child(contactNode, "contactInfo"))
				|| !(contactType = xmlnode_get_child(contactInfo, "contactType")))
			continue;

		g_free(passport);
		g_free(Name);
		g_free(alias);
		g_free(uid);
		g_free(type);
		g_free(mobile_number);
		passport = Name = uid = type = mobile_number = alias = NULL;
		mobile = FALSE;

		uid = xmlnode_get_data(contactId);
		type = xmlnode_get_data(contactType);

		/*setup the Display Name*/
		if (type && !strcmp(type, "Me")){
			char *friendly = NULL;
			if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
				friendly = xmlnode_get_data(displayName);
			purple_connection_set_display_name(session->account->gc, friendly ? purple_url_decode(friendly) : NULL);
			g_free(friendly);
			continue; /* Not adding own account as buddy to buddylist */
		}

		/* ignore non-messenger contacts */
		if ((messenger_user = xmlnode_get_child(contactInfo, "isMessengerUser"))) {
			char *is_messenger_user = xmlnode_get_data(messenger_user);

			if(is_messenger_user && !strcmp(is_messenger_user, "false")) {
				g_free(is_messenger_user);
				continue;
			}

			g_free(is_messenger_user);
		}

		networkId = msn_get_network(type);
		passportName = xmlnode_get_child(contactInfo, "passportName");
		if (passportName == NULL) {
			xmlnode *emailsNode, *contactEmailNode, *emailNode;
			xmlnode *messengerEnabledNode;
			char *msnEnabled;

			/*TODO: add it to the none-instant Messenger group and recognize as email Membership*/
			/*Yahoo User?*/
			emailsNode = xmlnode_get_child(contactInfo, "emails");
			if (emailsNode == NULL) {
				/*TODO:  need to support the Mobile type*/
				continue;
			}
			for (contactEmailNode = xmlnode_get_child(emailsNode, "ContactEmail"); contactEmailNode;
					contactEmailNode = xmlnode_get_next_twin(contactEmailNode) ){
				if (!(messengerEnabledNode = xmlnode_get_child(contactEmailNode, "isMessengerEnabled"))) {
					/* XXX: Should this be a continue instead of a break? It seems like it'd cause unpredictable results otherwise. */
					break;
				}

				msnEnabled = xmlnode_get_data(messengerEnabledNode);

				if ((emailNode = xmlnode_get_child(contactEmailNode, "email"))) {
					g_free(passport);
					passport = xmlnode_get_data(emailNode);
				}

				if (msnEnabled && !strcmp(msnEnabled, "true")) {
					/*Messenger enabled, Get the Passport*/
					purple_debug_info("msn", "AB Yahoo User %s\n", passport ? passport : "(null)");
					networkId = MSN_NETWORK_YAHOO;
					g_free(msnEnabled);
					break;
				} else {
					/*TODO maybe we can just ignore it in Purple?*/
					purple_debug_info("msn", "AB Other type user\n");
				}

				g_free(msnEnabled);
			}
		} else {
			passport = xmlnode_get_data(passportName);
		}

		if (passport == NULL)
			continue;

		if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
			Name = xmlnode_get_data(displayName);
		else
			Name = g_strdup(passport);

		for (annotation = xmlnode_get_child(contactInfo, "annotations/Annotation");
				annotation; annotation = xmlnode_get_next_twin(annotation)) {
			char *name;
			name = xmlnode_get_data(xmlnode_get_child(annotation, "Name"));
			if (!strcmp(name, "AB.NickName"))
				alias = xmlnode_get_data(xmlnode_get_child(annotation, "Value"));
			g_free(name);
		}

		mobile = msn_parse_addressbook_mobile(contactInfo, &mobile_number);

		purple_debug_misc("msn", "AB passport:{%s} uid:{%s} display:{%s} alias: {%s} mobile:{%s} mobile number:{%s}\n",
			passport, uid ? uid : "(null)", Name ? Name : "(null)", alias ? alias : "(null)",
			mobile ? "true" : "false", mobile_number ? mobile_number : "(null)");

		user = msn_userlist_find_add_user(session->userlist, passport, Name);
		msn_user_set_uid(user, uid);
		msn_user_set_network(user, networkId);
		msn_user_set_mobile_phone(user, mobile_number);

		groupIds = xmlnode_get_child(contactInfo, "groupIds");
		if (groupIds) {
			for (guid = xmlnode_get_child(groupIds, "guid"); guid;
							guid = xmlnode_get_next_twin(guid)) {
				char *group_id = xmlnode_get_data(guid);
				msn_user_add_group_id(user, group_id);
				purple_debug_misc("msn", "AB guid:%s\n", group_id ? group_id : "(null)");
				g_free(group_id);
			}
		} else {
			purple_debug_info("msn", "User not in any groups, adding to default group.\n");
			/*not in any group,Then set default group*/
			msn_user_add_group_id(user, MSN_INDIVIDUALS_GROUP_ID);
		}

		msn_got_lst_user(session, user, MSN_LIST_FL_OP, NULL);

		if (mobile && user)
		{
			user->mobile = TRUE;
			purple_prpl_got_user_status(session->account, user->passport, "mobile", NULL);
			purple_prpl_got_user_status(session->account, user->passport, "available", NULL);
		}
		if (alias)
			purple_serv_got_private_alias(pc, passport, alias);
	}

	g_free(passport);
	g_free(Name);
	g_free(uid);
	g_free(type);
	g_free(mobile_number);
}

static gboolean
msn_parse_addressbook(MsnSession *session, xmlnode *node)
{
	xmlnode *result;
	xmlnode *groups;
	xmlnode *contacts;
	xmlnode *abNode;
	xmlnode *fault;

	if ((fault = xmlnode_get_child(node, "Body/Fault"))) {
		xmlnode *faultnode;

		if ((faultnode = xmlnode_get_child(fault, "faultstring"))) {
			gchar *faultstring = xmlnode_get_data(faultnode);
			purple_debug_info("msn", "AB Faultstring: %s\n", faultstring);
			g_free(faultstring);
		}

		if ((faultnode = xmlnode_get_child(fault, "detail/errorcode"))) {
			gchar *errorcode = xmlnode_get_data(faultnode);

			purple_debug_info("msn", "AB Error Code: %s\n", errorcode);

			if (g_str_equal(errorcode, "ABDoesNotExist")) {
				g_free(errorcode);
				return TRUE;
			}
			g_free(errorcode);
		}

		return FALSE;
	}

	result = xmlnode_get_child(node, "Body/ABFindAllResponse/ABFindAllResult");
	if (result == NULL) {
		purple_debug_misc("msn", "Received no address book update\n");
		return TRUE;
	}

	/* I don't see this "groups" tag documented on msnpiki, need to find out
	   if they are really there, and update msnpiki */
	/*Process Group List*/
	groups = xmlnode_get_child(result, "groups");
	if (groups != NULL) {
		msn_parse_addressbook_groups(session, groups);
	}

	/*add a default No group to set up the no group Membership*/
	msn_group_new(session->userlist, MSN_INDIVIDUALS_GROUP_ID,
				  MSN_INDIVIDUALS_GROUP_NAME);
	purple_debug_misc("msn", "AB group_id:%s name:%s\n",
					  MSN_INDIVIDUALS_GROUP_ID, MSN_INDIVIDUALS_GROUP_NAME);
	if ((purple_find_group(MSN_INDIVIDUALS_GROUP_NAME)) == NULL){
		PurpleGroup *g = purple_group_new(MSN_INDIVIDUALS_GROUP_NAME);
		purple_blist_add_group(g, NULL);
	}

	/*add a default No group to set up the no group Membership*/
	msn_group_new(session->userlist, MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME);
	purple_debug_misc("msn", "AB group_id:%s name:%s\n", MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME);
	if ((purple_find_group(MSN_NON_IM_GROUP_NAME)) == NULL) {
		PurpleGroup *g = purple_group_new(MSN_NON_IM_GROUP_NAME);
		purple_blist_add_group(g, NULL);
	}

	/*Process contact List*/
	purple_debug_info("msn", "Process contact list...\n");
	contacts = xmlnode_get_child(result, "contacts");
	if (contacts != NULL) {
		msn_parse_addressbook_contacts(session, contacts);
	}

	abNode = xmlnode_get_child(result, "ab");
	if (abNode != NULL) {
		xmlnode *node2;
		char *tmp = NULL;

		if ((node2 = xmlnode_get_child(abNode, "lastChange")))
			tmp = xmlnode_get_data(node2);
		purple_debug_info("msn", "AB lastchanged Time:{%s}\n", tmp ? tmp : "(null)");
		purple_account_set_string(session->account, "ablastChange", tmp);

		g_free(tmp); tmp = NULL;
		if ((node2 = xmlnode_get_child(abNode, "DynamicItemLastChanged")))
			tmp = xmlnode_get_data(node2);
		purple_debug_info("msn", "AB DynamicItemLastChanged :{%s}\n", tmp ? tmp : "(null)");
		purple_account_set_string(session->account, "DynamicItemLastChanged", tmp);
		g_free(tmp);
	}

	return TRUE;
}

static void
msn_get_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
{
	MsnSession *session = data;

	if (resp == NULL)
		return;

	g_return_if_fail(session != NULL);

	purple_debug_misc("msn", "Got the Address Book!\n");

	if (msn_parse_addressbook(session, resp->xml)) {
		msn_send_privacy(session->account->gc);
		msn_notification_dump_contact(session);
	} else {
		/* This is making us loop infinitely when we fail to parse the
		  address book, disable for now (we should re-enable when we
		  send timestamps)
		*/
		/*
		msn_get_address_book(session, NULL, NULL);
		*/
		msn_session_disconnect(session);
		purple_connection_error_reason(session->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to retrieve MSN Address Book"));
	}
}

/*get the address book*/
void
msn_get_address_book(MsnSession *session,
	MsnSoapPartnerScenario partner_scenario, const char *LastChanged,
	const char *dynamicItemLastChange)
{
	char *body, *update_str = NULL;
	gchar *token_str;

	purple_debug_misc("msn", "Getting Address Book\n");

	/*build SOAP and POST it*/
	if (dynamicItemLastChange != NULL)
		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, dynamicItemLastChange);
	else if (LastChanged != NULL)
		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, LastChanged);

	token_str = g_markup_escape_text(
		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS), -1);
	body = g_strdup_printf(MSN_GET_ADDRESS_TEMPLATE,
		MsnSoapPartnerScenarioText[partner_scenario],
		token_str,
		update_str ? update_str : "");
	g_free(token_str);

	msn_soap_message_send(session,
		msn_soap_message_new(MSN_GET_ADDRESS_SOAP_ACTION,
			xmlnode_from_str(body, -1)),
		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, FALSE,
		msn_get_address_cb, session);

	g_free(update_str);
	g_free(body);
}

/***************************************************************
 * Contact Operations
 ***************************************************************/

static const char *
msn_contact_operation_str(MsnCallbackAction action)
{
	/* Make sure this is large enough when adding more */
	static char buf[BUF_LEN];
	buf[0] = '\0';

	if (action & MSN_ADD_BUDDY)
		strcat(buf, "Adding Buddy,");
	if (action & MSN_MOVE_BUDDY)
		strcat(buf, "Moving Buddy,");
	if (action & MSN_ACCEPTED_BUDDY)
		strcat(buf, "Accepted Buddy,");
	if (action & MSN_DENIED_BUDDY)
		strcat(buf, "Denied Buddy,");
	if (action & MSN_ADD_GROUP)
		strcat(buf, "Adding Group,");
	if (action & MSN_DEL_GROUP)
		strcat(buf, "Deleting Group,");
	if (action & MSN_RENAME_GROUP)
		strcat(buf, "Renaming Group,");
	if (action & MSN_UPDATE_INFO)
		strcat(buf, "Updating Contact Info,");

	return buf;
}

static gboolean msn_contact_request(MsnCallbackState *state);

static void
msn_contact_request_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;
	xmlnode *faultcode;
	char *faultcode_str;

	if (resp == NULL) {
		purple_debug_error("msn",
		                   "Operation {%s} failed. No response received from server.\n",
		                   msn_contact_operation_str(state->action));
		return;
	}

	faultcode = xmlnode_get_child(resp->xml, "Body/Fault/faultcode");

	if (faultcode == NULL) {
		/* No errors */
		if (state->cb)
			((MsnSoapCallback)state->cb)(req, resp, data);
		msn_callback_state_free(state);
		return;
	}

	faultcode_str = xmlnode_get_data(faultcode);

	if (faultcode_str && g_str_equal(faultcode_str, "q0:BadContextToken")) {
		purple_debug_info("msn",
		                  "Contact Operation {%s} failed because of bad token."
		                  " Updating token now and retrying operation.\n",
		                  msn_contact_operation_str(state->action));
		/* Token has expired, so renew it, and try again later */
		msn_nexus_update_token(state->session->nexus, MSN_AUTH_CONTACTS,
		                       (GSourceFunc)msn_contact_request, data);
	}
	else
	{
		/* We don't know how to respond to this faultcode, so just log it */
		/* XXX: Probably should notify the user or undo the change or something? */
		char *str = xmlnode_to_str(xmlnode_get_child(resp->xml, "Body/Fault"), NULL);
		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
		                   msn_contact_operation_str(state->action), str);
		g_free(str);
		msn_callback_state_free(state);
	}

	g_free(faultcode_str);
}

static gboolean
msn_contact_request(MsnCallbackState *state)
{
	if (state->token == NULL)
		state->token = xmlnode_get_child(state->body,
			"Header/ABAuthHeader/TicketToken");
	/* delete old & replace with new token */
	xmlnode_free(state->token->child);
	xmlnode_insert_data(state->token,
		msn_nexus_get_token_str(state->session->nexus, MSN_AUTH_CONTACTS), -1);
	msn_soap_message_send(state->session,
		msn_soap_message_new(state->post_action, xmlnode_copy(state->body)),
		MSN_CONTACT_SERVER, state->post_url, FALSE,
		msn_contact_request_cb, state);
	return FALSE;
}

static void
msn_add_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;
	MsnSession *session = state->session;

	MsnUserList *userlist;
	MsnUser *user;

	g_return_if_fail(session != NULL);

	userlist = session->userlist;

	purple_debug_info("msn", "Contact added successfully\n");

	/* the code this block is replacing didn't send ADL for yahoo contacts,
	 * but i haven't confirmed this is WLM's behaviour wrt yahoo contacts
	 */
	if ( !msn_user_is_yahoo(session->account, state->who) ) {
		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
	}

	msn_notification_send_fqy(session, state->who);

	user = msn_userlist_find_add_user(userlist, state->who, state->who);
	msn_user_add_group_id(user, state->guid);
}

/* add a Contact in MSN_INDIVIDUALS_GROUP */
void
msn_add_contact(MsnSession *session, MsnCallbackState *state, const char *passport)
{
	gchar *body = NULL;
	gchar *contact_xml = NULL;

#if 0
	gchar *escaped_displayname;


	 if (displayname != NULL) {
		escaped_displayname = g_markup_decode_text(displayname, -1);
	 } else {
		escaped_displayname = passport;
	 }
	contact_xml = g_strdup_printf(MSN_XML_ADD_CONTACT, escaped_displayname, passport);
#endif

	purple_debug_info("msn", "Adding contact %s to contact list\n", passport);

	contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
	body = g_strdup_printf(MSN_ADD_CONTACT_TEMPLATE, contact_xml);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_CONTACT_ADD_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_add_contact_read_cb;
	msn_contact_request(state);

	g_free(contact_xml);
	g_free(body);
}

static void
msn_add_contact_to_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;
	MsnUserList *userlist;

	g_return_if_fail(data != NULL);

	userlist = state->session->userlist;

	if (msn_userlist_add_buddy_to_group(userlist, state->who,
			state->new_group_name)) {
		purple_debug_info("msn", "Contact %s added to group %s successfully!\n", state->who, state->new_group_name);
	} else {
		purple_debug_info("msn", "Contact %s added to group %s successfully on server, but failed in the local list\n", state->who, state->new_group_name);
	}

	if (state->action & MSN_ADD_BUDDY) {
		MsnUser *user = msn_userlist_find_user(userlist, state->who);

		if ( !msn_user_is_yahoo(state->session->account, state->who) ) {
			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
		}
		msn_notification_send_fqy(state->session, state->who);

		if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) {
			msn_del_contact_from_list(state->session, NULL, state->who, MSN_LIST_PL);
			return;
		}
	}

	if (state->action & MSN_MOVE_BUDDY) {
		msn_del_contact_from_group(state->session, state->who, state->old_group_name);
	}
}

void
msn_add_contact_to_group(MsnSession *session, MsnCallbackState *state,
			 const char *passport, const char *groupId)
{
	MsnUserList *userlist;
	MsnUser *user;
	gchar *body = NULL, *contact_xml;

	g_return_if_fail(passport != NULL);
	g_return_if_fail(groupId != NULL);

	g_return_if_fail(session != NULL);

	userlist = session->userlist;

	if (!strcmp(groupId, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(groupId, MSN_NON_IM_GROUP_ID)) {

		user = msn_userlist_find_add_user(userlist, passport, passport);

		if (state->action & MSN_ADD_BUDDY) {
			msn_add_contact(session, state, passport);
			return;
		}

		if (state->action & MSN_MOVE_BUDDY) {
			msn_user_add_group_id(user, groupId);
			msn_del_contact_from_group(session, passport, state->old_group_name);
		}

		return;
	}

	purple_debug_info("msn", "Adding user %s to group %s\n", passport,
			  msn_userlist_find_group_name(userlist, groupId));

	user = msn_userlist_find_user(userlist, passport);
	if (user == NULL) {
		purple_debug_warning("msn", "Unable to retrieve user %s from the userlist!\n", passport);
		msn_callback_state_free(state);
		return; /* guess this never happened! */
	}

	if (user != NULL && user->uid != NULL) {
		contact_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
	} else {
		contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
	}

	body = g_strdup_printf(MSN_ADD_CONTACT_GROUP_TEMPLATE, groupId, contact_xml);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_ADD_CONTACT_GROUP_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_add_contact_to_group_read_cb;
	msn_contact_request(state);

	g_free(contact_xml);
	g_free(body);
}

static void
msn_delete_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;
	MsnUserList *userlist = state->session->userlist;
	MsnUser *user = msn_userlist_find_user_with_id(userlist, state->uid);

	purple_debug_info("msn", "Delete contact successful\n");

	if (user != NULL) {
		msn_userlist_remove_user(userlist, user);
	}
}

/*delete a Contact*/
void
msn_delete_contact(MsnSession *session, const char *contactId)
{
	gchar *body = NULL;
	gchar *contact_id_xml = NULL ;
	MsnCallbackState *state;

	g_return_if_fail(contactId != NULL);
	contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, contactId);

	state = msn_callback_state_new(session);
	msn_callback_state_set_uid(state, contactId);

	/* build SOAP request */
	purple_debug_info("msn", "Deleting contact with contactId: %s\n", contactId);
	body = g_strdup_printf(MSN_DEL_CONTACT_TEMPLATE, contact_id_xml);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_CONTACT_DEL_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_delete_contact_read_cb;
	msn_contact_request(state);

	g_free(contact_id_xml);
	g_free(body);
}

static void
msn_del_contact_from_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;

	if (msn_userlist_rem_buddy_from_group(state->session->userlist,
			state->who, state->old_group_name)) {
		purple_debug_info("msn", "Contact %s deleted successfully from group %s\n", state->who, state->old_group_name);
	} else {
		purple_debug_info("msn", "Contact %s deleted successfully from group %s in the server, but failed in the local list\n", state->who, state->old_group_name);
	}
}

void
msn_del_contact_from_group(MsnSession *session, const char *passport, const char *group_name)
{
	MsnUserList * userlist;
	MsnUser *user;
	MsnCallbackState *state;
	gchar *body, *contact_id_xml;
	const gchar *groupId;

	g_return_if_fail(passport != NULL);
	g_return_if_fail(group_name != NULL);
	g_return_if_fail(session != NULL);

	userlist = session->userlist;

	groupId = msn_userlist_find_group_id(userlist, group_name);
	if (groupId != NULL) {
		purple_debug_info("msn", "Deleting user %s from group %s\n", passport, group_name);
	} else {
		purple_debug_warning("msn", "Unable to retrieve group id from group %s !\n", group_name);
		return;
	}

	user = msn_userlist_find_user(userlist, passport);

	if (user == NULL) {
		purple_debug_warning("msn", "Unable to retrieve user from passport %s!\n", passport);
		return;
	}

	if ( !strcmp(groupId, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(groupId, MSN_NON_IM_GROUP_ID)) {
		msn_user_remove_group_id(user, groupId);
		return;
	}

	state = msn_callback_state_new(session);
	msn_callback_state_set_who(state, passport);
	msn_callback_state_set_guid(state, groupId);
	msn_callback_state_set_old_group_name(state, group_name);

	contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
	body = g_strdup_printf(MSN_CONTACT_DEL_GROUP_TEMPLATE, contact_id_xml, groupId);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_CONTACT_DEL_GROUP_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_del_contact_from_group_read_cb;
	msn_contact_request(state);

	g_free(contact_id_xml);
	g_free(body);
}


static void
msn_update_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	purple_debug_info("msn", "Contact updated successfully\n");
}

/* Update a contact's info */
void
msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateType type, const char* value)
{
	MsnCallbackState *state;
	xmlnode *contact;
	xmlnode *contact_info;
	xmlnode *changes;

	purple_debug_info("msn", "Update contact information with new %s: %s\n",
		type==MSN_UPDATE_DISPLAY ? "display name" : "alias", value);
	purple_debug_info("msn", "passport=%s\n", passport);
	g_return_if_fail(passport != NULL);
	contact_info = xmlnode_new("contactInfo");
	changes = xmlnode_new("propertiesChanged");

	switch (type) {
		xmlnode *annotations;
		xmlnode *display;
		xmlnode *a, *n, *v;
		case MSN_UPDATE_DISPLAY:
			display = xmlnode_new_child(contact_info, "displayName");
			xmlnode_insert_data(display, value, -1);
			xmlnode_insert_data(changes, "DisplayName", -1);
			break;

		case MSN_UPDATE_ALIAS:
			annotations = xmlnode_new_child(contact_info, "annotations");
			xmlnode_insert_data(changes, "Annotation ", -1);

			a = xmlnode_new_child(annotations, "Annotation");
			n = xmlnode_new_child(a, "Name");
			xmlnode_insert_data(n, "AB.NickName", -1);
			v = xmlnode_new_child(a, "Value");
			xmlnode_insert_data(v, value, -1);
			break;

		default:
			g_return_if_reached();
	}



	state = msn_callback_state_new(session);

	state->body = xmlnode_from_str(MSN_CONTACT_UPDATE_TEMPLATE, -1);
	state->action = MSN_UPDATE_INFO;
	state->post_action = MSN_CONTACT_UPDATE_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_update_contact_read_cb;

	contact = xmlnode_get_child(state->body, "Body/ABContactUpdate/contacts/Contact");
	xmlnode_insert_child(contact, contact_info);
	xmlnode_insert_child(contact, changes);

	if (!strcmp(passport, "Me")) {
		xmlnode *contactType = xmlnode_new_child(contact_info, "contactType");
		xmlnode_insert_data(contactType, "Me", -1);
	} else {
		MsnUser *user = msn_userlist_find_user(session->userlist, passport);
		xmlnode *contactId = xmlnode_new_child(contact, "contactId");
		msn_callback_state_set_uid(state, user->uid);
		xmlnode_insert_data(contactId, state->uid, -1);
	}

	msn_contact_request(state);
}

static void
msn_del_contact_from_list_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;
	MsnSession *session = state->session;

	purple_debug_info("msn", "Contact %s deleted successfully from %s list on server!\n", state->who, MsnMemberRole[state->list_id]);

	if (state->list_id == MSN_LIST_PL) {
		MsnUser *user = msn_userlist_find_user(session->userlist, state->who);
		MsnCallbackState *new_state = msn_callback_state_dup(state);

		if (user != NULL)
			msn_user_unset_op(user, MSN_LIST_PL_OP);

		msn_add_contact_to_list(session, new_state, state->who, MSN_LIST_RL);
		return;
	} else if (state->list_id == MSN_LIST_AL) {
		purple_privacy_permit_remove(session->account, state->who, TRUE);
		msn_add_contact_to_list(session, NULL, state->who, MSN_LIST_BL);
	} else if (state->list_id == MSN_LIST_BL) {
		purple_privacy_deny_remove(session->account, state->who, TRUE);
		msn_add_contact_to_list(session, NULL, state->who, MSN_LIST_AL);
	}

}

void
msn_del_contact_from_list(MsnSession *session, MsnCallbackState *state,
			  const gchar *passport, const MsnListId list)
{
	gchar *body = NULL, *member = NULL;
	MsnSoapPartnerScenario partner_scenario;
	MsnUser *user;

	g_return_if_fail(session != NULL);
	g_return_if_fail(passport != NULL);
	g_return_if_fail(list < 5);

	purple_debug_info("msn", "Deleting contact %s from %s list\n", passport, MsnMemberRole[list]);

	if (state == NULL) {
		state = msn_callback_state_new(session);
	}
	msn_callback_state_set_list_id(state, list);
	msn_callback_state_set_who(state, passport);

	if (list == MSN_LIST_PL) {
		g_return_if_fail(session->userlist != NULL);

		user = msn_userlist_find_user(session->userlist, passport);

		partner_scenario = MSN_PS_CONTACT_API;
		member = g_strdup_printf(MSN_MEMBER_MEMBERSHIPID_XML, user->membership_id[MSN_LIST_PL]);
	} else {
		/* list == MSN_LIST_AL || list == MSN_LIST_BL */
		partner_scenario = MSN_PS_BLOCK_UNBLOCK;
		
		member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, passport);
	}

	body = g_strdup_printf(MSN_CONTACT_DELECT_FROM_LIST_TEMPLATE,
		MsnSoapPartnerScenarioText[partner_scenario],
		MsnMemberRole[list], member);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION;
	state->post_url = MSN_SHARE_POST_URL;
	state->cb = msn_del_contact_from_list_read_cb;
	msn_contact_request(state);

	g_free(member);
	g_free(body);
}

static void
msn_add_contact_to_list_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
	gpointer data)
{
	MsnCallbackState *state = data;

	g_return_if_fail(state != NULL);
	g_return_if_fail(state->session != NULL);

	purple_debug_info("msn", "Contact %s added successfully to %s list on server!\n", state->who, MsnMemberRole[state->list_id]);

	if (state->list_id == MSN_LIST_RL) {
		MsnUser *user = msn_userlist_find_user(state->session->userlist, state->who);

		if (user != NULL) {
			msn_user_set_op(user, MSN_LIST_RL_OP);
		}

		if (state->action & MSN_DENIED_BUDDY) {
			msn_add_contact_to_list(state->session, NULL, state->who, MSN_LIST_BL);
		} else if (state->list_id == MSN_LIST_AL) {
			purple_privacy_permit_add(state->session->account, state->who, TRUE);
		} else if (state->list_id == MSN_LIST_BL) {
			purple_privacy_deny_add(state->session->account, state->who, TRUE);
		}
	}
}

void
msn_add_contact_to_list(MsnSession *session, MsnCallbackState *state,
			const gchar *passport, const MsnListId list)
{
	gchar *body = NULL, *member = NULL;
	MsnSoapPartnerScenario partner_scenario;

	g_return_if_fail(session != NULL);
	g_return_if_fail(passport != NULL);
	g_return_if_fail(list < 5);

	purple_debug_info("msn", "Adding contact %s to %s list\n", passport, MsnMemberRole[list]);

	if (state == NULL) {
		state = msn_callback_state_new(session);
	}
	msn_callback_state_set_list_id(state, list);
	msn_callback_state_set_who(state, passport);

	partner_scenario = (list == MSN_LIST_RL) ? MSN_PS_CONTACT_API : MSN_PS_BLOCK_UNBLOCK;

	member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, state->who);

	body = g_strdup_printf(MSN_CONTACT_ADD_TO_LIST_TEMPLATE,
		MsnSoapPartnerScenarioText[partner_scenario],
		MsnMemberRole[list], member);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION;
	state->post_url = MSN_SHARE_POST_URL;
	state->cb = msn_add_contact_to_list_read_cb;
	msn_contact_request(state);

	g_free(member);
	g_free(body);
}

#if 0
static void
msn_gleams_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
{
	purple_debug_info("msn", "Gleams read done\n");
}

/*get the gleams info*/
void
msn_get_gleams(MsnSession *session)
{
	MsnSoapReq *soap_request;

	purple_debug_info("msn", "msn get gleams info...\n");

	state = msn_callback_state_new(session);
	state->body = xmlnode_from_str(MSN_GLEAMS_TEMPLATE, -1);
	state->post_action = MSN_GET_GLEAMS_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_gleams_read_cb;
	msn_contact_request(state);
}
#endif


/***************************************************************
 * Group Operations
 ***************************************************************/

static void
msn_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
{
	MsnCallbackState *state = data;
	MsnSession *session;
	MsnUserList *userlist;

	purple_debug_info("msn", "Group request successful.\n");

	g_return_if_fail(state->session != NULL);
	g_return_if_fail(state->session->userlist != NULL);

	session = state->session;
	userlist = session->userlist;

	if (state->action & MSN_RENAME_GROUP) {
		msn_userlist_rename_group_id(session->userlist,
					     state->guid,
					     state->new_group_name);
	}

	if (state->action & MSN_ADD_GROUP) {
		/* the response is taken from
		   http://telepathy.freedesktop.org/wiki/Pymsn/MSNP/ContactListActions
		   should copy it to msnpiki some day */
		xmlnode *guid_node = xmlnode_get_child(resp->xml,
			"Body/ABGroupAddResponse/ABGroupAddResult/guid");

		if (guid_node) {
			char *guid = xmlnode_get_data(guid_node);

			/* create and add the new group to the userlist */
			purple_debug_info("msn", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid);
			msn_group_new(session->userlist, guid, state->new_group_name);

			if (state->action & MSN_ADD_BUDDY) {
				msn_userlist_add_buddy(session->userlist,
					state->who,
					state->new_group_name);
			} else if (state->action & MSN_MOVE_BUDDY) {
				/* This will be freed when the add contact callback fires */
				MsnCallbackState *new_state = msn_callback_state_dup(state);
				msn_add_contact_to_group(session, new_state, state->who, guid); 
				g_free(guid);
				return;
			}
			g_free(guid);
		} else {
			purple_debug_info("msn", "Adding group %s failed\n",
				state->new_group_name);
		}
	}

	if (state->action & MSN_DEL_GROUP) {
		GList *l;

		msn_userlist_remove_group_id(session->userlist, state->guid);
		for (l = userlist->users; l != NULL; l = l->next) {
			msn_user_remove_group_id( (MsnUser *)l->data, state->guid);
		}
	}
}

/* add group */
void
msn_add_group(MsnSession *session, MsnCallbackState *state, const char* group_name)
{
	char *body = NULL;
	char *escaped_group_name = NULL;

	g_return_if_fail(session != NULL);
	g_return_if_fail(group_name != NULL);

	purple_debug_info("msn", "Adding group %s to contact list.\n", group_name);

	if (state == NULL) {
		state = msn_callback_state_new(session);
	}

	msn_callback_state_set_action(state, MSN_ADD_GROUP);
	msn_callback_state_set_new_group_name(state, group_name);

	/* escape group name's html special chars so it can safely be sent
	* in a XML SOAP request
	*/
	escaped_group_name = g_markup_escape_text(group_name, -1);
	body = g_strdup_printf(MSN_GROUP_ADD_TEMPLATE, escaped_group_name);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_GROUP_ADD_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_group_read_cb;
	msn_contact_request(state);

	g_free(escaped_group_name);
	g_free(body);
}

/* delete group */
void
msn_del_group(MsnSession *session, const gchar *group_name)
{
	MsnCallbackState *state;
	char *body = NULL;
	const gchar *guid;

	g_return_if_fail(session != NULL);

	g_return_if_fail(group_name != NULL);
	purple_debug_info("msn", "Deleting group %s from contact list\n", group_name);

	guid = msn_userlist_find_group_id(session->userlist, group_name);

	/* if group uid we need to del is NULL,
	*  we need to delete nothing
	*/
	if (guid == NULL) {
		purple_debug_info("msn", "Group %s guid not found, returning.\n", group_name);
		return;
	}

	if ( !strcmp(guid, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(guid, MSN_NON_IM_GROUP_ID) ) {
		/* XXX add back PurpleGroup since it isn't really removed in the server? */
		return;
	}

	state = msn_callback_state_new(session);
	msn_callback_state_set_action(state, MSN_DEL_GROUP);
	msn_callback_state_set_guid(state, guid);

	body = g_strdup_printf(MSN_GROUP_DEL_TEMPLATE, guid);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_GROUP_DEL_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_group_read_cb;
	msn_contact_request(state);

	g_free(body);
}

/* rename group */
void
msn_contact_rename_group(MsnSession *session, const char *old_group_name, const char *new_group_name)
{
	gchar *body = NULL;
	const gchar * guid;
	MsnCallbackState *state;
	char *escaped_group_name;

	g_return_if_fail(session != NULL);
	g_return_if_fail(session->userlist != NULL);
	g_return_if_fail(old_group_name != NULL);
	g_return_if_fail(new_group_name != NULL);

	purple_debug_info("msn", "Renaming group %s to %s.\n", old_group_name, new_group_name);

	guid = msn_userlist_find_group_id(session->userlist, old_group_name);
	if (guid == NULL)
		return;

	state = msn_callback_state_new(session);
	msn_callback_state_set_guid(state, guid);
	msn_callback_state_set_new_group_name(state, new_group_name);

	if ( !strcmp(guid, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(guid, MSN_NON_IM_GROUP_ID) ) {
		MsnCallbackState *new_state = msn_callback_state_dup(state);
		msn_add_group(session, new_state, new_group_name);
		/* XXX move every buddy there (we probably need to fix concurrent SOAP reqs first) */
	}

	msn_callback_state_set_action(state, MSN_RENAME_GROUP);

	escaped_group_name = g_markup_escape_text(new_group_name, -1);
	body = g_strdup_printf(MSN_GROUP_RENAME_TEMPLATE, guid, escaped_group_name);

	state->body = xmlnode_from_str(body, -1);
	state->post_action = MSN_GROUP_RENAME_SOAP_ACTION;
	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
	state->cb = msn_group_read_cb;
	msn_contact_request(state);

	g_free(escaped_group_name);
	g_free(body);
}