changeset 5518:bf2a7a7b739d

[gaim-migrate @ 5918] Reworked the group support. You should now be able to delete groups by dragging a user into them and then back out. I stress _should_. This will likely have bugs. I cleaned up the ones I found, I think, but I'm not 100% confident here :) Please test! Preferably on test accounts, but it won't kill things, just reorder stuff. committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Sun, 25 May 2003 22:51:19 +0000
parents e9b9fbf89c42
children 42a20beb80a7
files src/protocols/msn/Makefile.am src/protocols/msn/group.c src/protocols/msn/group.h src/protocols/msn/msn.c src/protocols/msn/notification.c src/protocols/msn/session.c src/protocols/msn/session.h src/protocols/msn/user.c src/protocols/msn/user.h
diffstat 9 files changed, 740 insertions(+), 120 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/msn/Makefile.am	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/Makefile.am	Sun May 25 22:51:19 2003 +0000
@@ -9,6 +9,8 @@
 	dispatch.h \
 	error.c \
 	error.h \
+	group.c \
+	group.h \
 	md5.h \
 	msg.c \
 	msg.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/group.c	Sun May 25 22:51:19 2003 +0000
@@ -0,0 +1,274 @@
+/**
+ * @file group.c Group functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "msn.h"
+#include "group.h"
+
+MsnGroup *
+msn_group_new(MsnSession *session, int id, const char *name)
+{
+	MsnGroup *group;
+
+	g_return_val_if_fail(session != NULL, NULL);
+	g_return_val_if_fail(id >= 0,         NULL);
+	g_return_val_if_fail(name != NULL,    NULL);
+
+	group = msn_groups_find_with_id(session->groups, id);
+
+	if (group == NULL) {
+		group = g_new0(MsnGroup, 1);
+
+		group->session = session;
+		group->id      = id;
+		group->name    = g_strdup(name);
+		group->users   = msn_users_new();
+	}
+
+	msn_group_ref(group);
+
+	return group;
+}
+
+void
+msn_group_destroy(MsnGroup *group)
+{
+	g_return_if_fail(group != NULL);
+
+	if (group->ref_count > 0) {
+		msn_group_unref(group);
+
+		return;
+	}
+
+	if (group->session != NULL && group->session->groups != NULL)
+		msn_groups_remove(group->session->groups, group);
+
+	msn_users_destroy(group->users);
+
+	g_free(group->name);
+	g_free(group);
+}
+
+MsnGroup *
+msn_group_ref(MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, NULL);
+
+	group->ref_count++;
+
+	return group;
+}
+
+MsnGroup *
+msn_group_unref(MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, NULL);
+
+	if (group->ref_count <= 0)
+		return NULL;
+
+	group->ref_count--;
+
+	if (group->ref_count == 0) {
+		msn_group_destroy(group);
+
+		return NULL;
+	}
+
+	return group;
+}
+
+void
+msn_group_set_id(MsnGroup *group, int id)
+{
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(id >= 0);
+
+	group->id = id;
+}
+
+void
+msn_group_set_name(MsnGroup *group, const char *name)
+{
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(name  != NULL);
+
+	if (group->name != NULL)
+		g_free(group->name);
+
+	group->name = g_strdup(name);
+}
+
+int
+msn_group_get_id(const MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, -1);
+
+	return group->id;
+}
+
+const char *
+msn_group_get_name(const MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, NULL);
+
+	return group->name;
+}
+
+void
+msn_group_add_user(MsnGroup *group, MsnUser *user)
+{
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(user  != NULL);
+
+	msn_users_add(group->users, user);
+
+	msn_user_ref(user);
+
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Adding user %s to group %s (%d)\n",
+			   msn_user_get_passport(user), msn_group_get_name(group),
+			   msn_group_get_id(group));
+}
+
+void
+msn_group_remove_user(MsnGroup *group, MsnUser *user)
+{
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(user  != NULL);
+
+	msn_users_remove(group->users, user);
+
+	msn_user_unref(user);
+}
+
+MsnUsers *
+msn_group_get_users(const MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, NULL);
+
+	return group->users;
+}
+
+
+MsnGroups *
+msn_groups_new(void)
+{
+	return g_new0(MsnGroups, 1);
+}
+
+void
+msn_groups_destroy(MsnGroups *groups)
+{
+	g_return_if_fail(groups != NULL);
+
+	while (groups->groups != NULL)
+		msn_group_destroy(groups->groups->data);
+
+	/* See if we've leaked anybody. */
+	while (groups->groups != NULL) {
+		gaim_debug(GAIM_DEBUG_WARNING, "msn",
+				   "Leaking group %s (id %d)\n",
+				   msn_group_get_name(groups->groups->data),
+				   msn_group_get_id(groups->groups->data));
+	}
+
+	g_free(groups);
+}
+
+void
+msn_groups_add(MsnGroups *groups, MsnGroup *group)
+{
+	g_return_if_fail(groups != NULL);
+	g_return_if_fail(group != NULL);
+
+	groups->groups = g_list_append(groups->groups, group);
+
+	groups->count++;
+
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Adding group %s (%d)\n",
+			   msn_group_get_name(group), msn_group_get_id(group));
+}
+
+void
+msn_groups_remove(MsnGroups *groups, MsnGroup *group)
+{
+	g_return_if_fail(groups != NULL);
+	g_return_if_fail(group != NULL);
+
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Removing group %s (%d)\n",
+			   msn_group_get_name(group), msn_group_get_id(group));
+
+	groups->groups = g_list_remove(groups->groups, group);
+
+	groups->count--;
+}
+
+size_t
+msn_groups_get_count(const MsnGroups *groups)
+{
+	g_return_val_if_fail(groups != NULL, 0);
+
+	return groups->count;
+}
+
+GList *
+msn_groups_get_list(const MsnGroups *groups)
+{
+	g_return_val_if_fail(groups != NULL, NULL);
+
+	return groups->groups;
+}
+
+MsnGroup *
+msn_groups_find_with_id(MsnGroups *groups, int id)
+{
+	GList *l;
+
+	g_return_val_if_fail(groups != NULL, NULL);
+	g_return_val_if_fail(id >= 0,        NULL);
+
+	for (l = groups->groups; l != NULL; l = l->next) {
+		MsnGroup *group = l->data;
+
+		if (group->id == id)
+			return group;
+	}
+
+	return NULL;
+}
+
+MsnGroup *
+msn_groups_find_with_name(MsnGroups *groups, const char *name)
+{
+	GList *l;
+
+	g_return_val_if_fail(groups != NULL, NULL);
+	g_return_val_if_fail(name   != NULL, NULL);
+
+	for (l = groups->groups; l != NULL; l = l->next) {
+		MsnGroup *group = l->data;
+
+		if (group->name != NULL && !g_ascii_strcasecmp(name, group->name))
+			return group;
+	}
+
+	return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/group.h	Sun May 25 22:51:19 2003 +0000
@@ -0,0 +1,233 @@
+/**
+ * @file group.h Group functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _MSN_GROUP_H_
+#define _MSN_GROUP_H_
+
+typedef struct _MsnGroup  MsnGroup;
+typedef struct _MsnGroups MsnGroups;
+
+#include "session.h"
+#include "user.h"
+
+/**
+ * A group.
+ */
+struct _MsnGroup
+{
+	size_t ref_count;       /**< The reference count.       */
+
+	MsnSession *session;    /**< The MSN session.           */
+
+	int id;                 /**< The group ID.              */
+	char *name;             /**< The name of the group.     */
+
+	MsnUsers *users;        /**< The users in the group.    */
+};
+
+/**
+ * A list of groups.
+ */
+struct _MsnGroups
+{
+	size_t count;  /**< The number of groups. */
+
+	GList *groups; /**< The list of groups.   */
+};
+
+/**************************************************************************/
+/** @name Group API                                                       */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new group structure.
+ *
+ * @param session The MSN session.
+ * @param id      The group ID.
+ * @param name    The name of the group.
+ *
+ * @return A new group structure.
+ */
+MsnGroup *msn_group_new(MsnSession *session, int id, const char *name);
+
+/**
+ * Destroys a group structure.
+ *
+ * @param group The group to destroy.
+ */
+void msn_group_destroy(MsnGroup *group);
+
+/**
+ * Increments the reference count on a group.
+ *
+ * @param group The group.
+ *
+ * @return @a group
+ */
+MsnGroup *msn_group_ref(MsnGroup *group);
+
+/**
+ * Decrements the reference count on a group.
+ *
+ * This will destroy the structure if the count hits 0.
+ *
+ * @param group The group.
+ *
+ * @return @a group, or @c NULL if the new count is 0.
+ */
+MsnGroup *msn_group_unref(MsnGroup *group);
+
+/**
+ * Sets the ID for a group.
+ *
+ * @param group The group.
+ * @param id    The ID.
+ */
+void msn_group_set_id(MsnGroup *group, int id);
+
+/**
+ * Sets the name for a group.
+ *
+ * @param group The group.
+ * @param name  The name.
+ */
+void msn_group_set_name(MsnGroup *group, const char *name);
+
+/**
+ * Returns the ID for a group.
+ *
+ * @param group The group.
+ *
+ * @return The ID.
+ */
+int msn_group_get_id(const MsnGroup *group);
+
+/**
+ * Returns the name for a group.
+ *
+ * @param group The group.
+ *
+ * @return The name.
+ */
+const char *msn_group_get_name(const MsnGroup *group);
+
+/**
+ * Adds a user to the group.
+ *
+ * @param group The group.
+ * @param user  The user.
+ */
+void msn_group_add_user(MsnGroup *group, MsnUser *user);
+
+/**
+ * Removes a user from the group.
+ *
+ * @param group The group.
+ * @param user  The user.
+ */
+void msn_group_remove_user(MsnGroup *group, MsnUser *user);
+
+/**
+ * Returns the users in a group.
+ *
+ * @param group The group.
+ *
+ * @return The list of users.
+ */
+MsnUsers *msn_group_get_users(const MsnGroup *group);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Group List API                                                  */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new MsnGroups structure.
+ *
+ * @return A new MsnGroups structure.
+ */
+MsnGroups *msn_groups_new(void);
+
+/**
+ * Destroys a groups list.
+ *
+ * @param groups The groups list.
+ */
+void msn_groups_destroy(MsnGroups *groups);
+
+/**
+ * Adds a group to a groups list.
+ *
+ * @param groups The groups list.
+ * @param group  The group.
+ */
+void msn_groups_add(MsnGroups *groups, MsnGroup *group);
+
+/**
+ * Removes a group from a groups list.
+ *
+ * @param groups The groups list.
+ * @param group  The group.
+ */
+void msn_groups_remove(MsnGroups *groups, MsnGroup *group);
+
+/**
+ * Returns the number of groups in a groups list.
+ *
+ * @param groups The groups list.
+ * 
+ * @return The number of groups.
+ */
+size_t msn_groups_get_count(const MsnGroups *groups);
+
+/**
+ * Finds a group with the specified ID.
+ *
+ * @param groups A list of groups.
+ * @param id     The group ID.
+ *
+ * @return The group if found, or @c NULL otherwise.
+ */
+MsnGroup *msn_groups_find_with_id(MsnGroups *groups, int id);
+
+/**
+ * Returns a GList of all groups.
+ *
+ * @param groups The list of groups.
+ *
+ * @return A GList of all groups.
+ */
+GList *msn_groups_get_list(const MsnGroups *groups);
+
+/**
+ * Finds a group with the specified name.
+ *
+ * @param groups A list of groups.
+ * @param name   The group name.
+ *
+ * @return The group if found, or @c NULL otherwise.
+ */
+MsnGroup *msn_groups_find_with_name(MsnGroups *groups, const char *name);
+
+#endif /* _MSN_GROUP_H_ */
--- a/src/protocols/msn/msn.c	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/msn.c	Sun May 25 22:51:19 2003 +0000
@@ -989,18 +989,20 @@
 
 static void
 msn_group_buddy(struct gaim_connection *gc, const char *who,
-				const char *old_group, const char *new_group)
+				const char *old_group_name, const char *new_group_name)
 {
 	MsnSession *session = gc->proto_data;
 	char outparams[MSN_BUF_LEN];
-	gint *old_group_id, *new_group_id;
+	MsnGroup *old_group, *new_group;
+
+	old_group = msn_groups_find_with_name(session->groups, old_group_name);
+	new_group = msn_groups_find_with_name(session->groups, new_group_name);
 
-	old_group_id = g_hash_table_lookup(session->group_ids, old_group);
-	new_group_id = g_hash_table_lookup(session->group_ids, new_group);
+	gaim_debug(GAIM_DEBUG_MISC, "msn", "new_group = %p\n", new_group);
 
-	if (new_group_id == NULL) {
+	if (new_group == NULL) {
 		g_snprintf(outparams, sizeof(outparams), "%s 0",
-				   msn_url_encode(new_group));
+				   msn_url_encode(new_group_name));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "ADG", outparams)) {
@@ -1010,12 +1012,18 @@
 		}
 
 		/* I hate this. So much. */
