libpurple/status.c

changeset 16238
33bf2fd32108
parent 13039
ae0449c44528
parent 15884
4de1981757fc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/status.c	Mon Apr 16 00:44:33 2007 +0000
@@ -0,0 +1,1765 @@
+/**
+ * @file status.c Status API
+ * @ingroup core
+ *
+ * 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 "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 _PurpleStatusType
+{
+	PurpleStatusPrimitive primitive;
+
+	char *id;
+	char *name;
+	char *primary_attr_id;
+
+	gboolean saveable;
+	gboolean user_settable;
+	gboolean independent;
+
+	GList *attrs;
+};
+
+/**
+ * A status attribute.
+ */
+struct _PurpleStatusAttr
+{
+	char *id;
+	char *name;
+	PurpleValue *value_type;
+};
+
+/**
+ * A list of statuses.
+ */
+struct _PurplePresence
+{
+	PurplePresenceContext context;
+
+	gboolean idle;
+	time_t idle_time;
+	time_t login_time;
+
+	GList *statuses;
+	GHashTable *status_table;
+
+	PurpleStatus *active_status;
+
+	union
+	{
+		PurpleAccount *account;
+
+		struct
+		{
+			PurpleConversation *conv;
+			char *user;
+
+		} chat;
+
+		struct
+		{
+			PurpleAccount *account;
+			char *name;
+			size_t ref_count;
+			GList *buddies;
+
+		} buddy;
+
+	} u;
+};
+
+/**
+ * An active status.
+ */
+struct _PurpleStatus
+{
+	PurpleStatusType *type;
+	PurplePresence *presence;
+
+	const char *title;
+
+	gboolean active;
+
+	GHashTable *attr_values;
+};
+
+typedef struct
+{
+	PurpleAccount *account;
+	char *name;
+} PurpleStatusBuddyKey;
+
+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
+
+/**************************************************************************
+ * PurpleStatusPrimitive API
+ **************************************************************************/
+static struct PurpleStatusPrimitiveMap
+{
+	PurpleStatusPrimitive type;
+	const char *id;
+	const char *name;
+
+} const status_primitive_map[] =
+{
+	{ PURPLE_STATUS_UNSET,           "unset",           N_("Unset")           },
+	{ PURPLE_STATUS_OFFLINE,         "offline",         N_("Offline")         },
+	{ PURPLE_STATUS_AVAILABLE,       "available",       N_("Available")       },
+	{ PURPLE_STATUS_UNAVAILABLE,     "unavailable",     N_("Unavailable")     },
+	{ PURPLE_STATUS_INVISIBLE,       "invisible",       N_("Invisible")       },
+	{ PURPLE_STATUS_AWAY,            "away",            N_("Away")            },
+	{ PURPLE_STATUS_EXTENDED_AWAY,   "extended_away",   N_("Extended Away")   },
+	{ PURPLE_STATUS_MOBILE,          "mobile",          N_("Mobile")          }
+};
+
+const char *
+purple_primitive_get_id_from_type(PurpleStatusPrimitive type)
+{
+    int i;
+
+    for (i = 0; i < PURPLE_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 *
+purple_primitive_get_name_from_type(PurpleStatusPrimitive type)
+{
+    int i;
+
+    for (i = 0; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
+    {
+	if (type == status_primitive_map[i].type)
+		return _(status_primitive_map[i].name);
+    }
+
+    return _(status_primitive_map[0].name);
+}
+
+PurpleStatusPrimitive
+purple_primitive_get_type_from_id(const char *id)
+{
+    int i;
+
+    g_return_val_if_fail(id != NULL, PURPLE_STATUS_UNSET);
+
+    for (i = 0; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
+    {
+        if (!strcmp(id, status_primitive_map[i].id))
+            return status_primitive_map[i].type;
+    }
+
+    return status_primitive_map[0].type;
+}
+
+
+/**************************************************************************
+ * PurpleStatusType API
+ **************************************************************************/
+PurpleStatusType *
+purple_status_type_new_full(PurpleStatusPrimitive primitive, const char *id,
+						  const char *name, gboolean saveable,
+						  gboolean user_settable, gboolean independent)
+{
+	PurpleStatusType *status_type;
+
+	g_return_val_if_fail(primitive != PURPLE_STATUS_UNSET, NULL);
+
+	status_type = g_new0(PurpleStatusType, 1);
+	PURPLE_DBUS_REGISTER_POINTER(status_type, PurpleStatusType);
+
+	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(purple_primitive_get_id_from_type(primitive));
+
+	if (name != NULL)
+		status_type->name = g_strdup(name);
+	else
+		status_type->name = g_strdup(purple_primitive_get_name_from_type(primitive));
+
+	return status_type;
+}
+
+PurpleStatusType *
+purple_status_type_new(PurpleStatusPrimitive primitive, const char *id,
+					 const char *name, gboolean user_settable)
+{
+	g_return_val_if_fail(primitive != PURPLE_STATUS_UNSET, NULL);
+
+	return purple_status_type_new_full(primitive, id, name, FALSE,
+			user_settable, FALSE);
+}
+
+PurpleStatusType *
+purple_status_type_new_with_attrs(PurpleStatusPrimitive primitive,
+		const char *id, const char *name,
+		gboolean saveable, gboolean user_settable,
+		gboolean independent, const char *attr_id,
+		const char *attr_name, PurpleValue *attr_value,
+		...)
+{
+	PurpleStatusType *status_type;
+	va_list args;
+
+	g_return_val_if_fail(primitive  != PURPLE_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 = purple_status_type_new_full(primitive, id, name, saveable,
+			user_settable, independent);
+
+	/* Add the first attribute */
+	purple_status_type_add_attr(status_type, attr_id, attr_name, attr_value);
+
+	va_start(args, attr_value);
+	purple_status_type_add_attrs_vargs(status_type, args);
+	va_end(args);
+
+	return status_type;
+}
+
+void
+purple_status_type_destroy(PurpleStatusType *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)purple_status_attr_destroy, NULL);
+	g_list_free(status_type->attrs);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(status_type);
+	g_free(status_type);
+}
+
+void
+purple_status_type_set_primary_attr(PurpleStatusType *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
+purple_status_type_add_attr(PurpleStatusType *status_type, const char *id,
+		const char *name, PurpleValue *value)
+{
+	PurpleStatusAttr *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 = purple_status_attr_new(id, name, value);
+
+	status_type->attrs = g_list_append(status_type->attrs, attr);
+}
+
+void
+purple_status_type_add_attrs_vargs(PurpleStatusType *status_type, va_list args)
+{
+	const char *id, *name;
+	PurpleValue *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, PurpleValue *);
+		g_return_if_fail(value != NULL);
+
+		purple_status_type_add_attr(status_type, id, name, value);
+	}
+}
+
+void
+purple_status_type_add_attrs(PurpleStatusType *status_type, const char *id,
+		const char *name, PurpleValue *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 */
+	purple_status_type_add_attr(status_type, id, name, value);
+
+	va_start(args, value);
+	purple_status_type_add_attrs_vargs(status_type, args);
+	va_end(args);
+}
+
+PurpleStatusPrimitive
+purple_status_type_get_primitive(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, PURPLE_STATUS_UNSET);
+
+	return status_type->primitive;
+}
+
+const char *
+purple_status_type_get_id(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->id;
+}
+
+const char *
+purple_status_type_get_name(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->name;
+}
+
+gboolean
+purple_status_type_is_saveable(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return status_type->saveable;
+}
+
+gboolean
+purple_status_type_is_user_settable(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return status_type->user_settable;
+}
+
+gboolean
+purple_status_type_is_independent(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return status_type->independent;
+}
+
+gboolean
+purple_status_type_is_exclusive(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	return !status_type->independent;
+}
+
+gboolean
+purple_status_type_is_available(const PurpleStatusType *status_type)
+{
+	PurpleStatusPrimitive primitive;
+
+	g_return_val_if_fail(status_type != NULL, FALSE);
+
+	primitive = purple_status_type_get_primitive(status_type);
+
+	return (primitive == PURPLE_STATUS_AVAILABLE);
+}
+
+const char *
+purple_status_type_get_primary_attr(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->primary_attr_id;
+}
+
+PurpleStatusAttr *
+purple_status_type_get_attr(const PurpleStatusType *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)
+	{
+		PurpleStatusAttr *attr = (PurpleStatusAttr *)l->data;
+
+		if (!strcmp(purple_status_attr_get_id(attr), id))
+			return attr;
+	}
+
+	return NULL;
+}
+
+const GList *
+purple_status_type_get_attrs(const PurpleStatusType *status_type)
+{
+	g_return_val_if_fail(status_type != NULL, NULL);
+
+	return status_type->attrs;
+}
+
+const PurpleStatusType *
+purple_status_type_find_with_id(GList *status_types, const char *id)
+{
+	PurpleStatusType *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;
+}
+
+
+/**************************************************************************
+* PurpleStatusAttr API
+**************************************************************************/
+PurpleStatusAttr *
+purple_status_attr_new(const char *id, const char *name, PurpleValue *value_type)
+{
+	PurpleStatusAttr *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(PurpleStatusAttr, 1);
+	PURPLE_DBUS_REGISTER_POINTER(attr, PurpleStatusAttr);
+
+	attr->id         = g_strdup(id);
+	attr->name       = g_strdup(name);
+	attr->value_type = value_type;
+
+	return attr;
+}
+
+void
+purple_status_attr_destroy(PurpleStatusAttr *attr)
+{
+	g_return_if_fail(attr != NULL);
+
+	g_free(attr->id);
+	g_free(attr->name);
+
+	purple_value_destroy(attr->value_type);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(attr);
+	g_free(attr);
+}
+
+const char *
+purple_status_attr_get_id(const PurpleStatusAttr *attr)
+{
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return attr->id;
+}
+
+const char *
+purple_status_attr_get_name(const PurpleStatusAttr *attr)
+{
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return attr->name;
+}
+
+PurpleValue *
+purple_status_attr_get_value(const PurpleStatusAttr *attr)
+{
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return attr->value_type;
+}
+
+
+/**************************************************************************
+* PurpleStatus API
+**************************************************************************/
+PurpleStatus *
+purple_status_new(PurpleStatusType *status_type, PurplePresence *presence)
+{
+	PurpleStatus *status;
+	const GList *l;
+
+	g_return_val_if_fail(status_type != NULL, NULL);
+	g_return_val_if_fail(presence    != NULL, NULL);
+
+	status = g_new0(PurpleStatus, 1);
+	PURPLE_DBUS_REGISTER_POINTER(status, PurpleStatus);
+
+	status->type     = status_type;
+	status->presence = presence;
+
+	status->attr_values =
+		g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+		(GDestroyNotify)purple_value_destroy);
+
+	for (l = purple_status_type_get_attrs(status_type); l != NULL; l = l->next)
+	{
+		PurpleStatusAttr *attr = (PurpleStatusAttr *)l->data;
+		PurpleValue *value = purple_status_attr_get_value(attr);
+		PurpleValue *new_value = purple_value_dup(value);
+
+		g_hash_table_insert(status->attr_values,
+							g_strdup(purple_status_attr_get_id(attr)),
+							new_value);
+	}
+
+	return status;
+}
+
+/*
+ * TODO: If the PurpleStatus is in a PurplePresence, then
+ *       remove it from the PurplePresence?
+ */
+void
+purple_status_destroy(PurpleStatus *status)
+{
+	g_return_if_fail(status != NULL);
+
+	g_hash_table_destroy(status->attr_values);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(status);
+	g_free(status);
+}
+
+static void
+notify_buddy_status_update(PurpleBuddy *buddy, PurplePresence *presence,
+		PurpleStatus *old_status, PurpleStatus *new_status)
+{
+	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
+
+	if (purple_prefs_get_bool("/core/logging/log_system"))
+	{
+		time_t current_time = time(NULL);
+		const char *buddy_alias = purple_buddy_get_alias(buddy);
+		char *tmp;
+		PurpleLog *log;
+
+		if (old_status != NULL)
+		{
+			tmp = g_strdup_printf(_("%s changed status from %s to %s"), buddy_alias,
+			                      purple_status_get_name(old_status),
+			                      purple_status_get_name(new_status));
+		}
+		else
+		{
+			/* old_status == NULL when an independent status is toggled. */
+
+			if (purple_status_is_active(new_status))
+			{
+				tmp = g_strdup_printf(_("%s is now %s"), buddy_alias,
+				                      purple_status_get_name(new_status));
+			}
+			else
+			{
+				tmp = g_strdup_printf(_("%s is no longer %s"), buddy_alias,
+				                      purple_status_get_name(new_status));
+			}
+		}
+
+		log = purple_account_get_log(buddy->account, FALSE);
+		if (log != NULL)
+		{
+			purple_log_write(log, PURPLE_MESSAGE_SYSTEM, buddy_alias,
+			               current_time, tmp);
+		}
+
+		g_free(tmp);
+	}
+
+	if (ops != NULL && ops->update != NULL)
+		ops->update(purple_get_blist(), (PurpleBlistNode*)buddy);
+}
+
+static void
+notify_status_update(PurplePresence *presence, PurpleStatus *old_status,
+					 PurpleStatus *new_status)
+{
+	PurplePresenceContext context = purple_presence_get_context(presence);
+
+	if (context == PURPLE_PRESENCE_CONTEXT_ACCOUNT)
+	{
+		PurpleAccount *account = purple_presence_get_account(presence);
+		PurpleAccountUiOps *ops = purple_accounts_get_ui_ops();
+
+		if (purple_account_get_enabled(account, purple_core_get_ui()))
+			purple_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 == PURPLE_PRESENCE_CONTEXT_BUDDY)
+	{
+		const GList *l;
+
+		for (l = purple_presence_get_buddies(presence); l != NULL; l = l->next)
+		{
+			notify_buddy_status_update((PurpleBuddy *)l->data, presence,
+					old_status, new_status);
+		}
+	}
+}
+
+static void
+status_has_changed(PurpleStatus *status)
+{
+	PurplePresence *presence;
+	PurpleStatus *old_status;
+
+	presence   = purple_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 (purple_status_is_exclusive(status))
+	{
+		old_status = purple_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
+purple_status_set_active(PurpleStatus *status, gboolean active)
+{
+	purple_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 purple_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
+purple_status_set_active_with_attrs(PurpleStatus *status, gboolean active, va_list args)
+{
+	GList *attrs = NULL;
+	const gchar *id;
+	gpointer data;
+
+	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);
+	}
+	purple_status_set_active_with_attrs_list(status, active, attrs);
+	g_list_free(attrs);
+}
+
+void
+purple_status_set_active_with_attrs_list(PurpleStatus *status, gboolean active,
+									   const GList *attrs)
+{
+	gboolean changed = FALSE;
+	const GList *l;
+	GList *specified_attr_ids = NULL;
+	PurpleStatusType *status_type;
+
+	g_return_if_fail(status != NULL);
+
+	if (!active && purple_status_is_exclusive(status))
+	{
+		purple_debug_error("status",
+				   "Cannot deactivate an exclusive status (%s).\n",
+				   purple_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;
+		PurpleValue *value;
+
+		id = l->data;
+		l = l->next;
+		value = purple_status_get_attr_value(status, id);
+		if (value == NULL)
+		{
+			purple_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 == PURPLE_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;
+			}
+			purple_status_set_attr_string(status, id, string_data);
+			changed = TRUE;
+		}
+		else if (value->type == PURPLE_TYPE_INT)
+		{
+			int int_data = GPOINTER_TO_INT(l->data);
+			l = l->next;
+			if (int_data == value->data.int_data)
+				continue;
+			purple_status_set_attr_int(status, id, int_data);
+			changed = TRUE;
+		}
+		else if (value->type == PURPLE_TYPE_BOOLEAN)
+		{
+			gboolean boolean_data = GPOINTER_TO_INT(l->data);
+			l = l->next;
+			if (boolean_data == value->data.boolean_data)
+				continue;
+			purple_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 = purple_status_get_type(status);
+	l = purple_status_type_get_attrs(status_type);
+	while (l != NULL)
+	{
+		PurpleStatusAttr *attr;
+
+		attr = l->data;
+		if (!g_list_find_custom(specified_attr_ids, attr->id, (GCompareFunc)strcmp))
+		{
+			PurpleValue *default_value;
+			default_value = purple_status_attr_get_value(attr);
+			if (default_value->type == PURPLE_TYPE_STRING)
+				purple_status_set_attr_string(status, attr->id,
+						purple_value_get_string(default_value));
+			else if (default_value->type == PURPLE_TYPE_INT)
+				purple_status_set_attr_int(status, attr->id,
+						purple_value_get_int(default_value));
+			else if (default_value->type == PURPLE_TYPE_BOOLEAN)
+				purple_status_set_attr_boolean(status, attr->id,
+						purple_value_get_boolean(default_value));
+			changed = TRUE;
+		}
+
+		l = l->next;
+	}
+	g_list_free(specified_attr_ids);
+
+	if (!changed)
+		return;
+	status_has_changed(status);
+}
+
+void
+purple_status_set_attr_boolean(PurpleStatus *status, const char *id,
+		gboolean value)
+{
+	PurpleValue *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 = purple_status_get_attr_value(status, id);
+	g_return_if_fail(attr_value != NULL);
+	g_return_if_fail(purple_value_get_type(attr_value) == PURPLE_TYPE_BOOLEAN);
+
+	purple_value_set_boolean(attr_value, value);
+}
+
+void
+purple_status_set_attr_int(PurpleStatus *status, const char *id, int value)
+{
+	PurpleValue *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 = purple_status_get_attr_value(status, id);
+	g_return_if_fail(attr_value != NULL);
+	g_return_if_fail(purple_value_get_type(attr_value) == PURPLE_TYPE_INT);
+
+	purple_value_set_int(attr_value, value);
+}
+
+void
+purple_status_set_attr_string(PurpleStatus *status, const char *id,
+		const char *value)
+{
+	PurpleValue *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 = purple_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) {
+		purple_debug_error("status",
+				 "Attempted to set status attribute '%s' for "
+				 "status '%s', which is not legal.  Fix "
+                                 "this!\n", id,
+				 purple_status_type_get_name(purple_status_get_type(status)));
+		return;
+	}
+	g_return_if_fail(purple_value_get_type(attr_value) == PURPLE_TYPE_STRING);
+
+	purple_value_set_string(attr_value, value);
+}
+
+PurpleStatusType *
+purple_status_get_type(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return status->type;
+}
+
+PurplePresence *
+purple_status_get_presence(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return status->presence;
+}
+
+const char *
+purple_status_get_id(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return purple_status_type_get_id(purple_status_get_type(status));
+}
+
+const char *
+purple_status_get_name(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+
+	return purple_status_type_get_name(purple_status_get_type(status));
+}
+
+gboolean
+purple_status_is_independent(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return purple_status_type_is_independent(purple_status_get_type(status));
+}
+
+gboolean
+purple_status_is_exclusive(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return purple_status_type_is_exclusive(purple_status_get_type(status));
+}
+
+gboolean
+purple_status_is_available(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return purple_status_type_is_available(purple_status_get_type(status));
+}
+
+gboolean
+purple_status_is_active(const PurpleStatus *status)
+{
+	g_return_val_if_fail(status != NULL, FALSE);
+
+	return status->active;
+}
+
+gboolean
+purple_status_is_online(const PurpleStatus *status)
+{
+	PurpleStatusPrimitive primitive;
+
+	g_return_val_if_fail( status != NULL, FALSE);
+
+	primitive = purple_status_type_get_primitive(purple_status_get_type(status));
+
+	return (primitive != PURPLE_STATUS_UNSET &&
+			primitive != PURPLE_STATUS_OFFLINE);
+}
+
+PurpleValue *
+purple_status_get_attr_value(const PurpleStatus *status, const char *id)
+{
+	g_return_val_if_fail(status != NULL, NULL);
+	g_return_val_if_fail(id     != NULL, NULL);
+
+	return (PurpleValue *)g_hash_table_lookup(status->attr_values, id);
+}
+
+gboolean
+purple_status_get_attr_boolean(const PurpleStatus *status, const char *id)
+{
+	const PurpleValue *value;
+
+	g_return_val_if_fail(status != NULL, FALSE);
+	g_return_val_if_fail(id     != NULL, FALSE);
+
+	if ((value = purple_status_get_attr_value(status, id)) == NULL)
+		return FALSE;
+
+	g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN, FALSE);
+
+	return purple_value_get_boolean(value);
+}
+
+int
+purple_status_get_attr_int(const PurpleStatus *status, const char *id)
+{
+	const PurpleValue *value;
+
+	g_return_val_if_fail(status != NULL, 0);
+	g_return_val_if_fail(id     != NULL, 0);
+
+	if ((value = purple_status_get_attr_value(status, id)) == NULL)
+		return 0;
+
+	g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_INT, 0);
+
+	return purple_value_get_int(value);
+}
+
+const char *
+purple_status_get_attr_string(const PurpleStatus *status, const char *id)
+{
+	const PurpleValue *value;
+
+	g_return_val_if_fail(status != NULL, NULL);
+	g_return_val_if_fail(id     != NULL, NULL);
+
+	if ((value = purple_status_get_attr_value(status, id)) == NULL)
+		return NULL;
+
+	g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_STRING, NULL);
+
+	return purple_value_get_string(value);
+}
+
+gint
+purple_status_compare(const PurpleStatus *status1, const PurpleStatus *status2)
+{
+	PurpleStatusType *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 = purple_status_get_type(status1);
+	type2 = purple_status_get_type(status2);
+
+	if (purple_status_is_active(status1))
+		score1 = primitive_scores[purple_status_type_get_primitive(type1)];
+
+	if (purple_status_is_active(status2))
+		score2 = primitive_scores[purple_status_type_get_primitive(type2)];
+
+	if (score1 > score2)
+		return -1;
+	else if (score1 < score2)
+		return 1;
+
+	return 0;
+}
+
+
+/**************************************************************************
+* PurplePresence API
+**************************************************************************/
+PurplePresence *
+purple_presence_new(PurplePresenceContext context)
+{
+	PurplePresence *presence;
+
+	g_return_val_if_fail(context != PURPLE_PRESENCE_CONTEXT_UNSET, NULL);
+
+	presence = g_new0(PurplePresence, 1);
+	PURPLE_DBUS_REGISTER_POINTER(presence, PurplePresence);
+
+	presence->context = context;
+
+	presence->status_table =
+		g_hash_table_new_full(g_str_hash, g_str_equal,
+							  g_free, NULL);
+
+	return presence;
+}
+
+PurplePresence *
+purple_presence_new_for_account(PurpleAccount *account)
+{
+	PurplePresence *presence = NULL;
+	g_return_val_if_fail(account != NULL, NULL);
+
+	presence = purple_presence_new(PURPLE_PRESENCE_CONTEXT_ACCOUNT);
+	presence->u.account = account;
+	presence->statuses = purple_prpl_get_statuses(account, presence);
+
+	return presence;
+}
+
+PurplePresence *
+purple_presence_new_for_conv(PurpleConversation *conv)
+{
+	PurplePresence *presence;
+
+	g_return_val_if_fail(conv != NULL, NULL);
+
+	presence = purple_presence_new(PURPLE_PRESENCE_CONTEXT_CONV);
+	presence->u.chat.conv = conv;
+	/* presence->statuses = purple_prpl_get_statuses(conv->account, presence); ? */
+
+	return presence;
+}
+
+PurplePresence *
+purple_presence_new_for_buddy(PurpleBuddy *buddy)
+{
+	PurplePresence *presence;
+	PurpleStatusBuddyKey *key;
+	PurpleAccount *account;
+
+	g_return_val_if_fail(buddy != NULL, NULL);
+	account = buddy->account;
+
+	key = g_new0(PurpleStatusBuddyKey, 1);
+	key->account = buddy->account;
+	key->name    = g_strdup(buddy->name);
+
+	presence = g_hash_table_lookup(buddy_presences, key);
+	if (presence == NULL)
+	{
+		presence = purple_presence_new(PURPLE_PRESENCE_CONTEXT_BUDDY);
+
+		presence->u.buddy.name    = g_strdup(buddy->name);
+		presence->u.buddy.account = buddy->account;
+		presence->statuses = purple_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
+purple_presence_destroy(PurplePresence *presence)
+{
+	g_return_if_fail(presence != NULL);
+
+	if (purple_presence_get_context(presence) == PURPLE_PRESENCE_CONTEXT_BUDDY)
+	{
+		PurpleStatusBuddyKey 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 (purple_presence_get_context(presence) == PURPLE_PRESENCE_CONTEXT_CONV)
+	{
+		g_free(presence->u.chat.user);
+	}
+
+	g_list_foreach(presence->statuses, (GFunc)purple_status_destroy, NULL);
+	g_list_free(presence->statuses);
+
+	g_hash_table_destroy(presence->status_table);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(presence);
+	g_free(presence);
+}
+
+/*
+ * TODO: Maybe we should cal purple_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
+purple_presence_remove_buddy(PurplePresence *presence, PurpleBuddy *buddy)
+{
+	g_return_if_fail(presence != NULL);
+	g_return_if_fail(buddy    != NULL);
+	g_return_if_fail(purple_presence_get_context(presence) ==
+			PURPLE_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
+purple_presence_add_status(PurplePresence *presence, PurpleStatus *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(purple_status_get_id(status)), status);
+}
+
+void
+purple_presence_add_list(PurplePresence *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)
+		purple_presence_add_status(presence, (PurpleStatus *)l->data);
+}
+
+void
+purple_presence_set_status_active(PurplePresence *presence, const char *status_id,
+		gboolean active)
+{
+	PurpleStatus *status;
+
+	g_return_if_fail(presence  != NULL);
+	g_return_if_fail(status_id != NULL);
+
+	status = purple_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 (purple_status_is_exclusive(status))
+	{
+		if (!active)
+		{
+			purple_debug_warning("status",
+					"Attempted to set a non-independent status "
+					"(%s) inactive. Only independent statuses "
+					"can be specifically marked inactive.",
+					status_id);
+			return;
+		}
+	}
+
+	purple_status_set_active(status, active);
+}
+
+void
+purple_presence_switch_status(PurplePresence *presence, const char *status_id)
+{
+	purple_presence_set_status_active(presence, status_id, TRUE);
+}
+
+static void
+update_buddy_idle(PurpleBuddy *buddy, PurplePresence *presence,
+		time_t current_time, gboolean old_idle, gboolean idle)
+{
+	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
+
+	if (!old_idle && idle)
+	{
+		if (purple_prefs_get_bool("/core/logging/log_system"))
+		{
+			PurpleLog *log = purple_account_get_log(buddy->account, FALSE);
+
+			if (log != NULL)
+			{
+				char *tmp = g_strdup_printf(_("%s became idle"),
+				purple_buddy_get_alias(buddy));
+
+				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
+				purple_buddy_get_alias(buddy), current_time, tmp);
+				g_free(tmp);
+			}
+		}
+	}
+	else if (old_idle && !idle)
+	{
+		if (purple_prefs_get_bool("/core/logging/log_system"))
+		{
+			PurpleLog *log = purple_account_get_log(buddy->account, FALSE);
+
+			if (log != NULL)
+			{
+				char *tmp = g_strdup_printf(_("%s became unidle"),
+				purple_buddy_get_alias(buddy));
+
+				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
+				purple_buddy_get_alias(buddy), current_time, tmp);
+				g_free(tmp);
+			}
+		}
+	}
+
+	if (old_idle != idle)
+		purple_signal_emit(purple_blist_get_handle(), "buddy-idle-changed", buddy,
+		                 old_idle, idle);
+
+	purple_contact_invalidate_priority_buddy(purple_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(purple_get_blist(), (PurpleBlistNode *)buddy);
+}
+
+void
+purple_presence_set_idle(PurplePresence *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 (purple_presence_get_context(presence) == PURPLE_PRESENCE_CONTEXT_BUDDY)
+	{
+		const GList *l;
+		time_t current_time = time(NULL);
+
+		for (l = purple_presence_get_buddies(presence); l != NULL; l = l->next)
+		{
+			update_buddy_idle((PurpleBuddy *)l->data, presence, current_time,
+			old_idle, idle);
+		}
+	}
+	else if(purple_presence_get_context(presence) == PURPLE_PRESENCE_CONTEXT_ACCOUNT)
+	{
+		PurpleAccount *account;
+		PurpleConnection *gc;
+		PurplePluginProtocolInfo *prpl_info = NULL;
+
+		account = purple_presence_get_account(presence);
+
+		if (purple_prefs_get_bool("/core/logging/log_system"))
+		{
+			PurpleLog *log = purple_account_get_log(account, FALSE);
+
+			if (log != NULL)
+			{
+				char *msg;
+
+				if (idle)
+					msg = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account));
+				else
+					msg = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account));
+				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
+							   purple_account_get_username(account),
+							   idle_time, msg);
+				g_free(msg);
+			}
+		}
+
+		gc = purple_account_get_connection(account);
+
+		if (gc != NULL && PURPLE_CONNECTION_IS_CONNECTED(gc) &&
+				gc->prpl != NULL)
+			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+		if (prpl_info && prpl_info->set_idle)
+			prpl_info->set_idle(gc, (idle ? (time(NULL) - idle_time) : 0));
+	}
+}
+
+void
+purple_presence_set_login_time(PurplePresence *presence, time_t login_time)
+{
+	g_return_if_fail(presence != NULL);
+
+	if (presence->login_time == login_time)
+		return;
+
+	presence->login_time = login_time;
+}
+
+PurplePresenceContext
+purple_presence_get_context(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, PURPLE_PRESENCE_CONTEXT_UNSET);
+
+	return presence->context;
+}
+
+PurpleAccount *
+purple_presence_get_account(const PurplePresence *presence)
+{
+	PurplePresenceContext context;
+
+	g_return_val_if_fail(presence != NULL, NULL);
+
+	context = purple_presence_get_context(presence);
+
+	g_return_val_if_fail(context == PURPLE_PRESENCE_CONTEXT_ACCOUNT ||
+			context == PURPLE_PRESENCE_CONTEXT_BUDDY, NULL);
+
+	return presence->u.account;
+}
+
+PurpleConversation *
+purple_presence_get_conversation(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+	g_return_val_if_fail(purple_presence_get_context(presence) ==
+			PURPLE_PRESENCE_CONTEXT_CONV, NULL);
+
+	return presence->u.chat.conv;
+}
+
+const char *
+purple_presence_get_chat_user(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+	g_return_val_if_fail(purple_presence_get_context(presence) ==
+			PURPLE_PRESENCE_CONTEXT_CONV, NULL);
+
+	return presence->u.chat.user;
+}
+
+const GList *
+purple_presence_get_buddies(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+	g_return_val_if_fail(purple_presence_get_context(presence) ==
+			PURPLE_PRESENCE_CONTEXT_BUDDY, NULL);
+
+	return presence->u.buddy.buddies;
+}
+
+const GList *
+purple_presence_get_statuses(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+
+	return presence->statuses;
+}
+
+PurpleStatus *
+purple_presence_get_status(const PurplePresence *presence, const char *status_id)
+{
+	PurpleStatus *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 = (PurpleStatus *)g_hash_table_lookup(presence->status_table,
+						   status_id);
+
+	if (status == NULL) {
+		for (l = purple_presence_get_statuses(presence);
+			 l != NULL && status == NULL; l = l->next)
+		{
+			PurpleStatus *temp_status = l->data;
+
+			if (!strcmp(status_id, purple_status_get_id(temp_status)))
+				status = temp_status;
+		}
+
+		if (status != NULL)
+			g_hash_table_insert(presence->status_table,
+								g_strdup(purple_status_get_id(status)), status);
+	}
+
+	return status;
+}
+
+PurpleStatus *
+purple_presence_get_active_status(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, NULL);
+
+	return presence->active_status;
+}
+
+gboolean
+purple_presence_is_available(const PurplePresence *presence)
+{
+	PurpleStatus *status;
+
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	status = purple_presence_get_active_status(presence);
+
+	return ((status != NULL && purple_status_is_available(status)) &&
+			!purple_presence_is_idle(presence));
+}
+
+gboolean
+purple_presence_is_online(const PurplePresence *presence)
+{
+	PurpleStatus *status;
+
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	if ((status = purple_presence_get_active_status(presence)) == NULL)
+		return FALSE;
+
+	return purple_status_is_online(status);
+}
+
+gboolean
+purple_presence_is_status_active(const PurplePresence *presence,
+		const char *status_id)
+{
+	PurpleStatus *status;
+
+	g_return_val_if_fail(presence  != NULL, FALSE);
+	g_return_val_if_fail(status_id != NULL, FALSE);
+
+	status = purple_presence_get_status(presence, status_id);
+
+	return (status != NULL && purple_status_is_active(status));
+}
+
+gboolean
+purple_presence_is_status_primitive_active(const PurplePresence *presence,
+		PurpleStatusPrimitive primitive)
+{
+	const GList *l;
+
+	g_return_val_if_fail(presence  != NULL,              FALSE);
+	g_return_val_if_fail(primitive != PURPLE_STATUS_UNSET, FALSE);
+
+	for (l = purple_presence_get_statuses(presence);
+	     l != NULL; l = l->next)	{
+		PurpleStatus *temp_status = l->data;
+		PurpleStatusType *type = purple_status_get_type(temp_status);
+
+		if (purple_status_type_get_primitive(type) == primitive &&
+		    purple_status_is_active(temp_status))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+gboolean
+purple_presence_is_idle(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, FALSE);
+
+	return purple_presence_is_online(presence) && presence->idle;
+}
+
+time_t
+purple_presence_get_idle_time(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, 0);
+
+	return presence->idle_time;
+}
+
+time_t
+purple_presence_get_login_time(const PurplePresence *presence)
+{
+	g_return_val_if_fail(presence != NULL, 0);
+
+	return purple_presence_is_online(presence) ? presence->login_time : 0;
+}
+
+gint
+purple_presence_compare(const PurplePresence *presence1,
+		const PurplePresence *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 = purple_presence_get_statuses(presence1); l != NULL; l = l->next)
+	{
+		PurpleStatus *status = (PurpleStatus *)l->data;
+		PurpleStatusType *type = purple_status_get_type(status);
+
+		if (purple_status_is_active(status))
+			score1 += primitive_scores[purple_status_type_get_primitive(type)];
+	}
+	score1 += purple_account_get_int(purple_presence_get_account(presence1), "score", 0);
+
+	/* Compute the score of the second set of statuses. */
+	for (l = purple_presence_get_statuses(presence2); l != NULL; l = l->next)
+	{
+		PurpleStatus *status = (PurpleStatus *)l->data;
+		PurpleStatusType *type = purple_status_get_type(status);
+
+		if (purple_status_is_active(status))
+			score2 += primitive_scores[purple_status_type_get_primitive(type)];
+	}
+	score2 += purple_account_get_int(purple_presence_get_account(presence2), "score", 0);
+
+	idle1 = purple_presence_is_idle(presence1);
+	idle2 = purple_presence_is_idle(presence2);
+
+	if (idle1)
+		score1 += primitive_scores[SCORE_IDLE];
+
+	if (idle2)
+		score2 += primitive_scores[SCORE_IDLE];
+
+	idle_time_1 = time(NULL) - purple_presence_get_idle_time(presence1);
+	idle_time_2 = time(NULL) - purple_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, PurplePrefType type,
+					  gconstpointer value, gpointer data)
+{
+	int index = GPOINTER_TO_INT(data);
+
+	primitive_scores[index] = GPOINTER_TO_INT(value);
+}
+
+static guint
+purple_buddy_presences_hash(gconstpointer key)
+{
+	const PurpleStatusBuddyKey *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
+purple_buddy_presences_equal(gconstpointer a, gconstpointer b)
+{
+	PurpleStatusBuddyKey *key_a = (PurpleStatusBuddyKey *)a;
+	PurpleStatusBuddyKey *key_b = (PurpleStatusBuddyKey *)b;
+
+	if(key_a->account == key_b->account &&
+			!strcmp(key_a->name, key_b->name))
+		return TRUE;
+	else
+		return FALSE;
+}
+
+static void
+purple_buddy_presences_key_free(gpointer a)
+{
+	PurpleStatusBuddyKey *key = (PurpleStatusBuddyKey *)a;
+	g_free(key->name);
+	g_free(key);
+}
+
+void *
+purple_status_get_handle(void) {
+	static int handle;
+
+	return &handle;
+}
+
+void
+purple_status_init(void)
+{
+	void *handle = purple_status_get_handle;
+
+	purple_prefs_add_none("/core/status");
+	purple_prefs_add_none("/core/status/scores");
+
+	purple_prefs_add_int("/core/status/scores/offline",
+			primitive_scores[PURPLE_STATUS_OFFLINE]);
+	purple_prefs_add_int("/core/status/scores/available",
+			primitive_scores[PURPLE_STATUS_AVAILABLE]);
+	purple_prefs_add_int("/core/status/scores/invisible",
+			primitive_scores[PURPLE_STATUS_INVISIBLE]);
+	purple_prefs_add_int("/core/status/scores/away",
+			primitive_scores[PURPLE_STATUS_AWAY]);
+	purple_prefs_add_int("/core/status/scores/extended_away",
+			primitive_scores[PURPLE_STATUS_EXTENDED_AWAY]);
+	purple_prefs_add_int("/core/status/scores/idle",
+			primitive_scores[SCORE_IDLE]);
+
+	purple_prefs_connect_callback(handle, "/core/status/scores/offline",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(PURPLE_STATUS_OFFLINE));
+	purple_prefs_connect_callback(handle, "/core/status/scores/available",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE));
+	purple_prefs_connect_callback(handle, "/core/status/scores/invisible",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE));
+	purple_prefs_connect_callback(handle, "/core/status/scores/away",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(PURPLE_STATUS_AWAY));
+	purple_prefs_connect_callback(handle, "/core/status/scores/extended_away",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(PURPLE_STATUS_EXTENDED_AWAY));
+	purple_prefs_connect_callback(handle, "/core/status/scores/idle",
+			score_pref_changed_cb,
+			GINT_TO_POINTER(SCORE_IDLE));
+
+	buddy_presences = g_hash_table_new_full(purple_buddy_presences_hash,
+											purple_buddy_presences_equal,
+											purple_buddy_presences_key_free, NULL);
+}
+
+void
+purple_status_uninit(void)
+{
+	if (buddy_presences != NULL)
+	{
+		g_hash_table_destroy(buddy_presences);
+
+		buddy_presences = NULL;
+	}
+}

mercurial