/* 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); }