summaryrefslogtreecommitdiff
path: root/telepathy-glib/channel-contacts.c
diff options
context:
space:
mode:
authorXavier Claessens <xclaesse@gmail.com>2011-09-13 18:35:21 +0200
committerXavier Claessens <xclaesse@gmail.com>2011-09-30 10:20:29 +0200
commitc434a51062415ed37b9225117ccfde4d21a346f9 (patch)
tree57fe9ea4059f14a46f9db18c5daeab7f2767bd26 /telepathy-glib/channel-contacts.c
parent0aae81e14a7b40bb035fbaa4d5595665069d9b53 (diff)
downloadtelepathy-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.c1140
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);
+}