/* 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 "config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_CONTACTS
#include "telepathy-glib/base-contact-list-internal.h"
#include "telepathy-glib/connection-contact-list.h"
#include "telepathy-glib/connection-internal.h"
#include "telepathy-glib/contact-internal.h"
#include "telepathy-glib/debug-internal.h"
#include "telepathy-glib/util-internal.h"
#include "telepathy-glib/variant-util-internal.h"
static const gchar *
nonnull (const gchar *s)
{
if (s == NULL)
return "(null)";
return s;
}
/**
* 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)
* and #TpContact:location-vardict (since 0.19.10)
* @TP_CONTACT_FEATURE_CAPABILITIES: #TpContact:capabilities
* (available since 0.11.3)
* @TP_CONTACT_FEATURE_AVATAR_DATA: #TpContact:avatar-file and
* #TpContact:avatar-mime-type. Implies %TP_CONTACT_FEATURE_AVATAR_TOKEN
* (available since 0.11.6)
* @TP_CONTACT_FEATURE_CONTACT_INFO: #TpContact:contact-info
* (available since 0.11.7)
* @TP_CONTACT_FEATURE_CLIENT_TYPES: #TpContact:client-types
* (available since 0.13.1)
* @TP_CONTACT_FEATURE_SUBSCRIPTION_STATES: #TpContact:subscribe-state,
* #TpContact:publish-state and #TpContact:publish-request. Require a
* Connection implementing the %TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST
* interface. (available since 0.13.12)
* @TP_CONTACT_FEATURE_CONTACT_GROUPS: #TpContact:contact-groups
* (available since 0.13.14)
* @TP_CONTACT_FEATURE_CONTACT_BLOCKING: #TpContact:is-blocked. Require
* Connection implementing the %TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING
* interface. (available since 0.17.0)
*
* 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.5, there is a corresponding #GEnumClass type,
* %TP_TYPE_CONTACT_FEATURE.
*
* Since: 0.7.18
*/
/**
* TP_NUM_CONTACT_FEATURES:
*
* 1 higher than the highest #TpContactFeature supported by this version of
* telepathy-glib.
*
* Since: 0.19.0
*/
/**
* NUM_TP_CONTACT_FEATURES: (skip)
*
* 1 higher than the highest #TpContactFeature supported by this version of
* telepathy-glib. Use %TP_NUM_CONTACT_FEATURES in new code.
*
* Since: 0.7.18
*/
/**
* TP_CONTACT_FEATURE_INVALID: (skip)
*
* An invalid TpContactFeature. Used as list termination. See for example
* tp_simple_client_factory_add_contact_features_varargs().
*
* Since: 0.15.5
*/
/**
* TP_TYPE_CONTACT_FEATURE:
*
* The #GEnumClass type of a #TpContactFeature.
*
* Since: 0.11.5
*/
G_DEFINE_TYPE (TpContact, tp_contact, G_TYPE_OBJECT)
enum {
PROP_CONNECTION = 1,
PROP_HANDLE,
PROP_IDENTIFIER,
PROP_ALIAS,
PROP_AVATAR_TOKEN,
PROP_AVATAR_FILE,
PROP_AVATAR_MIME_TYPE,
PROP_PRESENCE_TYPE,
PROP_PRESENCE_STATUS,
PROP_PRESENCE_MESSAGE,
PROP_LOCATION,
PROP_LOCATION_VARDICT,
PROP_CAPABILITIES,
PROP_CONTACT_INFO,
PROP_CLIENT_TYPES,
PROP_SUBSCRIBE_STATE,
PROP_PUBLISH_STATE,
PROP_PUBLISH_REQUEST,
PROP_CONTACT_GROUPS,
PROP_IS_BLOCKED,
N_PROPS
};
enum {
SIGNAL_PRESENCE_CHANGED,
SIGNAL_SUBSCRIPTION_STATES_CHANGED,
SIGNAL_CONTACT_GROUPS_CHANGED,
N_SIGNALS
};
static guint signals[N_SIGNALS] = {0};
/* 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,
CONTACT_FEATURE_FLAG_AVATAR_DATA = 1 << TP_CONTACT_FEATURE_AVATAR_DATA,
CONTACT_FEATURE_FLAG_CONTACT_INFO = 1 << TP_CONTACT_FEATURE_CONTACT_INFO,
CONTACT_FEATURE_FLAG_CLIENT_TYPES = 1 << TP_CONTACT_FEATURE_CLIENT_TYPES,
CONTACT_FEATURE_FLAG_STATES = 1 << TP_CONTACT_FEATURE_SUBSCRIPTION_STATES,
CONTACT_FEATURE_FLAG_CONTACT_GROUPS = 1 << TP_CONTACT_FEATURE_CONTACT_GROUPS,
CONTACT_FEATURE_FLAG_CONTACT_BLOCKING = 1 << TP_CONTACT_FEATURE_CONTACT_BLOCKING,
} ContactFeatureFlags;
struct _TpContactPrivate {
/* basics */
TpConnection *connection;
TpHandle handle;
gchar *identifier;
ContactFeatureFlags has_features;
/* aliasing */
gchar *alias;
/* avatars */
gchar *avatar_token;
GFile *avatar_file;
gchar *avatar_mime_type;
/* presence */
TpConnectionPresenceType presence_type;
gchar *presence_status;
gchar *presence_message;
/* location */
GHashTable *location;
/* client types */
gchar **client_types;
/* capabilities */
TpCapabilities *capabilities;
/* a list of TpContactInfoField */
GList *contact_info;
/* Subscribe/Publish states */
TpSubscriptionState subscribe;
TpSubscriptionState publish;
gchar *publish_request;
/* ContactGroups */
/* array of dupped strings */
GPtrArray *contact_groups;
/* ContactBlocking */
gboolean is_blocked;
};
/**
* tp_contact_get_account:
* @self: a contact
*
* Return the #TpAccount of @self's #TpContact:connection.
* See tp_connection_get_account() for details.
*
* Returns: (transfer none): a borrowed reference to @self's account
* (it must be referenced with g_object_ref if it must remain valid
* longer than the contact)
*
* Since: 0.19.0
*/
TpAccount *
tp_contact_get_account (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
return tp_connection_get_account (self->priv->connection);
}
/**
* 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 < TP_NUM_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_avatar_file:
* @self: a contact
*
* Return the contact's avatar file. This remains valid until the main loop
* is re-entered; if the caller requires a #GFile that will persist for
* longer than that, it must be reffed with g_object_ref().
*
* Returns: (transfer none): the same #GFile as the #TpContact:avatar-file property
* (possibly %NULL)
*
* Since: 0.11.6
*/
GFile *
tp_contact_get_avatar_file (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->priv->avatar_file;
}
/**
* tp_contact_get_avatar_mime_type:
* @self: a contact
*
* Return the contact's avatar MIME type. 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 MIME type as the #TpContact:avatar-mime-type property
* (possibly %NULL)
*
* Since: 0.11.6
*/
const gchar *
tp_contact_get_avatar_mime_type (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->priv->avatar_mime_type;
}
/**
* 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_dup_location:
* @self: a contact
*
* Return the contact's user-defined location, or %NULL if the location is
* unspecified.
*
* This function returns the same information as tp_contact_get_location(),
* but in a different format.
*
* Returns: a variant of type %G_VARIANT_TYPE_VARDICT, the same as
* the #TpContact:location-vardict property
*
* Since: 0.19.10
*/
GVariant *
tp_contact_dup_location (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
if (self->priv->location == NULL)
return NULL;
return _tp_asv_to_vardict (self->priv->location);
}
/**
* tp_contact_get_client_types:
* @self: a contact
*
* Return the contact's client types or %NULL if the client types are
* unspecified.
*
* Returns: (array zero-terminated=1) (transfer none): the same
* #GStrv as the #TpContact:client-types property
*
* Since: 0.13.1
*/
const gchar * const *
tp_contact_get_client_types (TpContact *self)
{
g_return_val_if_fail (self != NULL, NULL);
return (const gchar * const *) self->priv->client_types;
}
/**
* tp_contact_get_capabilities:
* @self: a contact
*
*
*
* Returns: (transfer none): 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;
}
/**
* tp_contact_get_contact_info:
* @self: a #TpContact
*
* Returns a newly allocated #GList of contact's vCard fields. The list must be
* freed with g_list_free() after used.
*
* Note that the #TpContactInfoFields in the returned #GList are not
* dupped before returning from this function. One could copy every item in the
* list using tp_contact_info_field_copy().
*
* Same as the #TpContact:contact-info property.
*
* Returns: (element-type TelepathyGLib.ContactInfoField) (transfer container):
* a #GList of #TpContactInfoField, or %NULL if the feature is not yet
* prepared.
* Since: 0.11.7
* Deprecated: Since 0.19.9. New code should use
* tp_contact_dup_contact_info() instead.
*/
GList *
tp_contact_get_contact_info (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
return g_list_copy (self->priv->contact_info);
}
/**
* tp_contact_dup_contact_info:
* @self: a #TpContact
*
* Returns a newly allocated #GList of contact's vCard fields. The list must be
* freed with tp_contact_info_list_free() after used.
*
* Same as the #TpContact:contact-info property.
*
* Returns: (element-type TelepathyGLib.ContactInfoField) (transfer full):
* a #GList of #TpContactInfoField, or %NULL if the feature is not yet
* prepared.
* Since: 0.19.9
*/
GList *
tp_contact_dup_contact_info (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
return _tp_g_list_copy_deep (self->priv->contact_info,
(GCopyFunc) tp_contact_info_field_copy, NULL);
}
/**
* tp_contact_get_subscribe_state:
* @self: a #TpContact
*
* Return the state of the local user's subscription to this remote contact's
* presence.
*
* This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
* %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
*
* Returns: the value of #TpContact:subscribe-state.
*
* Since: 0.13.12
*/
TpSubscriptionState
tp_contact_get_subscribe_state (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), TP_SUBSCRIPTION_STATE_UNKNOWN);
return self->priv->subscribe;
}
/**
* tp_contact_get_publish_state:
* @self: a #TpContact
*
* Return the state of this remote contact's subscription to the local user's
* presence.
*
* This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
* %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
*
* Returns: the value of #TpContact:publish-state.
*
* Since: 0.13.12
*/
TpSubscriptionState
tp_contact_get_publish_state (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), TP_SUBSCRIPTION_STATE_UNKNOWN);
return self->priv->publish;
}
/**
* tp_contact_get_publish_request:
* @self: a #TpContact
*
* If #TpContact:publish-state is set to %TP_SUBSCRIPTION_STATE_ASK, return the
* message that this remote contact sent when they requested permission to see
* the local user's presence, an empty string ("") otherwise. 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().
*
* This is set to %NULL until %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been
* prepared, and it is guaranteed to be non-%NULL afterward.
* Returns: the value of #TpContact:publish-request.
*
* Since: 0.13.12
*/
const gchar *
tp_contact_get_publish_request (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
return self->priv->publish_request;
}
/**
* tp_contact_get_contact_groups:
* @self: a #TpContact
*
* Return names of groups of which a contact is a member. It is incorrect to
* call this method before %TP_CONTACT_FEATURE_CONTACT_GROUPS has been
* prepared. This remains valid until the main loop is re-entered; if the caller
* requires a #GStrv that will persist for longer than that, it must be copied
* with g_strdupv().
*
* Returns: (array zero-terminated=1) (transfer none): the same
* #GStrv as the #TpContact:contact-groups property
*
* Since: 0.13.14
*/
const gchar * const *
tp_contact_get_contact_groups (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
if (self->priv->contact_groups == NULL)
return NULL;
return (const gchar * const *) self->priv->contact_groups->pdata;
}
static void
set_contact_groups_cb (TpConnection *connection,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
GSimpleAsyncResult *result = user_data;
if (error != NULL)
{
DEBUG ("Failed to set contact groups: %s", error->message);
g_simple_async_result_set_from_error (result, error);
}
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
/**
* tp_contact_set_contact_groups_async:
* @self: a #TpContact
* @n_groups: the number of groups, or -1 if @groups is %NULL-terminated
* @groups: (array length=n_groups) (element-type utf8) (allow-none): the set of
* groups which the contact should be in (may be %NULL if @n_groups is 0)
* @callback: a callback to call when the request is satisfied
* @user_data: data to pass to @callback
*
* Add @self to the given groups (creating new groups if necessary), and remove
* it from all other groups. If the user is removed from a group of which they
* were the only member, the group MAY be removed automatically. You can then
* call tp_contact_set_contact_groups_finish() to get the result of the
* operation.
*
* If the operation is successful and %TP_CONTACT_FEATURE_CONTACT_GROUPS is
* prepared, the #TpContact:contact-groups property will be
* updated (emitting "notify::contact-groups" signal) and
* #TpContact::contact-groups-changed signal will be emitted before @callback
* is called. That means you can call tp_contact_get_contact_groups() to get the
* new contact groups inside @callback.
*
* Since: 0.13.14
*/
void
tp_contact_set_contact_groups_async (TpContact *self,
gint n_groups,
const gchar * const *groups,
GAsyncReadyCallback callback,
gpointer user_data)
{
static const gchar *empty_groups[] = { NULL };
GSimpleAsyncResult *result;
gchar **new_groups = NULL;
g_return_if_fail (TP_IS_CONTACT (self));
g_return_if_fail (n_groups >= -1);
g_return_if_fail (n_groups <= 0 || groups != NULL);
if (groups == NULL)
{
groups = empty_groups;
}
else if (n_groups > 0)
{
/* Create NULL-terminated array */
new_groups = g_new0 (gchar *, n_groups + 1);
g_memmove (new_groups, groups, n_groups * sizeof (gchar *));
groups = (const gchar * const *) new_groups;
}
result = g_simple_async_result_new (G_OBJECT (self),
callback, user_data, tp_contact_set_contact_groups_finish);
tp_cli_connection_interface_contact_groups_call_set_contact_groups (
self->priv->connection, -1, self->priv->handle, (const gchar **) groups,
set_contact_groups_cb, result, NULL, G_OBJECT (self));
g_free (new_groups);
}
/**
* tp_contact_set_contact_groups_finish:
* @self: a #TpContact
* @result: a #GAsyncResult
* @error: a #GError to be filled
*
* Finishes an async set of @self contact groups.
*
* Returns: %TRUE if the request call was successful, otherwise %FALSE
*
* Since: 0.13.14
*/
gboolean
tp_contact_set_contact_groups_finish (TpContact *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (self, tp_contact_set_contact_groups_finish);
}
void
_tp_contact_connection_disposed (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);
self->priv->handle = 0;
}
tp_clear_object (&self->priv->connection);
tp_clear_pointer (&self->priv->location, g_hash_table_unref);
tp_clear_object (&self->priv->capabilities);
tp_clear_object (&self->priv->avatar_file);
tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref);
((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->avatar_mime_type);
g_free (self->priv->presence_status);
g_free (self->priv->presence_message);
g_strfreev (self->priv->client_types);
tp_contact_info_list_free (self->priv->contact_info);
g_free (self->priv->publish_request);
((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_AVATAR_FILE:
g_value_set_object (value, self->priv->avatar_file);
break;
case PROP_AVATAR_MIME_TYPE:
g_value_set_string (value, self->priv->avatar_mime_type);
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_LOCATION_VARDICT:
g_value_take_variant (value, tp_contact_dup_location (self));
break;
case PROP_CAPABILITIES:
g_value_set_object (value, tp_contact_get_capabilities (self));
break;
case PROP_CONTACT_INFO:
g_value_set_boxed (value, self->priv->contact_info);
break;
case PROP_CLIENT_TYPES:
g_value_set_boxed (value, tp_contact_get_client_types (self));
break;
case PROP_SUBSCRIBE_STATE:
g_value_set_uint (value, tp_contact_get_subscribe_state (self));
break;
case PROP_PUBLISH_STATE:
g_value_set_uint (value, tp_contact_get_publish_state (self));
break;
case PROP_PUBLISH_REQUEST:
g_value_set_string (value, tp_contact_get_publish_request (self));
break;
case PROP_CONTACT_GROUPS:
g_value_set_boxed (value, tp_contact_get_contact_groups (self));
break;
case PROP_IS_BLOCKED:
g_value_set_boolean (value, tp_contact_is_blocked (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:avatar-file:
*
* #GFile to the latest cached avatar image, or %NULL if this contact has
* no avatar, or if the avatar data is not yet retrieved.
*
* When #TpContact:avatar-token changes, this property is not updated
* immediately, but will be updated when the new avatar data is retrieved and
* stored in cache. Until then, the file will keep its old value of the latest
* cached avatar image.
*
* This is set to %NULL if %TP_CONTACT_FEATURE_AVATAR_DATA is not set on this
* contact. Note that setting %TP_CONTACT_FEATURE_AVATAR_DATA will also
* implicitly set %TP_CONTACT_FEATURE_AVATAR_TOKEN.
*
* Since: 0.11.6
*/
param_spec = g_param_spec_object ("avatar-file",
"Avatar file",
"File to the latest cached avatar image, or %NULL",
G_TYPE_FILE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_AVATAR_FILE,
param_spec);
/**
* TpContact:avatar-mime-type:
*
* MIME type of the latest cached avatar image, or %NULL if this contact has
* no avatar, or if the avatar data is not yet retrieved.
*
* This is always the MIME type of the image given by #TpContact:avatar-file.
*
* Since: 0.11.6
*/
param_spec = g_param_spec_string ("avatar-mime-type",
"Avatar MIME type",
"MIME type of the latest cached avatar image, or %NULL",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_AVATAR_MIME_TYPE,
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:location-vardict:
*
* If this contact has set a user-defined location, a string to
* variant map containing his location. If not, %NULL.
* tp_vardict_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.
*
* This property contains the same information as #TpContact:location,
* in a different format.
*
* Since: 0.19.10
*/
param_spec = g_param_spec_variant ("location-vardict",
"Location",
"User-defined location, or NULL",
G_VARIANT_TYPE_VARDICT, NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_LOCATION_VARDICT,
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);
/**
* TpContact:contact-info:
*
* A #GList of #TpContactInfoField representing the vCard of this contact.
*
* This is set to %NULL if %TP_CONTACT_FEATURE_CONTACT_INFO is not set on this
* contact.
*
* Since: 0.11.7
*/
param_spec = g_param_spec_boxed ("contact-info",
"Contact Info",
"Information of the contact, or NULL",
TP_TYPE_CONTACT_INFO_LIST,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_INFO,
param_spec);
/**
* TpContact:client-types:
*
* A #GStrv containing the client types of this contact.
*
* This is set to %NULL if %TP_CONTACT_FEATURE_CLIENT_TYPES is not
* set on this contact; it may also be %NULL if that feature is prepared, but
* the contact's client types are unknown.
*
* Since: 0.13.1
*/
param_spec = g_param_spec_boxed ("client-types",
"Client types",
"Client types of the contact, or NULL",
G_TYPE_STRV,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CLIENT_TYPES,
param_spec);
/**
* TpContact:subscribe-state:
*
* A #TpSubscriptionState indicating the state of the local user's
* subscription to this contact's presence.
*
* This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
* %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
*
* Since: 0.13.12
*/
param_spec = g_param_spec_uint ("subscribe-state",
"Subscribe State",
"Subscribe state of the contact",
0,
G_MAXUINT,
TP_SUBSCRIPTION_STATE_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SUBSCRIBE_STATE,
param_spec);
/**
* TpContact:publish-state:
*
* A #TpSubscriptionState indicating the state of this contact's subscription
* to the local user's presence.
*
* This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
* %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
*
* Since: 0.13.12
*/
param_spec = g_param_spec_uint ("publish-state",
"Publish State",
"Publish state of the contact",
0,
G_MAXUINT,
TP_SUBSCRIPTION_STATE_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_PUBLISH_STATE,
param_spec);
/**
* TpContact:publish-request:
*
* The message that contact sent when they requested permission to see the
* local user's presence, if #TpContact:publish-state is
* %TP_SUBSCRIPTION_STATE_ASK, an empty string ("") otherwise.
*
* This is set to %NULL until %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been
* prepared, and it is guaranteed to be non-%NULL afterward.
*
* Since: 0.13.12
*/
param_spec = g_param_spec_string ("publish-request",
"Publish Request",
"Publish request message of the contact",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_PUBLISH_REQUEST,
param_spec);
/**
* TpContact:contact-groups:
*
* a #GStrv with names of groups of which a contact is a member.
*
* This is set to %NULL if %TP_CONTACT_FEATURE_CONTACT_GROUPS is not prepared
* on this contact, or if the connection does not implement ContactGroups
* interface.
*
* Since: 0.13.14
*/
param_spec = g_param_spec_boxed ("contact-groups",
"Contact Groups",
"Groups of the contact",
G_TYPE_STRV,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_GROUPS,
param_spec);
/**
* TpContact:is-blocked:
*
* %TRUE if the contact has been blocked.
*
* This is set to %FALSE if %TP_CONTACT_FEATURE_CONTACT_BLOCKING is not
* prepared on this contact, or if the connection does not implement
* ContactBlocking interface.
*
* Since: 0.17.0
*/
param_spec = g_param_spec_boolean ("is-blocked",
"is blocked",
"TRUE if contact is blocked",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_IS_BLOCKED, param_spec);
/**
* TpContact::contact-groups-changed:
* @contact: A #TpContact
* @added: A #GStrv with added contact groups
* @removed: A #GStrv with removed contact groups
*
* Emitted when this contact's groups changes. When this signal is emitted,
* #TpContact:contact-groups property is already updated.
*
* Since: 0.13.14
*/
signals[SIGNAL_CONTACT_GROUPS_CHANGED] = g_signal_new (
"contact-groups-changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_STRV, G_TYPE_STRV);
/**
* TpContact::subscription-states-changed:
* @contact: a #TpContact
* @subscribe: the new value of #TpContact:subscribe-state
* @publish: the new value of #TpContact:publish-state
* @publish_request: the new value of #TpContact:publish-request
*
* Emitted when this contact's subscription states changes.
*
* Since: 0.13.12
*/
signals[SIGNAL_SUBSCRIPTION_STATES_CHANGED] = g_signal_new (
"subscription-states-changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
/**
* TpContact::presence-changed:
* @contact: a #TpContact
* @type: The new value of #TpContact:presence-type
* @status: The new value of #TpContact:presence-status
* @message: The new value of #TpContact:presence-message
*
* Emitted when this contact's presence changes.
*
* Since: 0.11.7
*/
signals[SIGNAL_PRESENCE_CHANGED] = g_signal_new ("presence-changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
}
TpContact *
_tp_contact_new (TpConnection *connection,
TpHandle handle,
const gchar *identifier)
{
TpContact *self = TP_CONTACT (g_object_new (TP_TYPE_CONTACT, NULL));
self->priv->connection = g_object_ref (connection);
self->priv->handle = handle;
self->priv->identifier = g_strdup (identifier);
return self;
}
/* FIXME: Ideally this should be replaced with
*
* tp_simple_client_factory_ensure_contact (tp_proxy_get_factory (connection),
* handle, identifier);
*
* but we cannot assert CM has immortal handles (yet). That means we cannot
* guarantee that all TpContact objects are created through the factory and so
* let it make TpContact subclasses.
*/
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);
return g_object_ref (self);
}
self = _tp_contact_new (connection, handle, NULL);
_tp_connection_add_contact (connection, handle, self);
return self;
}
/**
* tp_connection_dup_contact_if_possible:
* @connection: a connection
* @handle: a handle of type %TP_HANDLE_TYPE_CONTACT
* @identifier: (transfer none): the normalized identifier (XMPP JID, etc.)
* corresponding to @handle, or %NULL if not known
*
* Try to return an existing contact object or create a new contact object
* immediately.
*
* If tp_connection_has_immortal_handles() would return %TRUE and
* @identifier is non-%NULL, this function always succeeds.
*
* On connections without immortal handles, it is not possible to guarantee
* that @handle remains valid without making asynchronous D-Bus calls, so
* it might be necessary to delay processing of messages or other events
* until a #TpContact can be constructed asynchronously, for instance by using
* tp_connection_get_contacts_by_id().
*
* Similarly, if @identifier is %NULL, it might not be possible to find the
* identifier for @handle without making asynchronous D-Bus calls, so
* it might be necessary to delay processing of messages or other events
* until a #TpContact can be constructed asynchronously, for instance by using
* tp_connection_get_contacts_by_handle().
*
* Returns: (transfer full): a contact or %NULL
*
* Since: 0.13.9
*/
TpContact *
tp_connection_dup_contact_if_possible (TpConnection *connection,
TpHandle handle,
const gchar *identifier)
{
TpContact *ret;
g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
g_return_val_if_fail (handle != 0, NULL);
ret = _tp_connection_lookup_contact (connection, handle);
if (ret != NULL && ret->priv->identifier != NULL)
{
g_object_ref (ret);
}
else if (tp_connection_has_immortal_handles (connection) &&
identifier != NULL)
{
ret = tp_contact_ensure (connection, handle);
if (ret->priv->identifier == NULL)
{
/* new object, I suppose we'll have to believe the caller */
ret->priv->identifier = g_strdup (identifier);
}
}
else
{
/* we don't already have a contact, and we can't make one without
* D-Bus calls (either because we can't rely on the handle staying
* static, or we don't know the identifier) */
return NULL;
}
g_assert (ret->priv->handle == handle);
if (G_UNLIKELY (identifier != NULL &&
tp_strdiff (ret->priv->identifier, identifier)))
{
WARNING ("Either this client, or connection manager %s, is broken: "
"handle %u is thought to be '%s', but we already have "
"a TpContact that thinks the identifier is '%s'",
tp_proxy_get_bus_name (connection), handle, identifier,
ret->priv->identifier);
g_object_unref (ret);
return NULL;
}
return ret;
}
static void
tp_contact_init (TpContact *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONTACT,
TpContactPrivate);
self->priv->client_types = NULL;
}
typedef struct _ContactsContext ContactsContext;
typedef void (*ContactsProc) (ContactsContext *self);
typedef enum { CB_BY_HANDLE, CB_BY_ID, CB_UPGRADE } ContactsSignature;
static const gchar *
contacts_signature_to_string (ContactsSignature sig)
{
switch (sig)
{
case CB_BY_HANDLE:
return "by handle";
case CB_BY_ID:
return "by ID";
case CB_UPGRADE:
return "upgrade";
default:
return "???";
}
}
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 to get, if possible, before this request can finish */
ContactFeatureFlags wanted;
/* features we can expect to get from GetContactAttributes
* (subset of wanted) */
ContactFeatureFlags getting;
/* 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;
/* Whether or not our weak object died*/
gboolean no_purpose_in_life;
/* 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;
/* TRUE if all contacts already have IDs */
gboolean contacts_have_ids;
};
/* 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. */
G_STATIC_ASSERT (sizeof (GCallback) == sizeof (gpointer));
static void
contacts_context_weak_notify (gpointer data,
GObject *dead)
{
ContactsContext *c = data;
g_assert (c->weak_object == dead);
c->no_purpose_in_life = TRUE;
c->weak_object = NULL;
}
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);
DEBUG ("%p, for %u contacts, %s", c, n_contacts,
contacts_signature_to_string (signature));
DEBUG ("want alias: %s",
(want_features & CONTACT_FEATURE_FLAG_ALIAS) ? "yes" : "no");
DEBUG ("want avatar token: %s",
(want_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) ? "yes" : "no");
DEBUG ("want presence: %s",
(want_features & CONTACT_FEATURE_FLAG_PRESENCE) ? "yes" : "no");
DEBUG ("want location: %s",
(want_features & CONTACT_FEATURE_FLAG_LOCATION) ? "yes" : "no");
DEBUG ("want caps: %s",
(want_features & CONTACT_FEATURE_FLAG_CAPABILITIES) ? "yes" : "no");
DEBUG ("want avatar data: %s",
(want_features & CONTACT_FEATURE_FLAG_AVATAR_DATA) ? "yes" : "no");
DEBUG ("want contact info: %s",
(want_features & CONTACT_FEATURE_FLAG_CONTACT_INFO) ? "yes" : "no");
DEBUG ("want client types: %s",
(want_features & CONTACT_FEATURE_FLAG_CLIENT_TYPES) ? "yes" : "no");
DEBUG ("want states: %s",
(want_features & CONTACT_FEATURE_FLAG_STATES) ? "yes" : "no");
DEBUG ("want contact groups: %s",
(want_features & CONTACT_FEATURE_FLAG_CONTACT_GROUPS) ? "yes" : "no");
DEBUG ("want contact blocking: %s",
(want_features & CONTACT_FEATURE_FLAG_CONTACT_BLOCKING) ? "yes" : "no");
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;
if (c->weak_object != NULL)
g_object_weak_ref (c->weak_object, contacts_context_weak_notify, c);
g_queue_init (&c->todo);
return c;
}
static void
contacts_context_unref (gpointer p)
{
ContactsContext *c = p;
if ((--c->refcount) > 0)
return;
DEBUG ("last-unref (%p)", c);
g_assert (c->connection != NULL);
tp_clear_object (&c->connection);
g_queue_clear (&c->todo);
g_assert (c->contacts != NULL);
g_ptr_array_foreach (c->contacts, (GFunc) g_object_unref, NULL);
g_ptr_array_unref (c->contacts);
c->contacts = NULL;
g_assert (c->handles != NULL);
g_array_unref (c->handles);
c->handles = NULL;
g_assert (c->invalid != NULL);
g_array_unref (c->invalid);
c->invalid = NULL;
if (c->request_ids != NULL)
g_strfreev ((gchar **) g_ptr_array_free (c->request_ids, FALSE));
c->request_ids = NULL;
tp_clear_pointer (&c->request_errors, g_hash_table_unref);
if (c->destroy != NULL)
c->destroy (c->user_data);
c->destroy = NULL;
c->user_data = NULL;
if (c->weak_object != NULL)
g_object_weak_unref (c->weak_object, contacts_context_weak_notify, c);
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_ERROR 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: (array length=n_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 (c->no_purpose_in_life)
{
DEBUG ("%p: no purpose in life", c);
return;
}
if (g_queue_is_empty (&c->todo))
{
/* do some final sanity checking then hand over the contacts to the
* library user */
guint i;
DEBUG ("%p: nothing more to do", c);
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);
if (G_UNLIKELY (tp_proxy_get_invalidated (c->connection) != NULL))
{
DEBUG ("%p: failing due to connection having been invalidated: %s",
c, tp_proxy_get_invalidated (c->connection)->message);
contacts_context_fail (c, tp_proxy_get_invalidated (c->connection));
}
else
{
DEBUG ("%p: on to the next thing", c);
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_ERROR &&
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)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
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);
G_GNUC_END_IGNORE_DEPRECATIONS
}
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);
g_assert (c->contacts->len == 0);
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_ERROR &&
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;
DEBUG ("%p: inspected %u handles", c, c->contacts->len);
for (i = 0; i < c->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (c->contacts, i);
g_assert (ids[i] != NULL);
DEBUG ("- #%u: \"%s\"", contact->priv->handle, ids[i]);
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_ERROR &&
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)
{
guint type;
const gchar *status;
const gchar *message;
if (contact == NULL)
return;
g_return_if_fail (presence != NULL);
contact->priv->has_features |= CONTACT_FEATURE_FLAG_PRESENCE;
tp_value_array_unpack (presence, 3, &type, &status, &message);
contact->priv->presence_type = type;
g_free (contact->priv->presence_status);
contact->priv->presence_status = g_strdup (status);
g_free (contact->priv->presence_message);
contact->priv->presence_message = g_strdup (message);
g_object_notify ((GObject *) contact, "presence-type");
g_object_notify ((GObject *) contact, "presence-status");
g_object_notify ((GObject *) contact, "presence-message");
g_signal_emit (contact, signals[SIGNAL_PRESENCE_CHANGED], 0,
contact->priv->presence_type,
contact->priv->presence_status,
contact->priv->presence_message);
}
static void
contact_maybe_set_location (TpContact *self,
GHashTable *location)
{
if (self == NULL)
return;
if (self->priv->location != NULL)
g_hash_table_unref (self->priv->location);
/* We guarantee that, if we've fetched a location for a contact, the
* :location property is non-NULL. This is mainly because Empathy assumed
* this and would crash if not.
*/
if (location == NULL)
location = tp_asv_new (NULL, NULL);
else
g_hash_table_ref (location);
self->priv->has_features |= CONTACT_FEATURE_FLAG_LOCATION;
self->priv->location = location;
g_object_notify ((GObject *) self, "location");
g_object_notify ((GObject *) self, "location-vardict");
}
static void
contact_set_capabilities (TpContact *self,
TpCapabilities *capabilities)
{
tp_clear_object (&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);
tp_connection_add_client_interest (connection,
TP_IFACE_CONNECTION_INTERFACE_LOCATION);
}
}
static void
contact_maybe_set_client_types (TpContact *self,
const gchar * const *types)
{
if (self == NULL)
return;
if (self->priv->client_types != NULL)
g_strfreev (self->priv->client_types);
self->priv->has_features |= CONTACT_FEATURE_FLAG_CLIENT_TYPES;
self->priv->client_types = g_strdupv ((gchar **) types);
g_object_notify ((GObject *) self, "client-types");
}
static void
contacts_client_types_updated (TpConnection *connection,
guint handle,
const gchar **types,
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_client_types (contact, types);
}
static void
contacts_bind_to_client_types_updated (TpConnection *connection)
{
if (!connection->priv->tracking_client_types_updated)
{
connection->priv->tracking_client_types_updated = TRUE;
tp_cli_connection_interface_client_types_connect_to_client_types_updated
(connection, contacts_client_types_updated, NULL, NULL, NULL, NULL);
}
}
static void
set_conn_capabilities_on_contacts (GPtrArray *contacts,
TpConnection *connection)
{
guint i;
TpCapabilities *conn_caps = tp_connection_get_capabilities (connection);
GPtrArray *rcc;
/* If the connection has no capabilities then don't bother setting them on
* the contact and pretend we just don't know.. In practise this will only
* happen if there was an error in getting the connections capabilities so
* claiming ignorance seems the most sensible thing to do */
if (conn_caps == NULL)
return;
rcc = tp_capabilities_get_channel_classes (conn_caps);
if (rcc == NULL || rcc->len == 0)
return;
for (i = 0; i < contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (contacts, i);
contact_set_capabilities (contact, conn_caps);
}
}
static void
connection_capabilities_fetched_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
ContactsContext *c = user_data;
DEBUG ("Connection capabilities prepared");
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)
{
g_assert (c->handles->len == c->contacts->len);
DEBUG ("Getting connection capabilities");
c->refcount++;
_tp_connection_get_capabilities_async (c->connection,
connection_capabilities_fetched_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 gboolean
build_avatar_filename (TpConnection *connection,
const gchar *avatar_token,
gboolean create_dir,
gchar **ret_filename,
gchar **ret_mime_filename)
{
gchar *dir;
gchar *token_escaped;
gboolean success = TRUE;
token_escaped = tp_escape_as_identifier (avatar_token);
dir = g_build_filename (g_get_user_cache_dir (),
"telepathy", "avatars",
tp_connection_get_cm_name (connection),
tp_connection_get_protocol_name (connection),
NULL);
if (create_dir)
{
if (g_mkdir_with_parents (dir, 0700) == -1)
{
DEBUG ("Error creating avatar cache dir: %s", g_strerror (errno));
success = FALSE;
goto out;
}
}
if (ret_filename != NULL)
*ret_filename = g_strconcat (dir, G_DIR_SEPARATOR_S, token_escaped, NULL);
if (ret_mime_filename != NULL)
*ret_mime_filename = g_strconcat (dir, G_DIR_SEPARATOR_S, token_escaped,
".mime", NULL);
out:
g_free (dir);
g_free (token_escaped);
return success;
}
static void contact_set_avatar_token (TpContact *self, const gchar *new_token,
gboolean request);
typedef struct {
GWeakRef contact;
TpConnection *connection;
gchar *token;
GFile *file;
GBytes *data;
GFile *mime_file;
gchar *mime_type;
} WriteAvatarData;
static void
write_avatar_data_free (WriteAvatarData *avatar_data)
{
g_weak_ref_clear (&avatar_data->contact);
g_clear_object (&avatar_data->connection);
tp_clear_pointer (&avatar_data->token, g_free);
g_clear_object (&avatar_data->file);
tp_clear_pointer (&avatar_data->data, g_bytes_unref);
g_clear_object (&avatar_data->mime_file);
tp_clear_pointer (&avatar_data->mime_type, g_free);
g_slice_free (WriteAvatarData, avatar_data);
}
static void
mime_file_written (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
WriteAvatarData *avatar_data = user_data;
GFile *file = G_FILE (source_object);
TpContact *self;
gchar *path = g_file_get_path (file);
g_assert (file == avatar_data->mime_file);
if (!g_file_replace_contents_finish (file, res, NULL, &error))
{
DEBUG ("Failed to store MIME type in cache (%s): %s", path,
error->message);
g_clear_error (&error);
}
else
{
DEBUG ("Contact avatar MIME type stored in cache: %s", path);
}
g_free (path);
self = g_weak_ref_get (&avatar_data->contact);
if (self == NULL)
{
DEBUG ("No relevant TpContact");
}
else if (tp_strdiff (avatar_data->token, self->priv->avatar_token))
{
DEBUG ("Contact's avatar token has changed from %s to %s, "
"this avatar is no longer relevant",
avatar_data->token, nonnull (self->priv->avatar_token));
}
else
{
gchar *data_path = g_file_get_path (avatar_data->file);
DEBUG ("Saved avatar '%s' of MIME type '%s' still used by '%s' to '%s'",
avatar_data->token, avatar_data->mime_type,
self->priv->identifier, data_path);
g_clear_object (&self->priv->avatar_file);
self->priv->avatar_file = g_object_ref (avatar_data->file);
g_free (self->priv->avatar_mime_type);
self->priv->avatar_mime_type = g_strdup (avatar_data->mime_type);
/* Notify both property changes together once both files have been
* written */
g_object_notify ((GObject *) self, "avatar-mime-type");
g_object_notify ((GObject *) self, "avatar-file");
g_object_unref (self);
g_free (data_path);
}
write_avatar_data_free (avatar_data);
}
static void
avatar_file_written (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
WriteAvatarData *avatar_data = user_data;
GFile *file = G_FILE (source_object);
gchar *path = g_file_get_path (file);
g_assert (file == avatar_data->file);
if (!g_file_replace_contents_finish (file, res, NULL, &error))
{
DEBUG ("Failed to store avatar in cache (%s): %s",
path, error->message);
DEBUG ("Storing the MIME type anyway");
g_clear_error (&error);
}
else
{
DEBUG ("Contact avatar stored in cache: %s",
path);
}
g_file_replace_contents_async (avatar_data->mime_file,
avatar_data->mime_type, strlen (avatar_data->mime_type),
NULL, FALSE, G_FILE_CREATE_PRIVATE|G_FILE_CREATE_REPLACE_DESTINATION,
NULL, mime_file_written, avatar_data);
g_free (path);
}
static void
contact_avatar_retrieved (TpConnection *connection,
guint handle,
const gchar *token,
const GArray *avatar,
const gchar *mime_type,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
TpContact *self = _tp_connection_lookup_contact (connection, handle);
gchar *filename;
gchar *mime_filename;
WriteAvatarData *avatar_data;
DEBUG ("token '%s', %u bytes, MIME type '%s'",
token, avatar->len, mime_type);
if (self == NULL)
DEBUG ("handle #%u is not associated with any TpContact", handle);
else
DEBUG ("used by contact #%u '%s'", handle,
tp_contact_get_identifier (self));
if (self != NULL)
{
/* Update the avatar token if a newer one is given
* (this emits notify::avatar-token if needed) */
contact_set_avatar_token (self, token, FALSE);
}
if (!build_avatar_filename (connection, token, TRUE, &filename,
&mime_filename))
{
DEBUG ("failed to set up cache");
return;
}
/* Save avatar in cache, even if the contact is unknown, to avoid as much as
* possible future avatar requests */
avatar_data = g_slice_new0 (WriteAvatarData);
avatar_data->connection = g_object_ref (connection);
g_weak_ref_set (&avatar_data->contact, self);
avatar_data->token = g_strdup (token);
avatar_data->file = g_file_new_for_path (filename);
/* g_file_replace_contents_async() doesn't copy its argument, see
* , so we have
* to keep a copy around */
avatar_data->data = g_bytes_new (avatar->data, avatar->len);
avatar_data->mime_file = g_file_new_for_path (mime_filename);
avatar_data->mime_type = g_strdup (mime_type);
g_file_replace_contents_async (avatar_data->file,
g_bytes_get_data (avatar_data->data, NULL), avatar->len,
NULL, FALSE, G_FILE_CREATE_PRIVATE|G_FILE_CREATE_REPLACE_DESTINATION,
NULL, avatar_file_written, avatar_data);
g_free (filename);
g_free (mime_filename);
}
static gboolean
connection_avatar_request_idle_cb (gpointer user_data)
{
TpConnection *connection = user_data;
DEBUG ("Request %d avatars", connection->priv->avatar_request_queue->len);
tp_cli_connection_interface_avatars_call_request_avatars (connection, -1,
connection->priv->avatar_request_queue, NULL, NULL, NULL, NULL);
g_array_unref (connection->priv->avatar_request_queue);
connection->priv->avatar_request_queue = NULL;
connection->priv->avatar_request_idle_id = 0;
return FALSE;
}
static void
contact_update_avatar_data (TpContact *self)
{
TpConnection *connection;
gchar *filename = NULL;
gchar *mime_filename = NULL;
/* If token is NULL, it means that CM doesn't know the token. In that case we
* have to request the avatar data to get the token. This happens with XMPP
* for offline contacts. We don't want to bypass the avatar cache, so we won't
* update avatar. */
if (self->priv->avatar_token == NULL)
return;
/* If token is empty (""), it means the contact has no avatar. */
if (tp_str_empty (self->priv->avatar_token))
{
tp_clear_object (&self->priv->avatar_file);
g_free (self->priv->avatar_mime_type);
self->priv->avatar_mime_type = NULL;
DEBUG ("contact#%u has no avatar", self->priv->handle);
g_object_notify ((GObject *) self, "avatar-file");
g_object_notify ((GObject *) self, "avatar-mime-type");
return;
}
/* We have a token, search in cache... */
if (build_avatar_filename (self->priv->connection, self->priv->avatar_token,
FALSE, &filename, &mime_filename))
{
if (g_file_test (filename, G_FILE_TEST_EXISTS))
{
GError *error = NULL;
tp_clear_object (&self->priv->avatar_file);
self->priv->avatar_file = g_file_new_for_path (filename);
g_free (self->priv->avatar_mime_type);
if (!g_file_get_contents (mime_filename, &self->priv->avatar_mime_type,
NULL, &error))
{
DEBUG ("Error reading avatar MIME type (%s): %s", mime_filename,
error ? error->message : "No error message");
self->priv->avatar_mime_type = NULL;
g_clear_error (&error);
}
DEBUG ("contact#%u avatar found in cache: %s, %s",
self->priv->handle, filename, self->priv->avatar_mime_type);
g_object_notify ((GObject *) self, "avatar-file");
g_object_notify ((GObject *) self, "avatar-mime_type");
goto out;
}
}
/* Not found in cache, queue this contact. We do this to group contacts
* for the AvatarRequest call */
connection = self->priv->connection;
if (connection->priv->avatar_request_queue == NULL)
connection->priv->avatar_request_queue = g_array_new (FALSE, FALSE,
sizeof (TpHandle));
g_array_append_val (connection->priv->avatar_request_queue,
self->priv->handle);
if (connection->priv->avatar_request_idle_id == 0)
connection->priv->avatar_request_idle_id = g_idle_add (
connection_avatar_request_idle_cb, connection);
out:
g_free (filename);
g_free (mime_filename);
}
static void
contact_maybe_update_avatar_data (TpContact *self)
{
if ((self->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_DATA) == 0 &&
(self->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0)
{
self->priv->has_features |= CONTACT_FEATURE_FLAG_AVATAR_DATA;
contact_update_avatar_data (self);
}
}
static void
contacts_bind_to_avatar_retrieved (TpConnection *connection)
{
if (!connection->priv->tracking_avatar_retrieved)
{
connection->priv->tracking_avatar_retrieved = TRUE;
tp_cli_connection_interface_avatars_connect_to_avatar_retrieved
(connection, contact_avatar_retrieved, NULL, NULL, NULL, NULL);
}
}
static void
contacts_get_avatar_data (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
contacts_bind_to_avatar_retrieved (c->connection);
for (i = 0; i < c->contacts->len; i++)
contact_maybe_update_avatar_data (g_ptr_array_index (c->contacts, i));
contacts_context_continue (c);
}
static void
contact_set_avatar_token (TpContact *self, const gchar *new_token,
gboolean request)
{
/* A no-op change (specifically from NULL to NULL) is still interesting if we
* don't have the AVATAR_TOKEN feature yet: it indicates that we've
* discovered it.
*/
if ((self->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) &&
!tp_strdiff (self->priv->avatar_token, new_token))
return;
DEBUG ("contact#%u token is %s", self->priv->handle, new_token);
self->priv->has_features |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
g_free (self->priv->avatar_token);
self->priv->avatar_token = g_strdup (new_token);
g_object_notify ((GObject *) self, "avatar-token");
if (request && tp_contact_has_feature (self, TP_CONTACT_FEATURE_AVATAR_DATA))
contact_update_avatar_data (self);
}
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);
if (contact != NULL)
contact_set_avatar_token (contact, new_token, TRUE);
}
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 void
contact_maybe_set_info (TpContact *self,
const GPtrArray *contact_info)
{
guint i;
if (self == NULL)
return;
tp_contact_info_list_free (self->priv->contact_info);
self->priv->contact_info = NULL;
self->priv->has_features |= CONTACT_FEATURE_FLAG_CONTACT_INFO;
if (contact_info != NULL)
{
for (i = contact_info->len; i > 0; i--)
{
GValueArray *va = g_ptr_array_index (contact_info, i - 1);
const gchar *field_name;
GStrv parameters;
GStrv field_value;
tp_value_array_unpack (va, 3, &field_name, ¶meters, &field_value);
self->priv->contact_info = g_list_prepend (self->priv->contact_info,
tp_contact_info_field_new (field_name, parameters, field_value));
}
}
/* else we don't know, but an empty list is perfectly valid. */
g_object_notify ((GObject *) self, "contact-info");
}
static void
contact_info_changed (TpConnection *connection,
guint handle,
const GPtrArray *contact_info,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
TpContact *self = _tp_connection_lookup_contact (connection, handle);
contact_maybe_set_info (self, contact_info);
}
static void
contacts_got_contact_info (TpConnection *connection,
GHashTable *info,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
if (error != NULL)
{
DEBUG ("GetContactInfo 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, info);
while (g_hash_table_iter_next (&iter, &key, &value))
{
contact_info_changed (connection, GPOINTER_TO_UINT (key),
value, NULL, NULL);
}
}
contacts_context_continue (c);
}
static void
contacts_bind_to_contact_info_changed (TpConnection *connection)
{
if (!connection->priv->tracking_contact_info_changed)
{
connection->priv->tracking_contact_info_changed = TRUE;
tp_cli_connection_interface_contact_info_connect_to_contact_info_changed (
connection, contact_info_changed, NULL, NULL, NULL, NULL);
}
}
static void
contacts_get_contact_info (ContactsContext *c)
{
guint i;
g_assert (c->handles->len == c->contacts->len);
contacts_bind_to_contact_info_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_CONTACT_INFO) == 0)
{
c->refcount++;
tp_cli_connection_interface_contact_info_call_get_contact_info (
c->connection, -1, c->handles, contacts_got_contact_info,
c, contacts_context_unref, c->weak_object);
return;
}
}
contacts_context_continue (c);
}
typedef struct
{
TpContact *contact;
GSimpleAsyncResult *result;
TpProxyPendingCall *call;
GCancellable *cancellable;
gulong cancelled_id;
} ContactInfoRequestData;
static void
contact_info_request_data_free (ContactInfoRequestData *data)
{
if (data != NULL)
{
g_object_unref (data->result);
if (data->cancellable != NULL)
g_object_unref (data->cancellable);
g_slice_free (ContactInfoRequestData, data);
}
}
static void
contact_info_request_cb (TpConnection *connection,
const GPtrArray *contact_info,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactInfoRequestData *data = user_data;
TpContact *self = data->contact;
if (data->cancellable != NULL)
{
/* At this point it's too late to cancel the operation. This will block
* until the signal handler has finished if it's already running, so
* we're guaranteed to never be in a partially-cancelled state after
* this call. */
g_cancellable_disconnect (data->cancellable, data->cancelled_id);
/* If this is true, the cancelled callback has already run and completed the
* async result, so just bail. */
if (data->cancelled_id == 0)
return;
data->cancelled_id = 0;
}
if (error != NULL)
{
DEBUG ("Failed to request ContactInfo: %s", error->message);
g_simple_async_result_set_from_error (data->result, error);
}
else
{
contact_maybe_set_info (self, contact_info);
}
g_simple_async_result_complete_in_idle (data->result);
data->call = NULL;
}
static void
contact_info_request_cancelled_cb (GCancellable *cancellable,
ContactInfoRequestData *data)
{
GError *error = NULL;
gboolean was_cancelled;
/* We disconnect from the signal manually; since we're in the cancelled
* callback, we hold the cancellable's lock so calling this instead of
* g_cancellable_disconnect() is fine. We do this here so that
* g_cancellable_disconnect() isn't called by contact_info_request_data_free()
* which is called by tp_proxy_pending_call_cancel().
* cancelled_id might already be 0 if the cancellable was cancelled before
* we connected to it. */
if (data->cancelled_id != 0)
g_signal_handler_disconnect (data->cancellable, data->cancelled_id);
data->cancelled_id = 0;
was_cancelled = g_cancellable_set_error_if_cancelled (data->cancellable,
&error);
g_assert (was_cancelled);
DEBUG ("Request ContactInfo cancelled");
g_simple_async_result_set_from_error (data->result, error);
g_simple_async_result_complete_in_idle (data->result);
g_clear_error (&error);
if (data->call != NULL)
tp_proxy_pending_call_cancel (data->call);
}
/**
* tp_contact_request_contact_info_async:
* @self: a #TpContact
* @cancellable: optional #GCancellable object, %NULL to ignore.
* @callback: a callback to call when the request is satisfied
* @user_data: data to pass to @callback
*
* Requests an asynchronous request of the contact info of @self. When
* the operation is finished, @callback will be called. You can then call
* tp_contact_request_contact_info_finish() to get the result of the operation.
*
* If the operation is successful, the #TpContact:contact-info property will be
* updated (emitting "notify::contact-info" signal) before @callback is called.
* That means you can call tp_contact_get_contact_info() to get the new vCard
* inside @callback.
*
* Note that requesting the vCard from the network can take significant time, so
* a bigger timeout is set on the underlying D-Bus call. @cancellable can be
* cancelled to free resources used in the D-Bus call if the caller is no longer
* interested in the vCard.
*
* If %TP_CONTACT_FEATURE_CONTACT_INFO is not yet set on @self, it will be
* set before its property gets updated and @callback is called.
*
* Since: 0.11.7
*/
void
tp_contact_request_contact_info_async (TpContact *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
ContactInfoRequestData *data;
g_return_if_fail (TP_IS_CONTACT (self));
contacts_bind_to_contact_info_changed (self->priv->connection);
data = g_slice_new0 (ContactInfoRequestData);
data->contact = self;
data->result = g_simple_async_result_new (G_OBJECT (self), callback,
user_data, tp_contact_request_contact_info_finish);
if (cancellable != NULL)
{
data->cancellable = g_object_ref (cancellable);
data->cancelled_id = g_cancellable_connect (data->cancellable,
G_CALLBACK (contact_info_request_cancelled_cb), data, NULL);
/* Return early if the cancellable has already been cancelled */
if (data->cancelled_id == 0)
return;
}
data->call = tp_cli_connection_interface_contact_info_call_request_contact_info (
self->priv->connection, 60*60*1000, self->priv->handle,
contact_info_request_cb,
data, (GDestroyNotify) contact_info_request_data_free,
NULL);
}
/**
* tp_contact_request_contact_info_finish:
* @self: a #TpContact
* @result: a #GAsyncResult
* @error: a #GError to be filled
*
* Finishes an async request of @self info. If the operation was successful,
* the contact's vCard can be accessed using tp_contact_get_contact_info().
*
* Returns: %TRUE if the request call was successful, otherwise %FALSE
*
* Since: 0.11.7
*/
gboolean
tp_contact_request_contact_info_finish (TpContact *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (self, tp_contact_request_contact_info_finish);
}
/**
* tp_connection_refresh_contact_info:
* @self: a #TpConnection
* @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
*
* Requests to refresh the #TpContact:contact-info property on each contact from
* @contacts, requesting it from the network if an up-to-date version is not
* cached locally. "notify::contact-info" will be emitted when the contact's
* information are updated.
*
* If %TP_CONTACT_FEATURE_CONTACT_INFO is not yet set on a contact, it will be
* set before its property gets updated.
*
* Since: 0.11.7
*/
void
tp_connection_refresh_contact_info (TpConnection *self,
guint n_contacts,
TpContact * const *contacts)
{
GArray *handles;
guint i;
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (n_contacts >= 1);
g_return_if_fail (contacts != NULL);
for (i = 0; i < n_contacts; i++)
{
g_return_if_fail (TP_IS_CONTACT (contacts[i]));
g_return_if_fail (contacts[i]->priv->connection == self);
}
contacts_bind_to_contact_info_changed (self);
handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_contacts);
for (i = 0; i < n_contacts; i++)
g_array_append_val (handles, contacts[i]->priv->handle);
tp_cli_connection_interface_contact_info_call_refresh_contact_info (self, -1,
handles, NULL, NULL, NULL, NULL);
g_array_unref (handles);
}
static void
contact_set_subscription_states (TpContact *self,
TpSubscriptionState subscribe,
TpSubscriptionState publish,
const gchar *publish_request)
{
if (publish_request == NULL)
publish_request = "";
DEBUG ("contact#%u state changed: subscribe=%c publish=%c '%s'",
self->priv->handle,
_tp_base_contact_list_presence_state_to_letter (subscribe),
_tp_base_contact_list_presence_state_to_letter (publish),
publish_request);
self->priv->has_features |= CONTACT_FEATURE_FLAG_STATES;
g_free (self->priv->publish_request);
self->priv->subscribe = subscribe;
self->priv->publish = publish;
self->priv->publish_request = g_strdup (publish_request);
g_object_notify ((GObject *) self, "subscribe-state");
g_object_notify ((GObject *) self, "publish-state");
g_object_notify ((GObject *) self, "publish-request");
g_signal_emit (self, signals[SIGNAL_SUBSCRIPTION_STATES_CHANGED], 0,
self->priv->subscribe, self->priv->publish, self->priv->publish_request);
}
void
_tp_contact_set_subscription_states (TpContact *self,
GValueArray *value_array)
{
TpSubscriptionState subscribe;
TpSubscriptionState publish;
const gchar *publish_request;
tp_value_array_unpack (value_array, 3,
&subscribe, &publish, &publish_request);
contact_set_subscription_states (self, subscribe, publish, publish_request);
}
static void
contacts_changed_cb (TpConnection *connection,
GHashTable *changes,
const GArray *removals,
gpointer user_data,
GObject *weak_object)
{
GHashTableIter iter;
gpointer key, value;
guint i;
g_hash_table_iter_init (&iter, changes);
while (g_hash_table_iter_next (&iter, &key, &value))
{
TpHandle handle = GPOINTER_TO_UINT (key);
TpContact *contact = _tp_connection_lookup_contact (connection, handle);
if (contact != NULL)
_tp_contact_set_subscription_states (contact, value);
}
for (i = 0; i < removals->len; i++)
{
TpHandle handle = g_array_index (removals, TpHandle, i);
TpContact *contact = _tp_connection_lookup_contact (connection, handle);
if (contact == NULL)
continue;
contact_set_subscription_states (contact, TP_SUBSCRIPTION_STATE_NO,
TP_SUBSCRIPTION_STATE_NO, NULL);
}
}
static void
contacts_bind_to_contacts_changed (TpConnection *connection)
{
if (!connection->priv->tracking_contacts_changed)
{
connection->priv->tracking_contacts_changed = TRUE;
tp_cli_connection_interface_contact_list_connect_to_contacts_changed
(connection, contacts_changed_cb, NULL, NULL, NULL, NULL);
}
}
static void
contact_maybe_set_contact_groups (TpContact *self,
GStrv contact_groups)
{
gchar **iter;
if (self == NULL || contact_groups == NULL)
return;
self->priv->has_features |= CONTACT_FEATURE_FLAG_CONTACT_GROUPS;
tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref);
self->priv->contact_groups = g_ptr_array_new_full (
g_strv_length (contact_groups) + 1, g_free);
for (iter = contact_groups; *iter != NULL; iter++)
g_ptr_array_add (self->priv->contact_groups, g_strdup (*iter));
g_ptr_array_add (self->priv->contact_groups, NULL);
g_object_notify ((GObject *) self, "contact-groups");
}
static void
contact_groups_changed_cb (TpConnection *connection,
const GArray *contacts,
const gchar **added,
const gchar **removed,
gpointer user_data,
GObject *weak_object)
{
guint i;
for (i = 0; i < contacts->len; i++)
{
TpHandle handle = g_array_index (contacts, TpHandle, i);
TpContact *contact = _tp_connection_lookup_contact (connection, handle);
const gchar **iter;
guint j;
if (contact == NULL || contact->priv->contact_groups == NULL)
continue;
/* Remove the ending NULL */
g_ptr_array_remove_index_fast (contact->priv->contact_groups,
contact->priv->contact_groups->len - 1);
/* Remove old groups */
for (iter = removed; *iter != NULL; iter++)
{
for (j = 0; j < contact->priv->contact_groups->len; j++)
{
const gchar *str;
str = g_ptr_array_index (contact->priv->contact_groups, j);
if (!tp_strdiff (str, *iter))
{
g_ptr_array_remove_index_fast (contact->priv->contact_groups, j);
break;
}
}
}
/* Add new groups */
for (iter = added; *iter != NULL; iter++)
g_ptr_array_add (contact->priv->contact_groups, g_strdup (*iter));
/* Add back the ending NULL */
g_ptr_array_add (contact->priv->contact_groups, NULL);
g_object_notify ((GObject *) contact, "contact-groups");
g_signal_emit (contact, signals[SIGNAL_CONTACT_GROUPS_CHANGED], 0,
added, removed);
}
}
static void
contacts_bind_to_contact_groups_changed (TpConnection *connection)
{
if (!connection->priv->tracking_contact_groups_changed)
{
connection->priv->tracking_contact_groups_changed = TRUE;
tp_cli_connection_interface_contact_groups_connect_to_groups_changed
(connection, contact_groups_changed_cb, NULL, NULL, NULL, NULL);
}
}
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;
if (contact_attribute_interfaces == NULL)
return FALSE;
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 = context->wanted;
/* 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);
}
/* There is no contact attribute for avatar data, always use slow path */
if ((feature_flags & CONTACT_FEATURE_FLAG_AVATAR_DATA) != 0 &&
tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
{
g_queue_push_tail (&context->todo, contacts_get_avatar_data);
}
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))
{
WARNING ("%s supports Location but not Contacts! Where did you find "
"this CM? TP_CONTACT_FEATURE_LOCATION is not gonna work",
tp_proxy_get_object_path (context->connection));
}
/* 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);
}
if ((feature_flags & CONTACT_FEATURE_FLAG_CONTACT_INFO) != 0 &&
!contacts_context_supports_iface (context,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO) &&
tp_proxy_has_interface_by_id (context->connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
{
g_queue_push_tail (&context->todo, contacts_get_contact_info);
}
}
static gboolean
tp_contact_set_attributes (TpContact *contact,
GHashTable *asv,
ContactFeatureFlags wanted,
ContactFeatureFlags getting,
GError **error)
{
TpConnection *connection = tp_contact_get_connection (contact);
const gchar *s;
gpointer boxed;
/* Identifier */
s = tp_asv_get_string (asv, TP_TOKEN_CONNECTION_CONTACT_ID);
if (s == NULL)
{
g_set_error (error, 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);
return FALSE;
}
DEBUG ("#%u: \"%s\"", contact->priv->handle, s);
{
GHashTableIter iter;
gpointer k, v;
g_hash_table_iter_init (&iter, asv);
while (g_hash_table_iter_next (&iter, &k, &v))
{
gchar *str = g_strdup_value_contents (v);
DEBUG ("- %s => %s", (const gchar *) k, str);
g_free (str);
}
}
if (contact->priv->identifier == NULL)
{
contact->priv->identifier = g_strdup (s);
}
else if (tp_strdiff (contact->priv->identifier, s))
{
g_set_error (error, 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);
return FALSE;
}
/* Alias */
if (wanted & CONTACT_FEATURE_FLAG_ALIAS)
{
s = tp_asv_get_string (asv,
TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS);
if (s == NULL)
{
if (getting & CONTACT_FEATURE_FLAG_ALIAS)
{
WARNING ("%s supposedly implements Contacts and Aliasing, but "
"omitted " TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
tp_proxy_get_object_path (connection));
}
}
else
{
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");
}
}
/* Avatar */
if (wanted & CONTACT_FEATURE_FLAG_AVATAR_TOKEN)
{
s = tp_asv_get_string (asv,
TP_TOKEN_CONNECTION_INTERFACE_AVATARS_TOKEN);
contact_set_avatar_token (contact, s, TRUE);
}
if (wanted & CONTACT_FEATURE_FLAG_AVATAR_DATA)
{
/* There is no attribute for the avatar data, this will set the avatar
* from cache or start the avatar request if its missing from cache. */
contact_maybe_update_avatar_data (contact);
}
/* Presence */
if (wanted & CONTACT_FEATURE_FLAG_PRESENCE)
{
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE,
TP_STRUCT_TYPE_SIMPLE_PRESENCE);
if (boxed == NULL)
{
if (getting & CONTACT_FEATURE_FLAG_PRESENCE)
{
WARNING ("%s supposedly implements Contacts and SimplePresence, "
"but omitted the mandatory "
TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE
" attribute",
tp_proxy_get_object_path (connection));
}
}
else
{
contact_maybe_set_simple_presence (contact, boxed);
}
}
/* Location */
if (wanted & CONTACT_FEATURE_FLAG_LOCATION)
{
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_LOCATION_LOCATION,
TP_HASH_TYPE_LOCATION);
contact_maybe_set_location (contact, boxed);
}
/* Capabilities */
if (wanted & CONTACT_FEATURE_FLAG_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);
}
/* ContactInfo */
if (wanted & CONTACT_FEATURE_FLAG_CONTACT_INFO)
{
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_INFO_INFO,
TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
contact_maybe_set_info (contact, boxed);
}
/* ClientTypes */
if (wanted & CONTACT_FEATURE_FLAG_CLIENT_TYPES)
{
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_CLIENT_TYPES_CLIENT_TYPES,
G_TYPE_STRV);
contact_maybe_set_client_types (contact, boxed);
}
/* ContactList subscription states */
if (wanted & CONTACT_FEATURE_FLAG_STATES)
{
TpSubscriptionState subscribe;
TpSubscriptionState publish;
const gchar *publish_request;
gboolean subscribe_valid = FALSE;
gboolean publish_valid = FALSE;
subscribe = tp_asv_get_uint32 (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_SUBSCRIBE,
&subscribe_valid);
publish = tp_asv_get_uint32 (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_PUBLISH,
&publish_valid);
publish_request = tp_asv_get_string (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_PUBLISH_REQUEST);
if (subscribe_valid && publish_valid)
{
contact_set_subscription_states (contact, subscribe, publish,
publish_request);
}
}
/* ContactGroups */
if (wanted & CONTACT_FEATURE_FLAG_CONTACT_GROUPS)
{
boxed = tp_asv_get_boxed (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_GROUPS_GROUPS,
G_TYPE_STRV);
contact_maybe_set_contact_groups (contact, boxed);
}
/* ContactBlocking */
if (wanted & CONTACT_FEATURE_FLAG_CONTACT_BLOCKING)
{
gboolean is_blocked, valid;
is_blocked = tp_asv_get_boolean (asv,
TP_TOKEN_CONNECTION_INTERFACE_CONTACT_BLOCKING_BLOCKED, &valid);
if (valid)
_tp_contact_set_is_blocked (contact, is_blocked);
}
return TRUE;
}
static gboolean get_feature_flags (guint n_features,
const TpContactFeature *features, ContactFeatureFlags *flags);
gboolean
_tp_contact_set_attributes (TpContact *contact,
GHashTable *asv,
guint n_features,
const TpContactFeature *features,
GError **error)
{
ContactFeatureFlags feature_flags = 0;
if (!get_feature_flags (n_features, features, &feature_flags))
return FALSE;
return tp_contact_set_attributes (contact, asv, feature_flags,
0 /* can't know what we expected to get */, error);
}
static void
contacts_got_attributes (TpConnection *connection,
GHashTable *attributes,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
ContactsContext *c = user_data;
guint i;
DEBUG ("%p: reply from GetContactAttributes: %s",
c, (error == NULL ? "OK" : error->message));
if (error != NULL)
{
contacts_context_fail (c, error);
return;
}
i = 0;
if (c->signature == CB_BY_HANDLE && 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 = tp_contact_ensure (connection, handle);
g_ptr_array_add (c->contacts, contact);
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);
GHashTable *asv = g_hash_table_lookup (attributes,
GUINT_TO_POINTER (contact->priv->handle));
GError *e = NULL;
if (asv == NULL)
{
g_set_error (&e, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"We hold a ref to handle #%u but it appears to be invalid",
contact->priv->handle);
}
else
{
/* set up the contact with its attributes */
tp_contact_set_attributes (contact, asv, c->wanted, c->getting, &e);
}
if (e != NULL)
{
contacts_context_fail (c, e);
g_error_free (e);
return;
}
}
contacts_context_continue (c);
}
static const gchar **
contacts_bind_to_signals (TpConnection *connection,
ContactFeatureFlags wanted,
ContactFeatureFlags *getting)
{
GArray *contact_attribute_interfaces =
connection->priv->contact_attribute_interfaces;
GPtrArray *array;
guint i;
guint len = 0;
if (getting != NULL)
*getting = 0;
if (contact_attribute_interfaces != NULL)
len = contact_attribute_interfaces->len;
g_assert (tp_proxy_has_interface_by_id (connection,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS));
array = g_ptr_array_sized_new (len);
for (i = 0; i < len; i++)
{
GQuark q = g_array_index (contact_attribute_interfaces, GQuark, i);
if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)
{
if ((wanted & CONTACT_FEATURE_FLAG_ALIAS) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_ALIASING);
contacts_bind_to_aliases_changed (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_ALIAS;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS)
{
if ((wanted & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_AVATARS);
contacts_bind_to_avatar_updated (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
}
if ((wanted & CONTACT_FEATURE_FLAG_AVATAR_DATA) != 0)
{
contacts_bind_to_avatar_retrieved (connection);
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE)
{
if ((wanted & CONTACT_FEATURE_FLAG_PRESENCE) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE);
contacts_bind_to_presences_changed (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_PRESENCE;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION)
{
if ((wanted & CONTACT_FEATURE_FLAG_LOCATION) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_LOCATION);
contacts_bind_to_location_updated (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_LOCATION;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES)
{
if ((wanted & CONTACT_FEATURE_FLAG_CAPABILITIES) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES);
contacts_bind_to_capabilities_updated (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_CAPABILITIES;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO)
{
if ((wanted & CONTACT_FEATURE_FLAG_CONTACT_INFO) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO);
contacts_bind_to_contact_info_changed (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_CONTACT_INFO;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CLIENT_TYPES)
{
if ((wanted & CONTACT_FEATURE_FLAG_CLIENT_TYPES) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CLIENT_TYPES);
contacts_bind_to_client_types_updated (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_CLIENT_TYPES;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_LIST)
{
if ((wanted & CONTACT_FEATURE_FLAG_STATES) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST);
contacts_bind_to_contacts_changed (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_STATES;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_GROUPS)
{
if ((wanted & CONTACT_FEATURE_FLAG_CONTACT_GROUPS) != 0)
{
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS);
contacts_bind_to_contact_groups_changed (connection);
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_CONTACT_GROUPS;
}
}
else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING)
{
if ((wanted & CONTACT_FEATURE_FLAG_CONTACT_BLOCKING) != 0)
{
GQuark features[] = { TP_CONNECTION_FEATURE_CONTACT_BLOCKING, 0 };
g_ptr_array_add (array,
TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING);
/* The BlockedContactsChanged signal is already handled by
* connection-contact-list.c so we just have to prepare
* TP_CONNECTION_FEATURE_CONTACT_BLOCKING to make sure it's
* connected. */
if (!tp_proxy_is_prepared (connection,
TP_CONNECTION_FEATURE_CONTACT_BLOCKING))
{
tp_proxy_prepare_async (connection, features, NULL, NULL);
}
if (getting != NULL)
*getting |= CONTACT_FEATURE_FLAG_CONTACT_BLOCKING;
}
}
}
g_ptr_array_add (array, NULL);
return (const gchar **) g_ptr_array_free (array, FALSE);
}
/*
* The connection must implement Contacts.
*/
const gchar **
_tp_contacts_bind_to_signals (TpConnection *connection,
guint n_features,
const TpContactFeature *features)
{
ContactFeatureFlags feature_flags = 0;
if (!get_feature_flags (n_features, features, &feature_flags))
return NULL;
return contacts_bind_to_signals (connection, feature_flags, NULL);
}
static void
contacts_get_attributes (ContactsContext *context)
{
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;
}
supported_interfaces = contacts_bind_to_signals (context->connection,
context->wanted, &context->getting);
if (supported_interfaces[0] == NULL &&
!(context->signature == CB_BY_HANDLE && context->contacts->len == 0) &&
context->contacts_have_ids)
{
/* We're not going to do anything useful: we're not holding/inspecting
* the handles, and we're not inspecting any extended interfaces
* either. Skip it. */
g_free (supported_interfaces);
contacts_context_continue (context);
return;
}
/* The Hold parameter is only true if we started from handles, and we don't
* already have all the contacts we need. */
context->refcount++;
DEBUG ("calling GetContactAttributes");
for (i = 0; supported_interfaces[i] != NULL; i++)
DEBUG ("- %s", supported_interfaces[i]);
tp_cli_connection_interface_contacts_call_get_contact_attributes (
context->connection, -1, context->handles, supported_interfaces,
(context->signature == CB_BY_HANDLE && context->contacts->len == 0),
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_unref (contacts);
contacts = NULL;
break;
}
}
return contacts;
}
static gboolean
get_feature_flags (guint n_features,
const TpContactFeature *features,
ContactFeatureFlags *flags)
{
ContactFeatureFlags feature_flags = 0;
guint i;
for (i = 0; i < n_features; i++)
{
g_return_val_if_fail (features[i] < TP_NUM_CONTACT_FEATURES, FALSE);
feature_flags |= (1 << features[i]);
}
/* Force AVATAR_TOKEN if we have AVATAR_DATA */
if ((feature_flags & CONTACT_FEATURE_FLAG_AVATAR_DATA) != 0)
feature_flags |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
*flags = feature_flags;
return TRUE;
}
static void
contacts_context_remove_common_features (ContactsContext *context)
{
ContactFeatureFlags minimal_feature_flags = 0xFFFFFFFF;
guint i;
context->contacts_have_ids = TRUE;
for (i = 0; i < context->contacts->len; i++)
{
TpContact *contact = g_ptr_array_index (context->contacts, i);
minimal_feature_flags &= contact->priv->has_features;
if (contact->priv->identifier == NULL)
context->contacts_have_ids = FALSE;
}
context->wanted &= (~minimal_feature_flags);
}
/**
* tp_connection_get_contacts_by_handle:
* @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
* feature prepared
* @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
* Deprecated: Use tp_simple_client_factory_ensure_contact() instead.
*/
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;
/* As an implementation detail, this method actually starts working slightly
* before we're officially ready. We use this to get the TpContact for the
* Connection. */
g_return_if_fail (self->priv->ready_enough_for_contacts);
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);
if (!get_feature_flags (n_features, features, &feature_flags))
return;
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 (and possibly inspected) handles, so we can
* skip that. */
g_ptr_array_foreach (contacts, (GFunc) g_object_ref, NULL);
tp_g_ptr_array_extend (context->contacts, contacts);
contacts_context_remove_common_features (context);
/* We do need to retrieve any features that aren't there yet, though. */
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);
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
contacts_context_idle_continue, context, contacts_context_unref);
g_ptr_array_unref (contacts);
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
* will give us everything). */
g_queue_push_head (&context->todo, contacts_get_attributes);
contacts_context_queue_features (context);
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
contacts_context_idle_continue, context, contacts_context_unref);
return;
}
/* if we haven't already returned, we're on the slow path */
DEBUG ("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);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
/* 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);
G_GNUC_END_IGNORE_DEPRECATIONS
}
/**
* tp_connection_upgrade_contacts:
* @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
* feature prepared
* @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
* Deprecated: Use tp_connection_upgrade_contacts_async() instead.
*/
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;
/* As an implementation detail, this method actually starts working slightly
* before we're officially ready. We use this to get the TpContact for the
* Connection. */
g_return_if_fail (self->priv->ready_enough_for_contacts);
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);
g_return_if_fail (contacts[i]->priv->identifier != NULL);
}
if (!get_feature_flags (n_features, features, &feature_flags))
return;
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);
contacts_context_remove_common_features (context);
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);
/* 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_ERROR &&
(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);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
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);
G_GNUC_END_IGNORE_DEPRECATIONS
}
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_ERROR &&
(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 have the %TP_CONNECTION_FEATURE_CONNECTED
* feature prepared
* @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
* Deprecated: Use tp_connection_dup_contact_by_id_async() instead.
*/
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_proxy_is_prepared (self,
TP_CONNECTION_FEATURE_CONNECTED));
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);
if (!get_feature_flags (n_features, features, &feature_flags))
return;
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);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
/* 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);
G_GNUC_END_IGNORE_DEPRECATIONS
}
static void
got_contact_by_id_fallback_cb (TpConnection *self,
guint n_contacts,
TpContact * const *contacts,
const gchar * const *requested_ids,
GHashTable *failed_id_errors,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
const gchar *id = user_data;
GSimpleAsyncResult *result = (GSimpleAsyncResult *) weak_object;
GError *e = NULL;
if (error != NULL)
{
g_simple_async_result_set_from_error (result, error);
}
else if (g_hash_table_size (failed_id_errors) > 0)
{
e = g_hash_table_lookup (failed_id_errors, id);
if (e == NULL)
{
g_set_error (&e, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"We requested 1 id, and got an error for another id - Broken CM");
g_simple_async_result_take_error (result, e);
}
else
{
g_simple_async_result_set_from_error (result, e);
}
}
else if (n_contacts != 1 || contacts[0] == NULL)
{
g_set_error (&e, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"We requested 1 id, but no contacts and no error - Broken CM");
g_simple_async_result_take_error (result, e);
}
else
{
g_simple_async_result_set_op_res_gpointer (result,
g_object_ref (contacts[0]), g_object_unref);
}
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
/**
* tp_connection_dup_contact_by_id_async:
* @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
* feature prepared
* @id: A strings representing the desired contact by its
* identifier in the IM protocol (an XMPP JID, SIP URI, MSN Passport,
* AOL screen-name 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 contact is ready
* @user_data: Data to pass to the callback
*
* Create a #TpContact object and make any asynchronous method calls necessary
* to 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.19.0
*/
void
tp_connection_dup_contact_by_id_async (TpConnection *self,
const gchar *id,
guint n_features,
const TpContactFeature *features,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new ((GObject *) self, callback, user_data,
tp_connection_dup_contact_by_id_async);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
tp_connection_get_contacts_by_id (self,
1, &id,
n_features, features,
got_contact_by_id_fallback_cb,
g_strdup (id), g_free, G_OBJECT (result));
G_GNUC_END_IGNORE_DEPRECATIONS
}
/**
* tp_connection_dup_contact_by_id_finish:
* @self: a #TpConnection
* @result: a #GAsyncResult
* @error: a #GError to fill
*
* Finishes tp_connection_dup_contact_by_id_async().
*
* Returns: (transfer full): a #TpContact or %NULL on error.
* Since: 0.19.0
*/
TpContact *
tp_connection_dup_contact_by_id_finish (TpConnection *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_return_copy_pointer (self,
tp_connection_dup_contact_by_id_async, g_object_ref);
}
static void
upgrade_contacts_fallback_cb (TpConnection *connection,
guint n_contacts,
TpContact * const *contacts,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
GSimpleAsyncResult *result = user_data;
GPtrArray *contacts_array;
guint i;
contacts_array = g_ptr_array_new_full (n_contacts, g_object_unref);
for (i = 0; i < n_contacts; i++)
g_ptr_array_add (contacts_array, g_object_ref (contacts[i]));
g_simple_async_result_set_op_res_gpointer (result, contacts_array,
(GDestroyNotify) g_ptr_array_unref);
if (error != NULL)
g_simple_async_result_set_from_error (result, error);
g_simple_async_result_complete_in_idle (result);
}
/**
* tp_connection_upgrade_contacts_async:
* @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
* feature prepared
* @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
*
* 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.19.0
*/
void
tp_connection_upgrade_contacts_async (TpConnection *self,
guint n_contacts,
TpContact * const *contacts,
guint n_features,
const TpContactFeature *features,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new ((GObject *) self, callback, user_data,
tp_connection_upgrade_contacts_async);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
tp_connection_upgrade_contacts (self,
n_contacts, contacts,
n_features, features,
upgrade_contacts_fallback_cb,
result, g_object_unref, NULL);
G_GNUC_END_IGNORE_DEPRECATIONS
}
/**
* tp_connection_upgrade_contacts_finish:
* @self: a #TpConnection
* @result: a #GAsyncResult
* @contacts: (element-type TelepathyGLib.Contact) (transfer container) (out) (allow-none):
* a location to set a #GPtrArray of upgraded #TpContact, or %NULL.
* @error: a #GError to fill
*
* Finishes tp_connection_upgrade_contacts_async().
*
* Returns: %TRUE on success, %FALSE otherwise.
* Since: 0.19.0
*/
gboolean
tp_connection_upgrade_contacts_finish (TpConnection *self,
GAsyncResult *result,
GPtrArray **contacts,
GError **error)
{
_tp_implement_finish_copy_pointer (self,
tp_connection_upgrade_contacts_async, g_ptr_array_ref, contacts);
}
void
_tp_contact_set_is_blocked (TpContact *self,
gboolean is_blocked)
{
if (self == NULL)
return;
self->priv->has_features |= CONTACT_FEATURE_FLAG_CONTACT_BLOCKING;
if (self->priv->is_blocked == is_blocked)
return;
self->priv->is_blocked = is_blocked;
g_object_notify ((GObject *) self, "is-blocked");
}
/**
* tp_contact_is_blocked:
* @self: a #TpContact
*
*
* Returns: the value of #TpContact:is-blocked.
*
* Since: 0.17.0
*/
gboolean
tp_contact_is_blocked (TpContact *self)
{
g_return_val_if_fail (TP_IS_CONTACT (self), FALSE);
return self->priv->is_blocked;
}