-		session->moving_buddy = TRUE;
-		session->dest_group_name = g_strdup(new_group);
+		session->moving_buddy    = TRUE;
+		session->dest_group_name = g_strdup(new_group_name);
+		session->old_group       = old_group;
+
+		session->moving_user =
+			msn_users_find_with_passport(session->users, who);
+
+		msn_user_ref(session->moving_user);
 	}
 	else {
 		g_snprintf(outparams, sizeof(outparams), "FL %s %s %d",
-				   who, who, *new_group_id);
+				   who, who, msn_group_get_id(new_group));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "ADD", outparams)) {
@@ -1025,9 +1033,9 @@
 		}
 	}
 
-	if (old_group_id != NULL) {
+	if (old_group != NULL) {
 		g_snprintf(outparams, sizeof(outparams), "FL %s %d",
-				   who, *old_group_id);
+				   who, msn_group_get_id(old_group));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "REM", outparams)) {
@@ -1035,36 +1043,55 @@
 			signoff(gc);
 			return;
 		}
+
+		if (msn_users_get_count(msn_group_get_users(old_group)) <= 0) {
+			g_snprintf(outparams, sizeof(outparams), "%d",
+					   msn_group_get_id(old_group));
+
+			if (!msn_servconn_send_command(session->notification_conn,
+										   "RMG", outparams)) {
+
+				hide_login_progress(gc, _("Write error"));
+				signoff(gc);
+				return;
+			}
+		}
 	}
 }
 
 static void
