static void
tp_contact_factory_capabilities_changed_cb (TpConnection    *connection,
					    const GPtrArray *capabilities,
					    gpointer         user_data,
					    GObject         *weak_object)
{
	EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);
	guint                    i;

	for (i = 0; i < capabilities->len; i++)	{
		GValueArray *values;
		guint        handle;
		const gchar *channel_type;
		guint        generic;
		guint        specific;

		values = g_ptr_array_index (capabilities, i);
		handle = g_value_get_uint (g_value_array_get_nth (values, 0));
		channel_type = g_value_get_string (g_value_array_get_nth (values, 1));
		generic = g_value_get_uint (g_value_array_get_nth (values, 3));
		specific = g_value_get_uint (g_value_array_get_nth (values, 5));

		tp_contact_factory_update_capabilities (tp_factory,
							handle,
							channel_type,
							generic,
							specific);
	}
}
static void
tp_contact_factory_avatar_updated_cb (TpConnection *connection,
				      guint         handle,
				      const gchar  *new_token,
				      gpointer      user_data,
				      GObject      *tp_factory)
{
	GArray *handles;

	if (tp_contact_factory_avatar_maybe_update (EMPATHY_TP_CONTACT_FACTORY (tp_factory),
						    handle, new_token)) {
		/* Avatar was cached, nothing to do */
		return;
	}

	DEBUG ("Need to request avatar for token %s", new_token);

	handles = g_array_new (FALSE, FALSE, sizeof (guint));
	g_array_append_val (handles, handle);

	tp_cli_connection_interface_avatars_call_request_avatars (connection,
								  -1,
								  handles,
								  tp_contact_factory_request_avatars_cb,
								  NULL, NULL,
								  tp_factory);
	g_array_free (handles, TRUE);
}
static void
tp_contact_factory_avatar_retrieved_cb (TpConnection *connection,
					guint         handle,
					const gchar  *token,
					const GArray *avatar_data,
					const gchar  *mime_type,
					gpointer      user_data,
					GObject      *tp_factory)
{
	EmpathyContact *contact;

	contact = tp_contact_factory_find_by_handle (EMPATHY_TP_CONTACT_FACTORY (tp_factory),
						     handle);
	if (!contact) {
		return;
	}

	DEBUG ("Avatar retrieved for contact %s (%d)",
		empathy_contact_get_id (contact),
		handle);

	empathy_contact_load_avatar_data (contact,
					  (guchar *) avatar_data->data,
					  avatar_data->len,
					  mime_type,
					  token);
}
static void
tp_contact_factory_got_locations (TpConnection                 *tp_conn,
				  GHashTable              *locations,
				  const GError            *error,
				  gpointer                 user_data,
				  GObject                 *weak_object)
{
	GHashTableIter iter;
	gpointer key, value;
	EmpathyTpContactFactory *tp_factory;

	tp_factory = EMPATHY_TP_CONTACT_FACTORY (user_data);
	if (error != NULL) {
		DEBUG ("Error: %s", error->message);
		return;
	}

	g_hash_table_iter_init (&iter, locations);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		guint           handle = GPOINTER_TO_INT (key);
		GHashTable     *location = value;

		tp_contact_factory_update_location (tp_factory, handle, location);
	}
}
static void
connection_ready_cb (TpConnection *connection,
				const GError *error,
				gpointer user_data)
{
	EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (user_data);
	EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);

	if (error != NULL)
		goto out;

	/* FIXME: This should be moved to TpContact */
	tp_cli_connection_interface_avatars_connect_to_avatar_updated (priv->connection,
								       tp_contact_factory_avatar_updated_cb,
								       NULL, NULL,
								       G_OBJECT (tp_factory),
								       NULL);
	tp_cli_connection_interface_avatars_connect_to_avatar_retrieved (priv->connection,
									 tp_contact_factory_avatar_retrieved_cb,
									 NULL, NULL,
									 G_OBJECT (tp_factory),
									 NULL);

	if (tp_proxy_has_interface_by_id (connection,
				TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES)) {
		priv->contact_caps_supported = TRUE;

		tp_cli_connection_interface_contact_capabilities_connect_to_contact_capabilities_changed (
			priv->connection, tp_contact_factory_contact_capabilities_changed_cb,
			NULL, NULL, G_OBJECT (tp_factory), NULL);
	}
	else {
		tp_cli_connection_interface_capabilities_connect_to_capabilities_changed (priv->connection,
											  tp_contact_factory_capabilities_changed_cb,
											  NULL, NULL,
											  G_OBJECT (tp_factory),
											  NULL);
	}

	tp_cli_connection_interface_avatars_call_get_avatar_requirements (priv->connection,
									  -1,
									  tp_contact_factory_got_avatar_requirements_cb,
									  NULL, NULL,
									  G_OBJECT (tp_factory));

	if (!priv->contact_caps_supported) {
		tp_cli_dbus_properties_call_get (priv->connection, -1,
			TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
			"RequestableChannelClasses",
			get_requestable_channel_classes_cb, NULL, NULL,
			G_OBJECT (tp_factory));
	}

