/*
* roster.c - Source for Gabble roster helper
*
* Copyright (C) 2006 Collabora Ltd.
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <dbus/dbus-glib.h>
#include <string.h>
#include "telepathy-interfaces.h"
#include "tp-channel-factory-iface.h"
#include "debug.h"
#include "gabble-connection.h"
#include "gabble-roster-channel.h"
#include "namespaces.h"
#include "roster.h"
#include "util.h"
#include "gabble_enums.h"
#define DBUS_API_SUBJECT_TO_CHANGE
#define DEBUG_FLAG GABBLE_DEBUG_ROSTER
#define GOOGLE_ROSTER_VERSION "2"
#ifdef DEBUG_FLAG
//#define DEBUG(format, ...)
#define DEBUGGING 0
#define NODE_DEBUG(n, s)
#endif /* DEBUG_FLAG */
/* Properties */
enum
{
PROP_CONNECTION = 1,
LAST_PROPERTY
};
/* signal enum */
enum
{
NICKNAME_UPDATE,
LAST_SIGNAL
#ifdef EMULATOR
= LAST_SIGNAL_ROSTER
#endif
};
#ifdef EMULATOR
#include "libgabble_wsd_solution.h"
GET_STATIC_ARRAY_FROM_TLS(signals,gabble_roster,guint)
#define signals (GET_WSD_VAR_NAME(signals,gabble_roster, s)())
#else
static guint signals[LAST_SIGNAL] = {0};
#endif
typedef struct _GabbleRosterPrivate GabbleRosterPrivate;
struct _GabbleRosterPrivate
{
GabbleConnection *conn;
LmMessageHandler *iq_cb;
LmMessageHandler *presence_cb;
GHashTable *channels;
GHashTable *items;
gboolean roster_received;
gboolean dispose_has_run;
};
typedef enum
{
GOOGLE_ITEM_TYPE_NORMAL = 0,
GOOGLE_ITEM_TYPE_BLOCKED,
GOOGLE_ITEM_TYPE_HIDDEN,
GOOGLE_ITEM_TYPE_PINNED
} GoogleItemType;
typedef struct _GabbleRosterItem GabbleRosterItem;
struct _GabbleRosterItem
{
GabbleRosterSubscription subscription;
gboolean ask_subscribe;
GoogleItemType google_type;
gchar *name;
gchar **groups;
};
static void gabble_roster_factory_iface_init ();
static void gabble_roster_init (GabbleRoster *roster);
static GObject * gabble_roster_constructor (GType type, guint n_props, GObjectConstructParam *props);
static void gabble_roster_dispose (GObject *object);
static void gabble_roster_finalize (GObject *object);
static void gabble_roster_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec);
static void gabble_roster_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec);
static void _gabble_roster_item_free (GabbleRosterItem *item);
#ifndef EMULATOR
G_DEFINE_TYPE_WITH_CODE (GabbleRoster, gabble_roster, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE, gabble_roster_factory_iface_init));
#else
GET_STATIC_VAR_FROM_TLS(gabble_roster_parent_class,gabble_roster,gpointer)
#define gabble_roster_parent_class (*GET_WSD_VAR_NAME(gabble_roster_parent_class,gabble_roster,s)())
GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_roster,GType)
#define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_roster,s)())
static void gabble_roster_init (GabbleRoster *self);
static void gabble_roster_class_init (GabbleRosterClass *klass);
static void gabble_roster_class_intern_init (gpointer klass) { gabble_roster_parent_class = g_type_class_peek_parent (klass); gabble_roster_class_init ((GabbleRosterClass*) klass); } EXPORT_C GType gabble_roster_get_type (void) { if ((g_define_type_id == 0)) { static const GTypeInfo g_define_type_info = { sizeof (GabbleRosterClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_roster_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleRoster), 0, (GInstanceInitFunc) gabble_roster_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleRoster"), &g_define_type_info, (GTypeFlags) 0); { { static const GInterfaceInfo g_implement_interface_info = { (GInterfaceInitFunc) gabble_roster_factory_iface_init }; g_type_add_interface_static (g_define_type_id, tp_channel_factory_iface_get_type(), &g_implement_interface_info); } ; } } return g_define_type_id; } ;
#endif
#define GABBLE_ROSTER_GET_PRIVATE(o) ((GabbleRosterPrivate*) ((o)->priv));
static void
gabble_roster_class_init (GabbleRosterClass *gabble_roster_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (gabble_roster_class);
GParamSpec *param_spec;
g_type_class_add_private (gabble_roster_class, sizeof (GabbleRosterPrivate));
object_class->constructor = gabble_roster_constructor;
object_class->dispose = gabble_roster_dispose;
object_class->finalize = gabble_roster_finalize;
object_class->get_property = gabble_roster_get_property;
object_class->set_property = gabble_roster_set_property;
param_spec = g_param_spec_object ("connection", "GabbleConnection object",
"Gabble connection object that owns this "
"XMPP roster object.",
GABBLE_TYPE_CONNECTION,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB);
g_object_class_install_property (object_class,
PROP_CONNECTION,
param_spec);
signals[NICKNAME_UPDATE] = g_signal_new (
"nickname-update",
G_TYPE_FROM_CLASS (gabble_roster_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
}
static void
gabble_roster_init (GabbleRoster *obj)
{
GabbleRosterPrivate *priv =
G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_ROSTER, GabbleRosterPrivate);
obj->priv = priv;
priv->channels = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
g_object_unref);
priv->items = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify) _gabble_roster_item_free);
}
static GObject *
gabble_roster_constructor (GType type, guint n_props,
GObjectConstructParam *props)
{
GObject *obj;
/* GabbleRosterPrivate *priv; */
obj = G_OBJECT_CLASS (gabble_roster_parent_class)->
constructor (type, n_props, props);
/* priv = GABBLE_ROSTER_GET_PRIVATE (GABBLE_ROSTER (obj)); */
return obj;
}
void
gabble_roster_dispose (GObject *object)
{
GabbleRoster *self = GABBLE_ROSTER (object);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (self);
if (priv->dispose_has_run)
return;
gabble_debug (DEBUG_FLAG, "dispose called");
priv->dispose_has_run = TRUE;
g_assert (priv->iq_cb == NULL);
g_assert (priv->presence_cb == NULL);
tp_channel_factory_iface_close_all (TP_CHANNEL_FACTORY_IFACE (object));
g_assert (priv->channels == NULL);
if (G_OBJECT_CLASS (gabble_roster_parent_class)->dispose)
G_OBJECT_CLASS (gabble_roster_parent_class)->dispose (object);
}
static void
item_handle_unref_foreach (gpointer key, gpointer data, gpointer user_data)
{
GabbleHandle handle = (GabbleHandle) key;
GabbleRosterPrivate *priv = (GabbleRosterPrivate *) user_data;
gabble_handle_unref (priv->conn->handles, TP_HANDLE_TYPE_CONTACT, handle);
}
void
gabble_roster_finalize (GObject *object)
{
GabbleRoster *self = GABBLE_ROSTER (object);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (self);
gabble_debug (DEBUG_FLAG, "called with %p", object);
g_hash_table_foreach (priv->items, item_handle_unref_foreach, priv);
g_hash_table_destroy (priv->items);
G_OBJECT_CLASS (gabble_roster_parent_class)->finalize (object);
}
static void
gabble_roster_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GabbleRoster *roster = GABBLE_ROSTER (object);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
switch (property_id) {
case PROP_CONNECTION:
g_value_set_object (value, priv->conn);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gabble_roster_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GabbleRoster *roster = GABBLE_ROSTER (object);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
switch (property_id) {
case PROP_CONNECTION:
priv->conn = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
_gabble_roster_item_free (GabbleRosterItem *item)
{
g_assert (item != NULL);
g_strfreev (item->groups);
g_free (item->name);
g_free (item);
}
static const gchar *
_subscription_to_string (GabbleRosterSubscription subscription)
{
switch (subscription)
{
case GABBLE_ROSTER_SUBSCRIPTION_NONE:
return "none";
case GABBLE_ROSTER_SUBSCRIPTION_FROM:
return "from";
case GABBLE_ROSTER_SUBSCRIPTION_TO:
return "to";
case GABBLE_ROSTER_SUBSCRIPTION_BOTH:
return "both";
case GABBLE_ROSTER_SUBSCRIPTION_REMOVE:
return "remove";
default:
g_assert_not_reached ();
return NULL;
}
}
static GabbleRosterSubscription
_parse_item_subscription (LmMessageNode *item_node)
{
const gchar *subscription;
g_assert (item_node != NULL);
subscription = lm_message_node_get_attribute (item_node, "subscription");
if (NULL == subscription || 0 == strcmp (subscription, "none"))
return GABBLE_ROSTER_SUBSCRIPTION_NONE;
else if (0 == strcmp (subscription, "from"))
return GABBLE_ROSTER_SUBSCRIPTION_FROM;
else if (0 == strcmp (subscription, "to"))
return GABBLE_ROSTER_SUBSCRIPTION_TO;
else if (0 == strcmp (subscription, "both"))
return GABBLE_ROSTER_SUBSCRIPTION_BOTH;
else if (0 == strcmp (subscription, "remove"))
return GABBLE_ROSTER_SUBSCRIPTION_REMOVE;
else
{
NODE_DEBUG (item_node, "got unexpected subscription value");
return GABBLE_ROSTER_SUBSCRIPTION_NONE;
}
}
static gchar **
_parse_item_groups (LmMessageNode *item_node)
{
LmMessageNode *group_node;
GPtrArray *strv;
strv = g_ptr_array_new ();
for (group_node = item_node->children;
NULL != group_node;
group_node = group_node->next)
{
if (0 != strcmp (group_node->name, "group"))
continue;
if (NULL == group_node->value)
continue;
g_ptr_array_add (strv, g_strdup (group_node->value));
}
g_ptr_array_add (strv, NULL);
return (gchar **) g_ptr_array_free (strv, FALSE);
}
static const gchar *
_google_item_type_to_string (GoogleItemType google_type)
{
switch (google_type)
{
case GOOGLE_ITEM_TYPE_NORMAL:
return NULL;
case GOOGLE_ITEM_TYPE_BLOCKED:
return "B";
case GOOGLE_ITEM_TYPE_HIDDEN:
return "H";
case GOOGLE_ITEM_TYPE_PINNED:
return "P";
}
g_assert_not_reached ();
return NULL;
}
static GoogleItemType
_parse_google_item_type (LmMessageNode *item_node)
{
const gchar *google_type;
g_assert (item_node != NULL);
google_type = lm_message_node_get_attribute (item_node, "gr:t");
if (NULL == google_type)
return GOOGLE_ITEM_TYPE_NORMAL;
else if (!g_strdiff (google_type, "B"))
return GOOGLE_ITEM_TYPE_BLOCKED;
else if (!g_strdiff (google_type, "H"))
return GOOGLE_ITEM_TYPE_HIDDEN;
else if (!g_strdiff (google_type, "P"))
return GOOGLE_ITEM_TYPE_PINNED;
NODE_DEBUG (item_node, "got unexpected google contact type value");
return GOOGLE_ITEM_TYPE_NORMAL;
}
static gboolean
_google_roster_item_should_keep (LmMessageNode *item_node,
GabbleRosterItem *item)
{
const gchar *attr;
/* skip automatically subscribed Google roster items */
attr = lm_message_node_get_attribute (item_node, "gr:autosub");
if (!g_strdiff (attr, "true"))
return FALSE;
/* skip email addresses that replied to an invite */
attr = lm_message_node_get_attribute (item_node, "gr:alias-for");
if (attr != NULL)
return FALSE;
/* allow items that we've requested a subscription from */
if (item->ask_subscribe)
return TRUE;
if (item->subscription != GABBLE_ROSTER_SUBSCRIPTION_NONE)
return TRUE;
/* discard anything else */
return FALSE;
}
static GabbleRosterItem *
_gabble_roster_item_get (GabbleRoster *roster,
GabbleHandle handle)
{
GabbleRosterItem *item;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (roster != NULL);
g_assert (GABBLE_IS_ROSTER (roster));
g_assert (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL));
item = g_hash_table_lookup (priv->items, GINT_TO_POINTER (handle));
if (NULL == item)
{
item = g_new0 (GabbleRosterItem, 1);
gabble_handle_ref (priv->conn->handles, TP_HANDLE_TYPE_CONTACT, handle);
g_hash_table_insert (priv->items, GINT_TO_POINTER (handle), item);
}
return item;
}
static void
_gabble_roster_item_remove (GabbleRoster *roster,
GabbleHandle handle)
{
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (roster != NULL);
g_assert (GABBLE_IS_ROSTER (roster));
g_assert (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL));
g_hash_table_remove (priv->items, GINT_TO_POINTER (handle));
gabble_handle_unref (priv->conn->handles, TP_HANDLE_TYPE_CONTACT, handle);
}
static GabbleRosterItem *
_gabble_roster_item_update (GabbleRoster *roster,
GabbleHandle handle,
LmMessageNode *node,
gboolean google_roster_mode)
{
GabbleRosterItem *item;
const gchar *ask, *name;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (roster != NULL);
g_assert (GABBLE_IS_ROSTER (roster));
g_assert (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL));
g_assert (node != NULL);
item = _gabble_roster_item_get (roster, handle);
item->subscription = _parse_item_subscription (node);
ask = lm_message_node_get_attribute (node, "ask");
if (NULL != ask && 0 == strcmp (ask, "subscribe"))
item->ask_subscribe = TRUE;
else
item->ask_subscribe = FALSE;
if (google_roster_mode)
{
item->google_type = _parse_google_item_type (node);
/* discard roster item if strange, just hide it if it's hidden */
if (item->google_type == GOOGLE_ITEM_TYPE_HIDDEN)
{
gabble_debug (DEBUG_FLAG, "Google roster: caching hidden contact %d (%s)", handle,
lm_message_node_get_attribute (node, "jid"));
item->subscription = GABBLE_ROSTER_SUBSCRIPTION_NONE;
}
else if (!_google_roster_item_should_keep (node, item))
{
gabble_debug (DEBUG_FLAG, "Google roster: discarding odd contact %d (%s)", handle,
lm_message_node_get_attribute (node, "jid"));
item->subscription = GABBLE_ROSTER_SUBSCRIPTION_REMOVE;
}
}
if (item->subscription == GABBLE_ROSTER_SUBSCRIPTION_REMOVE)
name = NULL;
else
name = lm_message_node_get_attribute (node, "name");
if (g_strdiff (item->name, name))
{
g_free (item->name);
item->name = g_strdup (name);
gabble_debug (DEBUG_FLAG, "name for handle %d changed to %s", handle, name);
g_signal_emit (G_OBJECT (roster), signals[NICKNAME_UPDATE], 0, handle);
}
g_strfreev (item->groups);
item->groups = _parse_item_groups (node);
return item;
}
#ifdef ENABLE_DEBUG
static gchar *
_gabble_roster_item_dump (GabbleRosterItem *item)
{
GString *str;
g_assert (item != NULL);
str = g_string_new ("subscription: ");
g_string_append (str, _subscription_to_string (item->subscription));
if (item->ask_subscribe)
g_string_append (str, ", ask: subscribe");
if (item->google_type != GOOGLE_ITEM_TYPE_NORMAL)
g_string_append_printf (str, ", google_type: %s",
_google_item_type_to_string (item->google_type));
if (item->name)
g_string_append_printf (str, ", name: %s", item->name);
if (item->groups)
{
gchar **tmp;
g_string_append (str, ", groups: { ");
for (tmp = item->groups; *tmp; tmp++)
{
g_string_append (str, *tmp);
g_string_append_c (str, ' ');
}
g_string_append (str, "}");
}
return g_string_free (str, FALSE);
}
#endif /* ENABLE_DEBUG */
static LmMessage *
_gabble_roster_message_new (GabbleRoster *roster,
LmMessageSubType sub_type,
LmMessageNode **query_return)
{
LmMessage *message;
LmMessageNode *query_node;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (roster != NULL);
g_assert (GABBLE_IS_ROSTER (roster));
message = lm_message_new_with_sub_type (NULL,
LM_MESSAGE_TYPE_IQ,
sub_type);
query_node = lm_message_node_add_child (message->node, "query", NULL);
if (NULL != query_return)
*query_return = query_node;
lm_message_node_set_attribute (query_node, "xmlns", NS_ROSTER);
if (priv->conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER)
{
lm_message_node_set_attributes (query_node,
"xmlns:gr", NS_GOOGLE_ROSTER,
"gr:ext", GOOGLE_ROSTER_VERSION,
"gr:include", "all",
NULL);
}
return message;
}
static LmMessage *
_gabble_roster_item_to_message (GabbleRoster *roster,
GabbleHandle handle,
LmMessageNode **item_return)
{
const gchar *jid;
LmMessage *message;
LmMessageNode *query_node, *item_node;
GabbleRosterItem *item;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (roster != NULL);
g_assert (GABBLE_IS_ROSTER (roster));
g_assert (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL));
item = _gabble_roster_item_get (roster, handle);
message = _gabble_roster_message_new (roster, LM_MESSAGE_SUB_TYPE_SET,
&query_node);
item_node = lm_message_node_add_child (query_node, "item", NULL);
if (NULL != item_return)
*item_return = item_node;
jid = gabble_handle_inspect (priv->conn->handles, TP_HANDLE_TYPE_CONTACT,
handle);
lm_message_node_set_attribute (item_node, "jid", jid);
if (item->subscription != GABBLE_ROSTER_SUBSCRIPTION_NONE)
{
const gchar *subscription = _subscription_to_string (item->subscription);
lm_message_node_set_attribute (item_node, "subscription", subscription);
}
if (item->subscription == GABBLE_ROSTER_SUBSCRIPTION_REMOVE)
goto DONE;
if ((priv->conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER) &&
item->google_type != GOOGLE_ITEM_TYPE_NORMAL)
lm_message_node_set_attribute (item_node, "gr:t",
_google_item_type_to_string (item->google_type));
if (item->ask_subscribe)
lm_message_node_set_attribute (item_node, "ask", "subscribe");
if (item->name)
lm_message_node_set_attribute (item_node, "name", item->name);
if (item->groups)
{
gchar **tmp;
for (tmp = item->groups; *tmp; tmp++)
{
lm_message_node_add_child (item_node, "group", *tmp);
}
}
DONE:
return message;
}
static GabbleRosterChannel *
_gabble_roster_create_channel (GabbleRoster *roster,
GabbleHandle handle)
{
const char *name;
char *object_path;
GabbleRosterChannel *chan;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (priv->channels != NULL);
g_assert (g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle)) == NULL);
name = gabble_handle_inspect (priv->conn->handles, TP_HANDLE_TYPE_LIST, handle);
object_path = g_strdup_printf ("%s/RosterChannel/%s", priv->conn->object_path, name);
chan = g_object_new (GABBLE_TYPE_ROSTER_CHANNEL,
"connection", priv->conn,
"object-path", object_path,
"handle", handle,
NULL);
gabble_debug (DEBUG_FLAG, "created %s", object_path);
g_free (object_path);
g_hash_table_insert (priv->channels, GINT_TO_POINTER (handle), chan);
if (priv->roster_received)
{
gabble_debug (DEBUG_FLAG, "roster already received, emitting signal for %s list channel",
name);
g_signal_emit_by_name (roster, "new-channel", chan);
}
else
{
gabble_debug (DEBUG_FLAG, "roster not yet received, not emitting signal for %s list channel",
name);
}
return chan;
}
static GabbleRosterChannel *
_gabble_roster_get_channel (GabbleRoster *roster,
GabbleHandle handle)
{
GabbleRosterChannel *chan;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (priv->channels != NULL);
g_assert (gabble_handle_is_valid (priv->conn->handles, TP_HANDLE_TYPE_LIST, handle, NULL));
chan = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle));
if (chan == NULL)
chan = _gabble_roster_create_channel (roster, handle);
return chan;
}
static void
_gabble_roster_emit_one (gpointer key,
gpointer value,
gpointer data)
{
GabbleRoster *roster = GABBLE_ROSTER (data);
GabbleRosterChannel *chan = GABBLE_ROSTER_CHANNEL (value);
#ifdef ENABLE_DEBUG
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
GabbleHandle handle = GPOINTER_TO_INT (key);
const gchar *name = gabble_handle_inspect (priv->conn->handles, TP_HANDLE_TYPE_LIST, handle);
gabble_debug (DEBUG_FLAG, "roster now received, emitting signal signal for %s list channel",
name);
#endif
g_signal_emit_by_name (roster, "new-channel", chan);
}
static void
_gabble_roster_received (GabbleRoster *roster)
{
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (priv->channels != NULL);
if (!priv->roster_received)
{
priv->roster_received = TRUE;
g_hash_table_foreach (priv->channels, _gabble_roster_emit_one, roster);
}
}
/**
* gabble_roster_iq_cb
*
* Called by loudmouth when we get an incoming <iq>. This handler
* is concerned only with roster queries, and allows other handlers
* if queries other than rosters are received.
*/
static LmHandlerResult
gabble_roster_iq_cb (LmMessageHandler *handler,
LmConnection *lmconn,
LmMessage *message,
gpointer user_data)
{
const gchar *from;
gboolean google_roster = FALSE;
LmMessageNode *iq_node, *query_node;
LmMessageSubType sub_type;
GabbleRoster *roster = GABBLE_ROSTER (user_data);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (lmconn == priv->conn->lmconn);
if (priv->channels == NULL)
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
iq_node = lm_message_get_node (message);
query_node = lm_message_node_get_child_with_namespace (iq_node, "query",
NS_ROSTER);
if (query_node == NULL)
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
from = lm_message_node_get_attribute (message->node, "from");
if (from != NULL)
{
GabbleHandle sender;
sender = gabble_handle_for_contact (priv->conn->handles,
from, FALSE);
if (sender != priv->conn->self_handle)
{
NODE_DEBUG (iq_node, "discarding roster IQ which is not from "
"ourselves or the server");
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
}
if (priv->conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER)
{
const char *gr_ext;
gr_ext = lm_message_node_get_attribute (query_node, "gr:ext");
if (!g_strdiff (gr_ext, GOOGLE_ROSTER_VERSION))
google_roster = TRUE;
}
sub_type = lm_message_get_sub_type (message);
/* if this is a result, it's from our initial query. if it's a set,
* it's a roster push. either way, parse the items. */
switch (sub_type)
{
LmMessageNode *item_node;
GIntSet *pub_add, *pub_rem,
*sub_add, *sub_rem, *sub_rp,
*known_add, *known_rem,
*deny_add, *deny_rem;
GArray *removed;
GabbleHandle handle;
GabbleRosterChannel *chan;
guint i;
case LM_MESSAGE_SUB_TYPE_RESULT:
case LM_MESSAGE_SUB_TYPE_SET:
/* asymmetry is because we don't get locally pending subscription
* requests via <roster>, we get it via <presence> */
pub_add = g_intset_new ();
pub_rem = g_intset_new ();
sub_add = g_intset_new ();
sub_rem = g_intset_new ();
sub_rp = g_intset_new ();
known_add = g_intset_new ();
known_rem = g_intset_new ();
removed = g_array_new (FALSE, FALSE, sizeof (GabbleHandle));
if (google_roster)
{
deny_add = g_intset_new ();
deny_rem = g_intset_new ();
}
else
{
deny_add = NULL;
deny_rem = NULL;
}
/* get the publish channel first because we need it when processing */
handle = GABBLE_LIST_HANDLE_PUBLISH;
chan = _gabble_roster_get_channel (roster, handle);
/* iterate every sub-node, which we expect to be <item>s */
for (item_node = query_node->children;
item_node;
item_node = item_node->next)
{
const char *jid;
GabbleRosterItem *item;
if (strcmp (item_node->name, "item"))
{
NODE_DEBUG (item_node, "query sub-node is not item, skipping");
continue;
}
jid = lm_message_node_get_attribute (item_node, "jid");
if (!jid)
{
NODE_DEBUG (item_node, "item node has no jid, skipping");
continue;
}
handle = gabble_handle_for_contact (priv->conn->handles, jid, FALSE);
if (handle == 0)
{
NODE_DEBUG (item_node, "item jid is malformed, skipping");
continue;
}
item = _gabble_roster_item_update (roster, handle, item_node,
google_roster);
#ifdef ENABLE_DEBUG
if (DEBUGGING)
{
gchar *dump = _gabble_roster_item_dump (item);
gabble_debug (DEBUG_FLAG, "jid: %s, %s", jid, dump);
g_free (dump);
}
#endif
/* handle publish list changes */
switch (item->subscription)
{
case GABBLE_ROSTER_SUBSCRIPTION_FROM:
case GABBLE_ROSTER_SUBSCRIPTION_BOTH:
g_intset_add (pub_add, handle);
break;
case GABBLE_ROSTER_SUBSCRIPTION_NONE:
case GABBLE_ROSTER_SUBSCRIPTION_TO:
case GABBLE_ROSTER_SUBSCRIPTION_REMOVE:
/* publish channel is a bit odd, the roster item doesn't tell us
* if someone is awaiting our approval - we get this via presence
* type=subscribe, so we have to not remove them if they're
* already local_pending in our publish channel */
if (!handle_set_is_member (chan->group.local_pending, handle))
{
g_intset_add (pub_rem, handle);
}
break;
default:
g_assert_not_reached ();
}
/* handle subscribe list changes */
switch (item->subscription)
{
case GABBLE_ROSTER_SUBSCRIPTION_TO:
case GABBLE_ROSTER_SUBSCRIPTION_BOTH:
g_intset_add (sub_add, handle);
break;
case GABBLE_ROSTER_SUBSCRIPTION_NONE:
case GABBLE_ROSTER_SUBSCRIPTION_FROM:
if (item->ask_subscribe)
g_intset_add (sub_rp, handle);
else
g_intset_add (sub_rem, handle);
break;
case GABBLE_ROSTER_SUBSCRIPTION_REMOVE:
g_intset_add (sub_rem, handle);
break;
default:
g_assert_not_reached ();
}
/* handle known list changes */
switch (item->subscription)
{
case GABBLE_ROSTER_SUBSCRIPTION_NONE:
case GABBLE_ROSTER_SUBSCRIPTION_TO:
case GABBLE_ROSTER_SUBSCRIPTION_FROM:
case GABBLE_ROSTER_SUBSCRIPTION_BOTH:
if (item->google_type == GOOGLE_ITEM_TYPE_HIDDEN)
g_intset_add (known_rem, handle);
else
g_intset_add (known_add, handle);
break;
case GABBLE_ROSTER_SUBSCRIPTION_REMOVE:
g_intset_add (known_rem, handle);
break;
default:
g_assert_not_reached ();
}
/* handle deny list changes */
if (google_roster)
{
switch (item->subscription)
{
case GABBLE_ROSTER_SUBSCRIPTION_NONE:
case GABBLE_ROSTER_SUBSCRIPTION_TO:
case GABBLE_ROSTER_SUBSCRIPTION_FROM:
case GABBLE_ROSTER_SUBSCRIPTION_BOTH:
if (item->google_type == GOOGLE_ITEM_TYPE_BLOCKED)
g_intset_add (deny_add, handle);
else
g_intset_add (deny_rem, handle);
break;
case GABBLE_ROSTER_SUBSCRIPTION_REMOVE:
g_intset_add (deny_rem, handle);
break;
default:
g_assert_not_reached ();
}
}
/* delay removing items from roster until signals have been emitted;
* otherwise handles go out of scope! */
if (GABBLE_ROSTER_SUBSCRIPTION_REMOVE == item->subscription)
g_array_append_val (removed, handle);
}
/* chan was initialised to the publish channel before the for loop */
gabble_debug (DEBUG_FLAG, "calling change members on publish channel");
gabble_group_mixin_change_members (G_OBJECT (chan),
"", pub_add, pub_rem, NULL, NULL, 0, 0);
handle = GABBLE_LIST_HANDLE_SUBSCRIBE;
chan = _gabble_roster_get_channel (roster, handle);
gabble_debug (DEBUG_FLAG, "calling change members on subscribe channel");
gabble_group_mixin_change_members (G_OBJECT (chan),
"", sub_add, sub_rem, NULL, sub_rp, 0, 0);
handle = GABBLE_LIST_HANDLE_KNOWN;
chan = _gabble_roster_get_channel (roster, handle);
gabble_debug (DEBUG_FLAG, "calling change members on known channel");
gabble_group_mixin_change_members (G_OBJECT (chan),
"", known_add, known_rem, NULL, NULL, 0, 0);
if (google_roster)
{
handle = GABBLE_LIST_HANDLE_DENY;
chan = _gabble_roster_get_channel (roster, handle);
gabble_debug (DEBUG_FLAG, "calling change members on deny channel");
gabble_group_mixin_change_members (G_OBJECT (chan),
"", deny_add, deny_rem, NULL, NULL, 0, 0);
g_intset_destroy (deny_add);
g_intset_destroy (deny_rem);
}
for (i = 0; i < removed->len; i++)
_gabble_roster_item_remove (roster,
g_array_index (removed, GabbleHandle, i));
g_intset_destroy (pub_add);
g_intset_destroy (pub_rem);
g_intset_destroy (sub_add);
g_intset_destroy (sub_rem);
g_intset_destroy (sub_rp);
g_intset_destroy (known_add);
g_intset_destroy (known_rem);
g_array_free (removed, TRUE);
break;
default:
NODE_DEBUG (iq_node, "unhandled roster IQ");
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
switch (sub_type)
{
case LM_MESSAGE_SUB_TYPE_RESULT:
/* result means it's a roster push, so the roster is now complete and we
* can emit signals */
_gabble_roster_received (roster);
break;
case LM_MESSAGE_SUB_TYPE_SET:
/* acknowledge roster */
_gabble_connection_acknowledge_set_iq (priv->conn, message);
break;
default:
break;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
static void
_gabble_roster_send_presence_ack (GabbleRoster *roster,
const gchar *from,
LmMessageSubType sub_type,
gboolean changed)
{
LmMessage *reply;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
if (!changed)
{
gabble_debug (DEBUG_FLAG, "not sending ack to avoid loop with buggy server");
return;
}
switch (sub_type)
{
case LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE:
sub_type = LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED;
break;
case LM_MESSAGE_SUB_TYPE_SUBSCRIBED:
sub_type = LM_MESSAGE_SUB_TYPE_SUBSCRIBE;
break;
case LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED:
sub_type = LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE;
break;
default:
g_assert_not_reached();
return;
}
reply = lm_message_new_with_sub_type (from,
LM_MESSAGE_TYPE_PRESENCE,
sub_type);
_gabble_connection_send (priv->conn, reply, NULL);
lm_message_unref (reply);
}
/**
* connection_presence_roster_cb:
* @handler: #LmMessageHandler for this message
* @connection: #LmConnection that originated the message
* @message: the presence message
* @user_data: callback data
*
* Called by loudmouth when we get an incoming <presence>.
*/
static LmHandlerResult
gabble_roster_presence_cb (LmMessageHandler *handler,
LmConnection *lmconn,
LmMessage *message,
gpointer user_data)
{
LmMessageNode *pres_node, *child_node;
const char *from;
LmMessageSubType sub_type;
GIntSet *tmp;
GabbleHandle handle;
const gchar *status_message = NULL;
GabbleRosterChannel *chan = NULL;
gboolean changed;
GabbleRoster *roster = GABBLE_ROSTER (user_data);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_assert (lmconn == priv->conn->lmconn);
if (priv->channels == NULL)
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
pres_node = lm_message_get_node (message);
from = lm_message_node_get_attribute (pres_node, "from");
if (from == NULL)
{
NODE_DEBUG (pres_node, "presence stanza without from attribute, ignoring");
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
sub_type = lm_message_get_sub_type (message);
handle = gabble_handle_for_contact (priv->conn->handles, from, FALSE);
if (handle == 0)
{
NODE_DEBUG (pres_node, "ignoring presence from malformed jid");
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
if (handle == priv->conn->self_handle)
{
NODE_DEBUG (pres_node, "ignoring presence from ourselves on another resource");
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
g_assert (handle != 0);
child_node = lm_message_node_get_child (pres_node, "status");
if (child_node)
status_message = lm_message_node_get_value (child_node);
switch (sub_type)
{
case LM_MESSAGE_SUB_TYPE_SUBSCRIBE:
gabble_debug (DEBUG_FLAG, "making %s (handle %u) local pending on the publish channel",
from, handle);
tmp = g_intset_new ();
g_intset_add (tmp, handle);
handle = GABBLE_LIST_HANDLE_PUBLISH;
chan = _gabble_roster_get_channel (roster, handle);
gabble_group_mixin_change_members (G_OBJECT (chan), status_message,
NULL, NULL, tmp, NULL, 0, 0);
g_intset_destroy (tmp);
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
case LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE:
gabble_debug (DEBUG_FLAG, "removing %s (handle %u) from the publish channel",
from, handle);
tmp = g_intset_new ();
g_intset_add (tmp, handle);
handle = GABBLE_LIST_HANDLE_PUBLISH;
chan = _gabble_roster_get_channel (roster, handle);
changed = gabble_group_mixin_change_members (G_OBJECT (chan),
status_message, NULL, tmp, NULL, NULL, 0, 0);
_gabble_roster_send_presence_ack (roster, from, sub_type, changed);
g_intset_destroy (tmp);
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
case LM_MESSAGE_SUB_TYPE_SUBSCRIBED:
gabble_debug (DEBUG_FLAG, "adding %s (handle %u) to the subscribe channel",
from, handle);
tmp = g_intset_new ();
g_intset_add (tmp, handle);
handle = GABBLE_LIST_HANDLE_SUBSCRIBE;
chan = _gabble_roster_get_channel (roster, handle);
changed = gabble_group_mixin_change_members (G_OBJECT (chan),
status_message, tmp, NULL, NULL, NULL, 0, 0);
_gabble_roster_send_presence_ack (roster, from, sub_type, changed);
g_intset_destroy (tmp);
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
case LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED:
gabble_debug (DEBUG_FLAG, "removing %s (handle %u) from the subscribe channel",
from, handle);
tmp = g_intset_new ();
g_intset_add (tmp, handle);
handle = GABBLE_LIST_HANDLE_SUBSCRIBE;
chan = _gabble_roster_get_channel (roster, handle);
changed = gabble_group_mixin_change_members (G_OBJECT (chan),
status_message, NULL, tmp, NULL, NULL, 0, 0);
_gabble_roster_send_presence_ack (roster, from, sub_type, changed);
g_intset_destroy (tmp);
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
}
static void
gabble_roster_factory_iface_close_all (TpChannelFactoryIface *iface)
{
GabbleRoster *roster = GABBLE_ROSTER (iface);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
gabble_debug (DEBUG_FLAG, "closing channels");
if (priv->channels)
{
g_hash_table_destroy (priv->channels);
priv->channels = NULL;
}
}
static void
gabble_roster_factory_iface_connecting (TpChannelFactoryIface *iface)
{
GabbleRoster *roster = GABBLE_ROSTER (iface);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
gabble_debug (DEBUG_FLAG, "adding callbacks");
g_assert (priv->iq_cb == NULL);
g_assert (priv->presence_cb == NULL);
priv->iq_cb = lm_message_handler_new (gabble_roster_iq_cb,
roster, NULL);
lm_connection_register_message_handler (priv->conn->lmconn,
priv->iq_cb,
LM_MESSAGE_TYPE_IQ,
LM_HANDLER_PRIORITY_NORMAL);
priv->presence_cb = lm_message_handler_new (gabble_roster_presence_cb,
roster, NULL);
lm_connection_register_message_handler (priv->conn->lmconn,
priv->presence_cb,
LM_MESSAGE_TYPE_PRESENCE,
LM_HANDLER_PRIORITY_LAST);
}
static void
gabble_roster_factory_iface_connected (TpChannelFactoryIface *iface)
{
LmMessage *message;
GabbleRoster *roster = GABBLE_ROSTER (iface);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
gabble_debug (DEBUG_FLAG, "requesting roster");
message = _gabble_roster_message_new (roster, LM_MESSAGE_SUB_TYPE_GET, NULL);
_gabble_connection_send (priv->conn, message, NULL);
lm_message_unref (message);
}
static void
gabble_roster_factory_iface_disconnected (TpChannelFactoryIface *iface)
{
GabbleRoster *roster = GABBLE_ROSTER (iface);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
gabble_debug (DEBUG_FLAG, "removing callbacks");
g_assert (priv->iq_cb != NULL);
g_assert (priv->presence_cb != NULL);
lm_connection_unregister_message_handler (priv->conn->lmconn,
priv->iq_cb,
LM_MESSAGE_TYPE_IQ);
lm_message_handler_unref (priv->iq_cb);
priv->iq_cb = NULL;
lm_connection_unregister_message_handler (priv->conn->lmconn,
priv->presence_cb,
LM_MESSAGE_TYPE_PRESENCE);
lm_message_handler_unref (priv->presence_cb);
priv->presence_cb = NULL;
}
struct foreach_data {
TpChannelFunc func;
gpointer data;
};
static void
_gabble_roster_factory_iface_foreach_one (gpointer key,
gpointer value,
gpointer data)
{
TpChannelIface *chan = TP_CHANNEL_IFACE (value);
struct foreach_data *foreach = (struct foreach_data *) data;
foreach->func (chan, foreach->data);
}
static void
gabble_roster_factory_iface_foreach (TpChannelFactoryIface *iface,
TpChannelFunc func,
gpointer data)
{
struct foreach_data foreach;
GabbleRoster *roster = GABBLE_ROSTER (iface);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
foreach.func = func;
foreach.data = data;
g_hash_table_foreach (priv->channels,
_gabble_roster_factory_iface_foreach_one, &foreach);
}
static TpChannelFactoryRequestStatus
gabble_roster_factory_iface_request (TpChannelFactoryIface *iface,
const gchar *chan_type,
TpHandleType handle_type,
guint handle,
TpChannelIface **ret,
GError **error)
{
GabbleRoster *roster = GABBLE_ROSTER (iface);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
if (strcmp (chan_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
if (handle_type != TP_HANDLE_TYPE_LIST)
return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
if (!gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_LIST,
handle,
NULL))
return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE;
/* disallow "deny" channels if we don't have google:roster support */
if (handle == GABBLE_LIST_HANDLE_DENY &&
!(priv->conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER))
return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
if (priv->roster_received)
{
GabbleRosterChannel *chan;
chan = _gabble_roster_get_channel (roster, handle);
*ret = TP_CHANNEL_IFACE (chan);
return TP_CHANNEL_FACTORY_REQUEST_STATUS_DONE;
}
else
{
return TP_CHANNEL_FACTORY_REQUEST_STATUS_QUEUED;
}
}
static void
gabble_roster_factory_iface_init (gpointer g_iface,
gpointer iface_data)
{
TpChannelFactoryIfaceClass *klass = (TpChannelFactoryIfaceClass *) g_iface;
klass->close_all = gabble_roster_factory_iface_close_all;
klass->connecting = gabble_roster_factory_iface_connecting;
klass->connected = gabble_roster_factory_iface_connected;
klass->disconnected = gabble_roster_factory_iface_disconnected;
klass->foreach = gabble_roster_factory_iface_foreach;
klass->request = gabble_roster_factory_iface_request;
}
GabbleRoster *
gabble_roster_new (GabbleConnection *conn)
{
g_return_val_if_fail (conn != NULL, NULL);
return g_object_new (GABBLE_TYPE_ROSTER,
"connection", conn,
NULL);
}
GabbleRosterSubscription
gabble_roster_handle_get_subscription (GabbleRoster *roster,
GabbleHandle handle)
{
GabbleRosterItem *item;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, GABBLE_ROSTER_SUBSCRIPTION_NONE);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster),
GABBLE_ROSTER_SUBSCRIPTION_NONE);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL),
GABBLE_ROSTER_SUBSCRIPTION_NONE);
item = g_hash_table_lookup (priv->items, GINT_TO_POINTER (handle));
if (NULL == item)
return GABBLE_ROSTER_SUBSCRIPTION_NONE;
return item->subscription;
}
gboolean
gabble_roster_handle_set_blocked (GabbleRoster *roster,
GabbleHandle handle,
gboolean blocked,
GError **error)
{
GabbleRosterItem *item;
GoogleItemType orig_type;
LmMessage *message;
gboolean ret;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, FALSE);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster), FALSE);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL), FALSE);
g_return_val_if_fail (priv->conn->features &
GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER, FALSE);
item = _gabble_roster_item_get (roster, handle);
orig_type = item->google_type;
if (blocked == (orig_type == GOOGLE_ITEM_TYPE_BLOCKED))
return TRUE;
/* temporarily set the desired block state and generate a message */
if (blocked)
item->google_type = GOOGLE_ITEM_TYPE_BLOCKED;
else
item->google_type = GOOGLE_ITEM_TYPE_NORMAL;
message = _gabble_roster_item_to_message (roster, handle, NULL);
item->google_type = orig_type;
ret = _gabble_connection_send (priv->conn, message, error);
lm_message_unref (message);
return ret;
}
gboolean
gabble_roster_handle_has_entry (GabbleRoster *roster,
GabbleHandle handle)
{
GabbleRosterItem *item;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, FALSE);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster), FALSE);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL), FALSE);
item = g_hash_table_lookup (priv->items, GINT_TO_POINTER (handle));
return (NULL != item);
}
const gchar *
gabble_roster_handle_get_name (GabbleRoster *roster,
GabbleHandle handle)
{
GabbleRosterItem *item;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, NULL);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster), NULL);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL), NULL);
item = g_hash_table_lookup (priv->items, GINT_TO_POINTER (handle));
if (NULL == item)
return NULL;
return item->name;
}
gboolean
gabble_roster_handle_set_name (GabbleRoster *roster,
GabbleHandle handle,
const gchar *name,
GError **error)
{
LmMessage *message;
LmMessageNode *item_node;
gboolean ret;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, FALSE);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster), FALSE);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
message = _gabble_roster_item_to_message (roster, handle, &item_node);
lm_message_node_set_attribute (item_node, "name", name);
ret = _gabble_connection_send (priv->conn, message, error);
lm_message_unref (message);
return ret;
}
gboolean
gabble_roster_handle_remove (GabbleRoster *roster,
GabbleHandle handle,
GError **error)
{
GabbleRosterItem *item;
GabbleRosterSubscription subscription;
LmMessage *message;
gboolean ret;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, FALSE);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster), FALSE);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL), FALSE);
item = _gabble_roster_item_get (roster, handle);
subscription = item->subscription;
item->subscription = GABBLE_ROSTER_SUBSCRIPTION_REMOVE;
message = _gabble_roster_item_to_message (roster, handle, NULL);
ret = _gabble_connection_send (priv->conn, message, error);
lm_message_unref (message);
item->subscription = subscription;
return ret;
}
gboolean
gabble_roster_handle_add (GabbleRoster *roster,
GabbleHandle handle,
GError **error)
{
GabbleRosterItem *item;
LmMessage *message;
gboolean do_add = FALSE;
gboolean ret;
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
g_return_val_if_fail (roster != NULL, FALSE);
g_return_val_if_fail (GABBLE_IS_ROSTER (roster), FALSE);
g_return_val_if_fail (gabble_handle_is_valid (priv->conn->handles,
TP_HANDLE_TYPE_CONTACT, handle, NULL), FALSE);
if (!gabble_roster_handle_has_entry (roster, handle))
do_add = TRUE;
item = _gabble_roster_item_get (roster, handle);
if (item->google_type == GOOGLE_ITEM_TYPE_HIDDEN)
{
item->google_type = GOOGLE_ITEM_TYPE_NORMAL;
do_add = TRUE;
}
if (!do_add)
return TRUE;
message = _gabble_roster_item_to_message (roster, handle, NULL);
ret = _gabble_connection_send (priv->conn, message, error);
lm_message_unref (message);
return ret;
}