-msn_rename_group(struct gaim_connection *gc, const char *old_group,
-				 const char *new_group, GList *members)
+msn_rename_group(struct gaim_connection *gc, const char *old_group_name,
+				 const char *new_group_name, GList *members)
 {
 	MsnSession *session = gc->proto_data;
 	char outparams[MSN_BUF_LEN];
-	int *group_id;
+	MsnGroup *old_group;
 
-	if (g_hash_table_lookup_extended(session->group_ids, old_group,
-									 NULL, (gpointer)&group_id)) {
+	if ((old_group = msn_groups_find_with_name(session->groups,
+											   old_group_name)) != NULL) {
+
 		g_snprintf(outparams, sizeof(outparams), "%d %s 0",
-				   *group_id, msn_url_encode(new_group));
+				   msn_group_get_id(old_group),
+				   msn_url_encode(new_group_name));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "REG", outparams)) {
 			hide_login_progress(gc, _("Write error"));
 			signoff(gc);
+			return;
 		}
+
+		msn_group_set_name(old_group, new_group_name);
 	}
 	else {
 		g_snprintf(outparams, sizeof(outparams), "%s 0",
-				   msn_url_encode(new_group));
+				   msn_url_encode(new_group_name));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "ADG", outparams)) {
 			hide_login_progress(gc, _("Write error"));
 			signoff(gc);
+			return;
 		}
 	}
 }
