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