1 /*
2  * channel-contacts.c - proxy for a Telepathy channel (contacts feature)
3  *
4  * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 #include "config.h"
22 
23 #include "telepathy-glib/channel-internal.h"
24 
25 #include <telepathy-glib/gtypes.h>
26 #include <telepathy-glib/proxy.h>
27 #include <telepathy-glib/simple-client-factory.h>
28 #include <telepathy-glib/util.h>
29 
30 #define DEBUG_FLAG TP_DEBUG_GROUPS
31 #include "telepathy-glib/connection-internal.h"
32 #include "telepathy-glib/debug-internal.h"
33 #include "telepathy-glib/util-internal.h"
34 
35 static GArray *
dup_handle_array(const GArray * source)36 dup_handle_array (const GArray *source)
37 {
38   GArray *target;
39 
40   target = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), source->len);
41   g_array_append_vals (target, source->data, source->len);
42 
43   return target;
44 }
45 
46 static TpContact *
dup_contact(TpChannel * self,TpHandle handle,GHashTable * identifiers)47 dup_contact (TpChannel *self,
48     TpHandle handle,
49     GHashTable *identifiers)
50 {
51   const gchar *id;
52 
53   if (handle == 0)
54     return NULL;
55 
56   id = g_hash_table_lookup (identifiers, GUINT_TO_POINTER (handle));
57   if (id == NULL)
58     {
59       DEBUG ("Missing identifier for handle %u - broken CM", handle);
60       return NULL;
61     }
62 
63   return tp_simple_client_factory_ensure_contact (
64       tp_proxy_get_factory (self->priv->connection), self->priv->connection,
65       handle, id);
66 }
67 
68 static GPtrArray *
dup_contact_array(TpChannel * self,const GArray * handles,GHashTable * identifiers)69 dup_contact_array (TpChannel *self,
70     const GArray *handles,
71     GHashTable *identifiers)
72 {
73   GPtrArray *array;
74   guint i;
75 
76   array = g_ptr_array_new_full (handles->len, g_object_unref);
77 
78   for (i = 0; i < handles->len; i++)
79     {
80       TpHandle handle = g_array_index (handles, TpHandle, i);
81       TpContact *contact = dup_contact (self, handle, identifiers);
82 
83       if (contact != NULL)
84         g_ptr_array_add (array, contact);
85     }
86 
87   return array;
88 }
89 
90 static GHashTable *
dup_contacts_table(TpChannel * self,TpIntset * source,GHashTable * identifiers)91 dup_contacts_table (TpChannel *self,
92     TpIntset *source,
93     GHashTable *identifiers)
94 {
95   GHashTable *target;
96   TpIntsetFastIter iter;
97   TpHandle handle;
98 
99   target = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
100 
101   tp_intset_fast_iter_init (&iter, source);
102   while (tp_intset_fast_iter_next (&iter, &handle))
103     {
104       TpContact *contact = dup_contact (self, handle, identifiers);
105 
106       if (contact != NULL)
107         g_hash_table_insert (target, GUINT_TO_POINTER (handle), contact);
108     }
109 
110   return target;
111 }
112 
113 /* self->priv->group_contact_owners may contain NULL TpContact and
114  * g_object_unref isn't NULL safe */
115 static void
safe_g_object_unref(gpointer data)116 safe_g_object_unref (gpointer data)
117 {
118   if (data == NULL)
119     return;
120 
121   g_object_unref (data);
122 }
123 
124 static gpointer
safe_g_object_ref(gpointer data)125 safe_g_object_ref (gpointer data)
126 {
127   if (data == NULL)
128     return NULL;
129 
130   return g_object_ref (data);
131 }
132 
133 static GHashTable *
dup_owners_table(TpChannel * self,GHashTable * source,GHashTable * identifiers)134 dup_owners_table (TpChannel *self,
135     GHashTable *source,
136     GHashTable *identifiers)
137 {
138   GHashTable *target;
139   GHashTableIter iter;
140   gpointer key, value;
141 
142   target = g_hash_table_new_full (NULL, NULL, NULL, safe_g_object_unref);
143 
144   g_hash_table_iter_init (&iter, source);
145   while (g_hash_table_iter_next (&iter, &key, &value))
146     {
147       TpHandle owner_handle = GPOINTER_TO_UINT (value);
148       TpContact *contact = dup_contact (self, owner_handle, identifiers);
149 
150       g_hash_table_insert (target, key, contact);
151     }
152 
153   return target;
154 }
155 
156 void
_tp_channel_contacts_init(TpChannel * self)157 _tp_channel_contacts_init (TpChannel *self)
158 {
159   /* Create TpContact objects if we have them for free */
160 
161   if (!tp_connection_has_immortal_handles (self->priv->connection))
162     {
163       self->priv->cm_too_old_for_contacts = TRUE;
164       return;
165     }
166 
167   g_assert (self->priv->target_contact == NULL);
168   g_assert (self->priv->initiator_contact == NULL);
169 
170   if (self->priv->handle != 0 && self->priv->identifier != NULL &&
171       self->priv->handle_type == TP_HANDLE_TYPE_CONTACT)
172     {
173       self->priv->target_contact = tp_simple_client_factory_ensure_contact (
174           tp_proxy_get_factory (self->priv->connection), self->priv->connection,
175           self->priv->handle, self->priv->identifier);
176     }
177 
178   G_GNUC_BEGIN_IGNORE_DEPRECATIONS
179   if (tp_channel_get_initiator_handle (self) != 0 &&
180       !tp_str_empty (tp_channel_get_initiator_identifier (self)))
181     {
182       self->priv->initiator_contact = tp_simple_client_factory_ensure_contact (
183           tp_proxy_get_factory (self->priv->connection), self->priv->connection,
184           tp_channel_get_initiator_handle (self),
185           tp_channel_get_initiator_identifier (self));
186     }
187   G_GNUC_END_IGNORE_DEPRECATIONS
188 }
189 
190 void
_tp_channel_contacts_group_init(TpChannel * self,GHashTable * identifiers)191 _tp_channel_contacts_group_init (TpChannel *self,
192     GHashTable *identifiers)
193 {
194   GHashTableIter iter;
195   gpointer value;
196 
197   /* Create TpContact objects if we have them for free */
198 
199   if (!tp_connection_has_immortal_handles (self->priv->connection) ||
200       identifiers == NULL)
201     {
202       self->priv->cm_too_old_for_contacts = TRUE;
203       return;
204     }
205 
206   g_assert (self->priv->group_self_contact == NULL);
207   g_assert (self->priv->group_members_contacts == NULL);
208   g_assert (self->priv->group_local_pending_contacts == NULL);
209   g_assert (self->priv->group_remote_pending_contacts == NULL);
210   g_assert (self->priv->group_contact_owners == NULL);
211 
212   self->priv->group_self_contact = dup_contact (self,
213       self->priv->group_self_handle, identifiers);
214 
215   self->priv->group_members_contacts = dup_contacts_table (self,
216       self->priv->group_members, identifiers);
217 
218   self->priv->group_local_pending_contacts = dup_contacts_table (self,
219       self->priv->group_local_pending, identifiers);
220 
221   self->priv->group_remote_pending_contacts = dup_contacts_table (self,
222       self->priv->group_remote_pending, identifiers);
223 
224   self->priv->group_contact_owners = dup_owners_table (self,
225       self->priv->group_handle_owners, identifiers);
226 
227   if (self->priv->group_local_pending_info != NULL)
228     {
229       g_hash_table_iter_init (&iter, self->priv->group_local_pending_info);
230       while (g_hash_table_iter_next (&iter, NULL, &value))
231         {
232           LocalPendingInfo *info = value;
233 
234           info->actor_contact = dup_contact (self, info->actor, identifiers);
235         }
236     }
237 }
238 
239 struct _ContactsQueueItem
240 {
241   GPtrArray *contacts;
242   GPtrArray *ids;
243   GArray *handles;
244 };
245 
246 static void
contacts_queue_item_free(ContactsQueueItem * item)247 contacts_queue_item_free (ContactsQueueItem *item)
248 {
249   tp_clear_pointer (&item->contacts, g_ptr_array_unref);
250   tp_clear_pointer (&item->ids, g_ptr_array_unref);
251   tp_clear_pointer (&item->handles, g_array_unref);
252   g_slice_free (ContactsQueueItem, item);
253 }
254 
255 static void process_contacts_queue (TpChannel *self);
256 
257 static void
contacts_queue_head_ready(TpChannel * self,const GError * error)258 contacts_queue_head_ready (TpChannel *self,
259     const GError *error)
260 {
261   GSimpleAsyncResult *result = self->priv->current_contacts_queue_result;
262 
263   if (error != NULL)
264     {
265       DEBUG ("Error preparing channel contacts queue item: %s", error->message);
266       g_simple_async_result_set_from_error (result, error);
267     }
268   g_simple_async_result_complete (result);
269 
270   self->priv->current_contacts_queue_result = NULL;
271   process_contacts_queue (self);
272 
273   g_object_unref (result);
274 }
275 
276 static void
contacts_queue_item_upgraded_cb(TpConnection * connection,guint n_contacts,TpContact * const * contacts,const GError * error,gpointer user_data,GObject * weak_object)277 contacts_queue_item_upgraded_cb (TpConnection *connection,
278     guint n_contacts,
279     TpContact * const *contacts,
280     const GError *error,
281     gpointer user_data,
282     GObject *weak_object)
283 {
284   TpChannel *self = (TpChannel *) weak_object;
285 
286   contacts_queue_head_ready (self, error);
287 }
288 
289 static void
contacts_queue_item_set_contacts(ContactsQueueItem * item,guint n_contacts,TpContact * const * contacts)290 contacts_queue_item_set_contacts (ContactsQueueItem *item,
291     guint n_contacts,
292     TpContact * const *contacts)
293 {
294   guint i;
295 
296   g_assert (item->contacts == NULL);
297   item->contacts = g_ptr_array_new_full (n_contacts, g_object_unref);
298   for (i = 0; i < n_contacts; i++)
299     g_ptr_array_add (item->contacts, g_object_ref (contacts[i]));
300 }
301 
302 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)303 contacts_queue_item_by_id_cb (TpConnection *connection,
304     guint n_contacts,
305     TpContact * const *contacts,
306     const gchar * const *requested_ids,
307     GHashTable *failed_id_errors,
308     const GError *error,
309     gpointer user_data,
310     GObject *weak_object)
311 {
312   TpChannel *self = (TpChannel *) weak_object;
313   ContactsQueueItem *item = user_data;
314 
315   contacts_queue_item_set_contacts (item, n_contacts, contacts);
316   contacts_queue_head_ready (self, error);
317 }
318 
319 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)320 contacts_queue_item_by_handle_cb (TpConnection *connection,
321     guint n_contacts,
322     TpContact * const *contacts,
323     guint n_failed,
324     const TpHandle *failed,
325     const GError *error,
326     gpointer user_data,
327     GObject *weak_object)
328 {
329   TpChannel *self = (TpChannel *) weak_object;
330   ContactsQueueItem *item = user_data;
331 
332   contacts_queue_item_set_contacts (item, n_contacts, contacts);
333   contacts_queue_head_ready (self, error);
334 }
335 
336 static gboolean
contacts_queue_item_idle_cb(gpointer user_data)337 contacts_queue_item_idle_cb (gpointer user_data)
338 {
339   TpChannel *self = user_data;
340 
341   contacts_queue_head_ready (self, NULL);
342 
343   return FALSE;
344 }
345 
346 static void
process_contacts_queue(TpChannel * self)347 process_contacts_queue (TpChannel *self)
348 {
349   GSimpleAsyncResult *result;
350   ContactsQueueItem *item;
351   GArray *features;
352   const GError *error = NULL;
353 
354   if (self->priv->current_contacts_queue_result != NULL)
355     return;
356 
357   /* self can't die while there are queued items because item->result keeps a
358    * ref to it. But it could have been invalidated. */
359   error = tp_proxy_get_invalidated (self);
360   if (error != NULL)
361     {
362       g_object_ref (self);
363       while ((result = g_queue_pop_head (self->priv->contacts_queue)) != NULL)
364         {
365           g_simple_async_result_set_from_error (result, error);
366           g_simple_async_result_complete (result);
367           g_object_unref (result);
368         }
369       g_object_unref (self);
370 
371       return;
372     }
373 
374   result = g_queue_pop_head (self->priv->contacts_queue);
375 
376   if (result == NULL)
377     return;
378 
379   self->priv->current_contacts_queue_result = result;
380   item = g_simple_async_result_get_op_res_gpointer (result);
381 
382   features = tp_simple_client_factory_dup_contact_features (
383       tp_proxy_get_factory (self->priv->connection), self->priv->connection);
384 
385   /* We can't use upgrade_contacts_async() because we need compat with older
386    * CMs. by_id and by_handle are used only by TpTextChannel and are needed for
387    * older CMs that does not give both message-sender and message-sender-id */
388   G_GNUC_BEGIN_IGNORE_DEPRECATIONS
389   if (item->contacts != NULL && item->contacts->len > 0)
390     {
391       g_assert (item->ids == NULL);
392       g_assert (item->handles == NULL);
393 
394       tp_connection_upgrade_contacts (self->priv->connection,
395           item->contacts->len, (TpContact **) item->contacts->pdata,
396           features->len, (TpContactFeature *) features->data,
397           contacts_queue_item_upgraded_cb,
398           item, NULL,
399           (GObject *) self);
400     }
401   else if (item->ids != NULL && item->ids->len > 0)
402     {
403       g_assert (item->contacts == NULL);
404       g_assert (item->handles == NULL);
405 
406       tp_connection_get_contacts_by_id (self->priv->connection,
407           item->ids->len, (const gchar * const*) item->ids->pdata,
408           features->len, (TpContactFeature *) features->data,
409           contacts_queue_item_by_id_cb,
410           item, NULL,
411           (GObject *) self);
412     }
413   else if (item->handles != NULL && item->handles->len > 0)
414     {
415       g_assert (item->contacts == NULL);
416       g_assert (item->ids == NULL);
417 
418       tp_connection_get_contacts_by_handle (self->priv->connection,
419           item->handles->len, (TpHandle *) item->handles->data,
420           features->len, (TpContactFeature *) features->data,
421           contacts_queue_item_by_handle_cb,
422           item, NULL,
423           (GObject *) self);
424     }
425   else
426     {
427       /* It can happen there is no contact to prepare, and can still be useful
428        * in order to not reorder some events.
429        * We have to use an idle though, to guarantee callback is never called
430        * without reentering mainloop first. */
431       g_idle_add (contacts_queue_item_idle_cb, self);
432     }
433   G_GNUC_END_IGNORE_DEPRECATIONS
434 
435   g_array_unref (features);
436 }
437 
438 static void
contacts_queue_item(TpChannel * self,GPtrArray * contacts,GPtrArray * ids,GArray * handles,GAsyncReadyCallback callback,gpointer user_data)439 contacts_queue_item (TpChannel *self,
440     GPtrArray *contacts,
441     GPtrArray *ids,
442     GArray *handles,
443     GAsyncReadyCallback callback,
444     gpointer user_data)
445 {
446   ContactsQueueItem *item = g_slice_new (ContactsQueueItem);
447   GSimpleAsyncResult *result;
448 
449   item->contacts = contacts != NULL ? g_ptr_array_ref (contacts) : NULL;
450   item->ids = ids != NULL ? g_ptr_array_ref (ids) : NULL;
451   item->handles = handles != NULL ? g_array_ref (handles) : NULL;
452   result = g_simple_async_result_new ((GObject *) self,
453       callback, user_data, contacts_queue_item);
454 
455   g_simple_async_result_set_op_res_gpointer (result, item,
456       (GDestroyNotify) contacts_queue_item_free);
457 
458   g_queue_push_tail (self->priv->contacts_queue, result);
459   process_contacts_queue (self);
460 }
461 
462 void
_tp_channel_contacts_queue_prepare_async(TpChannel * self,GPtrArray * contacts,GAsyncReadyCallback callback,gpointer user_data)463 _tp_channel_contacts_queue_prepare_async (TpChannel *self,
464     GPtrArray *contacts,
465     GAsyncReadyCallback callback,
466     gpointer user_data)
467 {
468   contacts_queue_item (self, contacts, NULL, NULL, callback, user_data);
469 }
470 
471 void
_tp_channel_contacts_queue_prepare_by_id_async(TpChannel * self,GPtrArray * ids,GAsyncReadyCallback callback,gpointer user_data)472 _tp_channel_contacts_queue_prepare_by_id_async (TpChannel *self,
473     GPtrArray *ids,
474     GAsyncReadyCallback callback,
475     gpointer user_data)
476 {
477   contacts_queue_item (self, NULL, ids, NULL, callback, user_data);
478 }
479 
480 void
_tp_channel_contacts_queue_prepare_by_handle_async(TpChannel * self,GArray * handles,GAsyncReadyCallback callback,gpointer user_data)481 _tp_channel_contacts_queue_prepare_by_handle_async (TpChannel *self,
482     GArray *handles,
483     GAsyncReadyCallback callback,
484     gpointer user_data)
485 {
486   contacts_queue_item (self, NULL, NULL, handles, callback, user_data);
487 }
488 
489 gboolean
_tp_channel_contacts_queue_prepare_finish(TpChannel * self,GAsyncResult * result,GPtrArray ** contacts,GError ** error)490 _tp_channel_contacts_queue_prepare_finish (TpChannel *self,
491     GAsyncResult *result,
492     GPtrArray **contacts,
493     GError **error)
494 {
495   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
496   ContactsQueueItem *item;
497 
498   item = g_simple_async_result_get_op_res_gpointer (simple);
499 
500   if (contacts != NULL)
501     {
502       if (item->contacts != NULL)
503         *contacts = g_ptr_array_ref (item->contacts);
504       else
505         *contacts = g_ptr_array_new ();
506     }
507 
508   if (g_simple_async_result_propagate_error (simple, error))
509     return FALSE;
510 
511   g_return_val_if_fail (g_simple_async_result_is_valid (result,
512       G_OBJECT (self), contacts_queue_item), FALSE);
513 
514   return TRUE;
515 }
516 
517 typedef struct
518 {
519   GPtrArray *added;
520   GArray *removed;
521   GPtrArray *local_pending;
522   GPtrArray *remote_pending;
523   TpContact *actor;
524   GHashTable *details;
525 } MembersChangedData;
526 
527 static void
members_changed_data_free(MembersChangedData * data)528 members_changed_data_free (MembersChangedData *data)
529 {
530   tp_clear_pointer (&data->added, g_ptr_array_unref);
531   tp_clear_pointer (&data->removed, g_array_unref);
532   tp_clear_pointer (&data->local_pending, g_ptr_array_unref);
533   tp_clear_pointer (&data->remote_pending, g_ptr_array_unref);
534   g_clear_object (&data->actor);
535   tp_clear_pointer (&data->details, g_hash_table_unref);
536 
537   g_slice_free (MembersChangedData, data);
538 }
539 
540 static void
members_changed_prepared_cb(GObject * object,GAsyncResult * result,gpointer user_data)541 members_changed_prepared_cb (GObject *object,
542     GAsyncResult *result,
543     gpointer user_data)
544 {
545   TpChannel *self = (TpChannel *) object;
546   MembersChangedData *data = user_data;
547   GPtrArray *removed;
548   guint i;
549 
550   _tp_channel_contacts_queue_prepare_finish (self, result, NULL, NULL);
551 
552   /* For removed contacts, we have only handles because we are supposed to
553    * already know them. So we have to search them in our tables, construct an
554    * array of removed contacts and then remove them from our tables */
555   removed = g_ptr_array_new_full (data->removed->len, g_object_unref);
556   for (i = 0; i < data->removed->len; i++)
557     {
558       TpHandle handle = g_array_index (data->removed, TpHandle, i);
559       gpointer key = GUINT_TO_POINTER (handle);
560       TpContact *contact;
561 
562       contact = g_hash_table_lookup (self->priv->group_members_contacts, key);
563       if (contact == NULL)
564           contact = g_hash_table_lookup (
565               self->priv->group_local_pending_contacts, key);
566       if (contact == NULL)
567           contact = g_hash_table_lookup (
568               self->priv->group_remote_pending_contacts, key);
569 
570       if (contact == NULL)
571         {
572           DEBUG ("Handle %u removed but not found in our tables - broken CM",
573               handle);
574           continue;
575         }
576 
577       g_ptr_array_add (removed, g_object_ref (contact));
578 
579       g_hash_table_remove (self->priv->group_members_contacts, key);
580       g_hash_table_remove (self->priv->group_local_pending_contacts, key);
581       g_hash_table_remove (self->priv->group_remote_pending_contacts, key);
582     }
583 
584   for (i = 0; i < data->added->len; i++)
585     {
586       TpContact *contact = g_ptr_array_index (data->added, i);
587       gpointer key = GUINT_TO_POINTER (tp_contact_get_handle (contact));
588 
589       g_hash_table_insert (self->priv->group_members_contacts, key,
590           g_object_ref (contact));
591       g_hash_table_remove (self->priv->group_local_pending_contacts, key);
592       g_hash_table_remove (self->priv->group_remote_pending_contacts, key);
593     }
594 
595   for (i = 0; i < data->local_pending->len; i++)
596     {
597       TpContact *contact = g_ptr_array_index (data->local_pending, i);
598       gpointer key = GUINT_TO_POINTER (tp_contact_get_handle (contact));
599 
600       g_hash_table_remove (self->priv->group_members_contacts, key);
601       g_hash_table_insert (self->priv->group_local_pending_contacts, key,
602           g_object_ref (contact));
603       g_hash_table_remove (self->priv->group_remote_pending_contacts, key);
604 
605       if (data->actor != NULL)
606         {
607           LocalPendingInfo *info;
608 
609           info = g_hash_table_lookup (self->priv->group_local_pending_info, key);
610           if (info != NULL)
611             info->actor_contact = g_object_ref (data->actor);
612         }
613     }
614 
615   for (i = 0; i < data->remote_pending->len; i++)
616     {
617       TpContact *contact = g_ptr_array_index (data->remote_pending, i);
618       gpointer key = GUINT_TO_POINTER (tp_contact_get_handle (contact));
619 
620       g_hash_table_remove (self->priv->group_members_contacts, key);
621       g_hash_table_remove (self->priv->group_local_pending_contacts, key);
622       g_hash_table_insert (self->priv->group_remote_pending_contacts, key,
623           g_object_ref (contact));
624     }
625 
626   g_signal_emit_by_name (self, "group-contacts-changed", data->added,
627       removed, data->local_pending, data->remote_pending, data->actor,
628       data->details);
629 
630   g_ptr_array_unref (removed);
631   members_changed_data_free (data);
632 }
633 
634 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)635 _tp_channel_contacts_members_changed (TpChannel *self,
636     const GArray *added,
637     const GArray *removed,
638     const GArray *local_pending,
639     const GArray *remote_pending,
640     guint actor,
641     GHashTable *details)
642 {
643   MembersChangedData *data;
644   GPtrArray *contacts;
645   GHashTable *ids;
646 
647   if (self->priv->cm_too_old_for_contacts)
648       return;
649 
650   ids = tp_asv_get_boxed (details, "contact-ids",
651       TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP);
652   if (ids == NULL && (added->len > 0 || local_pending->len > 0 ||
653       remote_pending->len > 0 || actor != 0 ))
654     {
655       DEBUG ("CM did not give identifiers, can't create TpContact");
656       return;
657     }
658 
659   g_assert (self->priv->group_members_contacts != NULL);
660   g_assert (self->priv->group_local_pending_contacts != NULL);
661   g_assert (self->priv->group_remote_pending_contacts != NULL);
662 
663   /* Ensure all TpContact, and push to a queue. This is to ensure that signals
664    * does not get reordered while we prepare them. */
665   data = g_slice_new (MembersChangedData);
666   data->added = dup_contact_array (self, added, ids);
667   data->removed = dup_handle_array (removed);
668   data->local_pending = dup_contact_array (self, local_pending, ids);
669   data->remote_pending = dup_contact_array (self, remote_pending, ids);
670   data->actor = dup_contact (self, actor, ids);
671   data->details = g_hash_table_ref (details);
672 
673   contacts = g_ptr_array_new ();
674   tp_g_ptr_array_extend (contacts, data->added);
675   tp_g_ptr_array_extend (contacts, data->local_pending);
676   tp_g_ptr_array_extend (contacts, data->remote_pending);
677   if (data->actor != NULL)
678     g_ptr_array_add (contacts, data->actor);
679 
680   _tp_channel_contacts_queue_prepare_async (self, contacts,
681       members_changed_prepared_cb, data);
682 
683   g_ptr_array_unref (contacts);
684 }
685 
686 typedef struct
687 {
688   GHashTable *added;
689   GArray *removed;
690 } HandleOwnersChangedData;
691 
692 static void
handle_owners_changed_data_free(HandleOwnersChangedData * data)693 handle_owners_changed_data_free (HandleOwnersChangedData *data)
694 {
695   tp_clear_pointer (&data->added, g_hash_table_unref);
696   tp_clear_pointer (&data->removed, g_array_unref);
697 
698   g_slice_free (HandleOwnersChangedData, data);
699 }
700 
701 static void
handle_owners_changed_prepared_cb(GObject * object,GAsyncResult * result,gpointer user_data)702 handle_owners_changed_prepared_cb (GObject *object,
703     GAsyncResult *result,
704     gpointer user_data)
705 {
706   TpChannel *self = (TpChannel *) object;
707   HandleOwnersChangedData *data = user_data;
708   guint i;
709 
710   _tp_channel_contacts_queue_prepare_finish (self, result, NULL, NULL);
711 
712   for (i = 0; i < data->removed->len; i++)
713     {
714       g_hash_table_remove (self->priv->group_contact_owners,
715           GUINT_TO_POINTER (g_array_index (data->removed, TpHandle, i)));
716     }
717 
718   tp_g_hash_table_update (self->priv->group_contact_owners, data->added, NULL,
719       safe_g_object_ref);
720 
721   handle_owners_changed_data_free (data);
722 }
723 
724 void
_tp_channel_contacts_handle_owners_changed(TpChannel * self,GHashTable * added,const GArray * removed,GHashTable * identifiers)725 _tp_channel_contacts_handle_owners_changed (TpChannel *self,
726     GHashTable *added,
727     const GArray *removed,
728     GHashTable *identifiers)
729 {
730   HandleOwnersChangedData *data;
731   GPtrArray *contacts;
732 
733   if (self->priv->cm_too_old_for_contacts)
734       return;
735 
736   g_assert (self->priv->group_contact_owners != NULL);
737 
738   data = g_slice_new (HandleOwnersChangedData);
739   data->added = dup_owners_table (self, added, identifiers);
740   data->removed = dup_handle_array (removed);
741 
742   contacts = _tp_contacts_from_values (data->added);
743 
744   _tp_channel_contacts_queue_prepare_async (self, contacts,
745       handle_owners_changed_prepared_cb, data);
746 
747   g_ptr_array_unref (contacts);
748 }
749 
750 static void
self_contact_changed_prepared_cb(GObject * object,GAsyncResult * result,gpointer user_data)751 self_contact_changed_prepared_cb (GObject *object,
752     GAsyncResult *result,
753     gpointer user_data)
754 {
755   TpChannel *self = (TpChannel *) object;
756   TpContact *contact = user_data;
757 
758   _tp_channel_contacts_queue_prepare_finish (self, result, NULL, NULL);
759 
760   g_clear_object (&self->priv->group_self_contact);
761   self->priv->group_self_contact = contact;
762 
763   g_object_notify ((GObject *) self, "group-self-contact");
764 }
765 
766 void
_tp_channel_contacts_self_contact_changed(TpChannel * self,guint self_handle,const gchar * identifier)767 _tp_channel_contacts_self_contact_changed (TpChannel *self,
768     guint self_handle,
769     const gchar *identifier)
770 {
771   TpContact *contact;
772   GPtrArray *contacts;
773 
774   if (self->priv->cm_too_old_for_contacts)
775       return;
776 
777   contacts = g_ptr_array_new_with_free_func (g_object_unref);
778   contact = tp_simple_client_factory_ensure_contact (
779       tp_proxy_get_factory (self->priv->connection), self->priv->connection,
780       self_handle, identifier);
781   g_ptr_array_add (contacts, g_object_ref (contact));
782 
783   _tp_channel_contacts_queue_prepare_async (self, contacts,
784       self_contact_changed_prepared_cb, contact);
785 
786   g_ptr_array_unref (contacts);
787 }
788 
789 /**
790  * tp_channel_get_target_contact:
791  * @self: a channel
792  *
793  * <!-- -->
794  *
795  * Returns: (transfer none): the value of #TpChannel:target-contact
796  * Since: 0.15.6
797  */
798 TpContact *
tp_channel_get_target_contact(TpChannel * self)799 tp_channel_get_target_contact (TpChannel *self)
800 {
801   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
802 
803   return self->priv->target_contact;
804 }
805 
806 /**
807  * tp_channel_get_initiator_contact:
808  * @self: a channel
809  *
810  * <!-- -->
811  *
812  * Returns: (transfer none): the value of #TpChannel:initiator-contact
813  * Since: 0.15.6
814  */
815 TpContact *
tp_channel_get_initiator_contact(TpChannel * self)816 tp_channel_get_initiator_contact (TpChannel *self)
817 {
818   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
819 
820   return self->priv->initiator_contact;
821 }
822 
823 /**
824  * tp_channel_group_get_self_contact:
825  * @self: a channel
826  *
827  * <!-- -->
828  *
829  * Returns: (transfer none): the value of #TpChannel:group-self-contact
830  * Since: 0.15.6
831  */
832 TpContact *
tp_channel_group_get_self_contact(TpChannel * self)833 tp_channel_group_get_self_contact (TpChannel *self)
834 {
835   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
836 
837   return self->priv->group_self_contact;
838 }
839 
840 /**
841  * tp_channel_group_dup_members_contacts:
842  * @self: a channel
843  *
844  * If @self is a group and the %TP_CHANNEL_FEATURE_CONTACTS feature has been
845  * prepared, return a #GPtrArray containing its members.
846  *
847  * If @self is a group but %TP_CHANNEL_FEATURE_CONTACTS has not been prepared,
848  * the result may either be a set of members, or %NULL.
849  *
850  * If @self is not a group, return %NULL.
851  *
852  * Returns: (transfer container) (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
853  *  a new #GPtrArray of #TpContact, free it with g_ptr_array_unref(), or %NULL.
854  *
855  * Since: 0.15.6
856  */
857 GPtrArray *
tp_channel_group_dup_members_contacts(TpChannel * self)858 tp_channel_group_dup_members_contacts (TpChannel *self)
859 {
860   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
861 
862   return _tp_contacts_from_values (self->priv->group_members_contacts);
863 }
864 
865 /**
866  * tp_channel_group_dup_local_pending_contacts:
867  * @self: a channel
868  *
869  * If @self is a group and the %TP_CHANNEL_FEATURE_CONTACTS feature has been
870  * prepared, return a #GPtrArray containing its local-pending members.
871  *
872  * If @self is a group but %TP_CHANNEL_FEATURE_CONTACTS has not been prepared,
873  * the result may either be a set of local-pending members, or %NULL.
874  *
875  * If @self is not a group, return %NULL.
876  *
877  * Returns: (transfer container) (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
878  *  a new #GPtrArray of #TpContact, free it with g_ptr_array_unref(), or %NULL.
879  *
880  * Since: 0.15.6
881  */
882 GPtrArray *
tp_channel_group_dup_local_pending_contacts(TpChannel * self)883 tp_channel_group_dup_local_pending_contacts (TpChannel *self)
884 {
885   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
886 
887   return _tp_contacts_from_values (self->priv->group_local_pending_contacts);
888 }
889 
890 /**
891  * tp_channel_group_dup_remote_pending_contacts:
892  * @self: a channel
893  *
894  * If @self is a group and the %TP_CHANNEL_FEATURE_CONTACTS feature has been
895  * prepared, return a #GPtrArray containing its remote-pending members.
896  *
897  * If @self is a group but %TP_CHANNEL_FEATURE_CONTACTS has not been prepared,
898  * the result may either be a set of remote-pending members, or %NULL.
899  *
900  * If @self is not a group, return %NULL.
901  *
902  * Returns: (transfer container) (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
903  *  a new #GPtrArray of #TpContact, free it with g_ptr_array_unref(), or %NULL.
904  *
905  * Since: 0.15.6
906  */
907 GPtrArray *
tp_channel_group_dup_remote_pending_contacts(TpChannel * self)908 tp_channel_group_dup_remote_pending_contacts (TpChannel *self)
909 {
910   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
911 
912   return _tp_contacts_from_values (self->priv->group_remote_pending_contacts);
913 }
914 
915 /**
916  * tp_channel_group_get_local_pending_contact_info:
917  * @self: a channel
918  * @local_pending: the #TpContact of a local-pending contact about whom more
919  *  information is needed
920  * @actor: (out) (allow-none) (transfer none): either %NULL or a location to
921  *  return the contact who requested the change
922  * @reason: (out) (allow-none): either %NULL or a location to return the reason
923  *  for the change
924  * @message: (out) (allow-none) (transfer none): either %NULL or a location to
925  *  return the
926  *  user-supplied message
927  *
928  * If @local_pending is actually a local-pending contact,
929  * write additional information into @actor, @reason and @message and return
930  * %TRUE. The contact and message are not referenced or copied, and can only be
931  * assumed to remain valid until the main loop is re-entered.
932  *
933  * If @local_pending is not the handle of a local-pending contact,
934  * write %NULL into @actor, %TP_CHANNEL_GROUP_CHANGE_REASON_NONE into @reason
935  * and "" into @message, and return %FALSE.
936  *
937  * Returns: %TRUE if the contact is in fact local-pending
938  * Since: 0.15.6
939  */
940 gboolean
tp_channel_group_get_local_pending_contact_info(TpChannel * self,TpContact * local_pending,TpContact ** actor,TpChannelGroupChangeReason * reason,const gchar ** message)941 tp_channel_group_get_local_pending_contact_info (TpChannel *self,
942     TpContact *local_pending,
943     TpContact **actor,
944     TpChannelGroupChangeReason *reason,
945     const gchar **message)
946 {
947   gboolean ret = FALSE;
948   TpContact *a = NULL;
949   TpChannelGroupChangeReason r = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
950   const gchar *m = "";
951 
952   g_return_val_if_fail (TP_IS_CHANNEL (self), FALSE);
953   g_return_val_if_fail (TP_IS_CONTACT (local_pending), FALSE);
954   g_return_val_if_fail (tp_contact_get_connection (local_pending) ==
955       self->priv->connection, FALSE);
956 
957   if (self->priv->group_local_pending != NULL)
958     {
959       TpHandle handle = tp_contact_get_handle (local_pending);
960 
961       /* it could conceivably be someone who is local-pending */
962 
963       ret = tp_intset_is_member (self->priv->group_local_pending, handle);
964 
965       if (ret && self->priv->group_local_pending_info != NULL)
966         {
967           /* we might even have information about them */
968           LocalPendingInfo *info = g_hash_table_lookup (
969               self->priv->group_local_pending_info,
970               GUINT_TO_POINTER (handle));
971 
972           if (info != NULL)
973             {
974               a = info->actor_contact;
975               r = info->reason;
976 
977               if (info->message != NULL)
978                 m = info->message;
979             }
980           /* else we have no info, which means (NULL, NONE, NULL) */
981         }
982     }
983 
984   if (actor != NULL)
985     *actor = a;
986 
987   if (message != NULL)
988     *message = m;
989 
990   if (reason != NULL)
991     *reason = r;
992 
993   return ret;
994 }
995 
996 /**
997  * tp_channel_group_get_contact_owner:
998  * @self: a channel
999  * @contact: a contact which is a member of this channel
1000  *
1001  * Synopsis (see below for further explanation):
1002  *
1003  * - if @self is not a group or @contact is not a member of this channel,
1004  *   result is undefined;
1005  * - if %TP_CHANNEL_FEATURE_CONTACTS has not yet been prepared, result is
1006  *   undefined;
1007  * - if @self does not have flags that include
1008  *   %TP_CHANNEL_GROUP_FLAG_PROPERTIES,
1009  *   result is undefined;
1010  * - if @contact is channel-specific and its globally valid "owner" is known,
1011  *   return that owner;
1012  * - if @contact is channel-specific and its globally valid "owner" is unknown,
1013  *   return %NULL;
1014  * - if @contact is globally valid, return @contact itself
1015  *
1016  * Some channels (those with flags that include
1017  * %TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) have a concept of
1018  * "channel-specific contacts". These are contacts that only have meaning within
1019  * the context of the channel - for instance, in XMPP Multi-User Chat,
1020  * participants in a chatroom are identified by an in-room JID consisting
1021  * of the JID of the chatroom plus a local nickname.
1022  *
1023  * Depending on the protocol and configuration, it might be possible to find
1024  * out what globally valid contact (i.e. a contact that you could add to
1025  * your contact list) "owns" a channel-specific contact. For instance, in
1026  * most XMPP MUC chatrooms, normal users cannot see what global JID
1027  * corresponds to an in-room JID, but moderators can.
1028  *
1029  * This is further complicated by the fact that channels with channel-specific
1030  * contacts can sometimes have members with globally valid contacts (for
1031  * instance, if you invite someone to an XMPP MUC using their globally valid
1032  * JID, you would expect to see the contact representing that JID in the
1033  * Group's remote-pending set).
1034  *
1035  * Returns: (transfer none): the global contact that owns the given contact,
1036  *  or %NULL.
1037  * Since: 0.15.6
1038  */
1039 TpContact *
tp_channel_group_get_contact_owner(TpChannel * self,TpContact * contact)1040 tp_channel_group_get_contact_owner (TpChannel *self,
1041     TpContact *contact)
1042 {
1043   TpHandle handle;
1044   gpointer value;
1045 
1046   g_return_val_if_fail (TP_IS_CHANNEL (self), NULL);
1047   g_return_val_if_fail (TP_IS_CONTACT (contact), NULL);
1048   g_return_val_if_fail (tp_contact_get_connection (contact) ==
1049       self->priv->connection, NULL);
1050 
1051   if (self->priv->group_contact_owners == NULL)
1052     {
1053       /* undefined result - pretending it's global is probably as good as
1054        * any other behaviour, since we can't know either way */
1055       return contact;
1056     }
1057 
1058   handle = tp_contact_get_handle (contact);
1059 
1060   if (g_hash_table_lookup_extended (self->priv->group_contact_owners,
1061         GUINT_TO_POINTER (handle), NULL, &value))
1062     {
1063       /* channel-specific, value is either owner or NULL if unknown */
1064       return value;
1065     }
1066   else
1067     {
1068       /* either already globally valid, or not a member */
1069       return contact;
1070     }
1071 }
1072 
1073 static void
contacts_prepared_cb(GObject * object,GAsyncResult * res,gpointer user_data)1074 contacts_prepared_cb (GObject *object,
1075     GAsyncResult *res,
1076     gpointer user_data)
1077 {
1078   TpChannel *self = (TpChannel *) object;
1079   GSimpleAsyncResult *result = user_data;
1080   GError *error = NULL;
1081 
1082   if (!_tp_channel_contacts_queue_prepare_finish (self, res, NULL, &error))
1083     g_simple_async_result_take_error (result, error);
1084 
1085   g_simple_async_result_complete (result);
1086   g_object_unref (result);
1087 }
1088 
1089 static void
append_contacts(GPtrArray * contacts,GHashTable * table)1090 append_contacts (GPtrArray *contacts,
1091     GHashTable *table)
1092 {
1093   GHashTableIter iter;
1094   gpointer value;
1095 
1096   if (table == NULL)
1097       return;
1098 
1099   g_hash_table_iter_init (&iter, table);
1100   while (g_hash_table_iter_next (&iter, NULL, &value))
1101     {
1102       if (value == NULL)
1103         continue;
1104 
1105       g_ptr_array_add (contacts, value);
1106     }
1107 }
1108 
1109 void
_tp_channel_contacts_prepare_async(TpProxy * proxy,const TpProxyFeature * feature,GAsyncReadyCallback callback,gpointer user_data)1110 _tp_channel_contacts_prepare_async (TpProxy *proxy,
1111     const TpProxyFeature *feature,
1112     GAsyncReadyCallback callback,
1113     gpointer user_data)
1114 {
1115   TpChannel *self = (TpChannel *) proxy;
1116   GSimpleAsyncResult *result;
1117   GHashTableIter iter;
1118   gpointer value;
1119   GPtrArray *contacts;
1120 
1121   if (self->priv->cm_too_old_for_contacts)
1122     {
1123       g_simple_async_report_error_in_idle ((GObject *) self, callback,
1124           user_data, TP_ERROR, TP_ERROR_SOFTWARE_UPGRADE_REQUIRED,
1125           "The Connection Manager does not implement the required telepathy "
1126           "specification (>= 0.23.4) to prepare TP_CHANNEL_FEATURE_CONTACTS.");
1127       return;
1128     }
1129 
1130   result = g_simple_async_result_new ((GObject *) self, callback, user_data,
1131       _tp_channel_contacts_prepare_async);
1132 
1133   contacts = g_ptr_array_new ();
1134 
1135   /* Collect all the TpContacts we have for this channel */
1136   if (self->priv->target_contact != NULL)
1137     g_ptr_array_add (contacts, self->priv->target_contact);
1138 
1139   if (self->priv->initiator_contact != NULL)
1140     g_ptr_array_add (contacts, self->priv->initiator_contact);
1141 
1142   if (self->priv->group_self_contact != NULL)
1143     g_ptr_array_add (contacts, self->priv->group_self_contact);
1144 
1145   append_contacts (contacts, self->priv->group_members_contacts);
1146   append_contacts (contacts, self->priv->group_local_pending_contacts);
1147   append_contacts (contacts, self->priv->group_remote_pending_contacts);
1148   append_contacts (contacts, self->priv->group_contact_owners);
1149 
1150   if (self->priv->group_local_pending_info != NULL)
1151     {
1152       g_hash_table_iter_init (&iter, self->priv->group_local_pending_info);
1153       while (g_hash_table_iter_next (&iter, NULL, &value))
1154         {
1155           LocalPendingInfo *info = value;
1156 
1157           if (info->actor_contact != NULL)
1158             g_ptr_array_add (contacts, info->actor_contact);
1159         }
1160     }
1161 
1162   _tp_channel_contacts_queue_prepare_async (self, contacts,
1163       contacts_prepared_cb, result);
1164 
1165   g_ptr_array_unref (contacts);
1166 }
1167