--- a/src/protocols/msn/notification.c	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/notification.c	Sun May 25 22:51:19 2003 +0000
@@ -45,40 +45,55 @@
 	MsnSession *session = servconn->session;
 	struct gaim_connection *gc = session->account->gc;
 	struct buddy *b;
+	MsnGroup *group = NULL;
+	struct group *g = NULL;
+	int group_id;
+
+	group_id = msn_user_get_group_id(user);
+
+	if (group_id > -1)
+		group = msn_groups_find_with_id(session->groups, group_id);
+
+	if (group == NULL) {
+		GList *l;
+		gaim_debug(GAIM_DEBUG_WARNING, "msn",
+				   "Group ID %d for user %s was not defined.\n",
+				   group_id, msn_user_get_passport(user));
+
+		/* Find a group that we can stick this guy into. Lamer. */
+		l = msn_groups_get_list(session->groups);
+
+		if (l != NULL) {
+			group = l->data;
+
+			msn_user_set_group_id(user, msn_group_get_id(group));
+		}
+	}
+
+	if (group == NULL ||
+		(g = gaim_find_group(msn_group_get_name(group))) == NULL) {
+
+		gaim_debug(GAIM_DEBUG_ERROR, "msn",
+				   "Group '%s' appears in server-side "
+				   "buddy list, but not here!",
+				   msn_group_get_name(group));
+	}
+
+	if (group != NULL)
+		msn_group_add_user(group, user);
+
+	if (g == NULL) {
+		/* Should never happen. */
+
+		if ((g = gaim_find_group(_("Buddies"))) == NULL) {
+			g = gaim_group_new(_("Buddies"));
+			gaim_blist_add_group(g, NULL);
+		}
+	}
 
 	b = gaim_find_buddy(gc->account, msn_user_get_passport(user));
 
 	if (b == NULL) {
-		struct group *g = NULL;
-		const char *group_name = NULL;
-		int group_id;
-
-		group_id = msn_user_get_group_id(user);
-
-		if (group_id > -1) {
-			group_name = g_hash_table_lookup(session->group_names,
-											 &group_id);
-		}
-
-		if (group_name == NULL) {
-			gaim_debug(GAIM_DEBUG_WARNING, "msn",
-					   "Group ID %d for user %s was not defined.\n",
-					   group_id, msn_user_get_passport(user));
-		}
-		else if ((g = gaim_find_group(group_name)) == NULL) {
-			gaim_debug(GAIM_DEBUG_ERROR, "msn",
-					   "Group '%s' appears in server-side "
-					   "buddy list, but not here!",
-					   group_name);
-		}
-
-		if (g == NULL) {
-			if ((g = gaim_find_group(_("Buddies"))) == NULL) {
-				g = gaim_group_new(_("Buddies"));
-				gaim_blist_add_group(g, NULL);
-			}
-		}
-
 		b = gaim_buddy_new(gc->account,
 						   msn_user_get_passport(user), NULL);
 
@@ -420,7 +435,9 @@
 	friend = msn_url_decode(params[4]);
 
 	if (!g_ascii_strcasecmp(list, "FL")) {
-		user = msn_user_new(session, passport, friend);
+		user = msn_user_new(session, passport, NULL);
+
+		msn_user_set_group_id(user, atoi(params[5]));
 
 		__add_buddy(servconn, user);
 
@@ -455,20 +472,18 @@
 __adg_cmd(MsnServConn *servconn, const char *command, const char **params,
 		  size_t param_count)
 {
+	MsnGroup *group;
 	MsnSession *session = servconn->session;
-	gint *group_id;
+	gint group_id;
 	char *group_name;
 
-	group_id = g_new(gint, 1);
-	*group_id = atoi(params[3]);
+	group_id = atoi(params[3]);
 
 	group_name = msn_url_decode(params[2]);
 
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Added group %s (id %d)\n",
-			   group_name, group_id);
+	group = msn_group_new(session, group_id, group_name);
 
-	g_hash_table_insert(session->group_ids, group_name, group_id);
-	g_hash_table_insert(session->group_names, group_id, g_strdup(group_name));
+	msn_groups_add(session->groups, group);
 
 	return TRUE;
 }
@@ -592,10 +607,10 @@
 		  size_t param_count)
 {
 	MsnSession *session = servconn->session;
+	MsnGroup *group;
 	struct group *g;
 	const char *name;
 	int group_num, num_groups, group_id;
-	gint *group_id_1, *group_id_2;
 
 	group_num  = atoi(params[2]);
 	num_groups = atoi(params[3]);
@@ -605,24 +620,12 @@
 	if (num_groups == 0)
 		return TRUE;
 
-	if (group_num == 1) {
-		session->group_names = g_hash_table_new_full(g_int_hash, g_int_equal,
-													 g_free, g_free);
-		session->group_ids = g_hash_table_new_full(g_str_hash, g_str_equal,
-												   g_free, g_free);
-	}
-
-	group_id_1 = g_new(gint, 1);
-	group_id_2 = g_new(gint, 1);
-
-	*group_id_1 = group_id;
-	*group_id_2 = group_id;
-
 	if (!strcmp(name, "~"))
 		name = _("Buddies");
 
-	g_hash_table_insert(session->group_names, group_id_1, g_strdup(name));
-	g_hash_table_insert(session->group_ids, g_strdup(name), group_id_2);
+	group = msn_group_new(session, group_id, name);
+
+	msn_groups_add(session->groups, group);
 
 	if ((g = gaim_find_group(name)) == NULL) {
 		g = gaim_group_new(name);
@@ -837,8 +840,6 @@
 
 	friend = msn_url_decode(params[3]);
 
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Setting friendly name to %s\n",
-			   friend);
 	g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", friend);
 
 	return TRUE;
@@ -849,22 +850,21 @@
 		  size_t param_count)
 {
 	MsnSession *session = servconn->session;
-	gint *group_id;
+	MsnGroup *group;
+	int group_id;
 	char *group_name;
 
-	group_id = g_new(gint, 1);
-	*group_id = atoi(params[2]);
+	group_id = atoi(params[2]);
 
 	group_name = msn_url_decode(params[3]);
 
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Renamed group %s to %s\n",
-			   g_hash_table_lookup(session->group_names, group_id),
-			   group_name);
+	group = msn_groups_find_with_id(session->groups, group_id);
 
