/*
* channel-contacts.c - proxy for a Telepathy channel (contacts feature)
*
* Copyright (C) 2011 Collabora Ltd.
*
* 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 "telepathy-glib/channel-internal.h"
#include
#include
#include
#include
#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 = 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;
}
/* self->priv->group_contact_owners may contain NULL TpContact and
* g_object_unref isn't NULL safe */
static void
safe_g_object_unref (gpointer data)
{
if (data == NULL)
return;
g_object_unref (data);
}
static gpointer
safe_g_object_ref (gpointer data)
{
if (data == NULL)
return NULL;
return g_object_ref (data);
}
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, safe_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);
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
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));
}
G_GNUC_END_IGNORE_DEPRECATIONS
}
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);
}
}
}
struct _ContactsQueueItem
{
GPtrArray *contacts;
GPtrArray *ids;
GArray *handles;
};
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_slice_free (ContactsQueueItem, item);
}
static void process_contacts_queue (TpChannel *self);
static void
contacts_queue_head_ready (TpChannel *self,
const GError *error)
{
GSimpleAsyncResult *result = self->priv->current_contacts_queue_result;
if (error != NULL)
{
DEBUG ("Error preparing channel contacts queue item: %s", error->message);
g_simple_async_result_set_from_error (result, error);
}
g_simple_async_result_complete (result);
self->priv->current_contacts_queue_result = NULL;
process_contacts_queue (self);
g_object_unref (result);
}
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 = 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)
{
GSimpleAsyncResult *result;
ContactsQueueItem *item;
GArray *features;
const GError *error = NULL;
if (self->priv->current_contacts_queue_result != 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 ((result = g_queue_pop_head (self->priv->contacts_queue)) != NULL)
{
g_simple_async_result_set_from_error (result, error);
g_simple_async_result_complete (result);
g_object_unref (result);
}
g_object_unref (self);
return;
}
result = g_queue_pop_head (self->priv->contacts_queue);
if (result == NULL)
return;
self->priv->current_contacts_queue_result = result;
item = g_simple_async_result_get_op_res_gpointer (result);
features = tp_simple_client_factory_dup_contact_features (
tp_proxy_get_factory (self->priv->connection), self->priv->connection);
/* We can't use upgrade_contacts_async() because we need compat with older
* CMs. by_id and by_handle are used only by TpTextChannel and are needed for
* older CMs that does not give both message-sender and message-sender-id */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
if (item->contacts != NULL && item->contacts->len > 0)
{
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 && item->ids->len > 0)
{
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 && item->handles->len > 0)
{
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_GNUC_END_IGNORE_DEPRECATIONS
g_array_unref (features);
}
static void
contacts_queue_item (TpChannel *self,
GPtrArray *contacts,
GPtrArray *ids,
GArray *handles,
GAsyncReadyCallback callback,
gpointer user_data)
{
ContactsQueueItem *item = g_slice_new (ContactsQueueItem);
GSimpleAsyncResult *result;
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;
result = g_simple_async_result_new ((GObject *) self,
callback, user_data, contacts_queue_item);
g_simple_async_result_set_op_res_gpointer (result, item,
(GDestroyNotify) contacts_queue_item_free);
g_queue_push_tail (self->priv->contacts_queue, result);
process_contacts_queue (self);
}
void
_tp_channel_contacts_queue_prepare_async (TpChannel *self,
GPtrArray *contacts,
GAsyncReadyCallback callback,
gpointer user_data)
{
contacts_queue_item (self, contacts, NULL, NULL, callback, user_data);
}
void
_tp_channel_contacts_queue_prepare_by_id_async (TpChannel *self,
GPtrArray *ids,
GAsyncReadyCallback callback,
gpointer user_data)
{
contacts_queue_item (self, NULL, ids, NULL, callback, user_data);
}
void
_tp_channel_contacts_queue_prepare_by_handle_async (TpChannel *self,
GArray *handles,
GAsyncReadyCallback callback,
gpointer user_data)
{
contacts_queue_item (self, NULL, NULL, handles, 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)
{
if (item->contacts != NULL)
*contacts = g_ptr_array_ref (item->contacts);
else
*contacts = g_ptr_array_new ();
}
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 = 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 && (added->len > 0 || local_pending->len > 0 ||
remote_pending->len > 0 || actor != 0 ))
{
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,
safe_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.15.6
*/
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.15.6
*/
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.15.6
*/
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.15.6
*/
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.15.6
*/
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.15.6
*/
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.15.6
*/
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.15.6
*/
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);
g_object_unref (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))
{
if (value == NULL)
continue;
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_ERROR, 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);
}