Mercurial > pidgin.yaz
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; + } +}