-	g_hash_table_replace(session->group_names, group_id, g_strdup(group_name));
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Renamed group %s to %s\n",
+			   msn_group_get_name(group), group_name);
 
-	g_hash_table_remove(session->group_ids, group_name);
-	g_hash_table_insert(session->group_ids, group_name, group_id);
+	if (group != NULL)
+		msn_group_set_name(group, group_name);
 
 	return TRUE;
 }
@@ -877,38 +877,84 @@
 
 	/* I hate this. */
 	if (session->moving_buddy) {
+		MsnGroup *group, *old_group;
 		struct gaim_connection *gc = session->account->gc;
 		const char *passport = params[3];
 		char outparams[MSN_BUF_LEN];
-		int *group_id;
+
+		group = msn_groups_find_with_name(session->groups,
+										  session->dest_group_name);
+
+		old_group = session->old_group;
+
+		session->moving_buddy = FALSE;
+		session->old_group    = NULL;
 
-		group_id = g_hash_table_lookup(session->group_ids,
-									   session->dest_group_name);
+		if (group == NULL) {
+			gaim_debug(GAIM_DEBUG_ERROR, "msn",
+					   "Still don't have a group ID for %s while moving %s!\n",
+					   session->dest_group_name, passport);
+
+			g_free(session->dest_group_name);
+			session->dest_group_name = NULL;
+
+			return TRUE;
+		}
 
 		g_free(session->dest_group_name);
 		session->dest_group_name = NULL;
-		session->moving_buddy = FALSE;
-
-		if (group_id == NULL) {
-			gaim_debug(GAIM_DEBUG_ERROR, "msn",
-					   "Still don't have a group ID for %s while moving %s!\n",
-					   session->dest_group_name, passport);
-			return TRUE;
-		}
 
 		g_snprintf(outparams, sizeof(outparams), "FL %s %s %d",
-				   passport, passport, *group_id);
+				   passport, passport, msn_group_get_id(group));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "ADD", outparams)) {
 			hide_login_progress(gc, _("Write error"));
 			signoff(gc);
+			return FALSE;
+		}
+
+		if (old_group != NULL)
+			msn_group_remove_user(old_group, session->moving_user);
+
+		msn_user_unref(session->moving_user);
+
+		session->moving_user = NULL;
+
+		if (old_group != NULL &&
+			msn_users_get_count(msn_group_get_users(old_group)) <= 0) {
+
+			g_snprintf(outparams, sizeof(outparams), "%d",
+					   msn_group_get_id(old_group));
+
+			if (!msn_servconn_send_command(session->notification_conn,
+										   "RMG", outparams)) {
+
+				hide_login_progress(gc, _("Write error"));
+				signoff(gc);
+				return FALSE;
+			}
 		}
 	}
 
 	return TRUE;
 }
 
