diff options
author | Xavier Claessens <xclaesse@gmail.com> | 2011-09-13 18:35:21 +0200 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2011-09-30 10:20:29 +0200 |
commit | c434a51062415ed37b9225117ccfde4d21a346f9 (patch) | |
tree | 57fe9ea4059f14a46f9db18c5daeab7f2767bd26 /telepathy-glib/channel-contacts.c | |
parent | 0aae81e14a7b40bb035fbaa4d5595665069d9b53 (diff) | |
download | telepathy-glib-c434a51062415ed37b9225117ccfde4d21a346f9.tar.gz |
Add TP_CHANNEL_FEATURE_CONTACTS feature
It ensures that all TpContact objects related to a channel are prepared
with factory features. It fail to prepare on old CMs.
Diffstat (limited to 'telepathy-glib/channel-contacts.c')
-rw-r--r-- | telepathy-glib/channel-contacts.c | 1140 |
1 files changed, 1140 insertions, 0 deletions
diff --git a/telepathy-glib/channel-contacts.c b/telepathy-glib/channel-contacts.c new file mode 100644 index 000000000..bdd8dce36 --- /dev/null +++ b/telepathy-glib/channel-contacts.c @@ -0,0 +1,1140 @@ +/* + * channel-contacts.c - proxy for a Telepathy channel (contacts feature) + * + * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * + * 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 "telepathy-glib/channel-internal.h" + +#include <telepathy-glib/gtypes.h> +#include <telepathy-glib/proxy.h> +#include <telepathy-glib/simple-client-factory.h> +#include <telepathy-glib/util.h> + +#define DEBUG_FLAG TP_DEBUG_GROUPS +#include "telepathy-glib/connection-internal.h" +#include "telepathy-glib/debug-internal.h" +#include "telepathy-glib/util-internal.h" + +static GArray * +dup_handle_array (const GArray *source) +{ + GArray *target; + + target = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), source->len); + g_array_append_vals (target, source->data, source->len); + + return target; +} + +static TpContact * +dup_contact (TpChannel *self, + TpHandle handle, + GHashTable *identifiers) +{ + const gchar *id; + + if (handle == 0) + return NULL; + + id = g_hash_table_lookup (identifiers, GUINT_TO_POINTER (handle)); + if (id == NULL) + { + DEBUG ("Missing identifier for handle %u - broken CM", handle); + return NULL; + } + + return tp_simple_client_factory_ensure_contact ( + tp_proxy_get_factory (self->priv->connection), self->priv->connection, + handle, id); +} + +static GPtrArray * +dup_contact_array (TpChannel *self, + const GArray *handles, + GHashTable *identifiers) +{ + GPtrArray *array; + guint i; + + array = _tp_g_ptr_array_new_full (handles->len, g_object_unref); + + for (i = 0; i < handles->len; i++) + { + TpHandle handle = g_array_index (handles, TpHandle, i); + TpContact *contact = dup_contact (self, handle, identifiers); + + if (contact != NULL) + g_ptr_array_add (array, contact); + } + + return array; +} + +static GHashTable * +dup_contacts_table (TpChannel *self, + TpIntset *source, + GHashTable *identifiers) +{ + GHashTable *target; + TpIntsetFastIter iter; + TpHandle handle; + + target = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref); + + tp_intset_fast_iter_init (&iter, source); + while (tp_intset_fast_iter_next (&iter, &handle)) + { + TpContact *contact = dup_contact (self, handle, identifiers); + + if (contact != NULL) + g_hash_table_insert (target, GUINT_TO_POINTER (handle), contact); + } + + return target; +} + +static GHashTable * +dup_owners_table (TpChannel *self, + GHashTable *source, + GHashTable *identifiers) +{ + GHashTable *target; + GHashTableIter iter; + gpointer key, value; + + target = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref); + + g_hash_table_iter_init (&iter, source); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + TpHandle owner_handle = GPOINTER_TO_UINT (value); + TpContact *contact = dup_contact (self, owner_handle, identifiers); + + g_hash_table_insert (target, key, contact); + } + + return target; +} + +void +_tp_channel_contacts_init (TpChannel *self) +{ + /* Create TpContact objects if we have them for free */ + + if (!tp_connection_has_immortal_handles (self->priv->connection)) + { + self->priv->cm_too_old_for_contacts = TRUE; + return; + } + + g_assert (self->priv->target_contact == NULL); + g_assert (self->priv->initiator_contact == NULL); + + if (self->priv->handle != 0 && self->priv->identifier != NULL && + self->priv->handle_type == TP_HANDLE_TYPE_CONTACT) + { + self->priv->target_contact = tp_simple_client_factory_ensure_contact ( + tp_proxy_get_factory (self->priv->connection), self->priv->connection, + self->priv->handle, self->priv->identifier); + } + + if (tp_channel_get_initiator_handle (self) != 0 && + !tp_str_empty (tp_channel_get_initiator_identifier (self))) + { + self->priv->initiator_contact = tp_simple_client_factory_ensure_contact ( + tp_proxy_get_factory (self->priv->connection), self->priv->connection, + tp_channel_get_initiator_handle (self), + tp_channel_get_initiator_identifier (self)); + } +} + +void +_tp_channel_contacts_group_init (TpChannel *self, + GHashTable *identifiers) +{ + GHashTableIter iter; + gpointer value; + + /* Create TpContact objects if we have them for free */ + + if (!tp_connection_has_immortal_handles (self->priv->connection) || + identifiers == NULL) + { + self->priv->cm_too_old_for_contacts = TRUE; + return; + } + + g_assert (self->priv->group_self_contact == NULL); + g_assert (self->priv->group_members_contacts == NULL); + g_assert (self->priv->group_local_pending_contacts == NULL); + g_assert (self->priv->group_remote_pending_contacts == NULL); + g_assert (self->priv->group_contact_owners == NULL); + + self->priv->group_self_contact = dup_contact (self, + self->priv->group_self_handle, identifiers); + + self->priv->group_members_contacts = dup_contacts_table (self, + self->priv->group_members, identifiers); + + self->priv->group_local_pending_contacts = dup_contacts_table (self, + self->priv->group_local_pending, identifiers); + + self->priv->group_remote_pending_contacts = dup_contacts_table (self, + self->priv->group_remote_pending, identifiers); + + self->priv->group_contact_owners = dup_owners_table (self, + self->priv->group_handle_owners, identifiers); + + if (self->priv->group_local_pending_info != NULL) + { + g_hash_table_iter_init (&iter, self->priv->group_local_pending_info); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + LocalPendingInfo *info = value; + + info->actor_contact = dup_contact (self, info->actor, identifiers); + } + } +} + +typedef struct +{ + GSimpleAsyncResult *result; + GPtrArray *contacts; + GPtrArray *ids; + GArray *handles; +} ContactsQueueItem; + +static ContactsQueueItem * +contacts_queue_item_new (GPtrArray *contacts, + GPtrArray *ids, + GArray *handles) +{ + ContactsQueueItem *item; + + item = g_slice_new (ContactsQueueItem); + item->contacts = contacts != NULL ? g_ptr_array_ref (contacts) : NULL; + item->ids = ids != NULL ? g_ptr_array_ref (ids) : NULL; + item->handles = handles != NULL ? g_array_ref (handles) : NULL; + item->result = NULL; + + return item; +} + +static void +contacts_queue_item_free (ContactsQueueItem *item) +{ + tp_clear_pointer (&item->contacts, g_ptr_array_unref); + tp_clear_pointer (&item->ids, g_ptr_array_unref); + tp_clear_pointer (&item->handles, g_array_unref); + g_clear_object (&item->result); + + g_slice_free (ContactsQueueItem, item); +} + +static void process_contacts_queue (TpChannel *self); + +static void +contacts_queue_head_ready (TpChannel *self, + const GError *error) +{ + ContactsQueueItem *item; + + item = g_queue_pop_head (self->priv->contacts_queue); + + if (error != NULL) + { + DEBUG ("Error preparing channel contacts queue item: %s", error->message); + g_simple_async_result_set_from_error (item->result, error); + } + g_simple_async_result_complete (item->result); + + process_contacts_queue (self); + + contacts_queue_item_free (item); +} + +static void +contacts_queue_item_upgraded_cb (TpConnection *connection, + guint n_contacts, + TpContact * const *contacts, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + TpChannel *self = (TpChannel *) weak_object; + + contacts_queue_head_ready (self, error); +} + +static void +contacts_queue_item_set_contacts (ContactsQueueItem *item, + guint n_contacts, + TpContact * const *contacts) +{ + guint i; + + g_assert (item->contacts == NULL); + item->contacts = _tp_g_ptr_array_new_full (n_contacts, g_object_unref); + for (i = 0; i < n_contacts; i++) + g_ptr_array_add (item->contacts, g_object_ref (contacts[i])); +} + +static void +contacts_queue_item_by_id_cb (TpConnection *connection, + guint n_contacts, + TpContact * const *contacts, + const gchar * const *requested_ids, + GHashTable *failed_id_errors, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + TpChannel *self = (TpChannel *) weak_object; + ContactsQueueItem *item = user_data; + + contacts_queue_item_set_contacts (item, n_contacts, contacts); + contacts_queue_head_ready (self, error); +} + +static void +contacts_queue_item_by_handle_cb (TpConnection *connection, + guint n_contacts, + TpContact * const *contacts, + guint n_failed, + const TpHandle *failed, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + TpChannel *self = (TpChannel *) weak_object; + ContactsQueueItem *item = user_data; + + contacts_queue_item_set_contacts (item, n_contacts, contacts); + contacts_queue_head_ready (self, error); +} + +static gboolean +contacts_queue_item_idle_cb (gpointer user_data) +{ + TpChannel *self = user_data; + + contacts_queue_head_ready (self, NULL); + + return FALSE; +} + +static void +process_contacts_queue (TpChannel *self) +{ + ContactsQueueItem *item; + GArray *features; + const GError *error = NULL; + + item = g_queue_peek_head (self->priv->contacts_queue); + if (item == NULL) + return; + + /* self can't die while there are queued items because item->result keeps a + * ref to it. But it could have been invalidated. */ + error = tp_proxy_get_invalidated (self); + if (error != NULL) + { + g_object_ref (self); + while ((item = g_queue_pop_head (self->priv->contacts_queue)) != NULL) + { + g_simple_async_result_set_from_error (item->result, error); + g_simple_async_result_complete (item->result); + contacts_queue_item_free (item); + } + g_object_unref (self); + + return; + } + + features = tp_simple_client_factory_dup_contact_features ( + tp_proxy_get_factory (self->priv->connection), self->priv->connection); + + if (item->contacts != NULL) + { + g_assert (item->ids == NULL); + g_assert (item->handles == NULL); + + tp_connection_upgrade_contacts (self->priv->connection, + item->contacts->len, (TpContact **) item->contacts->pdata, + features->len, (TpContactFeature *) features->data, + contacts_queue_item_upgraded_cb, + item, NULL, + (GObject *) self); + } + else if (item->ids != NULL) + { + g_assert (item->contacts == NULL); + g_assert (item->handles == NULL); + + tp_connection_get_contacts_by_id (self->priv->connection, + item->ids->len, (const gchar * const*) item->ids->pdata, + features->len, (TpContactFeature *) features->data, + contacts_queue_item_by_id_cb, + item, NULL, + (GObject *) self); + } + else if (item->handles != NULL) + { + g_assert (item->contacts == NULL); + g_assert (item->ids == NULL); + + tp_connection_get_contacts_by_handle (self->priv->connection, + item->handles->len, (TpHandle *) item->handles->data, + features->len, (TpContactFeature *) features->data, + contacts_queue_item_by_handle_cb, + item, NULL, + (GObject *) self); + } + else + { + /* It can happen there is no contact to prepare, and can still be useful + * in order to not reorder some events. + * We have to use an idle though, to guarantee callback is never called + * without reentering mainloop first. */ + g_idle_add (contacts_queue_item_idle_cb, self); + } + + g_array_unref (features); +} + +static void +contacts_queue_item (TpChannel *self, + ContactsQueueItem *item, + GAsyncReadyCallback callback, + gpointer user_data) +{ + item->result = g_simple_async_result_new ((GObject *) self, + callback, user_data, contacts_queue_item); + + g_simple_async_result_set_op_res_gpointer (item->result, item, NULL); + + g_queue_push_tail (self->priv->contacts_queue, item); + + /* If this is the only item in the queue, we can process it right away */ + if (self->priv->contacts_queue->length == 1) + process_contacts_queue (self); +} + +void +_tp_channel_contacts_queue_prepare_async (TpChannel *self, + GPtrArray *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ContactsQueueItem *item; + + item = contacts_queue_item_new (contacts, NULL, NULL); + contacts_queue_item (self, item, callback, user_data); +} + +void +_tp_channel_contacts_queue_prepare_by_id_async (TpChannel *self, + GPtrArray *ids, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ContactsQueueItem *item; + + item = contacts_queue_item_new (NULL, ids, NULL); + contacts_queue_item (self, item, callback, user_data); +} + +void +_tp_channel_contacts_queue_prepare_by_handle_async (TpChannel *self, + GArray *handles, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ContactsQueueItem *item; + + item = contacts_queue_item_new (NULL, NULL, handles); + contacts_queue_item (self, item, callback, user_data); +} + +gboolean +_tp_channel_contacts_queue_prepare_finish (TpChannel *self, + GAsyncResult *result, + GPtrArray **contacts, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + ContactsQueueItem *item; + + item = g_simple_async_result_get_op_res_gpointer (simple); + + if (contacts != NULL) + *contacts = g_ptr_array_ref (item->contacts); + + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, + G_OBJECT (self), contacts_queue_item), FALSE); + + return TRUE; +} + +typedef struct +{ + GPtrArray *added; + GArray *removed; + GPtrArray *local_pending; + GPtrArray *remote_pending; + TpContact *actor; + GHashTable *details; +} MembersChangedData; + +static void +members_changed_data_free (MembersChangedData *data) +{ + tp_clear_pointer (&data->added, g_ptr_array_unref); + tp_clear_pointer (&data->removed, g_array_unref); + tp_clear_pointer (&data->local_pending, g_ptr_array_unref); + tp_clear_pointer (&data->remote_pending, g_ptr_array_unref); + g_clear_object (&data->actor); + tp_clear_pointer (&data->details, g_hash_table_unref); + + g_slice_free (MembersChangedData, data); +} + +static void +members_changed_prepared_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TpChannel *self = (TpChannel *) object; + MembersChangedData *data = user_data; + GPtrArray *removed; + guint i; + + _tp_channel_contacts_queue_prepare_finish (self, result, NULL, NULL); + + /* For removed contacts, we have only handles because we are supposed to + * already know them. So we have to search them in our tables, construct an + * array of removed contacts and then remove them from our tables */ + removed = _tp_g_ptr_array_new_full (data->removed->len, g_object_unref); + for (i = 0; i < data->removed->len; i++) + { + TpHandle handle = g_array_index (data->removed, TpHandle, i); + gpointer key = GUINT_TO_POINTER (handle); + TpContact *contact; + + contact = g_hash_table_lookup (self->priv->group_members_contacts, key); + if (contact == NULL) + contact = g_hash_table_lookup ( + self->priv->group_local_pending_contacts, key); + if (contact == NULL) + contact = g_hash_table_lookup ( + self->priv->group_remote_pending_contacts, key); + + if (contact == NULL) + { + DEBUG ("Handle %u removed but not found in our tables - broken CM", + handle); + continue; + } + + g_ptr_array_add (removed, g_object_ref (contact)); + + g_hash_table_remove (self->priv->group_members_contacts, key); + g_hash_table_remove (self->priv->group_local_pending_contacts, key); + g_hash_table_remove (self->priv->group_remote_pending_contacts, key); + } + + for (i = 0; i < data->added->len; i++) + { + TpContact *contact = g_ptr_array_index (data->added, i); + gpointer key = GUINT_TO_POINTER (tp_contact_get_handle (contact)); + + g_hash_table_insert (self->priv->group_members_contacts, key, + g_object_ref (contact)); + g_hash_table_remove (self->priv->group_local_pending_contacts, key); + g_hash_table_remove (self->priv->group_remote_pending_contacts, key); + } + + for (i = 0; i < data->local_pending->len; i++) + { + TpContact *contact = g_ptr_array_index (data->local_pending, i); + gpointer key = GUINT_TO_POINTER (tp_contact_get_handle (contact)); + + g_hash_table_remove (self->priv->group_members_contacts, key); + g_hash_table_insert (self->priv->group_local_pending_contacts, key, + g_object_ref (contact)); + g_hash_table_remove (self->priv->group_remote_pending_contacts, key); + + if (data->actor != NULL) + { + LocalPendingInfo *info; + + info = g_hash_table_lookup (self->priv->group_local_pending_info, key); + if (info != NULL) + info->actor_contact = g_object_ref (data->actor); + } + } + + for (i = 0; i < data->remote_pending->len; i++) + { + TpContact *contact = g_ptr_array_index (data->remote_pending, i); + gpointer key = GUINT_TO_POINTER (tp_contact_get_handle (contact)); + + g_hash_table_remove (self->priv->group_members_contacts, key); + g_hash_table_remove (self->priv->group_local_pending_contacts, key); + g_hash_table_insert (self->priv->group_remote_pending_contacts, key, + g_object_ref (contact)); + } + + g_signal_emit_by_name (self, "group-contacts-changed", data->added, + removed, data->local_pending, data->remote_pending, data->actor, + data->details); + + g_ptr_array_unref (removed); + members_changed_data_free (data); +} + +void +_tp_channel_contacts_members_changed (TpChannel *self, + const GArray *added, + const GArray *removed, + const GArray *local_pending, + const GArray *remote_pending, + guint actor, + GHashTable *details) +{ + MembersChangedData *data; + GPtrArray *contacts; + GHashTable *ids; + + if (self->priv->cm_too_old_for_contacts) + return; + + ids = tp_asv_get_boxed (details, "contact-ids", + TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP); + if (ids == NULL) + { + DEBUG ("CM did not give identifiers, can't create TpContact"); + return; + } + + g_assert (self->priv->group_members_contacts != NULL); + g_assert (self->priv->group_local_pending_contacts != NULL); + g_assert (self->priv->group_remote_pending_contacts != NULL); + + /* Ensure all TpContact, and push to a queue. This is to ensure that signals + * does not get reordered while we prepare them. */ + data = g_slice_new (MembersChangedData); + data->added = dup_contact_array (self, added, ids); + data->removed = dup_handle_array (removed); + data->local_pending = dup_contact_array (self, local_pending, ids); + data->remote_pending = dup_contact_array (self, remote_pending, ids); + data->actor = dup_contact (self, actor, ids); + data->details = g_hash_table_ref (details); + + contacts = g_ptr_array_new (); + tp_g_ptr_array_extend (contacts, data->added); + tp_g_ptr_array_extend (contacts, data->local_pending); + tp_g_ptr_array_extend (contacts, data->remote_pending); + if (data->actor != NULL) + g_ptr_array_add (contacts, data->actor); + + _tp_channel_contacts_queue_prepare_async (self, contacts, + members_changed_prepared_cb, data); + + g_ptr_array_unref (contacts); +} + +typedef struct +{ + GHashTable *added; + GArray *removed; +} HandleOwnersChangedData; + +static void +handle_owners_changed_data_free (HandleOwnersChangedData *data) +{ + tp_clear_pointer (&data->added, g_hash_table_unref); + tp_clear_pointer (&data->removed, g_array_unref); + + g_slice_free (HandleOwnersChangedData, data); +} + +static void +handle_owners_changed_prepared_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TpChannel *self = (TpChannel *) object; + HandleOwnersChangedData *data = user_data; + guint i; + + _tp_channel_contacts_queue_prepare_finish (self, result, NULL, NULL); + + for (i = 0; i < data->removed->len; i++) + { + g_hash_table_remove (self->priv->group_contact_owners, + GUINT_TO_POINTER (g_array_index (data->removed, TpHandle, i))); + } + + tp_g_hash_table_update (self->priv->group_contact_owners, data->added, NULL, + g_object_ref); + + handle_owners_changed_data_free (data); +} + +void +_tp_channel_contacts_handle_owners_changed (TpChannel *self, + GHashTable *added, + const GArray *removed, + GHashTable *identifiers) +{ + HandleOwnersChangedData *data; + GPtrArray *contacts; + + if (self->priv->cm_too_old_for_contacts) + return; + + g_assert (self->priv->group_contact_owners != NULL); + + data = g_slice_new (HandleOwnersChangedData); + data->added = dup_owners_table (self, added, identifiers); + data->removed = dup_handle_array (removed); + + contacts = _tp_contacts_from_values (data->added); + + _tp_channel_contacts_queue_prepare_async (self, contacts, + handle_owners_changed_prepared_cb, data); + + g_ptr_array_unref (contacts); +} + +static void +self_contact_changed_prepared_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TpChannel *self = (TpChannel *) object; + TpContact *contact = user_data; + + _tp_channel_contacts_queue_prepare_finish (self, result, NULL, NULL); + + g_clear_object (&self->priv->group_self_contact); + self->priv->group_self_contact = contact; + + g_object_notify ((GObject *) self, "group-self-contact"); +} + +void +_tp_channel_contacts_self_contact_changed (TpChannel *self, + guint self_handle, + const gchar *identifier) +{ + TpContact *contact; + GPtrArray *contacts; + + if (self->priv->cm_too_old_for_contacts) + return; + + contacts = g_ptr_array_new_with_free_func (g_object_unref); + contact = tp_simple_client_factory_ensure_contact ( + tp_proxy_get_factory (self->priv->connection), self->priv->connection, + self_handle, identifier); + g_ptr_array_add (contacts, g_object_ref (contact)); + + _tp_channel_contacts_queue_prepare_async (self, contacts, + self_contact_changed_prepared_cb, contact); + + g_ptr_array_unref (contacts); +} + +/** + * tp_channel_get_target_contact: + * @self: a channel + * + * <!-- --> + * + * Returns: (transfer none): the value of #TpChannel:target-contact + * Since: 0.UNRELEASED + */ +TpContact * +tp_channel_get_target_contact (TpChannel *self) +{ + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + + return self->priv->target_contact; +} + +/** + * tp_channel_get_initiator_contact: + * @self: a channel + * + * <!-- --> + * + * Returns: (transfer none): the value of #TpChannel:initiator-contact + * Since: 0.UNRELEASED + */ +TpContact * +tp_channel_get_initiator_contact (TpChannel *self) +{ + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + + return self->priv->initiator_contact; +} + +/** + * tp_channel_group_get_self_contact: + * @self: a channel + * + * <!-- --> + * + * Returns: (transfer none): the value of #TpChannel:group-self-contact + * Since: 0.UNRELEASED + */ +TpContact * +tp_channel_group_get_self_contact (TpChannel *self) +{ + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + + return self->priv->group_self_contact; +} + +/** + * tp_channel_group_dup_members_contacts: + * @self: a channel + * + * If @self is a group and the %TP_CHANNEL_FEATURE_CONTACTS feature has been + * prepared, return a #GPtrArray containing its members. + * + * If @self is a group but %TP_CHANNEL_FEATURE_CONTACTS has not been prepared, + * the result may either be a set of members, or %NULL. + * + * If @self is not a group, return %NULL. + * + * Returns: (transfer container) (type GLib.PtrArray) (element-type TelepathyGLib.Contact): + * a new #GPtrArray of #TpContact, free it with g_ptr_array_unref(), or %NULL. + * + * Since: 0.UNRELEASED + */ +GPtrArray * +tp_channel_group_dup_members_contacts (TpChannel *self) +{ + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + + return _tp_contacts_from_values (self->priv->group_members_contacts); +} + +/** + * tp_channel_group_dup_local_pending_contacts: + * @self: a channel + * + * If @self is a group and the %TP_CHANNEL_FEATURE_CONTACTS feature has been + * prepared, return a #GPtrArray containing its local-pending members. + * + * If @self is a group but %TP_CHANNEL_FEATURE_CONTACTS has not been prepared, + * the result may either be a set of local-pending members, or %NULL. + * + * If @self is not a group, return %NULL. + * + * Returns: (transfer container) (type GLib.PtrArray) (element-type TelepathyGLib.Contact): + * a new #GPtrArray of #TpContact, free it with g_ptr_array_unref(), or %NULL. + * + * Since: 0.UNRELEASED + */ +GPtrArray * +tp_channel_group_dup_local_pending_contacts (TpChannel *self) +{ + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + + return _tp_contacts_from_values (self->priv->group_local_pending_contacts); +} + +/** + * tp_channel_group_dup_remote_pending_contacts: + * @self: a channel + * + * If @self is a group and the %TP_CHANNEL_FEATURE_CONTACTS feature has been + * prepared, return a #GPtrArray containing its remote-pending members. + * + * If @self is a group but %TP_CHANNEL_FEATURE_CONTACTS has not been prepared, + * the result may either be a set of remote-pending members, or %NULL. + * + * If @self is not a group, return %NULL. + * + * Returns: (transfer container) (type GLib.PtrArray) (element-type TelepathyGLib.Contact): + * a new #GPtrArray of #TpContact, free it with g_ptr_array_unref(), or %NULL. + * + * Since: 0.UNRELEASED + */ +GPtrArray * +tp_channel_group_dup_remote_pending_contacts (TpChannel *self) +{ + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + + return _tp_contacts_from_values (self->priv->group_remote_pending_contacts); +} + +/** + * tp_channel_group_get_local_pending_contact_info: + * @self: a channel + * @local_pending: the #TpContact of a local-pending contact about whom more + * information is needed + * @actor: (out) (allow-none) (transfer none): either %NULL or a location to + * return the contact who requested the change + * @reason: (out) (allow-none): either %NULL or a location to return the reason + * for the change + * @message: (out) (allow-none) (transfer none): either %NULL or a location to + * return the + * user-supplied message + * + * If @local_pending is actually a local-pending contact, + * write additional information into @actor, @reason and @message and return + * %TRUE. The contact and message are not referenced or copied, and can only be + * assumed to remain valid until the main loop is re-entered. + * + * If @local_pending is not the handle of a local-pending contact, + * write %NULL into @actor, %TP_CHANNEL_GROUP_CHANGE_REASON_NONE into @reason + * and "" into @message, and return %FALSE. + * + * Returns: %TRUE if the contact is in fact local-pending + * Since: 0.UNRELEASED + */ +gboolean +tp_channel_group_get_local_pending_contact_info (TpChannel *self, + TpContact *local_pending, + TpContact **actor, + TpChannelGroupChangeReason *reason, + const gchar **message) +{ + gboolean ret = FALSE; + TpContact *a = NULL; + TpChannelGroupChangeReason r = TP_CHANNEL_GROUP_CHANGE_REASON_NONE; + const gchar *m = ""; + + g_return_val_if_fail (TP_IS_CHANNEL (self), FALSE); + g_return_val_if_fail (TP_IS_CONTACT (local_pending), FALSE); + g_return_val_if_fail (tp_contact_get_connection (local_pending) == + self->priv->connection, FALSE); + + if (self->priv->group_local_pending != NULL) + { + TpHandle handle = tp_contact_get_handle (local_pending); + + /* it could conceivably be someone who is local-pending */ + + ret = tp_intset_is_member (self->priv->group_local_pending, handle); + + if (ret && self->priv->group_local_pending_info != NULL) + { + /* we might even have information about them */ + LocalPendingInfo *info = g_hash_table_lookup ( + self->priv->group_local_pending_info, + GUINT_TO_POINTER (handle)); + + if (info != NULL) + { + a = info->actor_contact; + r = info->reason; + + if (info->message != NULL) + m = info->message; + } + /* else we have no info, which means (NULL, NONE, NULL) */ + } + } + + if (actor != NULL) + *actor = a; + + if (message != NULL) + *message = m; + + if (reason != NULL) + *reason = r; + + return ret; +} + +/** + * tp_channel_group_get_contact_owner: + * @self: a channel + * @contact: a contact which is a member of this channel + * + * Synopsis (see below for further explanation): + * + * - if @self is not a group or @contact is not a member of this channel, + * result is undefined; + * - if %TP_CHANNEL_FEATURE_CONTACTS has not yet been prepared, result is + * undefined; + * - if @self does not have flags that include + * %TP_CHANNEL_GROUP_FLAG_PROPERTIES, + * result is undefined; + * - if @contact is channel-specific and its globally valid "owner" is known, + * return that owner; + * - if @contact is channel-specific and its globally valid "owner" is unknown, + * return %NULL; + * - if @contact is globally valid, return @contact itself + * + * Some channels (those with flags that include + * %TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) have a concept of + * "channel-specific contacts". These are contacts that only have meaning within + * the context of the channel - for instance, in XMPP Multi-User Chat, + * participants in a chatroom are identified by an in-room JID consisting + * of the JID of the chatroom plus a local nickname. + * + * Depending on the protocol and configuration, it might be possible to find + * out what globally valid contact (i.e. a contact that you could add to + * your contact list) "owns" a channel-specific contact. For instance, in + * most XMPP MUC chatrooms, normal users cannot see what global JID + * corresponds to an in-room JID, but moderators can. + * + * This is further complicated by the fact that channels with channel-specific + * contacts can sometimes have members with globally valid contacts (for + * instance, if you invite someone to an XMPP MUC using their globally valid + * JID, you would expect to see the contact representing that JID in the + * Group's remote-pending set). + * + * Returns: (transfer none): the global contact that owns the given contact, + * or %NULL. + * Since: 0.UNRELEASED + */ +TpContact * +tp_channel_group_get_contact_owner (TpChannel *self, + TpContact *contact) +{ + TpHandle handle; + gpointer value; + + g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); + g_return_val_if_fail (TP_IS_CONTACT (contact), NULL); + g_return_val_if_fail (tp_contact_get_connection (contact) == + self->priv->connection, NULL); + + if (self->priv->group_contact_owners == NULL) + { + /* undefined result - pretending it's global is probably as good as + * any other behaviour, since we can't know either way */ + return contact; + } + + handle = tp_contact_get_handle (contact); + + if (g_hash_table_lookup_extended (self->priv->group_contact_owners, + GUINT_TO_POINTER (handle), NULL, &value)) + { + /* channel-specific, value is either owner or NULL if unknown */ + return value; + } + else + { + /* either already globally valid, or not a member */ + return contact; + } +} + +static void +contacts_prepared_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + TpChannel *self = (TpChannel *) object; + GSimpleAsyncResult *result = user_data; + GError *error = NULL; + + if (!_tp_channel_contacts_queue_prepare_finish (self, res, NULL, &error)) + g_simple_async_result_take_error (result, error); + + g_simple_async_result_complete (result); +} + +static void +append_contacts (GPtrArray *contacts, + GHashTable *table) +{ + GHashTableIter iter; + gpointer value; + + if (table == NULL) + return; + + g_hash_table_iter_init (&iter, table); + while (g_hash_table_iter_next (&iter, NULL, &value)) + g_ptr_array_add (contacts, value); +} + +void +_tp_channel_contacts_prepare_async (TpProxy *proxy, + const TpProxyFeature *feature, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TpChannel *self = (TpChannel *) proxy; + GSimpleAsyncResult *result; + GHashTableIter iter; + gpointer value; + GPtrArray *contacts; + + if (self->priv->cm_too_old_for_contacts) + { + g_simple_async_report_error_in_idle ((GObject *) self, callback, + user_data, TP_ERRORS, TP_ERROR_SOFTWARE_UPGRADE_REQUIRED, + "The Connection Manager does not implement the required telepathy " + "specification (>= 0.23.4) to prepare TP_CHANNEL_FEATURE_CONTACTS."); + return; + } + + result = g_simple_async_result_new ((GObject *) self, callback, user_data, + _tp_channel_contacts_prepare_async); + + contacts = g_ptr_array_new (); + + /* Collect all the TpContacts we have for this channel */ + if (self->priv->target_contact != NULL) + g_ptr_array_add (contacts, self->priv->target_contact); + + if (self->priv->initiator_contact != NULL) + g_ptr_array_add (contacts, self->priv->initiator_contact); + + if (self->priv->group_self_contact != NULL) + g_ptr_array_add (contacts, self->priv->group_self_contact); + + append_contacts (contacts, self->priv->group_members_contacts); + append_contacts (contacts, self->priv->group_local_pending_contacts); + append_contacts (contacts, self->priv->group_remote_pending_contacts); + append_contacts (contacts, self->priv->group_contact_owners); + + if (self->priv->group_local_pending_info != NULL) + { + g_hash_table_iter_init (&iter, self->priv->group_local_pending_info); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + LocalPendingInfo *info = value; + + if (info->actor_contact != NULL) + g_ptr_array_add (contacts, info->actor_contact); + } + } + + _tp_channel_contacts_queue_prepare_async (self, contacts, + contacts_prepared_cb, result); + + g_ptr_array_unref (contacts); +} |