/* Object representing a Telepathy contact
*
* Copyright (C) 2008 Collabora Ltd.
* Copyright (C) 2008 Nokia Corporation
*
* 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
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_CONTACTS
#include "telepathy-glib/connection-internal.h"
#include "telepathy-glib/debug-internal.h"
/**
* SECTION:contact
* @title: TpContact
* @short_description: object representing a contact
* @see_also: #TpConnection
*
* #TpContact objects represent the contacts on a particular #TpConnection.
*
* Since: 0.7.18
*/
/**
* TpContact:
*
* An object representing a contact on a #TpConnection.
*
* Contact objects support tracking a number of attributes of contacts, as
* described by the #TpContactFeature flags. Features can be specified when
* instantiating contact objects (with tp_connection_get_contacts_by_id() or
* tp_connection_get_contacts_by_handle()), or added to an existing contact
* object with tp_connection_upgrade_contacts(). For example, a client wishing
* to keep track of a contact's alias would set #TP_CONTACT_FEATURE_ALIAS, and
* then listen for the "notify::alias" signal, emitted whenever the
* #TpContact:alias property changes.
*
* Note that releasing a #TpContact object might release handle references
* held by calling tp_cli_connection_call_request_handles(),
* tp_cli_connection_run_request_handles(),
* tp_cli_connection_call_hold_handles(),
* tp_cli_connection_run_hold_handles(),
* tp_cli_connection_interface_contacts_call_get_contact_attributes() or
* tp_cli_connection_interface_contacts_run_get_contact_attributes() directly.
* Those functions should be avoided in favour of using #TpContact,
* tp_connection_hold_handles(), tp_connection_request_handles() and
* tp_connection_get_contact_attributes().
*
* Since: 0.7.18
*/
struct _TpContactClass {
/**/
GObjectClass parent_class;
};
struct _TpContact {
/**/
GObject parent;
TpContactPrivate *priv;
};
/**
* TpContactFeature:
* @TP_CONTACT_FEATURE_ALIAS: #TpContact:alias
* @TP_CONTACT_FEATURE_AVATAR_TOKEN: #TpContact:avatar-token
* @TP_CONTACT_FEATURE_PRESENCE: #TpContact:presence-type,
* #TpContact:presence-status and #TpContact:presence-message
* @TP_CONTACT_FEATURE_LOCATION: #TpContact:location (available since 0.11.1)
* @TP_CONTACT_FEATURE_CAPABILITIES: #TpContact:capabilities
* (available since 0.11.3)
* @NUM_TP_CONTACT_FEATURES: 1 higher than the highest TpContactFeature
* supported by this version of telepathy-glib
*
* Enumeration representing the features a #TpContact can optionally support.
* When requesting a #TpContact, library users specify the desired features;
* the #TpContact code will only initialize state for those features, to
* avoid unwanted D-Bus round-trips and signal connections.
*
* Since 0.11.UNRELEASED, there is a corresponding #GEnumClass type,
* %TP_TYPE_CONTACT_FEATURE.
*
* Since: 0.7.18
*/
/**
* TP_TYPE_CONTACT_FEATURE:
*
* The #GEnumClass type of a #TpContactFeature.
*
* Since: 0.11.UNRELEASED
*/
G_DEFINE_TYPE (TpContact, tp_contact, G_TYPE_OBJECT);
enum {
PROP_CONNECTION = 1,
PROP_HANDLE,
PROP_IDENTIFIER,
PROP_ALIAS,
PROP_AVATAR_TOKEN,
PROP_PRESENCE_TYPE,
PROP_PRESENCE_STATUS,
PROP_PRESENCE_MESSAGE,
PROP_LOCATION,
PROP_CAPABILITIES,
N_PROPS
};
/* The API allows for more than 32 features, but this implementation does
* not. We can easily expand this later. */
typedef enum {
CONTACT_FEATURE_FLAG_ALIAS = 1 << TP_CONTACT_FEATURE_ALIAS,
CONTACT_FEATURE_FLAG_AVATAR_TOKEN = 1 << TP_CONTACT_FEATURE_AVATAR_TOKEN,
CONTACT_FEATURE_FLAG_PRESENCE = 1 << TP_CONTACT_FEATURE_PRESENCE,
CONTACT_FEATURE_FLAG_LOCATION = 1 << TP_CONTACT_FEATURE_LOCATION,
CONTACT_FEATURE_FLAG_CAPABILITIES = 1 << TP_CONTACT_FEATURE_CAPABILITIES,
} ContactFeatureFlags;
struct _TpContactPrivate {
/* basics */
TpConnection *connection;
TpHandle handle;
gchar *identifier;
ContactFeatureFlags has_features;
/* aliasing */
gchar *alias;
/* avatars */
gchar *avatar_token;
/* presence */
TpConnectionPresenceType presence_type;
gchar *presence_status;
gchar *presence_message;
/* location */
GHashTable *location;
/* capabilities */
TpCapabilities *capabilities;
};
/**
* tp_contact_get_connection:
* @self: a contact
*
*
*
* Returns: (transfer none): a borrowed reference to the #TpContact:connection
* (it must be referenced with g_object_ref if it must remain valid
* longer than the contact)
*
* Since: 0.7.18
*/
TpConnection *
tp_contact_get_connection (TpContact *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->priv->connection;
}
/**
* tp_contact_get_handle:
* @self: a contact
*
* Return the contact's handle, which is of type %TP_HANDLE_TYPE_CONTACT,
* or 0 if the #TpContact:connection has become invalid.
*
* This handle is referenced using the Telepathy D-Bus API and remains
* referenced for as long as @self exists and the
* #TpContact:connection remains valid.
*
* However, the caller of this function does not gain an additional reference
* to the handle.
*
* Returns: the same handle as the #TpContact:handle property
*
* Since: 0.7.18
*/
TpHandle
tp_contact_get_handle (TpContact *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->priv->handle;
}
/**
* tp_contact_get_identifier:
* @self: a contact
*
* Return the contact's identifier. This remains valid for as long as @self
* exists; if the caller requires a string that will persist for longer than
* that, it must be copied with g_strdup().
*
* Returns: the same non-%NULL identifier as the #TpContact:identifier property
*
* Since: 0.7.18
*/
const gchar *
tp_contact_get_identifier (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
/* identifier must be non-NULL by the time we're visible to library-user
* code */
g_return_val_if_fail (self->priv->identifier != NULL, NULL);
return self->priv->identifier;
}
/**
* tp_contact_has_feature:
* @self: a contact
* @feature: a desired feature
*
*
*
* Returns: %TRUE if @self has been set up to track the feature @feature
*
* Since: 0.7.18
*/
gboolean
tp_contact_has_feature (TpContact *self,
TpContactFeature feature)
{
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (feature < NUM_TP_CONTACT_FEATURES, FALSE);
return ((self->priv->has_features & (1 << feature)) != 0);
}
/**
* tp_contact_get_alias:
* @self: a contact
*
* Return the contact's alias. This remains valid until the main loop
* is re-entered; if the caller requires a string that will persist for
* longer than that, it must be copied with g_strdup().
*
* Returns: the same non-%NULL alias as the #TpContact:alias
*
* Since: 0.7.18
*/
const gchar *
tp_contact_get_alias (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
/* identifier must be non-NULL by the time we're visible to library-user
* code */
g_return_val_if_fail (self->priv->identifier != NULL, NULL);
if (self->priv->alias != NULL)
return self->priv->alias;
return self->priv->identifier;
}
/**
* tp_contact_get_avatar_token:
* @self: a contact
*
* Return the contact's avatar token. This remains valid until the main loop
* is re-entered; if the caller requires a string that will persist for
* longer than that, it must be copied with g_strdup().
*
* Returns: the same token as the #TpContact:avatar-token property
* (possibly %NULL)
*
* Since: 0.7.18
*/
const gchar *
tp_contact_get_avatar_token (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->priv->avatar_token;
}
/**
* tp_contact_get_presence_type:
* @self: a contact
*
* If this object has been set up to track %TP_CONTACT_FEATURE_PRESENCE
* and the underlying connection supports either the Presence or
* SimplePresence interfaces, return the type of the contact's presence.
*
* Otherwise, return %TP_CONNECTION_PRESENCE_TYPE_UNSET.
*
* Returns: the same presence type as the #TpContact:presence-type property
*
* Since: 0.7.18
*/
TpConnectionPresenceType
tp_contact_get_presence_type (TpContact *self)
{
g_return_val_if_fail (self != NULL, TP_CONNECTION_PRESENCE_TYPE_UNSET);
return self->priv->presence_type;
}
/**
* tp_contact_get_presence_status:
* @self: a contact
*
* Return the name of the contact's presence status, or an empty string.
* This remains valid until the main loop is re-entered; if the caller
* requires a string that will persist for longer than that, it must be
* copied with g_strdup().
*
* Returns: the same non-%NULL status name as the #TpContact:presence-status
* property
*
* Since: 0.7.18
*/
const gchar *
tp_contact_get_presence_status (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return (self->priv->presence_status == NULL ? "" :
self->priv->presence_status);
}
/**
* tp_contact_get_presence_message:
* @self: a contact
*
* Return the contact's user-defined status message, or an empty string.
* This remains valid until the main loop is re-entered; if the caller
* requires a string that will persist for longer than that, it must be
* copied with g_strdup().
*
* Returns: the same non-%NULL message as the #TpContact:presence-message
* property
*
* Since: 0.7.18
*/
const gchar *
tp_contact_get_presence_message (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return (self->priv->presence_message == NULL ? "" :
self->priv->presence_message);
}
/**
* tp_contact_get_location:
* @self: a contact
*
* Return the contact's user-defined location or %NULL if the location is
* unspecified.
* This remains valid until the main loop is re-entered; if the caller
* requires a hash table that will persist for longer than that, it must be
* reffed with g_hash_table_ref().
*
* Returns: (element-type utf8 GObject.Value) (transfer none): the same
* #GHashTable (or %NULL) as the #TpContact:location property
*
* Since: 0.11.1
*/
GHashTable *
tp_contact_get_location (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->priv->location;
}
/**
* tp_contact_get_capabilities:
* @self: a contact
*
*
*
* Returns: the same #TpCapabilities (or %NULL) as the
* #TpContact:capabilities property
*
* Since: 0.11.3
*/
TpCapabilities *
tp_contact_get_capabilities (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->priv->capabilities;
}
void
_tp_contact_connection_invalidated (TpContact *contact)
{
/* The connection has gone away, so we no longer have a meaningful handle,
* and will never have one again. */
g_assert (contact->priv->handle != 0);
contact->priv->handle = 0;
g_object_notify ((GObject *) contact, "handle");
}
static void
tp_contact_dispose (GObject *object)
{
TpContact *self = TP_CONTACT (object);
if (self->priv->handle != 0)
{
g_assert (self->priv->connection != NULL);
_tp_connection_remove_contact (self->priv->connection,
self->priv->handle, self);
tp_connection_unref_handles (self->priv->connection,
TP_HANDLE_TYPE_CONTACT, 1, &self->priv->handle);
self->priv->handle = 0;
}
if (self->priv->connection != NULL)
{
g_object_unref (self->priv->connection);
self->priv->connection = NULL;
}
if (self->priv->location != NULL)
{
g_hash_table_unref (self->priv->location);
self->priv->location = NULL;
}
if (self->priv->capabilities != NULL)
{
g_object_unref (self->priv->capabilities);
self->priv->capabilities = NULL;
}
((GObjectClass *) tp_contact_parent_class)->dispose (object);
}
static void
tp_contact_finalize (GObject *object)
{
TpContact *self = TP_CONTACT (object);
g_free (self->priv->identifier);
g_free (self->priv->alias);
g_free (self->priv->avatar_token);
g_free (self->priv->presence_status);
g_free (self->priv->presence_message);
((GObjectClass *) tp_contact_parent_class)->finalize (object);
}
static void
tp_contact_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
TpContact *self = TP_CONTACT (object);
switch (property_id)
{
case PROP_CONNECTION:
g_value_set_object (value, self->priv->connection);
break;
case PROP_HANDLE:
g_value_set_uint (value, self->priv->handle);
break;
case PROP_IDENTIFIER:
g_assert (self->priv->identifier != NULL);
g_value_set_string (value, self->priv->identifier);
break;
case PROP_ALIAS:
/* tp_contact_get_alias actually has some logic, so avoid
* duplicating it */
g_value_set_string (value, tp_contact_get_alias (self));
break;
case PROP_AVATAR_TOKEN:
g_value_set_string (value, self->priv->avatar_token);
break;
case PROP_PRESENCE_TYPE:
g_value_set_uint (value, self->priv->presence_type);
break;
case PROP_PRESENCE_STATUS:
g_value_set_string (value, tp_contact_get_presence_status (self));
break;
case PROP_PRESENCE_MESSAGE:
g_value_set_string (value, tp_contact_get_presence_message (self));
break;
case PROP_LOCATION:
g_value_set_boxed (value, tp_contact_get_location (self));
break;
case PROP_CAPABILITIES:
g_value_set_object (value, tp_contact_get_capabilities (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
tp_contact_class_init (TpContactClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
GParamSpec *param_spec;
g_type_class_add_private (klass, sizeof (TpContactPrivate));
object_class->get_property = tp_contact_get_property;
object_class->dispose = tp_contact_dispose;
object_class->finalize = tp_contact_finalize;
/**
* TpContact:connection:
*
* The #TpConnection to which this contact belongs.
*/
param_spec = g_param_spec_object ("connection", "TpConnection object",
"Connection object that owns this channel",
TP_TYPE_CONNECTION,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
/**
* TpContact:handle:
*
* The contact's handle in the Telepathy D-Bus API, a handle of type
* %TP_HANDLE_TYPE_CONTACT representing the string
* given by #TpContact:identifier.
*
* This handle is referenced using the Telepathy D-Bus API and remains
* referenced for as long as the #TpContact exists and the
* #TpContact:connection remains valid.
*
* However, getting this property does not cause an additional reference
* to the handle to be held.
*
* If the #TpContact:connection becomes invalid, this property is no longer
* meaningful and will be set to 0.
*/
param_spec = g_param_spec_uint ("handle",
"Handle",
"The TP_HANDLE_TYPE_CONTACT handle for this contact",
0, G_MAXUINT32, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_HANDLE, param_spec);
/**
* TpContact:identifier:
*
* The contact's identifier in the instant messaging protocol (e.g.
* XMPP JID, SIP URI, AOL screenname or IRC nick - whatever the underlying
* protocol uses to identify a user).
*
* This is never %NULL for contact objects that are visible to library-user
* code.
*/
param_spec = g_param_spec_string ("identifier",
"IM protocol identifier",
"The contact's identifier in the instant messaging protocol (e.g. "
"XMPP JID, SIP URI, AOL screenname or IRC nick)",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_IDENTIFIER, param_spec);
/**
* TpContact:alias:
*
* The contact's alias if available, falling back to their
* #TpContact:identifier if no alias is available or if the #TpContact has
* not been set up to track %TP_CONTACT_FEATURE_ALIAS.
*
* This alias may have been supplied by the contact themselves, or by the
* local user, so it does not necessarily unambiguously identify the contact.
* However, it is suitable for use as a main "display name" for the contact.
*
* This is never %NULL for contact objects that are visible to library-user
* code.
*/
param_spec = g_param_spec_string ("alias",
"Alias",
"The contact's alias (display name)",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_ALIAS, param_spec);
/**
* TpContact:avatar-token:
*
* An opaque string representing state of the contact's avatar (depending on
* the protocol, this might be a hash, a timestamp or something else), or
* an empty string if there is no avatar.
*
* This may be %NULL if it is not known whether this contact has an avatar
* or not (either for network protocol reasons, or because this #TpContact
* has not been set up to track %TP_CONTACT_FEATURE_AVATAR_TOKEN).
*/
param_spec = g_param_spec_string ("avatar-token",
"Avatar token",
"Opaque string representing the contact's avatar, or \"\", or NULL",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_AVATAR_TOKEN,
param_spec);
/**
* TpContact:presence-type:
*
* The #TpConnectionPresenceType representing the type of presence status
* for this contact.
*
* This is provided so even unknown values for #TpContact:presence-status
* can be classified into their fundamental types.
*
* This may be %TP_CONNECTION_PRESENCE_TYPE_UNSET if this #TpContact
* has not been set up to track %TP_CONTACT_FEATURE_PRESENCE.
*/
param_spec = g_param_spec_uint ("presence-type",
"Presence type",
"The TpConnectionPresenceType for this contact",
0, G_MAXUINT32, TP_CONNECTION_PRESENCE_TYPE_UNSET,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_PRESENCE_TYPE,
param_spec);
/**
* TpContact:presence-status:
*
* A string representing the presence status of this contact. This may be
* a well-known string from the Telepathy specification, like "available",
* or a connection-manager-specific string, like "out-to-lunch".
*
* This may be an empty string if this #TpContact object has not been set up
* to track %TP_CONTACT_FEATURE_PRESENCE. It is never %NULL.
*/
param_spec = g_param_spec_string ("presence-status",
"Presence status",
"Possibly connection-manager-specific string representing the "
"contact's presence status",
"",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_PRESENCE_STATUS,
param_spec);
/**
* TpContact:presence-message:
*
* If this contact has set a user-defined status message, that message;
* if not, an empty string (which user interfaces may replace with a
* localized form of the #TpContact:presence-status or
* #TpContact:presence-type).
*
* This may be an empty string even if the contact has set a message,
* if this #TpContact object has not been set up to track
* %TP_CONTACT_FEATURE_PRESENCE. It is never %NULL.
*/
param_spec = g_param_spec_string ("presence-message",
"Presence message",
"User-defined status message, or an empty string",
"",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_PRESENCE_MESSAGE,
param_spec);
/**
* TpContact:location:
*
* If this contact has set a user-defined location, a string to
* #GValue * hash table containing his location. If not, %NULL.
* tp_asv_get_string() and similar functions can be used to access
* the contents.
*
* This may be %NULL even if the contact has set a location,
* if this #TpContact object has not been set up to track
* %TP_CONTACT_FEATURE_LOCATION.
*
* Since: 0.11.1
*/
param_spec = g_param_spec_boxed ("location",
"Location",
"User-defined location, or NULL",
TP_HASH_TYPE_STRING_VARIANT_MAP,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_LOCATION,
param_spec);
/**
* TpContact:capabilities:
*
* The capabilities supported by this contact. If the underlying Connection
* doesn't support the ContactCapabilities interface, this property will
* contain the capabilities supported by the connection.
* Use tp_capabilities_is_specific_to_contact() to check if the capabilities
* are specific to this #TpContact or not.
*
* This may be %NULL if this #TpContact object has not been set up to track
* %TP_CONTACT_FEATURE_CAPABILITIES.
*
* Since: 0.11.3
*/
param_spec = g_param_spec_object ("capabilities",
"Capabilities",
"Capabilities of the contact, or NULL",
TP_TYPE_CAPABILITIES,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAPABILITIES,
param_spec);
}
/* Consumes one reference to @handle. */
static TpContact *
tp_contact_ensure (TpConnection *connection,
TpHandle handle)
{
TpContact *self = _tp_connection_lookup_contact (connection, handle);
if (self != NULL)
{
g_assert (self->priv->handle == handle);
/* we have one ref to this handle more than we need, so consume it */
tp_connection_unref_handles (self->priv->connection,
TP_HANDLE_TYPE_CONTACT, 1, &self->priv->handle);
return g_object_ref (self);
}
self = TP_CONTACT (g_object_new (TP_TYPE_CONTACT, NULL));
self->priv->handle = handle;
_tp_connection_add_contact (connection, handle, self);
self->priv->connection = g_object_ref (connection);
return self;
}
static void
tp_contact_init (TpContact *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONTACT,
TpContactPrivate);
}
typedef struct _ContactsContext ContactsContext;
typedef void (*ContactsProc) (ContactsContext *self);
typedef enum { CB_BY_HANDLE, CB_BY_ID, CB_UPGRADE } ContactsSignature;
struct _ContactsContext {
gsize refcount;
/* owned */
TpConnection *connection;
/* array of owned TpContact; preallocated but empty until handles have
* been held or requested */
GPtrArray *contacts;
/* array of handles; empty until RequestHandles has returned, if we
* started from IDs */
GArray *handles;
/* array of handles; empty until RequestHandles has returned, if we
* started from IDs */
GArray *invalid;
/* strv of IDs; NULL unless we started from IDs */
GPtrArray *request_ids;
/* ID => GError, NULL unless we started from IDs */
GHashTable *request_errors;
/* features we need before this request can finish */
ContactFeatureFlags wanted;
/* callback for when we've finished, plus the usual misc */
ContactsSignature signature;
union {
TpConnectionContactsByHandleCb by_handle;
TpConnectionContactsByIdCb by_id;
TpConnectionUpgradeContactsCb upgrade;
} callback;
gpointer user_data;
GDestroyNotify destroy;
GObject *weak_object;
/* queue of ContactsProc */
GQueue todo;
/* index into handles or ids, only used when the first HoldHandles call
* failed with InvalidHandle, or the RequestHandles call failed with
* NotAvailable */
guint next_index;
};
static ContactsContext *
contacts_context_new (TpConnection *connection,
guint n_contacts,
ContactFeatureFlags want_features,
ContactsSignature signature,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
ContactsContext *c = g_slice_new0 (ContactsContext);
c->refcount = 1;
c->connection = g_object_ref (connection);
c->contacts = g_ptr_array_sized_new (n_contacts);
c->handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_contacts);
c->invalid = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_contacts);
c->wanted = want_features;
c->signature = signature;
c->user_data = user_data;
c->destroy = destroy;
c->weak_object = weak_object;
/* This code (and lots of telepathy-glib, really) won't work if this
* assertion fails, because we put function pointers in a GQueue. If anyone
* cares about platforms where this fails, fixing this would involve
* slice-allocating sizeof (GCallback) bytes repeatedly, and putting *those*
* in the queue. */
tp_verify_statement (sizeof (GCallback) == sizeof (gpointer));
g_queue_init (&c->todo);
return c;
}
static void
contacts_context_unref (gpointer p)
{
ContactsContext *c = p;
if ((--c->refcount) > 0)
return;
g_assert (c->connection != NULL);
g_object_unref (c->connection);
c->connection = NULL;
g_queue_clear (&c->todo);
g_assert (c->contacts != NULL);
g_ptr_array_foreach (c->contacts, (GFunc) g_object_unref, NULL);
g_ptr_array_free (c->contacts, TRUE);
c->contacts = NULL;
g_assert (c->handles != NULL);
g_array_free (c->handles, TRUE);
c->handles = NULL;
g_assert (c->invalid != NULL);
g_array_free (c->invalid, TRUE);
c->invalid = NULL;
if (c->request_ids != NULL)
g_strfreev ((gchar **) g_ptr_array_free (c->request_ids, FALSE));
c->request_ids = NULL;
if (c->request_errors != NULL)
g_hash_table_destroy (c->request_errors);
c->request_errors = NULL;
if (c->destroy != NULL)
c->destroy (c->user_data);
c->destroy = NULL;
c->user_data = NULL;
c->weak_object = NULL;
g_slice_free (ContactsContext, c);
}
static void
contacts_context_fail (ContactsContext *c,
const GError *error)
{
guint i;
switch (c->signature)
{
case CB_BY_HANDLE:
g_array_append_vals (c->invalid, c->handles->data, c->handles->len);
c->callback.by_handle (c->connection, 0, NULL,
c->invalid->len, (const TpHandle *) c->invalid->data,
error, c->user_data, c->weak_object);
return;
case CB_BY_ID:
/* -1 because NULL terminator is explicit */
for (i = 0; i < c->request_ids->len - 1; i++)
{
const gchar *id = g_ptr_array_index (c->request_ids, i);
if (!g_hash_table_lookup (c->request_errors, id))
{
g_hash_table_insert (c->request_errors,
g_strdup (id), g_error_copy (error));
}
}
c->callback.by_id (c->connection, 0, NULL, NULL,
c->request_errors, error, c->user_data, c->weak_object);
return;
case CB_UPGRADE:
c->callback.upgrade (c->connection,
c->contacts->len, (TpContact * const *) c->contacts->pdata,
error, c->user_data, c->weak_object);
return;
default:
g_assert_not_reached ();
}
}
/**
* TpConnectionContactsByHandleCb:
* @connection: The connection
* @n_contacts: The number of TpContact objects successfully created
* (one per valid handle), or 0 on unrecoverable errors
* @contacts: (array length=n_contacts): An array of @n_contacts TpContact
* objects (this callback is not given a reference to any of these objects,
* and must call g_object_ref() on any that it will keep), or %NULL on
* unrecoverable errors
* @n_failed: The number of invalid handles that were passed to
* tp_connection_get_contacts_by_handle() (or on unrecoverable errors,
* the total number of handles that were given)
* @failed: (array length=n_failed): An array of @n_failed handles that were
* passed to tp_connection_get_contacts_by_handle() but turned out to be
* invalid (or on unrecoverable errors, all the handles that were given)
* @error: %NULL on success, or an unrecoverable error that caused everything
* to fail
* @user_data: the @user_data that was passed to
* tp_connection_get_contacts_by_handle()
* @weak_object: the @weak_object that was passed to
* tp_connection_get_contacts_by_handle()
*
* Signature of a callback used to receive the result of
* tp_connection_get_contacts_by_handle().
*
* If an unrecoverable error occurs (for instance, if @connection
* becomes disconnected) the whole operation fails, and no contacts or
* invalid handles are returned.
*
* If some or even all of the @handles passed to
* tp_connection_get_contacts_by_handle() were not valid, this is not
* considered to be a failure. @error will be %NULL in this situation,
* @contacts will contain contact objects for those handles that were
* valid (possibly none of them), and @invalid will contain the handles
* that were not valid.
*
* Since: 0.7.18
*/
/**
* TpConnectionContactsByIdCb:
* @connection: The connection
* @n_contacts: The number of TpContact objects successfully created
* (one per valid ID), or 0 on unrecoverable errors
* @contacts: (array length=n_contacts): An array of @n_contacts TpContact
* objects (this callback is
* not given a reference to any of these objects, and must call
* g_object_ref() on any that it will keep), or %NULL on unrecoverable errors
* @requested_ids: (array length=n_contacts): An array of @n_contacts valid IDs
* (JIDs, SIP URIs etc.)
* that were passed to tp_connection_get_contacts_by_id(), in an order
* corresponding to @contacts, or %NULL on unrecoverable errors
* @failed_id_errors: (element-type utf8 GLib.Error): A hash table in which
* the keys are IDs and the values are errors (#GError)
* @error: %NULL on success, or an unrecoverable error that caused everything
* to fail
* @user_data: the @user_data that was passed to
* tp_connection_get_contacts_by_id()
* @weak_object: the @weak_object that was passed to
* tp_connection_get_contacts_by_id()
*
* Signature of a callback used to receive the result of
* tp_connection_get_contacts_by_id().
*
* @requested_ids contains the IDs that were converted to handles successfully.
* The normalized form of requested_ids[i] is
* tp_contact_get_identifier (contacts[i]).
*
* If some or even all of the @ids passed to
* tp_connection_get_contacts_by_id() were not valid, this is not
* considered to be a fatal error. @error will be %NULL in this situation,
* @contacts will contain contact objects for those IDs that were
* valid (it may be empty), and @failed_id_errors will map the IDs
* that were not valid to a corresponding #GError (if the connection manager
* complies with the Telepathy spec, it will have domain %TP_ERRORS and code
* %TP_ERROR_INVALID_HANDLE).
*
* If an unrecoverable error occurs (for instance, if @connection
* becomes disconnected) the whole operation fails, and no contacts
* or requested IDs are returned. @failed_id_errors will contain all the IDs
* that were requested, mapped to a corresponding #GError (either one
* indicating that the ID was invalid, if that was determined before the
* fatal error occurred, or a copy of @error).
*
* Since: 0.7.18
*/
/**
* TpConnectionUpgradeContactsCb:
* @connection: The connection
* @n_contacts: The number of TpContact objects for which an upgrade was
* requested
* @contacts: An array of @n_contacts TpContact objects (this callback is
* not given an extra reference to any of these objects, and must call
* g_object_ref() on any that it will keep)
* @error: An unrecoverable error, or %NULL if the connection remains valid
* @user_data: the @user_data that was passed to
* tp_connection_upgrade_contacts()
* @weak_object: the @weak_object that was passed to
* tp_connection_upgrade_contacts()
*
* Signature of a callback used to receive the result of
* tp_connection_upgrade_contacts().
*
* If an unrecoverable error occurs (for instance, if @connection becomes
* disconnected) it is indicated by @error, but the contacts in @contacts
* are still provided.
*
* Since: 0.7.18
*/
static void
contacts_context_continue (ContactsContext *c)
{
if (g_queue_is_empty (&c->todo))
{
/* do some final sanity checking then hand over the contacts to the
* library user */
guint i;
g_assert (c->contacts != NULL);
g_assert (c->invalid != NULL);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = TP_CONTACT (g_ptr_array_index (c->contacts, i));
g_assert (contact->priv->identifier != NULL);
g_assert (contact->priv->handle != 0);
}
switch (c->signature)
{
case CB_BY_HANDLE:
c->callback.by_handle (c->connection,
c->contacts->len, (TpContact * const *) c->contacts->pdata,
c->invalid->len, (const TpHandle *) c->invalid->data,
NULL, c->user_data, c->weak_object);
break;
case CB_BY_ID:
c->callback.by_id (c->connection,
c->contacts->len, (TpContact * const *) c->contacts->pdata,
(const gchar * const *) c->request_ids->pdata,
c->request_errors, NULL, c->user_data, c->weak_object);
break;
case CB_UPGRADE:
c->callback.upgrade (c->connection,
c->contacts->len, (TpContact * const *) c->contacts->pdata,
NULL, c->user_data, c->weak_object);
break;
default:
g_assert_not_reached ();
}
}
else
{
/* bah! */
ContactsProc next = g_queue_pop_head (&c->todo);
next (c);
}
}
static gboolean
contacts_context_idle_continue (gpointer data)
{
contacts_context_continue (data);
return FALSE;
}
static void
contacts_held_one (TpConnection *connection,
TpHandleType handle_type,
guint n_handles,
const TpHandle *handles,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
g_assert (c->next_index < c->handles->len);
if (error == NULL)
{
/* I have a handle of my very own. Just what I always wanted! */
TpContact *contact;
g_assert (n_handles == 1);
g_assert (handles[0] != 0);
g_debug ("%u vs %u", g_array_index (c->handles, TpHandle, c->next_index),
handles[0]);
g_assert (g_array_index (c->handles, TpHandle, c->next_index)
== handles[0]);
contact = tp_contact_ensure (connection, handles[0]);
g_ptr_array_add (c->contacts, contact);
c->next_index++;
}
else if (error->domain == TP_ERRORS &&
error->code == TP_ERROR_INVALID_HANDLE)
{
g_array_append_val (c->invalid,
g_array_index (c->handles, TpHandle, c->next_index));
/* ignore the bad handle - we just won't return a TpContact for it */
g_array_remove_index_fast (c->handles, c->next_index);
/* do not increment next_index - another handle has been moved into that
* position */
}
else
{
/* the connection fell down a well or something */
contacts_context_fail (c, error);
return;
}
/* Either continue to hold handles, or proceed along the slow path. */
contacts_context_continue (c);
}
static void
contacts_hold_one (ContactsContext *c)
{
c->refcount++;
tp_connection_hold_handles (c->connection, -1,
TP_HANDLE_TYPE_CONTACT, 1,
&g_array_index (c->handles, TpHandle, c->next_index),
contacts_held_one, c, contacts_context_unref, c->weak_object);
}
static void
contacts_held_handles (TpConnection *connection,
TpHandleType handle_type,
guint n_handles,
const TpHandle *handles,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
g_assert (weak_object == c->weak_object);
if (error == NULL)
{
/* I now own all n handles. It's like Christmas morning! */
guint i;
g_assert (n_handles == c->handles->len);
for (i = 0; i < c->handles->len; i++)
{
g_ptr_array_add (c->contacts,
tp_contact_ensure (connection,
g_array_index (c->handles, TpHandle, i)));
}
}
else if (error->domain == TP_ERRORS &&
error->code == TP_ERROR_INVALID_HANDLE)
{
/* One of the handles is bad. We don't know which one :-( so split
* the batch into a chain of calls. */
guint i;
for (i = 0; i < c->handles->len; i++)
{
g_queue_push_head (&c->todo, contacts_hold_one);
}
g_assert (c->next_index == 0);
}
else
{
/* the connection fell down a well or something */
contacts_context_fail (c, error);
return;
}
/* Either hold the handles individually, or proceed along the slow path. */
contacts_context_continue (c);
}
static void
contacts_inspected (TpConnection *connection,
const gchar **ids,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
g_assert (weak_object == c->weak_object);
g_assert (c->handles->len == c->contacts->len);
if (error != NULL)
{
/* the connection fell down a well or something */
contacts_context_fail (c, error);
return;
}
else if (G_UNLIKELY (g_strv_length ((GStrv) ids) != c->handles->len))
{
GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"Connection manager %s is broken: we inspected %u "
"handles but InspectHandles returned %u strings",
tp_proxy_get_bus_name (connection), c->handles->len,
g_strv_length ((GStrv) ids));
WARNING ("%s", e->message);
contacts_context_fail (c, e);
g_error_free (e);
return;
}
else
{
guint i;
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
g_assert (ids[i] != NULL);
if (contact->priv->identifier == NULL)
{
contact->priv->identifier = g_strdup (ids[i]);
}
else if (tp_strdiff (contact->priv->identifier, ids[i]))
{
GError *e = g_error_new (TP_DBUS_ERRORS,
TP_DBUS_ERROR_INCONSISTENT,
"Connection manager %s is broken: contact handle %u "
"identifier changed from %s to %s",
tp_proxy_get_bus_name (connection), contact->priv->handle,
contact->priv->identifier, ids[i]);
WARNING ("%s", e->message);
contacts_context_fail (c, e);
g_error_free (e);
return;
}
}
}
contacts_context_continue (c);
}
static void
contacts_inspect (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
if (contact->priv->identifier == NULL)
{
c->refcount++;
tp_cli_connection_call_inspect_handles (c->connection, -1,
TP_HANDLE_TYPE_CONTACT, c->handles, contacts_inspected,
c, contacts_context_unref, c->weak_object);
return;
}
}
/* else there's no need to inspect the contacts' handles, because we already
* know all their identifiers */
contacts_context_continue (c);
}
static void
contacts_requested_aliases (TpConnection *connection,
const gchar **aliases,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
g_assert (c->handles->len == c->contacts->len);
if (error == NULL)
{
guint i;
if (G_UNLIKELY (g_strv_length ((GStrv) aliases) != c->contacts->len))
{
WARNING ("Connection manager %s is broken: we requested %u "
"handles' aliases but got %u strings back",
tp_proxy_get_bus_name (connection), c->contacts->len,
g_strv_length ((GStrv) aliases));
/* give up on the possibility of getting aliases, and just
* move on */
contacts_context_continue (c);
return;
}
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
const gchar *alias = aliases[i];
contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
g_free (contact->priv->alias);
contact->priv->alias = g_strdup (alias);
g_object_notify ((GObject *) contact, "alias");
}
}
else
{
/* never mind, we can live without aliases */
DEBUG ("GetAliases failed with %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
}
contacts_context_continue (c);
}
static void
contacts_got_aliases (TpConnection *connection,
GHashTable *handle_to_alias,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
if (error == NULL)
{
guint i;
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
const gchar *alias = g_hash_table_lookup (handle_to_alias,
GUINT_TO_POINTER (contact->priv->handle));
contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
g_free (contact->priv->alias);
contact->priv->alias = NULL;
if (alias != NULL)
{
contact->priv->alias = g_strdup (alias);
}
else
{
WARNING ("No alias returned for %u, will use ID instead",
contact->priv->handle);
}
g_object_notify ((GObject *) contact, "alias");
}
}
else if ((error->domain == TP_ERRORS &&
error->code == TP_ERROR_NOT_IMPLEMENTED) ||
(error->domain == DBUS_GERROR &&
error->code == DBUS_GERROR_UNKNOWN_METHOD))
{
/* GetAliases not implemented, fall back to (slow?) RequestAliases */
c->refcount++;
tp_cli_connection_interface_aliasing_call_request_aliases (connection,
-1, c->handles, contacts_requested_aliases,
c, contacts_context_unref, weak_object);
return;
}
else
{
/* never mind, we can live without aliases */
DEBUG ("GetAliases failed with %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
}
contacts_context_continue (c);
}
static void
contacts_aliases_changed (TpConnection *connection,
const GPtrArray *alias_structs,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
guint i;
for (i = 0; i < alias_structs->len; i++)
{
GValueArray *pair = g_ptr_array_index (alias_structs, i);
TpHandle handle = g_value_get_uint (pair->values + 0);
const gchar *alias = g_value_get_string (pair->values + 1);
TpContact *contact = _tp_connection_lookup_contact (connection, handle);
if (contact != NULL)
{
contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
DEBUG ("Contact \"%s\" alias changed from \"%s\" to \"%s\"",
contact->priv->identifier, contact->priv->alias, alias);
g_free (contact->priv->alias);
contact->priv->alias = g_strdup (alias);
g_object_notify ((GObject *) contact, "alias");
}
}
}
static void
contacts_bind_to_aliases_changed (TpConnection *connection)
{
if (!connection->priv->tracking_aliases_changed)
{
connection->priv->tracking_aliases_changed = TRUE;
tp_cli_connection_interface_aliasing_connect_to_aliases_changed (
connection, contacts_aliases_changed, NULL, NULL, NULL, NULL);
}
}
static void
contacts_get_aliases (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
contacts_bind_to_aliases_changed (c->connection);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_ALIAS) == 0)
{
c->refcount++;
tp_cli_connection_interface_aliasing_call_get_aliases (c->connection,
-1, c->handles, contacts_got_aliases, c, contacts_context_unref,
c->weak_object);
return;
}
}
/* else there's no need to get the contacts' aliases, because we already
* know them all */
contacts_context_continue (c);
}
static void
contact_maybe_set_simple_presence (TpContact *contact,
GValueArray *presence)
{
if (contact == NULL || presence == NULL)
return;
contact->priv->has_features |= CONTACT_FEATURE_FLAG_PRESENCE;
contact->priv->presence_type = g_value_get_uint (presence->values + 0);
g_free (contact->priv->presence_status);
contact->priv->presence_status = g_value_dup_string (
presence->values + 1);
g_free (contact->priv->presence_message);
contact->priv->presence_message = g_value_dup_string (
presence->values + 2);
g_object_notify ((GObject *) contact, "presence-type");
g_object_notify ((GObject *) contact, "presence-status");
g_object_notify ((GObject *) contact, "presence-message");
}
static void
contact_maybe_set_location (TpContact *self,
GHashTable *location)
{
if (self == NULL || location == NULL)
return;
if (self->priv->location != NULL)
g_hash_table_unref (self->priv->location);
self->priv->has_features |= CONTACT_FEATURE_FLAG_LOCATION;
self->priv->location = g_hash_table_ref (location);
g_object_notify ((GObject *) self, "location");
}
static void
contact_set_capabilities (TpContact *self,
TpCapabilities *capabilities)
{
if (self->priv->capabilities != NULL)
g_object_unref (self->priv->capabilities);
self->priv->has_features |= CONTACT_FEATURE_FLAG_CAPABILITIES;
self->priv->capabilities = g_object_ref (capabilities);
g_object_notify ((GObject *) self, "capabilities");
}
static void
contact_maybe_set_capabilities (TpContact *self,
GPtrArray *arr)
{
TpCapabilities *capabilities;
if (self == NULL || arr == NULL)
return;
capabilities = _tp_capabilities_new (arr, TRUE);
contact_set_capabilities (self, capabilities);
g_object_unref (capabilities);
}
static void
contacts_presences_changed (TpConnection *connection,
GHashTable *presences,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, presences);
while (g_hash_table_iter_next (&iter, &key, &value))
{
TpContact *contact = _tp_connection_lookup_contact (connection,
GPOINTER_TO_UINT (key));
contact_maybe_set_simple_presence (contact, value);
}
}
static void
contacts_got_simple_presence (TpConnection *connection,
GHashTable *presences,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
if (error == NULL)
{
contacts_presences_changed (connection, presences, NULL, NULL);
}
else
{
/* never mind, we can live without presences */
DEBUG ("GetPresences failed with %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
}
contacts_context_continue (c);
}
static void
contacts_bind_to_presences_changed (TpConnection *connection)
{
if (!connection->priv->tracking_presences_changed)
{
connection->priv->tracking_presences_changed = TRUE;
tp_cli_connection_interface_simple_presence_connect_to_presences_changed
(connection, contacts_presences_changed, NULL, NULL, NULL, NULL);
}
}
static void
contacts_get_simple_presence (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
contacts_bind_to_presences_changed (c->connection);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_PRESENCE) == 0)
{
c->refcount++;
tp_cli_connection_interface_simple_presence_call_get_presences (
c->connection, -1,
c->handles, contacts_got_simple_presence,
c, contacts_context_unref, c->weak_object);
return;
}
}
contacts_context_continue (c);
}
static void
contacts_location_updated (TpConnection *connection,
guint handle,
GHashTable *location,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
TpContact *contact = _tp_connection_lookup_contact (connection,
GPOINTER_TO_UINT (handle));
contact_maybe_set_location (contact, location);
}
static void
contacts_bind_to_location_updated (TpConnection *connection)
{
if (!connection->priv->tracking_location_changed)
{
connection->priv->tracking_location_changed = TRUE;
tp_cli_connection_interface_location_connect_to_location_updated
(connection, contacts_location_updated, NULL, NULL, NULL, NULL);
}
}
static void
contacts_got_locations (TpConnection *connection,
GHashTable *locations,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
if (error != NULL)
{
DEBUG ("GetLocations failed with %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
}
else
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, locations);
while (g_hash_table_iter_next (&iter, &key, &value))
{
contacts_location_updated (connection, GPOINTER_TO_UINT (key),
value, NULL, NULL);
}
}
contacts_context_continue (c);
}
static void
contacts_get_locations (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
contacts_bind_to_location_updated (c->connection);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_LOCATION) == 0)
{
c->refcount++;
tp_cli_connection_interface_location_call_get_locations (
c->connection, -1, c->handles, contacts_got_locations,
c, contacts_context_unref, c->weak_object);
return;
}
}
contacts_context_continue (c);
}
static void
set_conn_capabilities_on_contacts (GPtrArray *contacts,
TpConnection *connection)
{
guint i;
for (i = 0; i < contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (contacts, i);
contact_set_capabilities (contact, tp_connection_get_capabilities (
connection));
}
}
static void
connection_capabilities_prepare_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
ContactsContext *c = user_data;
if (!tp_proxy_prepare_finish (object, res, &error))
{
DEBUG ("Failed to prepare Connection capabilities feature: %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
}
else if (!tp_proxy_is_prepared (object, TP_CONNECTION_FEATURE_CAPABILITIES))
{
DEBUG ("Connection capabilities feature has not been actually prepared");
}
else
{
set_conn_capabilities_on_contacts (c->contacts, c->connection);
}
contacts_context_continue (c);
contacts_context_unref (c);
}
static void
contacts_get_conn_capabilities (ContactsContext *c)
{
GQuark features[] = { TP_CONNECTION_FEATURE_CAPABILITIES, 0 };
g_assert (c->handles->len == c->contacts->len);
c->refcount++;
tp_proxy_prepare_async (c->connection, features,
connection_capabilities_prepare_cb, c);
}
static void
contacts_capabilities_updated (TpConnection *connection,
GHashTable *capabilities,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
GHashTableIter iter;
gpointer handle, value;
g_hash_table_iter_init (&iter, capabilities);
while (g_hash_table_iter_next (&iter, &handle, &value))
{
TpContact *contact = _tp_connection_lookup_contact (connection,
GPOINTER_TO_UINT (handle));
contact_maybe_set_capabilities (contact, value);
}
}
static void
contacts_bind_to_capabilities_updated (TpConnection *connection)
{
if (!connection->priv->tracking_contact_caps_changed)
{
connection->priv->tracking_contact_caps_changed = TRUE;
tp_cli_connection_interface_contact_capabilities_connect_to_contact_capabilities_changed
(connection, contacts_capabilities_updated, NULL, NULL, NULL, NULL);
}
}
static void
contacts_avatar_updated (TpConnection *connection,
TpHandle handle,
const gchar *new_token,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
TpContact *contact = _tp_connection_lookup_contact (connection, handle);
DEBUG ("contact#%u token is %s", handle, new_token);
if (contact == NULL)
return;
contact->priv->has_features |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
g_free (contact->priv->avatar_token);
contact->priv->avatar_token = g_strdup (new_token);
g_object_notify ((GObject *) contact, "avatar-token");
}
static void
contacts_got_known_avatar_tokens (TpConnection *connection,
GHashTable *handle_to_token,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
GHashTableIter iter;
gpointer key, value;
if (error == NULL)
{
g_hash_table_iter_init (&iter, handle_to_token);
while (g_hash_table_iter_next (&iter, &key, &value))
{
contacts_avatar_updated (connection, GPOINTER_TO_UINT (key), value,
NULL, NULL);
}
}
/* FIXME: perhaps we could fall back to GetAvatarTokens (which should have
* been called RequestAvatarTokens, because it blocks on network traffic)
* if GetKnownAvatarTokens doesn't work? */
else
{
/* never mind, we can live without avatar tokens */
DEBUG ("GetKnownAvatarTokens failed with %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
}
contacts_context_continue (c);
}
static void
contacts_bind_to_avatar_updated (TpConnection *connection)
{
if (!connection->priv->tracking_avatar_updated)
{
connection->priv->tracking_avatar_updated = TRUE;
tp_cli_connection_interface_avatars_connect_to_avatar_updated
(connection, contacts_avatar_updated, NULL, NULL, NULL, NULL);
}
}
static void
contacts_get_avatar_tokens (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
contacts_bind_to_avatar_updated (c->connection);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN)
== 0)
{
c->refcount++;
tp_cli_connection_interface_avatars_call_get_known_avatar_tokens (
c->connection, -1,
c->handles, contacts_got_known_avatar_tokens,
c, contacts_context_unref, c->weak_object);
return;
}
}
contacts_context_continue (c);
}
static gboolean
contacts_context_supports_iface (ContactsContext *context,
GQuark iface)
{
GArray *contact_attribute_interfaces =
context->connection->priv->contact_attribute_interfaces;
guint i;
if (!tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
return FALSE;
g_assert (contact_attribute_interfaces != NULL);
for (i = 0; i < contact_attribute_interfaces->len; i++)
{
GQuark q = g_array_index (contact_attribute_interfaces, GQuark, i);
if (q == iface)
return TRUE;
}
return FALSE;
}
static void
contacts_context_queue_features (ContactsContext *context,
ContactFeatureFlags feature_flags)
{
/* Start slow path for requested features that are not in
* ContactAttributeInterfaces */
if ((feature_flags & CONTACT_FEATURE_FLAG_ALIAS) != 0 &&
!contacts_context_supports_iface (context,
TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING) &&
tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING))
{
g_queue_push_tail (&context->todo, contacts_get_aliases);
}
if ((feature_flags & CONTACT_FEATURE_FLAG_PRESENCE) != 0 &&
!contacts_context_supports_iface (context,
TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE))
{
if (tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE))
{
g_queue_push_tail (&context->todo, contacts_get_simple_presence);
}
#if 0
/* FIXME: Before doing this for the first time, we'd need to download
* from the CM the definition of what each status actually *means* */
else if (tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_PRESENCE))
{
g_queue_push_tail (&context->todo, contacts_get_complex_presence);
}
#endif
}
if ((feature_flags & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0 &&
!contacts_context_supports_iface (context,
TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS) &&
tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
{
g_queue_push_tail (&context->todo, contacts_get_avatar_tokens);
}
if ((feature_flags & CONTACT_FEATURE_FLAG_LOCATION) != 0 &&
!contacts_context_supports_iface (context,
TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION) &&
tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION))
{
g_queue_push_tail (&context->todo, contacts_get_locations);
}
/* Don't implement slow path for ContactCapabilities as Contacts is now
* mandatory so any CM supporting ContactCapabilities will implement
* Contacts as well.
*
* But if ContactCapabilities is NOT supported, we fallback to connection
* capabilities.
* */
if ((feature_flags & CONTACT_FEATURE_FLAG_CAPABILITIES) != 0 &&
!tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES))
{
DEBUG ("Connection doesn't support ContactCapabilities; fallback to "
"connection capabilities");
g_queue_push_tail (&context->todo, contacts_get_conn_capabilities);
}
}
static void
contacts_got_attributes (TpConnection *connection,
GHashTable *attributes,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
guint i;
if (error != NULL)
{
contacts_context_fail (c, error);
return;
}
i = 0;
if (c->signature == CB_BY_HANDLE)
{
g_assert (c->contacts->len == 0);
while (i < c->handles->len)
{
TpHandle handle = g_array_index (c->handles, guint, i);
GHashTable *asv = g_hash_table_lookup (attributes,
GUINT_TO_POINTER (handle));
if (asv == NULL)
{
/* not in the hash table => not valid */
g_array_append_val (c->invalid, handle);
g_array_remove_index_fast (c->handles, i);
}
else
{
TpContact *contact = NULL;
guint j;
/* we might already have consumed the only reference we have to
* the handle - if we have, we must recycle the same object
* rather than calling tp_contact_ensure again */
for (j = 0; j < i; j++)
{
if (handle == g_array_index (c->handles, guint, j))
{
contact = g_object_ref (g_ptr_array_index (c->contacts,
j));
}
}
if (contact == NULL)
contact = tp_contact_ensure (connection, handle);
g_ptr_array_add (c->contacts, contact);
/* save the contact and move on to the next handle */
i++;
}
}
}
g_assert (c->contacts->len == c->handles->len);
for (i = 0; i < c->handles->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
const gchar *s;
gpointer boxed;
GHashTable *asv = g_hash_table_lookup (attributes,
GUINT_TO_POINTER (contact->priv->handle));
if (asv == NULL)
{
GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"We hold a ref to handle #%u but it appears to be invalid",
contact->priv->handle);
contacts_context_fail (c, e);
g_error_free (e);
return;
}
/* set up the contact with its attributes */
s = tp_asv_get_string (asv, TP_TOKEN_CONNECTION_CONTACT_ID);
if (s == NULL)
{
GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"Connection manager %s is broken: contact #%u in the "
"GetContactAttributes result has no contact-id",
tp_proxy_get_bus_name (connection), contact->priv->handle);
contacts_context_fail (c, e);
g_error_free (e);
return;
}
if (contact->priv->identifier == NULL)
{
contact->priv->identifier = g_strdup (s);
}
else if (tp_strdiff (contact->priv->identifier, s))
{
GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"Connection manager %s is broken: contact #%u identifier "
"changed from %s to %s",
tp_proxy_get_bus_name (connection), contact->priv->handle,
contact->priv->identifier, s);
contacts_context_fail (c, e);
g_error_free (e);
return;
}
s = tp_asv_get_string (asv,
TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS);
if (s != NULL)
{
contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
g_free (contact->priv->alias);
contact->priv->alias = g_strdup (s);
g_object_notify ((GObject *) contact, "alias");
}
s = tp_asv_get_string (asv, TP_TOKEN_CONNECTION_INTERFACE_AVATARS_TOKEN);
if (s != NULL)
contacts_avatar_updated (connection, contact->priv->handle, s,
NULL, NULL);
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE,
TP_STRUCT_TYPE_SIMPLE_PRESENCE);
contact_maybe_set_simple_presence (contact, boxed);
/* Location */
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_LOCATION_LOCATION,
TP_HASH_TYPE_LOCATION);
contact_maybe_set_location (contact, boxed);
/* Capabilities */
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_CAPABILITIES_CAPABILITIES,
TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
contact_maybe_set_capabilities (contact, boxed);
}
contacts_context_continue (c);
}
static void
contacts_get_attributes (ContactsContext *context)
{
GArray *contact_attribute_interfaces =
context->connection->priv->contact_attribute_interfaces;
GPtrArray *array;
const gchar **supported_interfaces;
guint i;
/* tp_connection_get_contact_attributes insists that you have at least one
* handle; skip it if we don't (can only happen if we started from IDs) */
if (context->handles->len == 0)
{
contacts_context_continue (context);
return;
}
g_assert (tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS));
g_assert (contact_attribute_interfaces != NULL);
array = g_ptr_array_sized_new (contact_attribute_interfaces->len);
for (i = 0; i < contact_attribute_interfaces->len; i++)
{
GQuark q = g_array_index (contact_attribute_interfaces, GQuark, i);
if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)
{
if ((context->wanted & CONTACT_FEATURE_FLAG_ALIAS) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_ALIASING);
contacts_bind_to_aliases_changed (context->connection);
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS)
{
if ((context->wanted & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_AVATARS);
contacts_bind_to_avatar_updated (context->connection);
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE)
{
if ((context->wanted & CONTACT_FEATURE_FLAG_PRESENCE) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE);
contacts_bind_to_presences_changed (context->connection);
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION)
{
if ((context->wanted & CONTACT_FEATURE_FLAG_LOCATION) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_LOCATION);
contacts_bind_to_location_updated (context->connection);
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES)
{
if ((context->wanted & CONTACT_FEATURE_FLAG_CAPABILITIES) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES);
contacts_bind_to_capabilities_updated (context->connection);
}
}
}
g_ptr_array_add (array, NULL);
supported_interfaces = (const gchar **) g_ptr_array_free (array, FALSE);
/* we want to hold the handles if and only if the call is by_handle -
* for the other modes, we already have handles */
context->refcount++;
tp_connection_get_contact_attributes (context->connection, -1,
context->handles->len, (const TpHandle *) context->handles->data,
supported_interfaces, (context->signature == CB_BY_HANDLE),
contacts_got_attributes,
context, contacts_context_unref, context->weak_object);
g_free (supported_interfaces);
}
/*
* Returns a new GPtrArray of borrowed references to TpContacts,
* or NULL if any contacts could not be found.
*/
static GPtrArray *
lookup_all_contacts (ContactsContext *context)
{
GPtrArray *contacts = g_ptr_array_new ();
guint i;
for (i = 0; i < context->handles->len; i++)
{
TpContact *contact = _tp_connection_lookup_contact (context->connection,
g_array_index (context->handles, TpHandle, i));
if (contact != NULL)
{
g_ptr_array_add (contacts, contact);
}
else
{
g_ptr_array_free (contacts, TRUE);
contacts = NULL;
break;
}
}
return contacts;
}
/**
* tp_connection_get_contacts_by_handle:
* @self: A connection, which must be ready (#TpConnection:connection-ready
* must be %TRUE)
* @n_handles: The number of handles in @handles (must be at least 1)
* @handles: (array length=n_handles) (element-type uint): An array of handles
* of type %TP_HANDLE_TYPE_CONTACT representing the desired contacts
* @n_features: The number of features in @features (may be 0)
* @features: (array length=n_features) (allow-none) (element-type uint): An array of features that
* must be ready for use (if supported) before the callback is called (may
* be %NULL if @n_features is 0)
* @callback: A user callback to call when the contacts are ready
* @user_data: Data to pass to the callback
* @destroy: Called to destroy @user_data either after @callback has been
* called, or if the operation is cancelled
* @weak_object: (allow-none): An object to pass to the callback, which will be
* weakly referenced; if this object is destroyed, the operation will be
* cancelled
*
* Create a number of #TpContact objects and make asynchronous method calls
* to hold their handles and ensure that all the features specified in
* @features are ready for use (if they are supported at all).
*
* It is not an error to put features in @features even if the connection
* manager doesn't support them - users of this method should have a static
* list of features they would like to use if possible, and use it for all
* connection managers.
*
* Since: 0.7.18
*/
void
tp_connection_get_contacts_by_handle (TpConnection *self,
guint n_handles,
const TpHandle *handles,
guint n_features,
const TpContactFeature *features,
TpConnectionContactsByHandleCb callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
ContactFeatureFlags feature_flags = 0;
ContactsContext *context;
GPtrArray *contacts;
guint i;
g_return_if_fail (tp_connection_is_ready (self));
g_return_if_fail (tp_proxy_get_invalidated (self) == NULL);
g_return_if_fail (n_handles >= 1);
g_return_if_fail (handles != NULL);
g_return_if_fail (n_features == 0 || features != NULL);
g_return_if_fail (callback != NULL);
for (i = 0; i < n_features; i++)
{
g_return_if_fail (features[i] < NUM_TP_CONTACT_FEATURES);
feature_flags |= (1 << features[i]);
}
context = contacts_context_new (self, n_handles, feature_flags,
CB_BY_HANDLE, user_data, destroy, weak_object);
context->callback.by_handle = callback;
g_array_append_vals (context->handles, handles, n_handles);
contacts = lookup_all_contacts (context);
if (contacts != NULL)
{
/* We have already held/inspected handles, so we can skip that. */
for (i = 0; i < n_handles; i++)
{
TpContact *contact = g_object_ref (g_ptr_array_index (contacts, i));
g_ptr_array_add (context->contacts, contact);
}
contacts_context_queue_features (context, feature_flags);
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
contacts_context_idle_continue, context, contacts_context_unref);
g_ptr_array_free (contacts, TRUE);
return;
}
if (tp_proxy_has_interface_by_id (self,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
{
/* we support the Contacts interface, so we can hold the handles and
* simultaneously inspect them. After that, we'll fill in any
* features that are necessary (this becomes a no-op if Contacts
* gave us everything). */
contacts_get_attributes (context);
contacts_context_queue_features (context, feature_flags);
/* we have one excess ref to the context because we create it,
* and then contacts_get_attributes refs it */
contacts_context_unref (context);
return;
}
/* if we haven't already returned, we're on the slow path */
/* Before we return anything we'll want to inspect the handles */
g_queue_push_head (&context->todo, contacts_inspect);
/* After that we'll get the features */
contacts_context_queue_features (context, feature_flags);
/* but first, we need to hold onto them */
tp_connection_hold_handles (self, -1,
TP_HANDLE_TYPE_CONTACT, n_handles, handles,
contacts_held_handles, context, contacts_context_unref, weak_object);
}
/**
* tp_connection_upgrade_contacts:
* @self: A connection, which must be ready (#TpConnection:connection-ready
* must be %TRUE)
* @n_contacts: The number of contacts in @contacts (must be at least 1)
* @contacts: (array length=n_contacts): An array of #TpContact objects
* associated with @self
* @n_features: The number of features in @features (must be at least 1)
* @features: (array length=n_features): An array of features that must be
* ready for use (if supported) before the callback is called
* @callback: A user callback to call when the contacts are ready
* @user_data: Data to pass to the callback
* @destroy: Called to destroy @user_data either after @callback has been
* called, or if the operation is cancelled
* @weak_object: (allow-none): An object to pass to the callback, which will be
* weakly referenced; if this object is destroyed, the operation will be
* cancelled
*
* Given several #TpContact objects, make asynchronous method calls
* ensure that all the features specified in @features are ready for use
* (if they are supported at all).
*
* It is not an error to put features in @features even if the connection
* manager doesn't support them - users of this method should have a static
* list of features they would like to use if possible, and use it for all
* connection managers.
*
* Since: 0.7.18
*/
void
tp_connection_upgrade_contacts (TpConnection *self,
guint n_contacts,
TpContact * const *contacts,
guint n_features,
const TpContactFeature *features,
TpConnectionUpgradeContactsCb callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
ContactFeatureFlags feature_flags = 0;
ContactsContext *context;
guint i;
g_return_if_fail (tp_connection_is_ready (self));
g_return_if_fail (tp_proxy_get_invalidated (self) == NULL);
g_return_if_fail (n_contacts >= 1);
g_return_if_fail (contacts != NULL);
g_return_if_fail (n_features == 0 || features != NULL);
g_return_if_fail (callback != NULL);
for (i = 0; i < n_contacts; i++)
{
g_return_if_fail (contacts[i]->priv->connection == self);
}
for (i = 0; i < n_features; i++)
{
g_return_if_fail (features[i] < NUM_TP_CONTACT_FEATURES);
feature_flags |= (1 << features[i]);
}
context = contacts_context_new (self, n_contacts, feature_flags,
CB_UPGRADE, user_data, destroy, weak_object);
context->callback.upgrade = callback;
for (i = 0; i < n_contacts; i++)
{
g_ptr_array_add (context->contacts, g_object_ref (contacts[i]));
g_array_append_val (context->handles, contacts[i]->priv->handle);
}
g_assert (context->handles->len == n_contacts);
if (tp_proxy_has_interface_by_id (self,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
{
g_queue_push_head (&context->todo, contacts_get_attributes);
}
contacts_context_queue_features (context, feature_flags);
/* use an idle to make sure the callback is called after we return,
* even if all the contacts actually have all the features, just to be
* consistent */
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
contacts_context_idle_continue, context, contacts_context_unref);
}
static void
contacts_requested_one_handle (TpConnection *connection,
TpHandleType handle_type,
guint n_handles,
const TpHandle *handles,
const gchar * const *ids,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
if (error == NULL)
{
TpContact *contact;
g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
/* -1 because NULL terminator is explicit */
g_assert (c->next_index < c->request_ids->len - 1);
g_assert (n_handles == 1);
g_assert (handles[0] != 0);
contact = tp_contact_ensure (connection, handles[0]);
g_array_append_val (c->handles, handles[0]);
g_ptr_array_add (c->contacts, contact);
c->next_index++;
}
else if (error->domain == TP_ERRORS &&
(error->code == TP_ERROR_INVALID_HANDLE ||
error->code == TP_ERROR_NOT_AVAILABLE ||
error->code == TP_ERROR_INVALID_ARGUMENT))
{
g_hash_table_insert (c->request_errors,
g_ptr_array_index (c->request_ids, c->next_index),
g_error_copy (error));
/* shift the rest of the IDs down one and do not increment next_index */
g_ptr_array_remove_index (c->request_ids, c->next_index);
}
else
{
contacts_context_fail (c, error);
return;
}
contacts_context_continue (c);
}
static void
contacts_request_one_handle (ContactsContext *c)
{
const gchar *ids[] = { NULL, NULL };
ids[0] = g_ptr_array_index (c->request_ids, c->next_index);
g_assert (ids[0] != NULL);
c->refcount++;
tp_connection_request_handles (c->connection, -1,
TP_HANDLE_TYPE_CONTACT, ids,
contacts_requested_one_handle, c, contacts_context_unref,
c->weak_object);
}
static void
contacts_requested_handles (TpConnection *connection,
TpHandleType handle_type,
guint n_handles,
const TpHandle *handles,
const gchar * const *ids,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
g_assert (weak_object == c->weak_object);
if (error == NULL)
{
guint i;
for (i = 0; i < n_handles; i++)
{
TpContact *contact = tp_contact_ensure (connection, handles[i]);
g_array_append_val (c->handles, handles[i]);
g_ptr_array_add (c->contacts, contact);
}
}
else if (error->domain == TP_ERRORS &&
(error->code == TP_ERROR_INVALID_HANDLE ||
error->code == TP_ERROR_NOT_AVAILABLE ||
error->code == TP_ERROR_INVALID_ARGUMENT))
{
/* One of the strings is bad. We don't know which, so split them. */
guint i;
DEBUG ("A handle was bad, trying to recover: %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
/* -1 because NULL terminator is explicit */
for (i = 0; i < c->request_ids->len - 1; i++)
{
g_queue_push_head (&c->todo, contacts_request_one_handle);
}
g_assert (c->next_index == 0);
}
else
{
DEBUG ("RequestHandles failed: %s %u: %s",
g_quark_to_string (error->domain), error->code, error->message);
contacts_context_fail (c, error);
return;
}
contacts_context_continue (c);
}
/**
* tp_connection_get_contacts_by_id:
* @self: A connection, which must be ready (#TpConnection:connection-ready
* must be %TRUE)
* @n_ids: The number of IDs in @ids (must be at least 1)
* @ids: (array length=n_ids) (transfer none): An array of strings representing
* the desired contacts by their
* identifiers in the IM protocol (XMPP JIDs, SIP URIs, MSN Passports,
* AOL screen-names etc.)
* @n_features: The number of features in @features (may be 0)
* @features: (array length=n_features) (allow-none): An array of features
* that must be ready for use (if supported)
* before the callback is called (may be %NULL if @n_features is 0)
* @callback: A user callback to call when the contacts are ready
* @user_data: Data to pass to the callback
* @destroy: Called to destroy @user_data either after @callback has been
* called, or if the operation is cancelled
* @weak_object: (allow-none): An object to pass to the callback, which will
* be weakly referenced; if this object is destroyed, the operation will be
* cancelled
*
* Create a number of #TpContact objects and make asynchronous method calls
* to obtain their handles and ensure that all the features specified in
* @features are ready for use (if they are supported at all).
*
* It is not an error to put features in @features even if the connection
* manager doesn't support them - users of this method should have a static
* list of features they would like to use if possible, and use it for all
* connection managers.
*
* Since: 0.7.18
*/
void
tp_connection_get_contacts_by_id (TpConnection *self,
guint n_ids,
const gchar * const *ids,
guint n_features,
const TpContactFeature *features,
TpConnectionContactsByIdCb callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
ContactFeatureFlags feature_flags = 0;
ContactsContext *context;
guint i;
g_return_if_fail (tp_connection_is_ready (self));
g_return_if_fail (tp_proxy_get_invalidated (self) == NULL);
g_return_if_fail (n_ids >= 1);
g_return_if_fail (ids != NULL);
g_return_if_fail (ids[0] != NULL);
g_return_if_fail (n_features == 0 || features != NULL);
g_return_if_fail (callback != NULL);
for (i = 0; i < n_features; i++)
{
g_return_if_fail (features[i] < NUM_TP_CONTACT_FEATURES);
feature_flags |= (1 << features[i]);
}
context = contacts_context_new (self, n_ids, feature_flags,
CB_BY_ID, user_data, destroy, weak_object);
context->callback.by_id = callback;
context->request_errors = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_error_free);
context->request_ids = g_ptr_array_sized_new (n_ids);
for (i = 0; i < n_ids; i++)
{
g_return_if_fail (ids[i] != NULL);
g_ptr_array_add (context->request_ids, g_strdup (ids[i]));
}
g_ptr_array_add (context->request_ids, NULL);
/* set up the queue of feature introspection */
if (tp_proxy_has_interface_by_id (self,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
{
g_queue_push_head (&context->todo, contacts_get_attributes);
}
else
{
g_queue_push_head (&context->todo, contacts_inspect);
}
contacts_context_queue_features (context, feature_flags);
/* but first, we need to get the handles in the first place */
tp_connection_request_handles (self, -1,
TP_HANDLE_TYPE_CONTACT,
(const gchar * const *) context->request_ids->pdata,
contacts_requested_handles, context, contacts_context_unref,
weak_object);
}