+static gboolean
+__rmg_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	MsnGroup *group;
+
+	group = msn_groups_find_with_id(session->groups, atoi(params[2]));
+
+	if (group != NULL)
+		msn_groups_remove(session->groups, group);
+
+	return TRUE;
+}
+
 /**************************************************************************
  * Misc commands
  **************************************************************************/
@@ -1354,7 +1400,7 @@
 		msn_servconn_register_command(notification, "REA",       __rea_cmd);
 		msn_servconn_register_command(notification, "REG",       __reg_cmd);
 		msn_servconn_register_command(notification, "REM",       __rem_cmd);
-		msn_servconn_register_command(notification, "RMG",       __blank_cmd);
+		msn_servconn_register_command(notification, "RMG",       __rmg_cmd);
 		msn_servconn_register_command(notification, "RNG",       __rng_cmd);
 		msn_servconn_register_command(notification, "SYN",       __blank_cmd);
 		msn_servconn_register_command(notification, "URL",       __url_cmd);
--- a/src/protocols/msn/session.c	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/session.c	Sun May 25 22:51:19 2003 +0000
@@ -36,7 +36,8 @@
 	session->dispatch_server = g_strdup(server);
 	session->dispatch_port   = port;
 
-	session->users = msn_users_new();
+	session->users  = msn_users_new();
+	session->groups = msn_groups_new();
 
 	return session;
 }