out:
	g_object_unref (tp_factory);
}
static void
tp_contact_factory_contact_capabilities_changed_cb (TpConnection *connection,
						    GHashTable *caps,
						    gpointer user_data,
						    GObject *weak_object)
{
	EmpathyTpContactFactory *self = EMPATHY_TP_CONTACT_FACTORY (weak_object);

	update_contact_capabilities (self, caps);
}
static void
tp_contact_factory_location_updated_cb (TpConnection      *tp_conn,
					guint         handle,
					GHashTable   *location,
					gpointer      user_data,
					GObject      *weak_object)
{
	EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);
	tp_contact_factory_update_location (tp_factory, handle, location);
}
static void
get_requestable_channel_classes_cb (TpProxy *connection,
				    const GValue *value,
				    const GError *error,
				    gpointer user_data,
				    GObject *weak_object)
{
	EmpathyTpContactFactory     *self = EMPATHY_TP_CONTACT_FACTORY (weak_object);
	EmpathyTpContactFactoryPriv *priv = GET_PRIV (self);
	GPtrArray                   *classes;
	GList                       *l;
	EmpathyCapabilities         capabilities;

	if (error != NULL) {
		DEBUG ("Error: %s", error->message);
		return;
	}

	classes = g_value_get_boxed (value);

	DEBUG ("ContactCapabilities is not implemented; use RCC");
	capabilities = channel_classes_to_capabilities (classes, FALSE);
	if ((capabilities & EMPATHY_CAPABILITIES_FT) != 0) {
		DEBUG ("Assume all contacts support FT as CM implements it");
		priv->can_request_ft = TRUE;
	}

	if ((capabilities & EMPATHY_CAPABILITIES_STREAM_TUBE) != 0) {
		DEBUG ("Assume all contacts support stream tubes as CM implements them");
		priv->can_request_st = TRUE;
	}

	if (!priv->can_request_ft && !priv->can_request_st)
		return ;

	/* Update the capabilities of all contacts */
	for (l = priv->contacts; l != NULL; l = g_list_next (l)) {
		EmpathyContact *contact = l->data;
		EmpathyCapabilities caps;

		caps = empathy_contact_get_capabilities (contact);

		/* ContactCapabilities is not supported; assume all contacts can do file
		 * transfer if it's implemented in the CM */
		if (priv->can_request_ft)
			caps |= EMPATHY_CAPABILITIES_FT;

		if (priv->can_request_st)
			caps |= EMPATHY_CAPABILITIES_STREAM_TUBE;

		empathy_contact_set_capabilities (contact, caps);
	}
}
static void
tp_contact_factory_got_contact_capabilities (TpConnection *connection,
					     GHashTable *caps,
					     const GError *error,
					     gpointer user_data,
					     GObject *weak_object)
{
	EmpathyTpContactFactory *self = EMPATHY_TP_CONTACT_FACTORY (weak_object);

	if (error != NULL) {
		DEBUG ("Error: %s", error->message);
		return;
	}

	update_contact_capabilities (self, caps);
}
static void
tp_contact_factory_got_known_avatar_tokens (TpConnection *connection,
					    GHashTable   *tokens,
					    const GError *error,
					    gpointer      user_data,
					    GObject      *weak_object)
{
	EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);
	EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
	GArray *handles;
	GHashTableIter iter;
	gpointer key, value;

	if (error) {
		DEBUG ("Error: %s", error->message);
		return;
	}

	handles = g_array_new (FALSE, FALSE, sizeof (guint));

	g_hash_table_iter_init (&iter, tokens);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		guint handle = GPOINTER_TO_UINT (key);
		const gchar *token = value;

		if (!tp_contact_factory_avatar_maybe_update (tp_factory,
							     handle, token)) {
			g_array_append_val (handles, handle);
		}
	}

	DEBUG ("Got %d tokens, need to request %d avatars",
		g_hash_table_size (tokens), handles->len);

	/* Request needed avatars */
	if (handles->len > 0) {
		tp_cli_connection_interface_avatars_call_request_avatars (priv->connection,
									  -1,
									  handles,
									  tp_contact_factory_request_avatars_cb,
									  NULL, NULL,
									  G_OBJECT (tp_factory));
	}

	g_array_free (handles, TRUE);
}
static void
tp_contact_factory_got_capabilities (TpConnection    *connection,
				     const GPtrArray *capabilities,
				     const GError    *error,
				     gpointer         user_data,
				     GObject         *weak_object)
{
	EmpathyTpContactFactory *tp_factory;
	guint i;

	tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);

	if (error) {
		DEBUG ("Error: %s", error->message);
		/* FIXME Should set the capabilities of the contacts for which this request
		 * originated to NONE */
		return;
	}

	for (i = 0; i < capabilities->len; i++)	{
		GValueArray *values;
		guint        handle;
		const gchar *channel_type;
		guint        generic;
		guint        specific;

		values = g_ptr_array_index (capabilities, i);
		handle = g_value_get_uint (g_value_array_get_nth (values, 0));
		channel_type = g_value_get_string (g_value_array_get_nth (values, 1));
		generic = g_value_get_uint (g_value_array_get_nth (values, 2));
		specific = g_value_get_uint (g_value_array_get_nth (values, 3));

		tp_contact_factory_update_capabilities (tp_factory,
							handle,
							channel_type,
							generic,
							specific);
	}
}
static void
get_requestable_channel_classes_cb (TpProxy *connection,
				    const GValue *value,
				    const GError *error,
				    gpointer user_data,
				    GObject *weak_object)
{
	EmpathyTpContactFactory     *self = EMPATHY_TP_CONTACT_FACTORY (weak_object);
	EmpathyTpContactFactoryPriv *priv = GET_PRIV (self);
	GPtrArray                   *classes;
	guint                        i;
	GList                       *l;

	if (error != NULL) {
		DEBUG ("Error: %s", error->message);
		return;
	}

	classes = g_value_get_boxed (value);
	for (i = 0; i < classes->len; i++) {
		GValueArray *class_struct;
		GHashTable *fixed_prop;
		GValue *chan_type, *handle_type;

		class_struct = g_ptr_array_index (classes, i);
		fixed_prop = g_value_get_boxed (g_value_array_get_nth (class_struct, 0));

		handle_type = g_hash_table_lookup (fixed_prop,
			TP_IFACE_CHANNEL ".TargetHandleType");
		if (handle_type == NULL ||
		    g_value_get_uint (handle_type) != TP_HANDLE_TYPE_CONTACT)
			continue;

		chan_type = g_hash_table_lookup (fixed_prop,
			TP_IFACE_CHANNEL ".ChannelType");
		if (chan_type == NULL)
			continue;

		if (!tp_strdiff (g_value_get_string (chan_type),
		    TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
			priv->can_request_ft = TRUE;
		else if (!tp_strdiff (g_value_get_string (chan_type),
		         TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
			priv->can_request_st = TRUE;
	}

	if (!priv->can_request_ft && !priv->can_request_st)
		return ;

	/* Update the capabilities of all contacts */
	for (l = priv->contacts; l != NULL; l = g_list_next (l)) {
		EmpathyContact *contact = l->data;
		EmpathyCapabilities caps;

		caps = empathy_contact_get_capabilities (contact);

		if (priv->can_request_ft)
			caps |= EMPATHY_CAPABILITIES_FT;

		if (priv->can_request_st)
			caps |= EMPATHY_CAPABILITIES_STREAM_TUBE;

		empathy_contact_set_capabilities (contact, caps);
	}
}