diff libgaim/status.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children 6e89bfd2b33f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgaim/status.c	Sat Aug 19 01:50:10 2006 +0000
@@ -0,0 +1,1766 @@
+/**
+ * @file status.c Status API
+ * @ingroup core
+ *
+ * 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 "core.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "notify.h"
+#include "prefs.h"
+#include "status.h"
+
+/**
+ * A type of status.
+ */
+struct _GaimStatusType
+{
+	GaimStatusPrimitive primitive;
+
+	char *id;
+	char *name;
+	char *primary_attr_id;
+
+	gboolean saveable;
+	gboolean user_settable;
+	gboolean independent;
+
+	GList *attrs;
+};
+
+/**
+ * A status attribute.
+ */
+struct _GaimStatusAttr
+{
+	char *id;
+	char *name;
+	GaimValue *value_type;
+};
+
+/**
+ * A list of statuses.
+ */
+struct _GaimPresence
+{
+	GaimPresenceContext context;
+
+	gboolean idle;
+	time_t idle_time;
+	time_t login_time;
+
+	GList *statuses;
+	GHashTable *status_table;
+
+	GaimStatus *active_status;
+
+	union
+	{
+		GaimAccount *account;
+
+		struct
+		{
+			GaimConversation *conv;
+			char *user;
+
+		} chat;
+
+		struct
+		{
+			GaimAccount *account;
+			char *name;
+			size_t ref_count;
+			GList *buddies;
+
+		} buddy;
+
+	} u;
+};
+
+/**
+ * An active status.
+ */
+struct _GaimStatus
+{
+	GaimStatusType *type;
+	GaimPresence *presence;
+
+	const char *title;
+
+	gboolean active;
+
+	GHashTable *attr_values;
+};
+
+typedef struct
+{
+	GaimAccount *account;
+	char *name;
+} GaimStatusBuddyKey;
+
+static int primitive_scores[] =
+{
+	0,      /* unset                    */
+	-500,   /* offline                  */
+	100,    /* available                */
+	-75,    /* unavailable              */
+	-50,    /* invisible                */
+	-100,   /* away                     */
+	-200,   /* extended away            */
+	-400,   /* mobile                   */
+	-10,    /* idle, special case.      */
+	-5      /* idle time, special case. */
+};
+
+static GHashTable *buddy_presences = NULL;
+
+#define SCORE_IDLE      8
+#define SCORE_IDLE_TIME 9
+
+/**************************************************************************
+ * GaimStatusPrimitive API
+ **************************************************************************/
+static struct GaimStatusPrimitiveMap
+{
+	GaimStatusPrimitive type;
+	const char *id;
+	const char *name;
+
+} const status_primitive_map[] =
+{
+	{ GAIM_STATUS_UNSET,           "unset",           N_("Unset")           },
+	{ GAIM_STATUS_OFFLINE,         "offline",         N_("Offline")         },
+	{ GAIM_STATUS_AVAILABLE,       "available",       N_("Available")       },
+	{ GAIM_STATUS_UNAVAILABLE,     "unavailable",     N_("Unavailable")     },
+	{ GAIM_STATUS_INVISIBLE,       "invisible",       N_("Invisible")       },
+	{ GAIM_STATUS_AWAY,            "away",            N_("Away")            },
+	{ GAIM_STATUS_EXTENDED_AWAY,   "extended_away",   N_("Extended Away")   },
+	{ GAIM_STATUS_MOBILE,          "mobile",          N_("Mobile")          }
+};
+
+const char *
+gaim_primitive_get_id_from_type(GaimStatusPrimitive type)
+{
+    int i;
+
+    for (i = 0; i < GAIM_STATUS_NUM_PRIMITIVES; i++)
+    {
+		if (type == status_primitive_map[i].type)
+			return status_primitive_map[i].id;
+    }
+
+    return status_primitive_map[0].id;
+}
+
+const char *
+gaim_primitive_get_name_from_type(GaimStatusPrimitive type)
+{
+    int i;
+
+    for (i = 0; i < GAIM_STATUS_NUM_PRIMITIVES; i++)
+    {
+	if (type == status_primitive_map[i].type)
+		return _(status_primitive_map[i].name);
+    }
+
+    return _(status_primitive_map[0].name);
+}
+
+GaimStatusPrimitive
+gaim_primitive_get_type_from_id(const char *id)
+{
+    int i;
+
+    g_return_val_if_fail(id != NULL, GAIM_STATUS_UNSET);
+
+    for (i = 0; i < GAIM_STATUS_NUM_PRIMITIVES; i++)
+    {
+        if (!strcmp(id, status_primitive_map[i].id))
+            return status_primitive_map[i].type;
+    }
+
+    return status_primitive_map[0].type;
+}
+
+
+/**************************************************************************
+ * GaimStatusType API
+ **************************************************************************/
+GaimStatusType *
+gaim_status_type_new_full(GaimStatusPrimitive primitive, const char *id,
+						  const char *name, gboolean saveable,
+						  gboolean user_settable, gboolean independent)
+{
+	GaimStatusType *status_type;
+
+	g_return_val_if_fail(primitive != GAIM_STATUS_UNSET, NULL);
+
+	status_type = g_new0(GaimStatusType, 1);
+	GAIM_DBUS_REGISTER_POINTER(status_type, GaimStatusType);
+
+	status_type->primitive     = primitive;
+	status_type->saveable      = saveable;
+	status_type->user_settable = user_settable;
+	status_type->independent   = independent;
+
+	if (id != NULL)
+		status_type->id = g_strdup(id);
+	else
+		status_type->id = g_strdup(gaim_primitive_get_id_from_type(primitive));
+
+	if (name != NULL)
+		status_type->name = g_strdup(name);
+	else
+		status_type->name = g_strdup(gaim_primitive_get_name_from_type(primitive));
+
+	return status_type;
+}
+
+GaimStatusType *
+gaim_status_type_new(GaimStatusPrimitive primitive, const char *id,
+					 const char *name, gboolean user_settable)
+{
+	g_return_val_if_fail(primitive != GAIM_STATUS_UNSET, NULL);
+
+	return gaim_status_type_new_full(primitive, id, name, FALSE,
+			user_settable, FALSE);
+}
+
+GaimStatusType *
+gaim_status_type_new_with_attrs(GaimStatusPrimitive primitive,
+		const char *id, const char *name,
+		gboolean saveable, gboolean user_settable,
+		gboolean independent, const char *attr_id,
+		const char *attr_name, GaimValue *attr_value,
+		...)
+{
+	GaimStatusType *status_type;
+	va_list args;
+
+	g_return_val_if_fail(primitive  != GAIM_STATUS_UNSET, NULL);
+	g_return_val_if_fail(attr_id    != NULL,              NULL);
+	g_return_val_if_fail(attr_name  != NULL,              NULL);
+	g_return_val_if_fail(attr_value != NULL,              NULL);
+
+	status_type = gaim_status_type_new_full(primitive, id, name, saveable,
+			user_settable, independent);
+
+	/* Add the first attribute */
+	gaim_status_type_add_attr(status_type, attr_id, attr_name, attr_value);
+
+	va_start(args, attr_value);
+	gaim_status_type_add_attrs_vargs(status_type, args);
+	va_end(args);
+
+	return status_type;
+}
+
+void
+gaim_status_type_destroy(GaimStatusType *status_type)
+{
+	g_return_if_fail(status_type != NULL);
+
+	g_free(status_type->id);
+	g_free(status_type->name);
+	g_free(status_type->primary_attr_id);
+
+	g_list_foreach(status_type->attrs, (GFunc)gaim_status_attr_destroy, NULL);
+	g_list_free(status_type->attrs);
+
+	GAIM_DBUS_UNREGISTER_POINTER(status_type);
+	g_free(status_type);
+}
+
+void
+gaim_status_type_set_primary_attr(GaimStatusType *status_type, const char *id)
+{
+	g_return_if_fail(status_type != NULL);
+
+	g_free(status_type->primary_attr_id);
+	status_type->primary_attr_id = g_strdup(id);
+}
+
+void
+gaim_status_type_add_attr(GaimStatusType *status_type, const char *id,
+		const char *name, GaimValue *value)
+{
+	GaimStatusAttr *attr;
+
+	g_return_if_fail(status_type != NULL);
+	g_return_if_fail(id          != NULL);
+	g_return_if_fail(name        != NULL);
+	g_return_if_fail(value       != NULL);
+
+	attr = gaim_status_attr_new(id, name, value);
+
+	status_type->attrs = g_list_append(status_type->attrs, attr);
+}
+
+void
+gaim_status_type_add_attrs_vargs(GaimStatusType *status_type, va_list args)
+{
+	const char *id, *name;
+	GaimValue *value;
+
+	g_return_if_fail(status_type != NULL);
+
+	while ((id = va_arg(args, const char *)) != NULL)
+	{
+		name = va_arg(args, const char *);
+		g_return_if_fail(name != NULL);
+
+		value = va_arg(args, GaimValue *);
+		g_return_if_fail(value != NULL);
+
+		gaim_status_type_add_attr(status_type, id, name, value);
+	}
+}
+
+void
+gaim_status_type_add_attrs(GaimStatusType *status_type, const char *id,
+		const char *name, GaimValue *value, ...)
+{
+	va_list args;
+
+	g_return_if_fail(status_type != NULL);
+	g_return_if_fail(id          != NULL);
+	g_return_if_fail(name        != NULL);
+	g_return_if_fail(value       != NULL);
+
+	/* Add the first attribute */
+	gaim_status_type_add_attr(status_type, id, name, value);
+
+	va_start(args, value);
+	gaim_status_type_add_attrs_vargs(status_type, args);
+	va_end(args);
+}
+
+GaimStatusPrimitive
+gaim_status_type_get_primitive(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, GAIM_STATUS_UNSET);
+
+	return status_type->primitive;
+}
+
+const char *
+gaim_status_type_get_id(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->id;
+}
+
+const char *
+gaim_status_type_get_name(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->name;
+}
+
+gboolean
+gaim_status_type_is_saveable(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return status_type->saveable;
+}
+
+gboolean
+gaim_status_type_is_user_settable(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return status_type->user_settable;
+}
+
+gboolean
+gaim_status_type_is_independent(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return status_type->independent;
+}
+
+gboolean
+gaim_status_type_is_exclusive(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return !status_type->independent;
+}
+
+gboolean
+gaim_status_type_is_available(const GaimStatusType *status_type)
+{
+	GaimStatusPrimitive primitive;
+
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	primitive = gaim_status_type_get_primitive(status_type);
+
+	return (primitive == GAIM_STATUS_AVAILABLE);
+}
+
+const char *
+gaim_status_type_get_primary_attr(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->primary_attr_id;
+}
+
+GaimStatusAttr *
+gaim_status_type_get_attr(const GaimStatusType *status_type, const char *id)
+{
+	GList *l;
+
+	g_return_val_if_fail(status_type != NULL, NULL);
+	g_return_val_if_fail(id          != NULL, NULL);
+
+	for (l = status_type->attrs; l != NULL; l = l->next)
+	{
+		GaimStatusAttr *attr = (GaimStatusAttr *)l->data;
+
+		if (!strcmp(gaim_status_attr_get_id(attr), id))
+			return attr;
+	}
+
+	return NULL;
+}
+
+const GList *
+gaim_status_type_get_attrs(const GaimStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->attrs;
+}
+
+const GaimStatusType *
+gaim_status_type_find_with_id(GList *status_types, const char *id)
+{
+	GaimStatusType *status_type;
+
+	g_return_val_if_fail(id != NULL, NULL);
+
+	while (status_types != NULL)
+	{
+		status_type = status_types->data;
+
+		if (!strcmp(id, status_type->id))
+			return status_type;
+
+		status_types = status_types->next;
+	}
+
+	return NULL;
+}
+
+
+/**************************************************************************
+* GaimStatusAttr API
+**************************************************************************/
+GaimStatusAttr *
+gaim_status_attr_new(const char *id, const char *name, GaimValue *value_type)
+{
+	GaimStatusAttr *attr;
+
+	g_return_val_if_fail(id         != NULL, NULL);
+	g_return_val_if_fail(name       != NULL, NULL);
+	g_return_val_if_fail(value_type != NULL, NULL);
+
+	attr = g_new0(GaimStatusAttr, 1);
+	GAIM_DBUS_REGISTER_POINTER(attr, GaimStatusAttr);
+
+	attr->id         = g_strdup(id);
+	attr->name       = g_strdup(name);
+	attr->value_type = value_type;
+
+	return attr;
+}
+
+void
+gaim_status_attr_destroy(GaimStatusAttr *attr)
+{
+	g_return_if_fail(attr != NULL);
+
+	g_free(attr->id);
+	g_free(attr->name);
+
+	gaim_value_destroy(attr->value_type);
+
+	GAIM_DBUS_UNREGISTER_POINTER(attr);
+	g_free(attr);
+}
+
+const char *
+gaim_status_attr_get_id(const GaimStatusAttr *attr)
+{
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return attr->id;
+}
+
+const char *
+gaim_status_attr_get_name(const GaimStatusAttr *attr)
+{
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return attr->name;
+}
+
+GaimValue *
+gaim_status_attr_get_value(const GaimStatusAttr *attr)
+{
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return attr->value_type;
+}
+
+
+/**************************************************************************
+* GaimStatus API
+**************************************************************************/
+GaimStatus *
+gaim_status_new(GaimStatusType *status_type, GaimPresence *presence)
+{
+	GaimStatus *status;
+	const GList *l;
+
+	g_return_val_if_fail(status_type != NULL, NULL);
+	g_return_val_if_fail(presence    != NULL, NULL);
+
+	status = g_new0(GaimStatus, 1);
+	GAIM_DBUS_REGISTER_POINTER(status, GaimStatus);
+
+	status->type     = status_type;
+	status->presence = presence;
+
+	status->attr_values =
+		g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+		(GDestroyNotify)gaim_value_destroy);
+
+	for (l = gaim_status_type_get_attrs(status_type); l != NULL; l = l->next)
+	{
+		GaimStatusAttr *attr = (GaimStatusAttr *)l->data;
+		GaimValue *value = gaim_status_attr_get_value(attr);
+		GaimValue *new_value = gaim_value_dup(value);
+
+		g_hash_table_insert(status->attr_values,
+							g_strdup(gaim_status_attr_get_id(attr)),
+							new_value);
+	}
+
+	return status;
+}
+
+/*
+ * TODO: If the GaimStatus is in a GaimPresence, then
+ *       remove it from the GaimPresence?
+ */
+void
+gaim_status_destroy(GaimStatus *status)
+{
+	g_return_if_fail(status != NULL);
+
+	g_hash_table_destroy(status->attr_values);
+
+	GAIM_DBUS_UNREGISTER_POINTER(status);
+	g_free(status);
+}
+
+static void
+notify_buddy_status_update(GaimBuddy *buddy, GaimPresence *presence,
+		GaimStatus *old_status, GaimStatus *new_status)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	if (gaim_prefs_get_bool("/core/logging/log_system"))
+	{
+		time_t current_time = time(NULL);
+		const char *buddy_alias = gaim_buddy_get_alias(buddy);
+		char *tmp;
+		GaimLog *log;
+
+		if (old_status != NULL)
+		{
+			tmp = g_strdup_printf(_("%s changed status from %s to %s"), buddy_alias,
+			                      gaim_status_get_name(old_status),
+			                      gaim_status_get_name(new_status));
+		}
+		else
+		{
+			/* old_status == NULL when an independent status is toggled. */
+
+			if (gaim_status_is_active(new_status))
+			{
+				tmp = g_strdup_printf(_("%s is now %s"), buddy_alias,
+				                      gaim_status_get_name(new_status));
+			}
+			else
+			{
+				tmp = g_strdup_printf(_("%s is no longer %s"), buddy_alias,
+				                      gaim_status_get_name(new_status));
+			}
+		}
+
+		log = gaim_account_get_log(buddy->account, FALSE);
+		if (log != NULL)
+		{
+			gaim_log_write(log, GAIM_MESSAGE_SYSTEM, buddy_alias,
+			               current_time, tmp);
+		}
+
+		g_free(tmp);
+	}
+
+	if (ops != NULL && ops->update != NULL)
+		ops->update(gaim_get_blist(), (GaimBlistNode*)buddy);
+}
+
+static void
+notify_status_update(GaimPresence *presence, GaimStatus *old_status,
+					 GaimStatus *new_status)
+{
+	GaimPresenceContext context = gaim_presence_get_context(presence);
+
+	if (context == GAIM_PRESENCE_CONTEXT_ACCOUNT)
+	{
+		GaimAccount *account = gaim_presence_get_account(presence);
+		GaimAccountUiOps *ops = gaim_accounts_get_ui_ops();
+
+		if (gaim_account_get_enabled(account, gaim_core_get_ui()))
+			gaim_prpl_change_account_status(account, old_status, new_status);
+
+		if (ops != NULL && ops->status_changed != NULL)
+		{
+			ops->status_changed(account, new_status);
+		}
+	}
+	else if (context == GAIM_PRESENCE_CONTEXT_BUDDY)
+	{
+		const GList *l;
+
+		for (l = gaim_presence_get_buddies(presence); l != NULL; l = l->next)
+		{
+			notify_buddy_status_update((GaimBuddy *)l->data, presence,
+					old_status, new_status);
+		}
+	}
+}
+
+static void
+status_has_changed(GaimStatus *status)
+{
+	GaimPresence *presence;
+	GaimStatus *old_status;
+
+	presence   = gaim_status_get_presence(status);
+
+	/*
+	 * If this status is exclusive, then we must be setting it to "active."
+	 * Since we are setting it to active, we want to set the currently
+	 * active status to "inactive."
+	 */
+	if (gaim_status_is_exclusive(status))
+	{
+		old_status = gaim_presence_get_active_status(presence);
+		if (old_status != NULL && (old_status != status))
+			old_status->active = FALSE;
+		presence->active_status = status;
+	}
+	else
+		old_status = NULL;
+
+	notify_status_update(presence, old_status, status);
+}
+
+void
+gaim_status_set_active(GaimStatus *status, gboolean active)
+{
+	gaim_status_set_active_with_attrs_list(status, active, NULL);
+}
+
+/*
+ * This used to parse the va_list directly, but now it creates a GList
+ * and passes it to gaim_status_set_active_with_attrs_list().  That
+ * function was created because accounts.c needs to pass a GList of
+ * attributes to the status API.
+ */
+void
+gaim_status_set_active_with_attrs(GaimStatus *status, gboolean active, va_list args)
+{
+	GList *attrs = NULL;
+	const gchar *id;
+	gpointer data;
+
+	if (args != NULL)
+	{
+		while ((id = va_arg(args, const char *)) != NULL)
+		{
+			attrs = g_list_append(attrs, (char *)id);
+			data = va_arg(args, void *);
+			attrs = g_list_append(attrs, data);
+		}
+	}
+	gaim_status_set_active_with_attrs_list(status, active, attrs);
+	g_list_free(attrs);
+}
+
+void
+gaim_status_set_active_with_attrs_list(GaimStatus *status, gboolean active,
+									   const GList *attrs)
+{
+	gboolean changed = FALSE;
+	const GList *l;
+	GList *specified_attr_ids = NULL;
+	GaimStatusType *status_type;
+
+	g_return_if_fail(status != NULL);
+
+	if (!active && gaim_status_is_exclusive(status))
+	{
+		gaim_debug_error("status",
+				   "Cannot deactivate an exclusive status (%s).\n",
+				   gaim_status_get_id(status));
+		return;
+	}
+
+	if (status->active != active)
+	{
+		changed = TRUE;
+	}
+
+	status->active = active;
+
+	/* Set any attributes */
+	l = attrs;
+	while (l != NULL)
+	{
+		const gchar *id;
+		GaimValue *value;
+
+		id = l->data;
+		l = l->next;
+		value = gaim_status_get_attr_value(status, id);
+		if (value == NULL)
+		{
+			gaim_debug_warning("status", "The attribute \"%s\" on the status \"%s\" is "
+							   "not supported.\n", id, status->type->name);
+			/* Skip over the data and move on to the next attribute */
+			l = l->next;
+			continue;
+		}
+
+		specified_attr_ids = g_list_prepend(specified_attr_ids, (gpointer)id);
+
+		if (value->type == GAIM_TYPE_STRING)
+		{
+			const gchar *string_data = l->data;
+			l = l->next;
+			if (((string_data == NULL) && (value->data.string_data == NULL)) ||
+				((string_data != NULL) && (value->data.string_data != NULL) &&
+				!strcmp(string_data, value->data.string_data)))
+			{
+				continue;
+			}
+			gaim_status_set_attr_string(status, id, string_data);
+			changed = TRUE;
+		}
+		else if (value->type == GAIM_TYPE_INT)
+		{
+			int int_data = GPOINTER_TO_INT(l->data);
+			l = l->next;
+			if (int_data == value->data.int_data)
+				continue;
+			gaim_status_set_attr_int(status, id, int_data);
+			changed = TRUE;
+		}
+		else if (value->type == GAIM_TYPE_BOOLEAN)
+		{
+			gboolean boolean_data = GPOINTER_TO_INT(l->data);
+			l = l->next;
+			if (boolean_data == value->data.boolean_data)
+				continue;
+			gaim_status_set_attr_boolean(status, id, boolean_data);
+			changed = TRUE;
+		}
+		else
+		{
+			/* We don't know what the data is--skip over it */
+			l = l->next;
+		}
+	}
+
+	/* Reset any unspecified attributes to their default value */
+	status_type = gaim_status_get_type(status);
+	l = gaim_status_type_get_attrs(status_type);
+	while (l != NULL)
+	{
+		GaimStatusAttr *attr;
+
+		attr = l->data;
+		if (!g_list_find_custom(specified_attr_ids, attr->id, (GCompareFunc)strcmp))
+		{
+			GaimValue *default_value;
+			default_value = gaim_status_attr_get_value(attr);
+			if (default_value->type == GAIM_TYPE_STRING)
+				gaim_status_set_attr_string(status, attr->id,
+						gaim_value_get_string(default_value));
+			else if (default_value->type == GAIM_TYPE_INT)
+				gaim_status_set_attr_int(status, attr->id,
+						gaim_value_get_int(default_value));
+			else if (default_value->type == GAIM_TYPE_BOOLEAN)
+				gaim_status_set_attr_boolean(status, attr->id,
+						gaim_value_get_boolean(default_value));
+			changed = TRUE;
+		}
+
+		l = l->next;
+	}
+	g_list_free(specified_attr_ids);
+
+	if (!changed)
+		return;
+	status_has_changed(status);
+}
+
+void
+gaim_status_set_attr_boolean(GaimStatus *status, const char *id,
+		gboolean value)
+{
+	GaimValue *attr_value;
+
+	g_return_if_fail(status != NULL);
+	g_return_if_fail(id     != NULL);
+
+	/* Make sure this attribute exists and is the correct type. */
+	attr_value = gaim_status_get_attr_value(status, id);
+	g_return_if_fail(attr_value != NULL);
+	g_return_if_fail(gaim_value_get_type(attr_value) == GAIM_TYPE_BOOLEAN);
+
+	gaim_value_set_boolean(attr_value, value);
+}
+
+void
+gaim_status_set_attr_int(GaimStatus *status, const char *id, int value)
+{
+	GaimValue *attr_value;
+
+	g_return_if_fail(status != NULL);
+	g_return_if_fail(id     != NULL);
+
+	/* Make sure this attribute exists and is the correct type. */
+	attr_value = gaim_status_get_attr_value(status, id);
+	g_return_if_fail(attr_value != NULL);
+	g_return_if_fail(gaim_value_get_type(attr_value) == GAIM_TYPE_INT);
+
+	gaim_value_set_int(attr_value, value);
+}
+
+void
+gaim_status_set_attr_string(GaimStatus *status, const char *id,
+		const char *value)
+{
+	GaimValue *attr_value;
+
+	g_return_if_fail(status != NULL);
+	g_return_if_fail(id     != NULL);
+
+	/* Make sure this attribute exists and is the correct type. */
+	attr_value = gaim_status_get_attr_value(status, id);
+	/* This used to be g_return_if_fail, but it's failing a LOT, so
+	 * let's generate a log error for now. */
+	/* g_return_if_fail(attr_value != NULL); */
+	if (attr_value == NULL) {
+		gaim_debug_error("status",
+				 "Attempted to set status attribute '%s' for "
+				 "status '%s', which is not legal.  Fix "
+                                 "this!\n", id,
+				 gaim_status_type_get_name(gaim_status_get_type(status)));
+		return;
+	}
+	g_return_if_fail(gaim_value_get_type(attr_value) == GAIM_TYPE_STRING);
+
+	gaim_value_set_string(attr_value, value);
+}
+
+GaimStatusType *
+gaim_status_get_type(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return status->type;
+}
+
+GaimPresence *
+gaim_status_get_presence(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return status->presence;
+}
+
+const char *
+gaim_status_get_id(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return gaim_status_type_get_id(gaim_status_get_type(status));
+}
+
+const char *
+gaim_status_get_name(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return gaim_status_type_get_name(gaim_status_get_type(status));
+}
+
+gboolean
+gaim_status_is_independent(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return gaim_status_type_is_independent(gaim_status_get_type(status));
+}
+
+gboolean
+gaim_status_is_exclusive(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return gaim_status_type_is_exclusive(gaim_status_get_type(status));
+}
+
+gboolean
+gaim_status_is_available(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return gaim_status_type_is_available(gaim_status_get_type(status));
+}
+
+gboolean
+gaim_status_is_active(const GaimStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return status->active;
+}
+
+gboolean
+gaim_status_is_online(const GaimStatus *status)
+{
+	GaimStatusPrimitive primitive;
+
+	g_return_val_if_fail( status != NULL, FALSE);
+
+	primitive = gaim_status_type_get_primitive(gaim_status_get_type(status));
+
+	return (primitive != GAIM_STATUS_UNSET &&
+			primitive != GAIM_STATUS_OFFLINE);
+}
+
+GaimValue *
+gaim_status_get_attr_value(const GaimStatus *status, const char *id)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+	g_return_val_if_fail(id     != NULL, NULL);
+
+	return (GaimValue *)g_hash_table_lookup(status->attr_values, id);
+}
+
+gboolean
+gaim_status_get_attr_boolean(const GaimStatus *status, const char *id)
+{
+	const GaimValue *value;
+
+	g_return_val_if_fail(status != NULL, FALSE);
+	g_return_val_if_fail(id     != NULL, FALSE);
+
+	if ((value = gaim_status_get_attr_value(status, id)) == NULL)
+		return FALSE;
+
+	g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_BOOLEAN, FALSE);
+
+	return gaim_value_get_boolean(value);
+}
+
+int
+gaim_status_get_attr_int(const GaimStatus *status, const char *id)
+{
+	const GaimValue *value;
+
+	g_return_val_if_fail(status != NULL, 0);
+	g_return_val_if_fail(id     != NULL, 0);
+
+	if ((value = gaim_status_get_attr_value(status, id)) == NULL)
+		return 0;
+
+	g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_INT, 0);
+
+	return gaim_value_get_int(value);
+}
+
+const char *
+gaim_status_get_attr_string(const GaimStatus *status, const char *id)
+{
+	const GaimValue *value;
+
+	g_return_val_if_fail(status != NULL, NULL);
+	g_return_val_if_fail(id     != NULL, NULL);
+
+	if ((value = gaim_status_get_attr_value(status, id)) == NULL)
+		return NULL;
+
+	g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_STRING, NULL);
+
+	return gaim_value_get_string(value);
+}
+
+gint
+gaim_status_compare(const GaimStatus *status1, const GaimStatus *status2)
+{
+	GaimStatusType *type1, *type2;
+	int score1 = 0, score2 = 0;
+
+	if ((status1 == NULL && status2 == NULL) ||
+			(status1 == status2))
+	{
+		return 0;
+	}
+	else if (status1 == NULL)
+		return 1;
+	else if (status2 == NULL)
+		return -1;
+
+	type1 = gaim_status_get_type(status1);
+	type2 = gaim_status_get_type(status2);
+
+	if (gaim_status_is_active(status1))
+		score1 = primitive_scores[gaim_status_type_get_primitive(type1)];
+
+	if (gaim_status_is_active(status2))
+		score2 = primitive_scores[gaim_status_type_get_primitive(type2)];
+
+	if (score1 > score2)
+		return -1;
+	else if (score1 < score2)
+		return 1;
+
+	return 0;
+}
+
+
+/**************************************************************************
+* GaimPresence API
+**************************************************************************/
+GaimPresence *
+gaim_presence_new(GaimPresenceContext context)
+{
+	GaimPresence *presence;
+
+	g_return_val_if_fail(context != GAIM_PRESENCE_CONTEXT_UNSET, NULL);
+
+	presence = g_new0(GaimPresence, 1);
+	GAIM_DBUS_REGISTER_POINTER(presence, GaimPresence);
+
+	presence->context = context;
+
+	presence->status_table =
+		g_hash_table_new_full(g_str_hash, g_str_equal,
+							  g_free, NULL);
+
+	return presence;
+}
+
+GaimPresence *
+gaim_presence_new_for_account(GaimAccount *account)
+{
+	GaimPresence *presence = NULL;
+	g_return_val_if_fail(account != NULL, NULL);
+
+	presence = gaim_presence_new(GAIM_PRESENCE_CONTEXT_ACCOUNT);
+	presence->u.account = account;
+	presence->statuses = gaim_prpl_get_statuses(account, presence);
+
+	return presence;
+}
+
+GaimPresence *
+gaim_presence_new_for_conv(GaimConversation *conv)
+{
+	GaimPresence *presence;
+
+	g_return_val_if_fail(conv != NULL, NULL);
+
+	presence = gaim_presence_new(GAIM_PRESENCE_CONTEXT_CONV);
+	presence->u.chat.conv = conv;
+	/* presence->statuses = gaim_prpl_get_statuses(conv->account, presence); ? */
+
+	return presence;
+}
+
+GaimPresence *
+gaim_presence_new_for_buddy(GaimBuddy *buddy)
+{
+	GaimPresence *presence;
+	GaimStatusBuddyKey *key;
+	GaimAccount *account;
+
+	g_return_val_if_fail(buddy != NULL, NULL);
+	account = buddy->account;
+
+	key = g_new0(GaimStatusBuddyKey, 1);
+	key->account = buddy->account;
+	key->name    = g_strdup(buddy->name);
+
+	presence = g_hash_table_lookup(buddy_presences, key);
+	if (presence == NULL)
+	{
+		presence = gaim_presence_new(GAIM_PRESENCE_CONTEXT_BUDDY);
+
+		presence->u.buddy.name    = g_strdup(buddy->name);
+		presence->u.buddy.account = buddy->account;
+		presence->statuses = gaim_prpl_get_statuses(buddy->account, presence);
+
+		g_hash_table_insert(buddy_presences, key, presence);
+	}
+	else
+	{
+		g_free(key->name);
+		g_free(key);
+	}
+
+	presence->u.buddy.ref_count++;
+	presence->u.buddy.buddies = g_list_append(presence->u.buddy.buddies,
+			buddy);
+
+	return presence;
+}
+
+void
+gaim_presence_destroy(GaimPresence *presence)
+{
+	g_return_if_fail(presence != NULL);
+
+	if (gaim_presence_get_context(presence) == GAIM_PRESENCE_CONTEXT_BUDDY)
+	{
+		GaimStatusBuddyKey key;
+
+		if(presence->u.buddy.ref_count != 0)
+			return;
+
+		key.account = presence->u.buddy.account;
+		key.name    = presence->u.buddy.name;
+
+		g_hash_table_remove(buddy_presences, &key);
+
+		g_free(presence->u.buddy.name);
+	}
+	else if (gaim_presence_get_context(presence) == GAIM_PRESENCE_CONTEXT_CONV)
+	{
+		g_free(presence->u.chat.user);
+	}
+
+	g_list_foreach(presence->statuses, (GFunc)gaim_status_destroy, NULL);
+	g_list_free(presence->statuses);
+
+	g_hash_table_destroy(presence->status_table);
+
+	GAIM_DBUS_UNREGISTER_POINTER(presence);
+	g_free(presence);
+}
+
+/*
+ * TODO: Maybe we should cal gaim_presence_destroy() after we
+ *       decrement the ref count?  I don't see why we should
+ *       make other places do it manually when we can do it here.
+ */
+void
+gaim_presence_remove_buddy(GaimPresence *presence, GaimBuddy *buddy)
+{
+	g_return_if_fail(presence != NULL);
+	g_return_if_fail(buddy    != NULL);
+	g_return_if_fail(gaim_presence_get_context(presence) ==
+			GAIM_PRESENCE_CONTEXT_BUDDY);
+
+	if (g_list_find(presence->u.buddy.buddies, buddy) != NULL)
+	{
+		presence->u.buddy.buddies = g_list_remove(presence->u.buddy.buddies,
+				buddy);
+		presence->u.buddy.ref_count--;
+	}
+}
+
+void
+gaim_presence_add_status(GaimPresence *presence, GaimStatus *status)
+{
+	g_return_if_fail(presence != NULL);
+	g_return_if_fail(status   != NULL);
+
+	presence->statuses = g_list_append(presence->statuses, status);
+
+	g_hash_table_insert(presence->status_table,
+	g_strdup(gaim_status_get_id(status)), status);
+}
+
+void
+gaim_presence_add_list(GaimPresence *presence, const GList *source_list)
+{
+	const GList *l;
+
+	g_return_if_fail(presence    != NULL);
+	g_return_if_fail(source_list != NULL);
+
+	for (l = source_list; l != NULL; l = l->next)
+		gaim_presence_add_status(presence, (GaimStatus *)l->data);
+}
+
+void
+gaim_presence_set_status_active(GaimPresence *presence, const char *status_id,
+		gboolean active)
+{
+	GaimStatus *status;
+
+	g_return_if_fail(presence  != NULL);
+	g_return_if_fail(status_id != NULL);
+
+	status = gaim_presence_get_status(presence, status_id);
+
+	g_return_if_fail(status != NULL);
+	/* TODO: Should we do the following? */
+	/* g_return_if_fail(active == status->active); */
+
+	if (gaim_status_is_exclusive(status))
+	{
+		if (!active)
+		{
+			gaim_debug_warning("status",
+					"Attempted to set a non-independent status "
+					"(%s) inactive. Only independent statuses "
+					"can be specifically marked inactive.",
+					status_id);
+			return;
+		}
+	}
+
+	gaim_status_set_active(status, active);
+}
+
+void
+gaim_presence_switch_status(GaimPresence *presence, const char *status_id)
+{
+	gaim_presence_set_status_active(presence, status_id, TRUE);
+}
+
+static void
+update_buddy_idle(GaimBuddy *buddy, GaimPresence *presence,
+		time_t current_time, gboolean old_idle, gboolean idle)
+{
+	GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
+
+	if (!old_idle && idle)
+	{
+		if (gaim_prefs_get_bool("/core/logging/log_system"))
+		{
+			GaimLog *log = gaim_account_get_log(buddy->account, FALSE);
+
+			if (log != NULL)
+			{
+				char *tmp = g_strdup_printf(_("%s became idle"),
+				gaim_buddy_get_alias(buddy));
+
+				gaim_log_write(log, GAIM_MESSAGE_SYSTEM,
+				gaim_buddy_get_alias(buddy), current_time, tmp);
+				g_free(tmp);
+			}
+		}
+	}
+	else if (old_idle && !idle)
+	{
+		if (gaim_prefs_get_bool("/core/logging/log_system"))
+		{
+			GaimLog *log = gaim_account_get_log(buddy->account, FALSE);
+
+			if (log != NULL)
+			{
+				char *tmp = g_strdup_printf(_("%s became unidle"),
+				gaim_buddy_get_alias(buddy));
+
+				gaim_log_write(log, GAIM_MESSAGE_SYSTEM,
+				gaim_buddy_get_alias(buddy), current_time, tmp);
+				g_free(tmp);
+			}
+		}
+	}
+
+	if (old_idle != idle)
+		gaim_signal_emit(gaim_blist_get_handle(), "buddy-idle-changed", buddy,
+		                 old_idle, idle);
+
+	gaim_contact_invalidate_priority_buddy(gaim_buddy_get_contact(buddy));
+
+	/* Should this be done here? It'd perhaps make more sense to
+	 * connect to buddy-[un]idle signals and update from there
+	 */
+
+	if (ops != NULL && ops->update != NULL)
+		ops->update(gaim_get_blist(), (GaimBlistNode *)buddy);
+}
+
+void
+gaim_presence_set_idle(GaimPresence *presence, gboolean idle, time_t idle_time)
+{
+	gboolean old_idle;
+
+	g_return_if_fail(presence != NULL);
+
+	if (presence->idle == idle && presence->idle_time == idle_time)
+		return;
+
+	old_idle            = presence->idle;
+	presence->idle      = idle;
+	presence->idle_time = (idle ? idle_time : 0);
+
+	if (gaim_presence_get_context(presence) == GAIM_PRESENCE_CONTEXT_BUDDY)
+	{
+		const GList *l;
+		time_t current_time = time(NULL);
+
+		for (l = gaim_presence_get_buddies(presence); l != NULL; l = l->next)
+		{
+			update_buddy_idle((GaimBuddy *)l->data, presence, current_time,
+			old_idle, idle);
+		}
+	}
+	else if(gaim_presence_get_context(presence) == GAIM_PRESENCE_CONTEXT_ACCOUNT)
+	{
+		GaimAccount *account;
+		GaimConnection *gc;
+		GaimPluginProtocolInfo *prpl_info = NULL;
+
+		account = gaim_presence_get_account(presence);
+
+		if (gaim_prefs_get_bool("/core/logging/log_system"))
+		{
+			GaimLog *log = gaim_account_get_log(account, FALSE);
+
+			if (log != NULL)
+			{
+				char *msg;
+
+				if (idle)
+					msg = g_strdup_printf(_("+++ %s became idle"), gaim_account_get_username(account));
+				else
+					msg = g_strdup_printf(_("+++ %s became unidle"), gaim_account_get_username(account));
+				gaim_log_write(log, GAIM_MESSAGE_SYSTEM,
+							   gaim_account_get_username(account),
+							   idle_time, msg);
+				g_free(msg);
+			}
+		}
+
+		gc = gaim_account_get_connection(account);
+
+		if (gc != NULL && gc->prpl != NULL)
+			prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+		if (prpl_info && g_list_find(gaim_connections_get_all(), gc) &&
+				prpl_info->set_idle)
+			prpl_info->set_idle(gc, (idle ? (time(NULL) - idle_time) : 0));
+	}
+}
+
+void
+gaim_presence_set_login_time(GaimPresence *presence, time_t login_time)
+{
+	g_return_if_fail(presence != NULL);
+
+	if (presence->login_time == login_time)
+		return;
+
+	presence->login_time = login_time;
+}
+
+GaimPresenceContext
+gaim_presence_get_context(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, GAIM_PRESENCE_CONTEXT_UNSET);
+
+	return presence->context;
+}
+
+GaimAccount *
+gaim_presence_get_account(const GaimPresence *presence)
+{
+	GaimPresenceContext context;
+
+	g_return_val_if_fail(presence != NULL, NULL);
+
+	context = gaim_presence_get_context(presence);
+
+	g_return_val_if_fail(context == GAIM_PRESENCE_CONTEXT_ACCOUNT ||
+			context == GAIM_PRESENCE_CONTEXT_BUDDY, NULL);
+
+	return presence->u.account;
+}
+
+GaimConversation *
+gaim_presence_get_conversation(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+	g_return_val_if_fail(gaim_presence_get_context(presence) ==
+			GAIM_PRESENCE_CONTEXT_CONV, NULL);
+
+	return presence->u.chat.conv;
+}
+
+const char *
+gaim_presence_get_chat_user(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+	g_return_val_if_fail(gaim_presence_get_context(presence) ==
+			GAIM_PRESENCE_CONTEXT_CONV, NULL);
+
+	return presence->u.chat.user;
+}
+
+const GList *
+gaim_presence_get_buddies(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+	g_return_val_if_fail(gaim_presence_get_context(presence) ==
+			GAIM_PRESENCE_CONTEXT_BUDDY, NULL);
+
+	return presence->u.buddy.buddies;
+}
+
+const GList *
+gaim_presence_get_statuses(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+
+	return presence->statuses;
+}
+
+GaimStatus *
+gaim_presence_get_status(const GaimPresence *presence, const char *status_id)
+{
+	GaimStatus *status;
+	const GList *l = NULL;
+
+	g_return_val_if_fail(presence  != NULL, NULL);
+	g_return_val_if_fail(status_id != NULL, NULL);
+
+	/* What's the purpose of this hash table? */
+	status = (GaimStatus *)g_hash_table_lookup(presence->status_table,
+						   status_id);
+
+	if (status == NULL) {
+		for (l = gaim_presence_get_statuses(presence);
+			 l != NULL && status == NULL; l = l->next)
+		{
+			GaimStatus *temp_status = l->data;
+
+			if (!strcmp(status_id, gaim_status_get_id(temp_status)))
+				status = temp_status;
+		}
+
+		if (status != NULL)
+			g_hash_table_insert(presence->status_table,
+								g_strdup(gaim_status_get_id(status)), status);
+	}
+
+	return status;
+}
+
+GaimStatus *
+gaim_presence_get_active_status(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+
+	return presence->active_status;
+}
+
+gboolean
+gaim_presence_is_available(const GaimPresence *presence)
+{
+	GaimStatus *status;
+
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	status = gaim_presence_get_active_status(presence);
+
+	return ((status != NULL && gaim_status_is_available(status)) &&
+			!gaim_presence_is_idle(presence));
+}
+
+gboolean
+gaim_presence_is_online(const GaimPresence *presence)
+{
+	GaimStatus *status;
+
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	if ((status = gaim_presence_get_active_status(presence)) == NULL)
+		return FALSE;
+
+	return gaim_status_is_online(status);
+}
+
+gboolean
+gaim_presence_is_status_active(const GaimPresence *presence,
+		const char *status_id)
+{
+	GaimStatus *status;
+
+	g_return_val_if_fail(presence  != NULL, FALSE);
+	g_return_val_if_fail(status_id != NULL, FALSE);
+
+	status = gaim_presence_get_status(presence, status_id);
+
+	return (status != NULL && gaim_status_is_active(status));
+}
+
+gboolean
+gaim_presence_is_status_primitive_active(const GaimPresence *presence,
+		GaimStatusPrimitive primitive)
+{
+	GaimStatus *status;
+	GaimStatusType *status_type;
+
+	g_return_val_if_fail(presence  != NULL,              FALSE);
+	g_return_val_if_fail(primitive != GAIM_STATUS_UNSET, FALSE);
+
+	status      = gaim_presence_get_active_status(presence);
+	status_type = gaim_status_get_type(status);
+
+	if (gaim_status_type_get_primitive(status_type) == primitive)
+		return TRUE;
+
+	return FALSE;
+}
+
+gboolean
+gaim_presence_is_idle(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	return gaim_presence_is_online(presence) && presence->idle;
+}
+
+time_t
+gaim_presence_get_idle_time(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, 0);
+
+	return presence->idle_time;
+}
+
+time_t
+gaim_presence_get_login_time(const GaimPresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, 0);
+
+	return gaim_presence_is_online(presence) ? presence->login_time : 0;
+}
+
+gint
+gaim_presence_compare(const GaimPresence *presence1,
+		const GaimPresence *presence2)
+{
+	gboolean idle1, idle2;
+	time_t idle_time_1, idle_time_2;
+	int score1 = 0, score2 = 0;
+	const GList *l;
+
+	if (presence1 == presence2)
+		return 0;
+	else if (presence1 == NULL)
+		return 1;
+	else if (presence2 == NULL)
+		return -1;
+
+	/* Compute the score of the first set of statuses. */
+	for (l = gaim_presence_get_statuses(presence1); l != NULL; l = l->next)
+	{
+		GaimStatus *status = (GaimStatus *)l->data;
+		GaimStatusType *type = gaim_status_get_type(status);
+
+		if (gaim_status_is_active(status))
+			score1 += primitive_scores[gaim_status_type_get_primitive(type)];
+	}
+	score1 += gaim_account_get_int(gaim_presence_get_account(presence1), "score", 0);
+
+	/* Compute the score of the second set of statuses. */
+	for (l = gaim_presence_get_statuses(presence2); l != NULL; l = l->next)
+	{
+		GaimStatus *status = (GaimStatus *)l->data;
+		GaimStatusType *type = gaim_status_get_type(status);
+
+		if (gaim_status_is_active(status))
+			score2 += primitive_scores[gaim_status_type_get_primitive(type)];
+	}
+	score2 += gaim_account_get_int(gaim_presence_get_account(presence2), "score", 0);
+
+	idle1 = gaim_presence_is_idle(presence1);
+	idle2 = gaim_presence_is_idle(presence2);
+
+	if (idle1)
+		score1 += primitive_scores[SCORE_IDLE];
+
+	if (idle2)
+		score2 += primitive_scores[SCORE_IDLE];
+
+	idle_time_1 = time(NULL) - gaim_presence_get_idle_time(presence1);
+	idle_time_2 = time(NULL) - gaim_presence_get_idle_time(presence2);
+
+	if (idle_time_1 > idle_time_2)
+		score1 += primitive_scores[SCORE_IDLE_TIME];
+	else if (idle_time_1 < idle_time_2)
+		score2 += primitive_scores[SCORE_IDLE_TIME];
+
+	if (score1 < score2)
+		return 1;
+	else if (score1 > score2)
+		return -1;
+
+	return 0;
+}
+
+
+/**************************************************************************
+* Status subsystem
+**************************************************************************/
+static void
+score_pref_changed_cb(const char *name, GaimPrefType type,
+					  gconstpointer value, gpointer data)
+{
+	int index = GPOINTER_TO_INT(data);
+
+	primitive_scores[index] = GPOINTER_TO_INT(value);
+}
+
+static guint
+gaim_buddy_presences_hash(gconstpointer key)
+{
+	const GaimStatusBuddyKey *me = key;
+	guint ret;
+	char *str;
+
+	str = g_strdup_printf("%p%s", me->account, me->name);
+	ret = g_str_hash(str);
+	g_free(str);
+
+	return ret;
+}
+
+static gboolean
+gaim_buddy_presences_equal(gconstpointer a, gconstpointer b)
+{
+	GaimStatusBuddyKey *key_a = (GaimStatusBuddyKey *)a;
+	GaimStatusBuddyKey *key_b = (GaimStatusBuddyKey *)b;
+
+	if(key_a->account == key_b->account &&
+			!strcmp(key_a->name, key_b->name))
+		return TRUE;
+	else
+		return FALSE;
+}
+
+static void
+gaim_buddy_presences_key_free(gpointer a)
+{
+	GaimStatusBuddyKey *key = (GaimStatusBuddyKey *)a;
+	g_free(key->name);
+	g_free(key);
+}
+
+void *
+gaim_status_get_handle(void) {
+	static int handle;
+
+	return &handle;
+}
+
+void
+gaim_status_init(void)
+{
+	void *handle = gaim_status_get_handle;
+
+	gaim_prefs_add_none("/core/status");
+	gaim_prefs_add_none("/core/status/scores");
+
+	gaim_prefs_add_int("/core/status/scores/offline",
+			primitive_scores[GAIM_STATUS_OFFLINE]);
+	gaim_prefs_add_int("/core/status/scores/available",
+			primitive_scores[GAIM_STATUS_AVAILABLE]);
+	gaim_prefs_add_int("/core/status/scores/invisible",
+			primitive_scores[GAIM_STATUS_INVISIBLE]);
+	gaim_prefs_add_int("/core/status/scores/away",
+			primitive_scores[GAIM_STATUS_AWAY]);
+	gaim_prefs_add_int("/core/status/scores/extended_away",
+			primitive_scores[GAIM_STATUS_EXTENDED_AWAY]);
+	gaim_prefs_add_int("/core/status/scores/idle",
+			primitive_scores[SCORE_IDLE]);
+
+	gaim_prefs_connect_callback(handle, "/core/status/scores/offline",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(GAIM_STATUS_OFFLINE));
+	gaim_prefs_connect_callback(handle, "/core/status/scores/available",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(GAIM_STATUS_AVAILABLE));
+	gaim_prefs_connect_callback(handle, "/core/status/scores/invisible",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(GAIM_STATUS_INVISIBLE));
+	gaim_prefs_connect_callback(handle, "/core/status/scores/away",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(GAIM_STATUS_AWAY));
+	gaim_prefs_connect_callback(handle, "/core/status/scores/extended_away",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(GAIM_STATUS_EXTENDED_AWAY));
+	gaim_prefs_connect_callback(handle, "/core/status/scores/idle",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(SCORE_IDLE));
+
+	buddy_presences = g_hash_table_new_full(gaim_buddy_presences_hash,
+											gaim_buddy_presences_equal,
+											gaim_buddy_presences_key_free, NULL);
+}
+
+void
+gaim_status_uninit(void)
+{
+	if (buddy_presences != NULL)
+	{
+		g_hash_table_destroy(buddy_presences);
+
+		buddy_presences = NULL;
+	}
+}