@@ -58,12 +59,10 @@
 	while (session->lists.forward)
 		msn_user_destroy(session->lists.forward->data);
 
-	if (session->group_ids)   g_hash_table_destroy(session->group_ids);
-	if (session->group_names) g_hash_table_destroy(session->group_names);
-
 	g_slist_free(session->lists.allow);
 	g_slist_free(session->lists.block);
 
+	msn_groups_destroy(session->groups);
 	msn_users_destroy(session->users);
 
 	g_free(session->passport_info.kv);
--- a/src/protocols/msn/session.h	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/session.h	Sun May 25 22:51:19 2003 +0000
@@ -27,6 +27,7 @@
 #include "servconn.h"
 #include "switchboard.h"
 #include "user.h"
+#include "group.h"
 
 struct _MsnSession
 {
@@ -44,10 +45,9 @@
 	unsigned int trId;
 
 	MsnUsers *users;
+	MsnGroups *groups;
 
 	GList *switches;
-	GHashTable *group_names; /* ID -> name */
-	GHashTable *group_ids;   /* Name -> ID */
 
 	struct
 	{
@@ -77,6 +77,8 @@
 	/* For moving buddies from one group to another. Ugh. */
 	gboolean moving_buddy;
 	char *dest_group_name;
+	MsnUser *moving_user;
+	MsnGroup *old_group;
 };
 
 /**
--- a/src/protocols/msn/user.c	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/user.c	Sun May 25 22:51:19 2003 +0000
@@ -29,27 +29,20 @@
 
 	user = msn_users_find_with_passport(session->users, passport);
 
-	if (user != NULL) {
-		if (name != NULL)
-			msn_user_set_name(user, name);
+	if (user == NULL) {
+		user = g_new0(MsnUser, 1);
 
-		msn_user_ref(user);
+		user->session = session;
 
-		return user;
-	}
+		msn_user_set_passport(user, passport);
+		msn_user_set_group_id(user, -1);
 
-	user = g_new0(MsnUser, 1);
-
-	user->session = session;
+		msn_users_add(session->users, user);
+	}
 
 	if (name != NULL)
 		msn_user_set_name(user, name);
 
-	msn_user_set_passport(user, passport);
-	msn_user_set_group_id(user, -1);
-
-	msn_users_add(session->users, user);
-
 	msn_user_ref(user);
 
 	return user;
@@ -254,10 +247,17 @@
 void
 msn_users_destroy(MsnUsers *users)
 {
+	GList *l, *l_next = NULL;
+
 	g_return_if_fail(users != NULL);
 
-	while (users->users != NULL)
-		msn_user_destroy(users->users->data);
+	for (l = users->users; l != NULL; l = l_next) {
+		l_next = l->next;
+
+		msn_user_destroy(l->data);
+
+		users->users = g_list_remove(users->users, l->data);
+	}
 
 	/* See if we've leaked anybody. */
 	while (users->users != NULL) {
@@ -276,15 +276,27 @@
 	g_return_if_fail(user != NULL);
 
 	users->users = g_list_append(users->users, user);
+
+	users->count++;
 }
 
 void
 msn_users_remove(MsnUsers *users, MsnUser *user)
 {
 	g_return_if_fail(users != NULL);
-	g_return_if_fail(user != NULL);
+	g_return_if_fail(user  != NULL);
 
 	users->users = g_list_remove(users->users, user);
+
+	users->count--;
+}
+
+size_t
+msn_users_get_count(const MsnUsers *users)
+{
+	g_return_val_if_fail(users != NULL, 0);
+
+	return users->count;
 }
 
 MsnUser *
--- a/src/protocols/msn/user.h	Sun May 25 19:47:13 2003 +0000
+++ b/src/protocols/msn/user.h	Sun May 25 22:51:19 2003 +0000
@@ -59,9 +59,16 @@
  */
 struct _MsnUsers
 {
-	GList *users; /** The list of users. */
+	size_t count; /**< The number of users. */
+
+	GList *users; /**< The list of users.   */
 };
 
+/**************************************************************************/
+/** @name User API                                                        */
+/**************************************************************************/
+/*@{*/
+
 /**
  * Creates a new user structure.
  * 
@@ -220,6 +227,13 @@
  */
 GHashTable *msn_user_get_client_caps(const MsnUser *user);
 
+/*@}*/
+
+/**************************************************************************/
+/** @name User List API                                                   */
+/**************************************************************************/
+/*@{*/
+
 /**
  * Creates a new MsnUsers structure.
  *
@@ -251,6 +265,15 @@
 void msn_users_remove(MsnUsers *users, MsnUser *user);
 
 /**
+ * Returns the number of users in a users list.
+ *
+ * @param users The users list.
+ * 
+ * @return The number of users.
+ */
+size_t msn_users_get_count(const MsnUsers *users);
+
+/**
  * Finds a user with the specified passport.
  *
  * @param users    A list of users.
@@ -260,4 +283,6 @@
  */
 MsnUser *msn_users_find_with_passport(MsnUsers *users, const char *passport);
 
+/*@}*/
+
 #endif /* _MSN_USER_H_ */