1 /* Object representing a Telepathy contact
2 *
3 * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
4 * Copyright (C) 2008 Nokia Corporation
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/contact.h>
24
25 #include <errno.h>
26 #include <string.h>
27
28 #include <telepathy-glib/capabilities-internal.h>
29 #include <telepathy-glib/dbus.h>
30 #include <telepathy-glib/gtypes.h>
31 #include <telepathy-glib/interfaces.h>
32 #include <telepathy-glib/util.h>
33
34 #define DEBUG_FLAG TP_DEBUG_CONTACTS
35 #include "telepathy-glib/base-contact-list-internal.h"
36 #include "telepathy-glib/connection-contact-list.h"
37 #include "telepathy-glib/connection-internal.h"
38 #include "telepathy-glib/contact-internal.h"
39 #include "telepathy-glib/debug-internal.h"
40 #include "telepathy-glib/util-internal.h"
41 #include "telepathy-glib/variant-util-internal.h"
42
43 static const gchar *
nonnull(const gchar * s)44 nonnull (const gchar *s)
45 {
46 if (s == NULL)
47 return "(null)";
48
49 return s;
50 }
51
52 /**
53 * SECTION:contact
54 * @title: TpContact
55 * @short_description: object representing a contact
56 * @see_also: #TpConnection
57 *
58 * #TpContact objects represent the contacts on a particular #TpConnection.
59 *
60 * Since: 0.7.18
61 */
62
63 /**
64 * TpContact:
65 *
66 * An object representing a contact on a #TpConnection.
67 *
68 * Contact objects support tracking a number of attributes of contacts, as
69 * described by the #TpContactFeature flags. Features can be specified when
70 * instantiating contact objects (with tp_connection_get_contacts_by_id() or
71 * tp_connection_get_contacts_by_handle()), or added to an existing contact
72 * object with tp_connection_upgrade_contacts(). For example, a client wishing
73 * to keep track of a contact's alias would set #TP_CONTACT_FEATURE_ALIAS, and
74 * then listen for the "notify::alias" signal, emitted whenever the
75 * #TpContact:alias property changes.
76 *
77 * Note that releasing a #TpContact object might release handle references
78 * held by calling tp_cli_connection_call_request_handles(),
79 * tp_cli_connection_run_request_handles(),
80 * tp_cli_connection_call_hold_handles(),
81 * tp_cli_connection_run_hold_handles(),
82 * tp_cli_connection_interface_contacts_call_get_contact_attributes() or
83 * tp_cli_connection_interface_contacts_run_get_contact_attributes() directly.
84 * Those functions should be avoided in favour of using #TpContact,
85 * tp_connection_hold_handles(), tp_connection_request_handles() and
86 * tp_connection_get_contact_attributes().
87 *
88 * Since: 0.7.18
89 */
90
91 struct _TpContactClass {
92 /*<private>*/
93 GObjectClass parent_class;
94 };
95
96 struct _TpContact {
97 /*<private>*/
98 GObject parent;
99 TpContactPrivate *priv;
100 };
101
102 /**
103 * TpContactFeature:
104 * @TP_CONTACT_FEATURE_ALIAS: #TpContact:alias
105 * @TP_CONTACT_FEATURE_AVATAR_TOKEN: #TpContact:avatar-token
106 * @TP_CONTACT_FEATURE_PRESENCE: #TpContact:presence-type,
107 * #TpContact:presence-status and #TpContact:presence-message
108 * @TP_CONTACT_FEATURE_LOCATION: #TpContact:location (available since 0.11.1)
109 * and #TpContact:location-vardict (since 0.19.10)
110 * @TP_CONTACT_FEATURE_CAPABILITIES: #TpContact:capabilities
111 * (available since 0.11.3)
112 * @TP_CONTACT_FEATURE_AVATAR_DATA: #TpContact:avatar-file and
113 * #TpContact:avatar-mime-type. Implies %TP_CONTACT_FEATURE_AVATAR_TOKEN
114 * (available since 0.11.6)
115 * @TP_CONTACT_FEATURE_CONTACT_INFO: #TpContact:contact-info
116 * (available since 0.11.7)
117 * @TP_CONTACT_FEATURE_CLIENT_TYPES: #TpContact:client-types
118 * (available since 0.13.1)
119 * @TP_CONTACT_FEATURE_SUBSCRIPTION_STATES: #TpContact:subscribe-state,
120 * #TpContact:publish-state and #TpContact:publish-request. Require a
121 * Connection implementing the %TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST
122 * interface. (available since 0.13.12)
123 * @TP_CONTACT_FEATURE_CONTACT_GROUPS: #TpContact:contact-groups
124 * (available since 0.13.14)
125 * @TP_CONTACT_FEATURE_CONTACT_BLOCKING: #TpContact:is-blocked. Require
126 * Connection implementing the %TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING
127 * interface. (available since 0.17.0)
128 *
129 * Enumeration representing the features a #TpContact can optionally support.
130 * When requesting a #TpContact, library users specify the desired features;
131 * the #TpContact code will only initialize state for those features, to
132 * avoid unwanted D-Bus round-trips and signal connections.
133 *
134 * Since 0.11.5, there is a corresponding #GEnumClass type,
135 * %TP_TYPE_CONTACT_FEATURE.
136 *
137 * Since: 0.7.18
138 */
139
140 /**
141 * TP_NUM_CONTACT_FEATURES:
142 *
143 * 1 higher than the highest #TpContactFeature supported by this version of
144 * telepathy-glib.
145 *
146 * Since: 0.19.0
147 */
148
149 /**
150 * NUM_TP_CONTACT_FEATURES: (skip)
151 *
152 * 1 higher than the highest #TpContactFeature supported by this version of
153 * telepathy-glib. Use %TP_NUM_CONTACT_FEATURES in new code.
154 *
155 * Since: 0.7.18
156 */
157
158 /**
159 * TP_CONTACT_FEATURE_INVALID: (skip)
160 *
161 * An invalid TpContactFeature. Used as list termination. See for example
162 * tp_simple_client_factory_add_contact_features_varargs().
163 *
164 * Since: 0.15.5
165 */
166
167 /**
168 * TP_TYPE_CONTACT_FEATURE:
169 *
170 * The #GEnumClass type of a #TpContactFeature.
171 *
172 * Since: 0.11.5
173 */
174
175 G_DEFINE_TYPE (TpContact, tp_contact, G_TYPE_OBJECT)
176
177
178 enum {
179 PROP_CONNECTION = 1,
180 PROP_HANDLE,
181 PROP_IDENTIFIER,
182 PROP_ALIAS,
183 PROP_AVATAR_TOKEN,
184 PROP_AVATAR_FILE,
185 PROP_AVATAR_MIME_TYPE,
186 PROP_PRESENCE_TYPE,
187 PROP_PRESENCE_STATUS,
188 PROP_PRESENCE_MESSAGE,
189 PROP_LOCATION,
190 PROP_LOCATION_VARDICT,
191 PROP_CAPABILITIES,
192 PROP_CONTACT_INFO,
193 PROP_CLIENT_TYPES,
194 PROP_SUBSCRIBE_STATE,
195 PROP_PUBLISH_STATE,
196 PROP_PUBLISH_REQUEST,
197 PROP_CONTACT_GROUPS,
198 PROP_IS_BLOCKED,
199 N_PROPS
200 };
201
202 enum {
203 SIGNAL_PRESENCE_CHANGED,
204 SIGNAL_SUBSCRIPTION_STATES_CHANGED,
205 SIGNAL_CONTACT_GROUPS_CHANGED,
206 N_SIGNALS
207 };
208
209 static guint signals[N_SIGNALS] = {0};
210
211 /* The API allows for more than 32 features, but this implementation does
212 * not. We can easily expand this later. */
213 typedef enum {
214 CONTACT_FEATURE_FLAG_ALIAS = 1 << TP_CONTACT_FEATURE_ALIAS,
215 CONTACT_FEATURE_FLAG_AVATAR_TOKEN = 1 << TP_CONTACT_FEATURE_AVATAR_TOKEN,
216 CONTACT_FEATURE_FLAG_PRESENCE = 1 << TP_CONTACT_FEATURE_PRESENCE,
217 CONTACT_FEATURE_FLAG_LOCATION = 1 << TP_CONTACT_FEATURE_LOCATION,
218 CONTACT_FEATURE_FLAG_CAPABILITIES = 1 << TP_CONTACT_FEATURE_CAPABILITIES,
219 CONTACT_FEATURE_FLAG_AVATAR_DATA = 1 << TP_CONTACT_FEATURE_AVATAR_DATA,
220 CONTACT_FEATURE_FLAG_CONTACT_INFO = 1 << TP_CONTACT_FEATURE_CONTACT_INFO,
221 CONTACT_FEATURE_FLAG_CLIENT_TYPES = 1 << TP_CONTACT_FEATURE_CLIENT_TYPES,
222 CONTACT_FEATURE_FLAG_STATES = 1 << TP_CONTACT_FEATURE_SUBSCRIPTION_STATES,
223 CONTACT_FEATURE_FLAG_CONTACT_GROUPS = 1 << TP_CONTACT_FEATURE_CONTACT_GROUPS,
224 CONTACT_FEATURE_FLAG_CONTACT_BLOCKING = 1 << TP_CONTACT_FEATURE_CONTACT_BLOCKING,
225 } ContactFeatureFlags;
226
227 struct _TpContactPrivate {
228 /* basics */
229 TpConnection *connection;
230 TpHandle handle;
231 gchar *identifier;
232 ContactFeatureFlags has_features;
233
234 /* aliasing */
235 gchar *alias;
236
237 /* avatars */
238 gchar *avatar_token;
239 GFile *avatar_file;
240 gchar *avatar_mime_type;
241
242 /* presence */
243 TpConnectionPresenceType presence_type;
244 gchar *presence_status;
245 gchar *presence_message;
246
247 /* location */
248 GHashTable *location;
249
250 /* client types */
251 gchar **client_types;
252
253 /* capabilities */
254 TpCapabilities *capabilities;
255
256 /* a list of TpContactInfoField */
257 GList *contact_info;
258
259 /* Subscribe/Publish states */
260 TpSubscriptionState subscribe;
261 TpSubscriptionState publish;
262 gchar *publish_request;
263
264 /* ContactGroups */
265 /* array of dupped strings */
266 GPtrArray *contact_groups;
267
268 /* ContactBlocking */
269 gboolean is_blocked;
270 };
271
272
273 /**
274 * tp_contact_get_account:
275 * @self: a contact
276 *
277 * Return the #TpAccount of @self's #TpContact:connection.
278 * See tp_connection_get_account() for details.
279 *
280 * Returns: (transfer none): a borrowed reference to @self's account
281 * (it must be referenced with g_object_ref if it must remain valid
282 * longer than the contact)
283 *
284 * Since: 0.19.0
285 */
286 TpAccount *
tp_contact_get_account(TpContact * self)287 tp_contact_get_account (TpContact *self)
288 {
289 g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
290
291 return tp_connection_get_account (self->priv->connection);
292 }
293
294 /**
295 * tp_contact_get_connection:
296 * @self: a contact
297 *
298 * <!-- nothing more to say -->
299 *
300 * Returns: (transfer none): a borrowed reference to the #TpContact:connection
301 * (it must be referenced with g_object_ref if it must remain valid
302 * longer than the contact)
303 *
304 * Since: 0.7.18
305 */
306 TpConnection *
tp_contact_get_connection(TpContact * self)307 tp_contact_get_connection (TpContact *self)
308 {
309 g_return_val_if_fail (self != NULL, 0);
310
311 return self->priv->connection;
312 }
313
314 /**
315 * tp_contact_get_handle:
316 * @self: a contact
317 *
318 * Return the contact's handle, which is of type %TP_HANDLE_TYPE_CONTACT,
319 * or 0 if the #TpContact:connection has become invalid.
320 *
321 * This handle is referenced using the Telepathy D-Bus API and remains
322 * referenced for as long as @self exists and the
323 * #TpContact:connection remains valid.
324 *
325 * However, the caller of this function does not gain an additional reference
326 * to the handle.
327 *
328 * Returns: the same handle as the #TpContact:handle property
329 *
330 * Since: 0.7.18
331 */
332 TpHandle
tp_contact_get_handle(TpContact * self)333 tp_contact_get_handle (TpContact *self)
334 {
335 g_return_val_if_fail (self != NULL, 0);
336
337 return self->priv->handle;
338 }
339
340 /**
341 * tp_contact_get_identifier:
342 * @self: a contact
343 *
344 * Return the contact's identifier. This remains valid for as long as @self
345 * exists; if the caller requires a string that will persist for longer than
346 * that, it must be copied with g_strdup().
347 *
348 * Returns: the same non-%NULL identifier as the #TpContact:identifier property
349 *
350 * Since: 0.7.18
351 */
352 const gchar *
tp_contact_get_identifier(TpContact * self)353 tp_contact_get_identifier (TpContact *self)
354 {
355 g_return_val_if_fail (self != NULL, NULL);
356 /* identifier must be non-NULL by the time we're visible to library-user
357 * code */
358 g_return_val_if_fail (self->priv->identifier != NULL, NULL);
359
360 return self->priv->identifier;
361 }
362
363 /**
364 * tp_contact_has_feature:
365 * @self: a contact
366 * @feature: a desired feature
367 *
368 * <!-- -->
369 *
370 * Returns: %TRUE if @self has been set up to track the feature @feature
371 *
372 * Since: 0.7.18
373 */
374 gboolean
tp_contact_has_feature(TpContact * self,TpContactFeature feature)375 tp_contact_has_feature (TpContact *self,
376 TpContactFeature feature)
377 {
378 g_return_val_if_fail (self != NULL, FALSE);
379 g_return_val_if_fail (feature < TP_NUM_CONTACT_FEATURES, FALSE);
380
381 return ((self->priv->has_features & (1 << feature)) != 0);
382 }
383
384
385 /**
386 * tp_contact_get_alias:
387 * @self: a contact
388 *
389 * Return the contact's alias. This remains valid until the main loop
390 * is re-entered; if the caller requires a string that will persist for
391 * longer than that, it must be copied with g_strdup().
392 *
393 * Returns: the same non-%NULL alias as the #TpContact:alias
394 *
395 * Since: 0.7.18
396 */
397 const gchar *
tp_contact_get_alias(TpContact * self)398 tp_contact_get_alias (TpContact *self)
399 {
400 g_return_val_if_fail (self != NULL, NULL);
401 /* identifier must be non-NULL by the time we're visible to library-user
402 * code */
403 g_return_val_if_fail (self->priv->identifier != NULL, NULL);
404
405 if (self->priv->alias != NULL)
406 return self->priv->alias;
407
408 return self->priv->identifier;
409 }
410
411
412 /**
413 * tp_contact_get_avatar_token:
414 * @self: a contact
415 *
416 * Return the contact's avatar token. This remains valid until the main loop
417 * is re-entered; if the caller requires a string that will persist for
418 * longer than that, it must be copied with g_strdup().
419 *
420 * Returns: the same token as the #TpContact:avatar-token property
421 * (possibly %NULL)
422 *
423 * Since: 0.7.18
424 */
425 const gchar *
tp_contact_get_avatar_token(TpContact * self)426 tp_contact_get_avatar_token (TpContact *self)
427 {
428 g_return_val_if_fail (self != NULL, NULL);
429
430 return self->priv->avatar_token;
431 }
432
433 /**
434 * tp_contact_get_avatar_file:
435 * @self: a contact
436 *
437 * Return the contact's avatar file. This remains valid until the main loop
438 * is re-entered; if the caller requires a #GFile that will persist for
439 * longer than that, it must be reffed with g_object_ref().
440 *
441 * Returns: (transfer none): the same #GFile as the #TpContact:avatar-file property
442 * (possibly %NULL)
443 *
444 * Since: 0.11.6
445 */
446 GFile *
tp_contact_get_avatar_file(TpContact * self)447 tp_contact_get_avatar_file (TpContact *self)
448 {
449 g_return_val_if_fail (self != NULL, NULL);
450
451 return self->priv->avatar_file;
452 }
453
454 /**
455 * tp_contact_get_avatar_mime_type:
456 * @self: a contact
457 *
458 * Return the contact's avatar MIME type. This remains valid until the main loop
459 * is re-entered; if the caller requires a string that will persist for
460 * longer than that, it must be copied with g_strdup().
461 *
462 * Returns: the same MIME type as the #TpContact:avatar-mime-type property
463 * (possibly %NULL)
464 *
465 * Since: 0.11.6
466 */
467 const gchar *
tp_contact_get_avatar_mime_type(TpContact * self)468 tp_contact_get_avatar_mime_type (TpContact *self)
469 {
470 g_return_val_if_fail (self != NULL, NULL);
471
472 return self->priv->avatar_mime_type;
473 }
474
475 /**
476 * tp_contact_get_presence_type:
477 * @self: a contact
478 *
479 * If this object has been set up to track %TP_CONTACT_FEATURE_PRESENCE
480 * and the underlying connection supports either the Presence or
481 * SimplePresence interfaces, return the type of the contact's presence.
482 *
483 * Otherwise, return %TP_CONNECTION_PRESENCE_TYPE_UNSET.
484 *
485 * Returns: the same presence type as the #TpContact:presence-type property
486 *
487 * Since: 0.7.18
488 */
489 TpConnectionPresenceType
tp_contact_get_presence_type(TpContact * self)490 tp_contact_get_presence_type (TpContact *self)
491 {
492 g_return_val_if_fail (self != NULL, TP_CONNECTION_PRESENCE_TYPE_UNSET);
493
494 return self->priv->presence_type;
495 }
496
497
498 /**
499 * tp_contact_get_presence_status:
500 * @self: a contact
501 *
502 * Return the name of the contact's presence status, or an empty string.
503 * This remains valid until the main loop is re-entered; if the caller
504 * requires a string that will persist for longer than that, it must be
505 * copied with g_strdup().
506 *
507 * Returns: the same non-%NULL status name as the #TpContact:presence-status
508 * property
509 *
510 * Since: 0.7.18
511 */
512 const gchar *
tp_contact_get_presence_status(TpContact * self)513 tp_contact_get_presence_status (TpContact *self)
514 {
515 g_return_val_if_fail (self != NULL, NULL);
516
517 return (self->priv->presence_status == NULL ? "" :
518 self->priv->presence_status);
519 }
520
521
522 /**
523 * tp_contact_get_presence_message:
524 * @self: a contact
525 *
526 * Return the contact's user-defined status message, or an empty string.
527 * This remains valid until the main loop is re-entered; if the caller
528 * requires a string that will persist for longer than that, it must be
529 * copied with g_strdup().
530 *
531 * Returns: the same non-%NULL message as the #TpContact:presence-message
532 * property
533 *
534 * Since: 0.7.18
535 */
536 const gchar *
tp_contact_get_presence_message(TpContact * self)537 tp_contact_get_presence_message (TpContact *self)
538 {
539 g_return_val_if_fail (self != NULL, NULL);
540
541 return (self->priv->presence_message == NULL ? "" :
542 self->priv->presence_message);
543 }
544
545 /**
546 * tp_contact_get_location:
547 * @self: a contact
548 *
549 * Return the contact's user-defined location or %NULL if the location is
550 * unspecified.
551 * This remains valid until the main loop is re-entered; if the caller
552 * requires a hash table that will persist for longer than that, it must be
553 * reffed with g_hash_table_ref().
554 *
555 * Returns: (element-type utf8 GObject.Value) (transfer none): the same
556 * #GHashTable (or %NULL) as the #TpContact:location property
557 *
558 * Since: 0.11.1
559 */
560 GHashTable *
tp_contact_get_location(TpContact * self)561 tp_contact_get_location (TpContact *self)
562 {
563 g_return_val_if_fail (self != NULL, NULL);
564
565 return self->priv->location;
566 }
567
568 /**
569 * tp_contact_dup_location:
570 * @self: a contact
571 *
572 * Return the contact's user-defined location, or %NULL if the location is
573 * unspecified.
574 *
575 * This function returns the same information as tp_contact_get_location(),
576 * but in a different format.
577 *
578 * Returns: a variant of type %G_VARIANT_TYPE_VARDICT, the same as
579 * the #TpContact:location-vardict property
580 *
581 * Since: 0.19.10
582 */
583 GVariant *
tp_contact_dup_location(TpContact * self)584 tp_contact_dup_location (TpContact *self)
585 {
586 g_return_val_if_fail (self != NULL, NULL);
587
588 if (self->priv->location == NULL)
589 return NULL;
590
591 return _tp_asv_to_vardict (self->priv->location);
592 }
593
594 /**
595 * tp_contact_get_client_types:
596 * @self: a contact
597 *
598 * Return the contact's client types or %NULL if the client types are
599 * unspecified.
600 *
601 * Returns: (array zero-terminated=1) (transfer none): the same
602 * #GStrv as the #TpContact:client-types property
603 *
604 * Since: 0.13.1
605 */
606 const gchar * const *
tp_contact_get_client_types(TpContact * self)607 tp_contact_get_client_types (TpContact *self)
608 {
609 g_return_val_if_fail (self != NULL, NULL);
610
611 return (const gchar * const *) self->priv->client_types;
612 }
613
614 /**
615 * tp_contact_get_capabilities:
616 * @self: a contact
617 *
618 * <!-- -->
619 *
620 * Returns: (transfer none): the same #TpCapabilities (or %NULL) as the
621 * #TpContact:capabilities property
622 *
623 * Since: 0.11.3
624 */
625 TpCapabilities *
tp_contact_get_capabilities(TpContact * self)626 tp_contact_get_capabilities (TpContact *self)
627 {
628 g_return_val_if_fail (self != NULL, NULL);
629
630 return self->priv->capabilities;
631 }
632
633 /**
634 * tp_contact_get_contact_info:
635 * @self: a #TpContact
636 *
637 * Returns a newly allocated #GList of contact's vCard fields. The list must be
638 * freed with g_list_free() after used.
639 *
640 * Note that the #TpContactInfoField<!-- -->s in the returned #GList are not
641 * dupped before returning from this function. One could copy every item in the
642 * list using tp_contact_info_field_copy().
643 *
644 * Same as the #TpContact:contact-info property.
645 *
646 * Returns: (element-type TelepathyGLib.ContactInfoField) (transfer container):
647 * a #GList of #TpContactInfoField, or %NULL if the feature is not yet
648 * prepared.
649 * Since: 0.11.7
650 * Deprecated: Since 0.19.9. New code should use
651 * tp_contact_dup_contact_info() instead.
652 */
653 GList *
tp_contact_get_contact_info(TpContact * self)654 tp_contact_get_contact_info (TpContact *self)
655 {
656 g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
657
658 return g_list_copy (self->priv->contact_info);
659 }
660
661 /**
662 * tp_contact_dup_contact_info:
663 * @self: a #TpContact
664 *
665 * Returns a newly allocated #GList of contact's vCard fields. The list must be
666 * freed with tp_contact_info_list_free() after used.
667 *
668 * Same as the #TpContact:contact-info property.
669 *
670 * Returns: (element-type TelepathyGLib.ContactInfoField) (transfer full):
671 * a #GList of #TpContactInfoField, or %NULL if the feature is not yet
672 * prepared.
673 * Since: 0.19.9
674 */
675 GList *
tp_contact_dup_contact_info(TpContact * self)676 tp_contact_dup_contact_info (TpContact *self)
677 {
678 g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
679
680 return _tp_g_list_copy_deep (self->priv->contact_info,
681 (GCopyFunc) tp_contact_info_field_copy, NULL);
682 }
683
684 /**
685 * tp_contact_get_subscribe_state:
686 * @self: a #TpContact
687 *
688 * Return the state of the local user's subscription to this remote contact's
689 * presence.
690 *
691 * This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
692 * %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
693 *
694 * Returns: the value of #TpContact:subscribe-state.
695 *
696 * Since: 0.13.12
697 */
698 TpSubscriptionState
tp_contact_get_subscribe_state(TpContact * self)699 tp_contact_get_subscribe_state (TpContact *self)
700 {
701 g_return_val_if_fail (TP_IS_CONTACT (self), TP_SUBSCRIPTION_STATE_UNKNOWN);
702
703 return self->priv->subscribe;
704 }
705
706 /**
707 * tp_contact_get_publish_state:
708 * @self: a #TpContact
709 *
710 * Return the state of this remote contact's subscription to the local user's
711 * presence.
712 *
713 * This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
714 * %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
715 *
716 * Returns: the value of #TpContact:publish-state.
717 *
718 * Since: 0.13.12
719 */
720 TpSubscriptionState
tp_contact_get_publish_state(TpContact * self)721 tp_contact_get_publish_state (TpContact *self)
722 {
723 g_return_val_if_fail (TP_IS_CONTACT (self), TP_SUBSCRIPTION_STATE_UNKNOWN);
724
725 return self->priv->publish;
726 }
727
728 /**
729 * tp_contact_get_publish_request:
730 * @self: a #TpContact
731 *
732 * If #TpContact:publish-state is set to %TP_SUBSCRIPTION_STATE_ASK, return the
733 * message that this remote contact sent when they requested permission to see
734 * the local user's presence, an empty string ("") otherwise. This remains valid
735 * until the main loop is re-entered; if the caller requires a string that will
736 * persist for longer than that, it must be copied with g_strdup().
737 *
738 * This is set to %NULL until %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been
739 * prepared, and it is guaranteed to be non-%NULL afterward.
740
741 * Returns: the value of #TpContact:publish-request.
742 *
743 * Since: 0.13.12
744 */
745 const gchar *
tp_contact_get_publish_request(TpContact * self)746 tp_contact_get_publish_request (TpContact *self)
747 {
748 g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
749
750 return self->priv->publish_request;
751 }
752
753 /**
754 * tp_contact_get_contact_groups:
755 * @self: a #TpContact
756 *
757 * Return names of groups of which a contact is a member. It is incorrect to
758 * call this method before %TP_CONTACT_FEATURE_CONTACT_GROUPS has been
759 * prepared. This remains valid until the main loop is re-entered; if the caller
760 * requires a #GStrv that will persist for longer than that, it must be copied
761 * with g_strdupv().
762 *
763 * Returns: (array zero-terminated=1) (transfer none): the same
764 * #GStrv as the #TpContact:contact-groups property
765 *
766 * Since: 0.13.14
767 */
768 const gchar * const *
tp_contact_get_contact_groups(TpContact * self)769 tp_contact_get_contact_groups (TpContact *self)
770 {
771 g_return_val_if_fail (TP_IS_CONTACT (self), NULL);
772
773 if (self->priv->contact_groups == NULL)
774 return NULL;
775
776 return (const gchar * const *) self->priv->contact_groups->pdata;
777 }
778
779 static void
set_contact_groups_cb(TpConnection * connection,const GError * error,gpointer user_data,GObject * weak_object)780 set_contact_groups_cb (TpConnection *connection,
781 const GError *error,
782 gpointer user_data,
783 GObject *weak_object)
784 {
785 GSimpleAsyncResult *result = user_data;
786
787 if (error != NULL)
788 {
789 DEBUG ("Failed to set contact groups: %s", error->message);
790 g_simple_async_result_set_from_error (result, error);
791 }
792
793 g_simple_async_result_complete_in_idle (result);
794 g_object_unref (result);
795 }
796
797 /**
798 * tp_contact_set_contact_groups_async:
799 * @self: a #TpContact
800 * @n_groups: the number of groups, or -1 if @groups is %NULL-terminated
801 * @groups: (array length=n_groups) (element-type utf8) (allow-none): the set of
802 * groups which the contact should be in (may be %NULL if @n_groups is 0)
803 * @callback: a callback to call when the request is satisfied
804 * @user_data: data to pass to @callback
805 *
806 * Add @self to the given groups (creating new groups if necessary), and remove
807 * it from all other groups. If the user is removed from a group of which they
808 * were the only member, the group MAY be removed automatically. You can then
809 * call tp_contact_set_contact_groups_finish() to get the result of the
810 * operation.
811 *
812 * If the operation is successful and %TP_CONTACT_FEATURE_CONTACT_GROUPS is
813 * prepared, the #TpContact:contact-groups property will be
814 * updated (emitting "notify::contact-groups" signal) and
815 * #TpContact::contact-groups-changed signal will be emitted before @callback
816 * is called. That means you can call tp_contact_get_contact_groups() to get the
817 * new contact groups inside @callback.
818 *
819 * Since: 0.13.14
820 */
821 void
tp_contact_set_contact_groups_async(TpContact * self,gint n_groups,const gchar * const * groups,GAsyncReadyCallback callback,gpointer user_data)822 tp_contact_set_contact_groups_async (TpContact *self,
823 gint n_groups,
824 const gchar * const *groups,
825 GAsyncReadyCallback callback,
826 gpointer user_data)
827 {
828 static const gchar *empty_groups[] = { NULL };
829 GSimpleAsyncResult *result;
830 gchar **new_groups = NULL;
831
832 g_return_if_fail (TP_IS_CONTACT (self));
833 g_return_if_fail (n_groups >= -1);
834 g_return_if_fail (n_groups <= 0 || groups != NULL);
835
836 if (groups == NULL)
837 {
838 groups = empty_groups;
839 }
840 else if (n_groups > 0)
841 {
842 /* Create NULL-terminated array */
843 new_groups = g_new0 (gchar *, n_groups + 1);
844 g_memmove (new_groups, groups, n_groups * sizeof (gchar *));
845 groups = (const gchar * const *) new_groups;
846 }
847
848 result = g_simple_async_result_new (G_OBJECT (self),
849 callback, user_data, tp_contact_set_contact_groups_finish);
850
851 tp_cli_connection_interface_contact_groups_call_set_contact_groups (
852 self->priv->connection, -1, self->priv->handle, (const gchar **) groups,
853 set_contact_groups_cb, result, NULL, G_OBJECT (self));
854
855 g_free (new_groups);
856 }
857
858 /**
859 * tp_contact_set_contact_groups_finish:
860 * @self: a #TpContact
861 * @result: a #GAsyncResult
862 * @error: a #GError to be filled
863 *
864 * Finishes an async set of @self contact groups.
865 *
866 * Returns: %TRUE if the request call was successful, otherwise %FALSE
867 *
868 * Since: 0.13.14
869 */
870 gboolean
tp_contact_set_contact_groups_finish(TpContact * self,GAsyncResult * result,GError ** error)871 tp_contact_set_contact_groups_finish (TpContact *self,
872 GAsyncResult *result,
873 GError **error)
874 {
875 _tp_implement_finish_void (self, tp_contact_set_contact_groups_finish);
876 }
877
878 void
_tp_contact_connection_disposed(TpContact * contact)879 _tp_contact_connection_disposed (TpContact *contact)
880 {
881 /* The connection has gone away, so we no longer have a meaningful handle,
882 * and will never have one again. */
883 g_assert (contact->priv->handle != 0);
884 contact->priv->handle = 0;
885 g_object_notify ((GObject *) contact, "handle");
886 }
887
888 static void
tp_contact_dispose(GObject * object)889 tp_contact_dispose (GObject *object)
890 {
891 TpContact *self = TP_CONTACT (object);
892
893 if (self->priv->handle != 0)
894 {
895 g_assert (self->priv->connection != NULL);
896
897 _tp_connection_remove_contact (self->priv->connection,
898 self->priv->handle, self);
899
900 self->priv->handle = 0;
901 }
902
903 tp_clear_object (&self->priv->connection);
904 tp_clear_pointer (&self->priv->location, g_hash_table_unref);
905 tp_clear_object (&self->priv->capabilities);
906 tp_clear_object (&self->priv->avatar_file);
907 tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref);
908
909 ((GObjectClass *) tp_contact_parent_class)->dispose (object);
910 }
911
912
913 static void
tp_contact_finalize(GObject * object)914 tp_contact_finalize (GObject *object)
915 {
916 TpContact *self = TP_CONTACT (object);
917
918 g_free (self->priv->identifier);
919 g_free (self->priv->alias);
920 g_free (self->priv->avatar_token);
921 g_free (self->priv->avatar_mime_type);
922 g_free (self->priv->presence_status);
923 g_free (self->priv->presence_message);
924 g_strfreev (self->priv->client_types);
925 tp_contact_info_list_free (self->priv->contact_info);
926 g_free (self->priv->publish_request);
927
928 ((GObjectClass *) tp_contact_parent_class)->finalize (object);
929 }
930
931
932 static void
tp_contact_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)933 tp_contact_get_property (GObject *object,
934 guint property_id,
935 GValue *value,
936 GParamSpec *pspec)
937 {
938 TpContact *self = TP_CONTACT (object);
939
940 switch (property_id)
941 {
942 case PROP_CONNECTION:
943 g_value_set_object (value, self->priv->connection);
944 break;
945
946 case PROP_HANDLE:
947 g_value_set_uint (value, self->priv->handle);
948 break;
949
950 case PROP_IDENTIFIER:
951 g_assert (self->priv->identifier != NULL);
952 g_value_set_string (value, self->priv->identifier);
953 break;
954
955 case PROP_ALIAS:
956 /* tp_contact_get_alias actually has some logic, so avoid
957 * duplicating it */
958 g_value_set_string (value, tp_contact_get_alias (self));
959 break;
960
961 case PROP_AVATAR_TOKEN:
962 g_value_set_string (value, self->priv->avatar_token);
963 break;
964
965 case PROP_AVATAR_FILE:
966 g_value_set_object (value, self->priv->avatar_file);
967 break;
968
969 case PROP_AVATAR_MIME_TYPE:
970 g_value_set_string (value, self->priv->avatar_mime_type);
971 break;
972
973 case PROP_PRESENCE_TYPE:
974 g_value_set_uint (value, self->priv->presence_type);
975 break;
976
977 case PROP_PRESENCE_STATUS:
978 g_value_set_string (value, tp_contact_get_presence_status (self));
979 break;
980
981 case PROP_PRESENCE_MESSAGE:
982 g_value_set_string (value, tp_contact_get_presence_message (self));
983 break;
984
985 case PROP_LOCATION:
986 g_value_set_boxed (value, tp_contact_get_location (self));
987 break;
988
989 case PROP_LOCATION_VARDICT:
990 g_value_take_variant (value, tp_contact_dup_location (self));
991 break;
992
993 case PROP_CAPABILITIES:
994 g_value_set_object (value, tp_contact_get_capabilities (self));
995 break;
996
997 case PROP_CONTACT_INFO:
998 g_value_set_boxed (value, self->priv->contact_info);
999 break;
1000
1001 case PROP_CLIENT_TYPES:
1002 g_value_set_boxed (value, tp_contact_get_client_types (self));
1003 break;
1004
1005 case PROP_SUBSCRIBE_STATE:
1006 g_value_set_uint (value, tp_contact_get_subscribe_state (self));
1007 break;
1008
1009 case PROP_PUBLISH_STATE:
1010 g_value_set_uint (value, tp_contact_get_publish_state (self));
1011 break;
1012
1013 case PROP_PUBLISH_REQUEST:
1014 g_value_set_string (value, tp_contact_get_publish_request (self));
1015 break;
1016
1017 case PROP_CONTACT_GROUPS:
1018 g_value_set_boxed (value, tp_contact_get_contact_groups (self));
1019 break;
1020
1021 case PROP_IS_BLOCKED:
1022 g_value_set_boolean (value, tp_contact_is_blocked (self));
1023 break;
1024
1025 default:
1026 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1027 break;
1028 }
1029 }
1030
1031
1032 static void
tp_contact_class_init(TpContactClass * klass)1033 tp_contact_class_init (TpContactClass *klass)
1034 {
1035 GObjectClass *object_class = (GObjectClass *) klass;
1036 GParamSpec *param_spec;
1037
1038 g_type_class_add_private (klass, sizeof (TpContactPrivate));
1039 object_class->get_property = tp_contact_get_property;
1040 object_class->dispose = tp_contact_dispose;
1041 object_class->finalize = tp_contact_finalize;
1042
1043 /**
1044 * TpContact:connection:
1045 *
1046 * The #TpConnection to which this contact belongs.
1047 */
1048 param_spec = g_param_spec_object ("connection", "TpConnection object",
1049 "Connection object that owns this channel",
1050 TP_TYPE_CONNECTION,
1051 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1052 g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
1053
1054 /**
1055 * TpContact:handle:
1056 *
1057 * The contact's handle in the Telepathy D-Bus API, a handle of type
1058 * %TP_HANDLE_TYPE_CONTACT representing the string
1059 * given by #TpContact:identifier.
1060 *
1061 * This handle is referenced using the Telepathy D-Bus API and remains
1062 * referenced for as long as the #TpContact exists and the
1063 * #TpContact:connection remains valid.
1064 *
1065 * However, getting this property does not cause an additional reference
1066 * to the handle to be held.
1067 *
1068 * If the #TpContact:connection becomes invalid, this property is no longer
1069 * meaningful and will be set to 0.
1070 */
1071 param_spec = g_param_spec_uint ("handle",
1072 "Handle",
1073 "The TP_HANDLE_TYPE_CONTACT handle for this contact",
1074 0, G_MAXUINT32, 0,
1075 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1076 g_object_class_install_property (object_class, PROP_HANDLE, param_spec);
1077
1078 /**
1079 * TpContact:identifier:
1080 *
1081 * The contact's identifier in the instant messaging protocol (e.g.
1082 * XMPP JID, SIP URI, AOL screenname or IRC nick - whatever the underlying
1083 * protocol uses to identify a user).
1084 *
1085 * This is never %NULL for contact objects that are visible to library-user
1086 * code.
1087 */
1088 param_spec = g_param_spec_string ("identifier",
1089 "IM protocol identifier",
1090 "The contact's identifier in the instant messaging protocol (e.g. "
1091 "XMPP JID, SIP URI, AOL screenname or IRC nick)",
1092 NULL,
1093 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1094 g_object_class_install_property (object_class, PROP_IDENTIFIER, param_spec);
1095
1096 /**
1097 * TpContact:alias:
1098 *
1099 * The contact's alias if available, falling back to their
1100 * #TpContact:identifier if no alias is available or if the #TpContact has
1101 * not been set up to track %TP_CONTACT_FEATURE_ALIAS.
1102 *
1103 * This alias may have been supplied by the contact themselves, or by the
1104 * local user, so it does not necessarily unambiguously identify the contact.
1105 * However, it is suitable for use as a main "display name" for the contact.
1106 *
1107 * This is never %NULL for contact objects that are visible to library-user
1108 * code.
1109 */
1110 param_spec = g_param_spec_string ("alias",
1111 "Alias",
1112 "The contact's alias (display name)",
1113 NULL,
1114 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1115 g_object_class_install_property (object_class, PROP_ALIAS, param_spec);
1116
1117 /**
1118 * TpContact:avatar-token:
1119 *
1120 * An opaque string representing state of the contact's avatar (depending on
1121 * the protocol, this might be a hash, a timestamp or something else), or
1122 * an empty string if there is no avatar.
1123 *
1124 * This may be %NULL if it is not known whether this contact has an avatar
1125 * or not (either for network protocol reasons, or because this #TpContact
1126 * has not been set up to track %TP_CONTACT_FEATURE_AVATAR_TOKEN).
1127 */
1128 param_spec = g_param_spec_string ("avatar-token",
1129 "Avatar token",
1130 "Opaque string representing the contact's avatar, or \"\", or NULL",
1131 NULL,
1132 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1133 g_object_class_install_property (object_class, PROP_AVATAR_TOKEN,
1134 param_spec);
1135
1136 /**
1137 * TpContact:avatar-file:
1138 *
1139 * #GFile to the latest cached avatar image, or %NULL if this contact has
1140 * no avatar, or if the avatar data is not yet retrieved.
1141 *
1142 * When #TpContact:avatar-token changes, this property is not updated
1143 * immediately, but will be updated when the new avatar data is retrieved and
1144 * stored in cache. Until then, the file will keep its old value of the latest
1145 * cached avatar image.
1146 *
1147 * This is set to %NULL if %TP_CONTACT_FEATURE_AVATAR_DATA is not set on this
1148 * contact. Note that setting %TP_CONTACT_FEATURE_AVATAR_DATA will also
1149 * implicitly set %TP_CONTACT_FEATURE_AVATAR_TOKEN.
1150 *
1151 * Since: 0.11.6
1152 */
1153 param_spec = g_param_spec_object ("avatar-file",
1154 "Avatar file",
1155 "File to the latest cached avatar image, or %NULL",
1156 G_TYPE_FILE,
1157 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1158 g_object_class_install_property (object_class, PROP_AVATAR_FILE,
1159 param_spec);
1160
1161 /**
1162 * TpContact:avatar-mime-type:
1163 *
1164 * MIME type of the latest cached avatar image, or %NULL if this contact has
1165 * no avatar, or if the avatar data is not yet retrieved.
1166 *
1167 * This is always the MIME type of the image given by #TpContact:avatar-file.
1168 *
1169 * Since: 0.11.6
1170 */
1171 param_spec = g_param_spec_string ("avatar-mime-type",
1172 "Avatar MIME type",
1173 "MIME type of the latest cached avatar image, or %NULL",
1174 NULL,
1175 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1176 g_object_class_install_property (object_class, PROP_AVATAR_MIME_TYPE,
1177 param_spec);
1178
1179 /**
1180 * TpContact:presence-type:
1181 *
1182 * The #TpConnectionPresenceType representing the type of presence status
1183 * for this contact.
1184 *
1185 * This is provided so even unknown values for #TpContact:presence-status
1186 * can be classified into their fundamental types.
1187 *
1188 * This may be %TP_CONNECTION_PRESENCE_TYPE_UNSET if this #TpContact
1189 * has not been set up to track %TP_CONTACT_FEATURE_PRESENCE.
1190 */
1191 param_spec = g_param_spec_uint ("presence-type",
1192 "Presence type",
1193 "The TpConnectionPresenceType for this contact",
1194 0, G_MAXUINT32, TP_CONNECTION_PRESENCE_TYPE_UNSET,
1195 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1196 g_object_class_install_property (object_class, PROP_PRESENCE_TYPE,
1197 param_spec);
1198
1199 /**
1200 * TpContact:presence-status:
1201 *
1202 * A string representing the presence status of this contact. This may be
1203 * a well-known string from the Telepathy specification, like "available",
1204 * or a connection-manager-specific string, like "out-to-lunch".
1205 *
1206 * This may be an empty string if this #TpContact object has not been set up
1207 * to track %TP_CONTACT_FEATURE_PRESENCE. It is never %NULL.
1208 */
1209 param_spec = g_param_spec_string ("presence-status",
1210 "Presence status",
1211 "Possibly connection-manager-specific string representing the "
1212 "contact's presence status",
1213 "",
1214 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1215 g_object_class_install_property (object_class, PROP_PRESENCE_STATUS,
1216 param_spec);
1217
1218 /**
1219 * TpContact:presence-message:
1220 *
1221 * If this contact has set a user-defined status message, that message;
1222 * if not, an empty string (which user interfaces may replace with a
1223 * localized form of the #TpContact:presence-status or
1224 * #TpContact:presence-type).
1225 *
1226 * This may be an empty string even if the contact has set a message,
1227 * if this #TpContact object has not been set up to track
1228 * %TP_CONTACT_FEATURE_PRESENCE. It is never %NULL.
1229 */
1230 param_spec = g_param_spec_string ("presence-message",
1231 "Presence message",
1232 "User-defined status message, or an empty string",
1233 "",
1234 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1235 g_object_class_install_property (object_class, PROP_PRESENCE_MESSAGE,
1236 param_spec);
1237
1238 /**
1239 * TpContact:location:
1240 *
1241 * If this contact has set a user-defined location, a string to
1242 * #GValue * hash table containing his location. If not, %NULL.
1243 * tp_asv_get_string() and similar functions can be used to access
1244 * the contents.
1245 *
1246 * This may be %NULL even if the contact has set a location,
1247 * if this #TpContact object has not been set up to track
1248 * %TP_CONTACT_FEATURE_LOCATION.
1249 *
1250 * Since: 0.11.1
1251 */
1252 param_spec = g_param_spec_boxed ("location",
1253 "Location",
1254 "User-defined location, or NULL",
1255 TP_HASH_TYPE_STRING_VARIANT_MAP,
1256 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1257 g_object_class_install_property (object_class, PROP_LOCATION,
1258 param_spec);
1259
1260 /**
1261 * TpContact:location-vardict:
1262 *
1263 * If this contact has set a user-defined location, a string to
1264 * variant map containing his location. If not, %NULL.
1265 * tp_vardict_get_string() and similar functions can be used to access
1266 * the contents.
1267 *
1268 * This may be %NULL even if the contact has set a location,
1269 * if this #TpContact object has not been set up to track
1270 * %TP_CONTACT_FEATURE_LOCATION.
1271 *
1272 * This property contains the same information as #TpContact:location,
1273 * in a different format.
1274 *
1275 * Since: 0.19.10
1276 */
1277 param_spec = g_param_spec_variant ("location-vardict",
1278 "Location",
1279 "User-defined location, or NULL",
1280 G_VARIANT_TYPE_VARDICT, NULL,
1281 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1282 g_object_class_install_property (object_class, PROP_LOCATION_VARDICT,
1283 param_spec);
1284
1285 /**
1286 * TpContact:capabilities:
1287 *
1288 * The capabilities supported by this contact. If the underlying Connection
1289 * doesn't support the ContactCapabilities interface, this property will
1290 * contain the capabilities supported by the connection.
1291 * Use tp_capabilities_is_specific_to_contact() to check if the capabilities
1292 * are specific to this #TpContact or not.
1293 *
1294 * This may be %NULL if this #TpContact object has not been set up to track
1295 * %TP_CONTACT_FEATURE_CAPABILITIES.
1296 *
1297 * Since: 0.11.3
1298 */
1299 param_spec = g_param_spec_object ("capabilities",
1300 "Capabilities",
1301 "Capabilities of the contact, or NULL",
1302 TP_TYPE_CAPABILITIES,
1303 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1304 g_object_class_install_property (object_class, PROP_CAPABILITIES,
1305 param_spec);
1306
1307 /**
1308 * TpContact:contact-info:
1309 *
1310 * A #GList of #TpContactInfoField representing the vCard of this contact.
1311 *
1312 * This is set to %NULL if %TP_CONTACT_FEATURE_CONTACT_INFO is not set on this
1313 * contact.
1314 *
1315 * Since: 0.11.7
1316 */
1317 param_spec = g_param_spec_boxed ("contact-info",
1318 "Contact Info",
1319 "Information of the contact, or NULL",
1320 TP_TYPE_CONTACT_INFO_LIST,
1321 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1322 g_object_class_install_property (object_class, PROP_CONTACT_INFO,
1323 param_spec);
1324
1325 /**
1326 * TpContact:client-types:
1327 *
1328 * A #GStrv containing the client types of this contact.
1329 *
1330 * This is set to %NULL if %TP_CONTACT_FEATURE_CLIENT_TYPES is not
1331 * set on this contact; it may also be %NULL if that feature is prepared, but
1332 * the contact's client types are unknown.
1333 *
1334 * Since: 0.13.1
1335 */
1336 param_spec = g_param_spec_boxed ("client-types",
1337 "Client types",
1338 "Client types of the contact, or NULL",
1339 G_TYPE_STRV,
1340 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1341 g_object_class_install_property (object_class, PROP_CLIENT_TYPES,
1342 param_spec);
1343
1344 /**
1345 * TpContact:subscribe-state:
1346 *
1347 * A #TpSubscriptionState indicating the state of the local user's
1348 * subscription to this contact's presence.
1349 *
1350 * This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
1351 * %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
1352 *
1353 * Since: 0.13.12
1354 */
1355 param_spec = g_param_spec_uint ("subscribe-state",
1356 "Subscribe State",
1357 "Subscribe state of the contact",
1358 0,
1359 G_MAXUINT,
1360 TP_SUBSCRIPTION_STATE_UNKNOWN,
1361 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1362 g_object_class_install_property (object_class, PROP_SUBSCRIBE_STATE,
1363 param_spec);
1364
1365 /**
1366 * TpContact:publish-state:
1367 *
1368 * A #TpSubscriptionState indicating the state of this contact's subscription
1369 * to the local user's presence.
1370 *
1371 * This is set to %TP_SUBSCRIPTION_STATE_UNKNOWN until
1372 * %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been prepared
1373 *
1374 * Since: 0.13.12
1375 */
1376 param_spec = g_param_spec_uint ("publish-state",
1377 "Publish State",
1378 "Publish state of the contact",
1379 0,
1380 G_MAXUINT,
1381 TP_SUBSCRIPTION_STATE_UNKNOWN,
1382 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1383 g_object_class_install_property (object_class, PROP_PUBLISH_STATE,
1384 param_spec);
1385
1386 /**
1387 * TpContact:publish-request:
1388 *
1389 * The message that contact sent when they requested permission to see the
1390 * local user's presence, if #TpContact:publish-state is
1391 * %TP_SUBSCRIPTION_STATE_ASK, an empty string ("") otherwise.
1392 *
1393 * This is set to %NULL until %TP_CONTACT_FEATURE_SUBSCRIPTION_STATES has been
1394 * prepared, and it is guaranteed to be non-%NULL afterward.
1395 *
1396 * Since: 0.13.12
1397 */
1398 param_spec = g_param_spec_string ("publish-request",
1399 "Publish Request",
1400 "Publish request message of the contact",
1401 NULL,
1402 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1403 g_object_class_install_property (object_class, PROP_PUBLISH_REQUEST,
1404 param_spec);
1405
1406 /**
1407 * TpContact:contact-groups:
1408 *
1409 * a #GStrv with names of groups of which a contact is a member.
1410 *
1411 * This is set to %NULL if %TP_CONTACT_FEATURE_CONTACT_GROUPS is not prepared
1412 * on this contact, or if the connection does not implement ContactGroups
1413 * interface.
1414 *
1415 * Since: 0.13.14
1416 */
1417 param_spec = g_param_spec_boxed ("contact-groups",
1418 "Contact Groups",
1419 "Groups of the contact",
1420 G_TYPE_STRV,
1421 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1422 g_object_class_install_property (object_class, PROP_CONTACT_GROUPS,
1423 param_spec);
1424
1425 /**
1426 * TpContact:is-blocked:
1427 *
1428 * %TRUE if the contact has been blocked.
1429 *
1430 * This is set to %FALSE if %TP_CONTACT_FEATURE_CONTACT_BLOCKING is not
1431 * prepared on this contact, or if the connection does not implement
1432 * ContactBlocking interface.
1433 *
1434 * Since: 0.17.0
1435 */
1436 param_spec = g_param_spec_boolean ("is-blocked",
1437 "is blocked",
1438 "TRUE if contact is blocked",
1439 FALSE,
1440 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1441 g_object_class_install_property (object_class, PROP_IS_BLOCKED, param_spec);
1442
1443 /**
1444 * TpContact::contact-groups-changed:
1445 * @contact: A #TpContact
1446 * @added: A #GStrv with added contact groups
1447 * @removed: A #GStrv with removed contact groups
1448 *
1449 * Emitted when this contact's groups changes. When this signal is emitted,
1450 * #TpContact:contact-groups property is already updated.
1451 *
1452 * Since: 0.13.14
1453 */
1454 signals[SIGNAL_CONTACT_GROUPS_CHANGED] = g_signal_new (
1455 "contact-groups-changed",
1456 G_TYPE_FROM_CLASS (object_class),
1457 G_SIGNAL_RUN_LAST,
1458 0,
1459 NULL, NULL, NULL,
1460 G_TYPE_NONE, 2, G_TYPE_STRV, G_TYPE_STRV);
1461
1462 /**
1463 * TpContact::subscription-states-changed:
1464 * @contact: a #TpContact
1465 * @subscribe: the new value of #TpContact:subscribe-state
1466 * @publish: the new value of #TpContact:publish-state
1467 * @publish_request: the new value of #TpContact:publish-request
1468 *
1469 * Emitted when this contact's subscription states changes.
1470 *
1471 * Since: 0.13.12
1472 */
1473 signals[SIGNAL_SUBSCRIPTION_STATES_CHANGED] = g_signal_new (
1474 "subscription-states-changed",
1475 G_TYPE_FROM_CLASS (object_class),
1476 G_SIGNAL_RUN_LAST,
1477 0,
1478 NULL, NULL, NULL,
1479 G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
1480
1481 /**
1482 * TpContact::presence-changed:
1483 * @contact: a #TpContact
1484 * @type: The new value of #TpContact:presence-type
1485 * @status: The new value of #TpContact:presence-status
1486 * @message: The new value of #TpContact:presence-message
1487 *
1488 * Emitted when this contact's presence changes.
1489 *
1490 * Since: 0.11.7
1491 */
1492 signals[SIGNAL_PRESENCE_CHANGED] = g_signal_new ("presence-changed",
1493 G_TYPE_FROM_CLASS (object_class),
1494 G_SIGNAL_RUN_LAST,
1495 0,
1496 NULL, NULL, NULL,
1497 G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
1498 }
1499
1500 TpContact *
_tp_contact_new(TpConnection * connection,TpHandle handle,const gchar * identifier)1501 _tp_contact_new (TpConnection *connection,
1502 TpHandle handle,
1503 const gchar *identifier)
1504 {
1505 TpContact *self = TP_CONTACT (g_object_new (TP_TYPE_CONTACT, NULL));
1506
1507 self->priv->connection = g_object_ref (connection);
1508 self->priv->handle = handle;
1509 self->priv->identifier = g_strdup (identifier);
1510
1511 return self;
1512 }
1513
1514 /* FIXME: Ideally this should be replaced with
1515 *
1516 * tp_simple_client_factory_ensure_contact (tp_proxy_get_factory (connection),
1517 * handle, identifier);
1518 *
1519 * but we cannot assert CM has immortal handles (yet). That means we cannot
1520 * guarantee that all TpContact objects are created through the factory and so
1521 * let it make TpContact subclasses.
1522 */
1523 static TpContact *
tp_contact_ensure(TpConnection * connection,TpHandle handle)1524 tp_contact_ensure (TpConnection *connection,
1525 TpHandle handle)
1526 {
1527 TpContact *self = _tp_connection_lookup_contact (connection, handle);
1528
1529 if (self != NULL)
1530 {
1531 g_assert (self->priv->handle == handle);
1532 return g_object_ref (self);
1533 }
1534
1535 self = _tp_contact_new (connection, handle, NULL);
1536 _tp_connection_add_contact (connection, handle, self);
1537
1538 return self;
1539 }
1540
1541 /**
1542 * tp_connection_dup_contact_if_possible:
1543 * @connection: a connection
1544 * @handle: a handle of type %TP_HANDLE_TYPE_CONTACT
1545 * @identifier: (transfer none): the normalized identifier (XMPP JID, etc.)
1546 * corresponding to @handle, or %NULL if not known
1547 *
1548 * Try to return an existing contact object or create a new contact object
1549 * immediately.
1550 *
1551 * If tp_connection_has_immortal_handles() would return %TRUE and
1552 * @identifier is non-%NULL, this function always succeeds.
1553 *
1554 * On connections without immortal handles, it is not possible to guarantee
1555 * that @handle remains valid without making asynchronous D-Bus calls, so
1556 * it might be necessary to delay processing of messages or other events
1557 * until a #TpContact can be constructed asynchronously, for instance by using
1558 * tp_connection_get_contacts_by_id().
1559 *
1560 * Similarly, if @identifier is %NULL, it might not be possible to find the
1561 * identifier for @handle without making asynchronous D-Bus calls, so
1562 * it might be necessary to delay processing of messages or other events
1563 * until a #TpContact can be constructed asynchronously, for instance by using
1564 * tp_connection_get_contacts_by_handle().
1565 *
1566 * Returns: (transfer full): a contact or %NULL
1567 *
1568 * Since: 0.13.9
1569 */
1570 TpContact *
tp_connection_dup_contact_if_possible(TpConnection * connection,TpHandle handle,const gchar * identifier)1571 tp_connection_dup_contact_if_possible (TpConnection *connection,
1572 TpHandle handle,
1573 const gchar *identifier)
1574 {
1575 TpContact *ret;
1576
1577 g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
1578 g_return_val_if_fail (handle != 0, NULL);
1579
1580 ret = _tp_connection_lookup_contact (connection, handle);
1581
1582 if (ret != NULL && ret->priv->identifier != NULL)
1583 {
1584 g_object_ref (ret);
1585 }
1586 else if (tp_connection_has_immortal_handles (connection) &&
1587 identifier != NULL)
1588 {
1589 ret = tp_contact_ensure (connection, handle);
1590
1591 if (ret->priv->identifier == NULL)
1592 {
1593 /* new object, I suppose we'll have to believe the caller */
1594 ret->priv->identifier = g_strdup (identifier);
1595 }
1596 }
1597 else
1598 {
1599 /* we don't already have a contact, and we can't make one without
1600 * D-Bus calls (either because we can't rely on the handle staying
1601 * static, or we don't know the identifier) */
1602 return NULL;
1603 }
1604
1605 g_assert (ret->priv->handle == handle);
1606
1607 if (G_UNLIKELY (identifier != NULL &&
1608 tp_strdiff (ret->priv->identifier, identifier)))
1609 {
1610 WARNING ("Either this client, or connection manager %s, is broken: "
1611 "handle %u is thought to be '%s', but we already have "
1612 "a TpContact that thinks the identifier is '%s'",
1613 tp_proxy_get_bus_name (connection), handle, identifier,
1614 ret->priv->identifier);
1615 g_object_unref (ret);
1616 return NULL;
1617 }
1618
1619 return ret;
1620 }
1621
1622 static void
tp_contact_init(TpContact * self)1623 tp_contact_init (TpContact *self)
1624 {
1625 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONTACT,
1626 TpContactPrivate);
1627
1628 self->priv->client_types = NULL;
1629 }
1630
1631
1632 typedef struct _ContactsContext ContactsContext;
1633 typedef void (*ContactsProc) (ContactsContext *self);
1634 typedef enum { CB_BY_HANDLE, CB_BY_ID, CB_UPGRADE } ContactsSignature;
1635
1636 static const gchar *
contacts_signature_to_string(ContactsSignature sig)1637 contacts_signature_to_string (ContactsSignature sig)
1638 {
1639 switch (sig)
1640 {
1641 case CB_BY_HANDLE:
1642 return "by handle";
1643 case CB_BY_ID:
1644 return "by ID";
1645 case CB_UPGRADE:
1646 return "upgrade";
1647 default:
1648 return "???";
1649 }
1650 }
1651
1652 struct _ContactsContext {
1653 gsize refcount;
1654
1655 /* owned */
1656 TpConnection *connection;
1657 /* array of owned TpContact; preallocated but empty until handles have
1658 * been held or requested */
1659 GPtrArray *contacts;
1660 /* array of handles; empty until RequestHandles has returned, if we
1661 * started from IDs */
1662 GArray *handles;
1663 /* array of handles; empty until RequestHandles has returned, if we
1664 * started from IDs */
1665 GArray *invalid;
1666
1667 /* strv of IDs; NULL unless we started from IDs */
1668 GPtrArray *request_ids;
1669 /* ID => GError, NULL unless we started from IDs */
1670 GHashTable *request_errors;
1671
1672 /* features we need to get, if possible, before this request can finish */
1673 ContactFeatureFlags wanted;
1674
1675 /* features we can expect to get from GetContactAttributes
1676 * (subset of wanted) */
1677 ContactFeatureFlags getting;
1678
1679 /* callback for when we've finished, plus the usual misc */
1680 ContactsSignature signature;
1681 union {
1682 TpConnectionContactsByHandleCb by_handle;
1683 TpConnectionContactsByIdCb by_id;
1684 TpConnectionUpgradeContactsCb upgrade;
1685 } callback;
1686 gpointer user_data;
1687 GDestroyNotify destroy;
1688 GObject *weak_object;
1689
1690 /* Whether or not our weak object died*/
1691 gboolean no_purpose_in_life;
1692
1693 /* queue of ContactsProc */
1694 GQueue todo;
1695
1696 /* index into handles or ids, only used when the first HoldHandles call
1697 * failed with InvalidHandle, or the RequestHandles call failed with
1698 * NotAvailable */
1699 guint next_index;
1700
1701 /* TRUE if all contacts already have IDs */
1702 gboolean contacts_have_ids;
1703 };
1704
1705 /* This code (and lots of telepathy-glib, really) won't work if this
1706 * assertion fails, because we put function pointers in a GQueue. If anyone
1707 * cares about platforms where this fails, fixing this would involve
1708 * slice-allocating sizeof (GCallback) bytes repeatedly, and putting *those*
1709 * in the queue. */
1710 G_STATIC_ASSERT (sizeof (GCallback) == sizeof (gpointer));
1711
1712 static void
contacts_context_weak_notify(gpointer data,GObject * dead)1713 contacts_context_weak_notify (gpointer data,
1714 GObject *dead)
1715 {
1716 ContactsContext *c = data;
1717
1718 g_assert (c->weak_object == dead);
1719 c->no_purpose_in_life = TRUE;
1720 c->weak_object = NULL;
1721 }
1722
1723 static ContactsContext *
contacts_context_new(TpConnection * connection,guint n_contacts,ContactFeatureFlags want_features,ContactsSignature signature,gpointer user_data,GDestroyNotify destroy,GObject * weak_object)1724 contacts_context_new (TpConnection *connection,
1725 guint n_contacts,
1726 ContactFeatureFlags want_features,
1727 ContactsSignature signature,
1728 gpointer user_data,
1729 GDestroyNotify destroy,
1730 GObject *weak_object)
1731 {
1732 ContactsContext *c = g_slice_new0 (ContactsContext);
1733
1734 DEBUG ("%p, for %u contacts, %s", c, n_contacts,
1735 contacts_signature_to_string (signature));
1736
1737 DEBUG ("want alias: %s",
1738 (want_features & CONTACT_FEATURE_FLAG_ALIAS) ? "yes" : "no");
1739 DEBUG ("want avatar token: %s",
1740 (want_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) ? "yes" : "no");
1741 DEBUG ("want presence: %s",
1742 (want_features & CONTACT_FEATURE_FLAG_PRESENCE) ? "yes" : "no");
1743 DEBUG ("want location: %s",
1744 (want_features & CONTACT_FEATURE_FLAG_LOCATION) ? "yes" : "no");
1745 DEBUG ("want caps: %s",
1746 (want_features & CONTACT_FEATURE_FLAG_CAPABILITIES) ? "yes" : "no");
1747 DEBUG ("want avatar data: %s",
1748 (want_features & CONTACT_FEATURE_FLAG_AVATAR_DATA) ? "yes" : "no");
1749 DEBUG ("want contact info: %s",
1750 (want_features & CONTACT_FEATURE_FLAG_CONTACT_INFO) ? "yes" : "no");
1751 DEBUG ("want client types: %s",
1752 (want_features & CONTACT_FEATURE_FLAG_CLIENT_TYPES) ? "yes" : "no");
1753 DEBUG ("want states: %s",
1754 (want_features & CONTACT_FEATURE_FLAG_STATES) ? "yes" : "no");
1755 DEBUG ("want contact groups: %s",
1756 (want_features & CONTACT_FEATURE_FLAG_CONTACT_GROUPS) ? "yes" : "no");
1757 DEBUG ("want contact blocking: %s",
1758 (want_features & CONTACT_FEATURE_FLAG_CONTACT_BLOCKING) ? "yes" : "no");
1759
1760 c->refcount = 1;
1761 c->connection = g_object_ref (connection);
1762 c->contacts = g_ptr_array_sized_new (n_contacts);
1763 c->handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_contacts);
1764 c->invalid = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_contacts);
1765
1766 c->wanted = want_features;
1767 c->signature = signature;
1768 c->user_data = user_data;
1769 c->destroy = destroy;
1770 c->weak_object = weak_object;
1771
1772 if (c->weak_object != NULL)
1773 g_object_weak_ref (c->weak_object, contacts_context_weak_notify, c);
1774
1775 g_queue_init (&c->todo);
1776
1777 return c;
1778 }
1779
1780
1781 static void
contacts_context_unref(gpointer p)1782 contacts_context_unref (gpointer p)
1783 {
1784 ContactsContext *c = p;
1785
1786 if ((--c->refcount) > 0)
1787 return;
1788
1789 DEBUG ("last-unref (%p)", c);
1790
1791 g_assert (c->connection != NULL);
1792 tp_clear_object (&c->connection);
1793
1794 g_queue_clear (&c->todo);
1795
1796 g_assert (c->contacts != NULL);
1797 g_ptr_array_foreach (c->contacts, (GFunc) g_object_unref, NULL);
1798 g_ptr_array_unref (c->contacts);
1799 c->contacts = NULL;
1800
1801 g_assert (c->handles != NULL);
1802 g_array_unref (c->handles);
1803 c->handles = NULL;
1804
1805 g_assert (c->invalid != NULL);
1806 g_array_unref (c->invalid);
1807 c->invalid = NULL;
1808
1809 if (c->request_ids != NULL)
1810 g_strfreev ((gchar **) g_ptr_array_free (c->request_ids, FALSE));
1811
1812 c->request_ids = NULL;
1813
1814 tp_clear_pointer (&c->request_errors, g_hash_table_unref);
1815
1816 if (c->destroy != NULL)
1817 c->destroy (c->user_data);
1818
1819 c->destroy = NULL;
1820 c->user_data = NULL;
1821
1822 if (c->weak_object != NULL)
1823 g_object_weak_unref (c->weak_object, contacts_context_weak_notify, c);
1824 c->weak_object = NULL;
1825
1826 g_slice_free (ContactsContext, c);
1827 }
1828
1829
1830 static void
contacts_context_fail(ContactsContext * c,const GError * error)1831 contacts_context_fail (ContactsContext *c,
1832 const GError *error)
1833 {
1834 guint i;
1835
1836 switch (c->signature)
1837 {
1838 case CB_BY_HANDLE:
1839 g_array_append_vals (c->invalid, c->handles->data, c->handles->len);
1840
1841 c->callback.by_handle (c->connection, 0, NULL,
1842 c->invalid->len, (const TpHandle *) c->invalid->data,
1843 error, c->user_data, c->weak_object);
1844 return;
1845 case CB_BY_ID:
1846 /* -1 because NULL terminator is explicit */
1847 for (i = 0; i < c->request_ids->len - 1; i++)
1848 {
1849 const gchar *id = g_ptr_array_index (c->request_ids, i);
1850
1851 if (!g_hash_table_lookup (c->request_errors, id))
1852 {
1853 g_hash_table_insert (c->request_errors,
1854 g_strdup (id), g_error_copy (error));
1855 }
1856 }
1857
1858 c->callback.by_id (c->connection, 0, NULL, NULL,
1859 c->request_errors, error, c->user_data, c->weak_object);
1860 return;
1861 case CB_UPGRADE:
1862 c->callback.upgrade (c->connection,
1863 c->contacts->len, (TpContact * const *) c->contacts->pdata,
1864 error, c->user_data, c->weak_object);
1865 return;
1866 default:
1867 g_assert_not_reached ();
1868 }
1869 }
1870
1871
1872 /**
1873 * TpConnectionContactsByHandleCb:
1874 * @connection: The connection
1875 * @n_contacts: The number of TpContact objects successfully created
1876 * (one per valid handle), or 0 on unrecoverable errors
1877 * @contacts: (array length=n_contacts): An array of @n_contacts TpContact
1878 * objects (this callback is not given a reference to any of these objects,
1879 * and must call g_object_ref() on any that it will keep), or %NULL on
1880 * unrecoverable errors
1881 * @n_failed: The number of invalid handles that were passed to
1882 * tp_connection_get_contacts_by_handle() (or on unrecoverable errors,
1883 * the total number of handles that were given)
1884 * @failed: (array length=n_failed): An array of @n_failed handles that were
1885 * passed to tp_connection_get_contacts_by_handle() but turned out to be
1886 * invalid (or on unrecoverable errors, all the handles that were given)
1887 * @error: %NULL on success, or an unrecoverable error that caused everything
1888 * to fail
1889 * @user_data: the @user_data that was passed to
1890 * tp_connection_get_contacts_by_handle()
1891 * @weak_object: the @weak_object that was passed to
1892 * tp_connection_get_contacts_by_handle()
1893 *
1894 * Signature of a callback used to receive the result of
1895 * tp_connection_get_contacts_by_handle().
1896 *
1897 * If an unrecoverable error occurs (for instance, if @connection
1898 * becomes disconnected) the whole operation fails, and no contacts or
1899 * invalid handles are returned.
1900 *
1901 * If some or even all of the @handles passed to
1902 * tp_connection_get_contacts_by_handle() were not valid, this is not
1903 * considered to be a failure. @error will be %NULL in this situation,
1904 * @contacts will contain contact objects for those handles that were
1905 * valid (possibly none of them), and @invalid will contain the handles
1906 * that were not valid.
1907 *
1908 * Since: 0.7.18
1909 */
1910
1911 /**
1912 * TpConnectionContactsByIdCb:
1913 * @connection: The connection
1914 * @n_contacts: The number of TpContact objects successfully created
1915 * (one per valid ID), or 0 on unrecoverable errors
1916 * @contacts: (array length=n_contacts): An array of @n_contacts TpContact
1917 * objects (this callback is
1918 * not given a reference to any of these objects, and must call
1919 * g_object_ref() on any that it will keep), or %NULL on unrecoverable errors
1920 * @requested_ids: (array length=n_contacts): An array of @n_contacts valid IDs
1921 * (JIDs, SIP URIs etc.)
1922 * that were passed to tp_connection_get_contacts_by_id(), in an order
1923 * corresponding to @contacts, or %NULL on unrecoverable errors
1924 * @failed_id_errors: (element-type utf8 GLib.Error): A hash table in which
1925 * the keys are IDs and the values are errors (#GError)
1926 * @error: %NULL on success, or an unrecoverable error that caused everything
1927 * to fail
1928 * @user_data: the @user_data that was passed to
1929 * tp_connection_get_contacts_by_id()
1930 * @weak_object: the @weak_object that was passed to
1931 * tp_connection_get_contacts_by_id()
1932 *
1933 * Signature of a callback used to receive the result of
1934 * tp_connection_get_contacts_by_id().
1935 *
1936 * @requested_ids contains the IDs that were converted to handles successfully.
1937 * The normalized form of requested_ids[i] is
1938 * tp_contact_get_identifier (contacts[i]).
1939 *
1940 * If some or even all of the @ids passed to
1941 * tp_connection_get_contacts_by_id() were not valid, this is not
1942 * considered to be a fatal error. @error will be %NULL in this situation,
1943 * @contacts will contain contact objects for those IDs that were
1944 * valid (it may be empty), and @failed_id_errors will map the IDs
1945 * that were not valid to a corresponding #GError (if the connection manager
1946 * complies with the Telepathy spec, it will have domain %TP_ERROR and code
1947 * %TP_ERROR_INVALID_HANDLE).
1948 *
1949 * If an unrecoverable error occurs (for instance, if @connection
1950 * becomes disconnected) the whole operation fails, and no contacts
1951 * or requested IDs are returned. @failed_id_errors will contain all the IDs
1952 * that were requested, mapped to a corresponding #GError (either one
1953 * indicating that the ID was invalid, if that was determined before the
1954 * fatal error occurred, or a copy of @error).
1955 *
1956 * Since: 0.7.18
1957 */
1958
1959 /**
1960 * TpConnectionUpgradeContactsCb:
1961 * @connection: The connection
1962 * @n_contacts: The number of TpContact objects for which an upgrade was
1963 * requested
1964 * @contacts: (array length=n_contacts): An array of @n_contacts TpContact
1965 * objects (this callback is
1966 * not given an extra reference to any of these objects, and must call
1967 * g_object_ref() on any that it will keep)
1968 * @error: An unrecoverable error, or %NULL if the connection remains valid
1969 * @user_data: the @user_data that was passed to
1970 * tp_connection_upgrade_contacts()
1971 * @weak_object: the @weak_object that was passed to
1972 * tp_connection_upgrade_contacts()
1973 *
1974 * Signature of a callback used to receive the result of
1975 * tp_connection_upgrade_contacts().
1976 *
1977 * If an unrecoverable error occurs (for instance, if @connection becomes
1978 * disconnected) it is indicated by @error, but the contacts in @contacts
1979 * are still provided.
1980 *
1981 * Since: 0.7.18
1982 */
1983
1984
1985 static void
contacts_context_continue(ContactsContext * c)1986 contacts_context_continue (ContactsContext *c)
1987 {
1988 if (c->no_purpose_in_life)
1989 {
1990 DEBUG ("%p: no purpose in life", c);
1991 return;
1992 }
1993
1994 if (g_queue_is_empty (&c->todo))
1995 {
1996 /* do some final sanity checking then hand over the contacts to the
1997 * library user */
1998 guint i;
1999
2000 DEBUG ("%p: nothing more to do", c);
2001
2002 g_assert (c->contacts != NULL);
2003 g_assert (c->invalid != NULL);
2004
2005 for (i = 0; i < c->contacts->len; i++)
2006 {
2007 TpContact *contact = TP_CONTACT (g_ptr_array_index (c->contacts, i));
2008
2009 g_assert (contact->priv->identifier != NULL);
2010 g_assert (contact->priv->handle != 0);
2011 }
2012
2013 switch (c->signature)
2014 {
2015 case CB_BY_HANDLE:
2016 c->callback.by_handle (c->connection,
2017 c->contacts->len, (TpContact * const *) c->contacts->pdata,
2018 c->invalid->len, (const TpHandle *) c->invalid->data,
2019 NULL, c->user_data, c->weak_object);
2020 break;
2021 case CB_BY_ID:
2022 c->callback.by_id (c->connection,
2023 c->contacts->len, (TpContact * const *) c->contacts->pdata,
2024 (const gchar * const *) c->request_ids->pdata,
2025 c->request_errors, NULL, c->user_data, c->weak_object);
2026 break;
2027 case CB_UPGRADE:
2028 c->callback.upgrade (c->connection,
2029 c->contacts->len, (TpContact * const *) c->contacts->pdata,
2030 NULL, c->user_data, c->weak_object);
2031 break;
2032 default:
2033 g_assert_not_reached ();
2034 }
2035 }
2036 else
2037 {
2038 /* bah! */
2039 ContactsProc next = g_queue_pop_head (&c->todo);
2040
2041 if (G_UNLIKELY (tp_proxy_get_invalidated (c->connection) != NULL))
2042 {
2043 DEBUG ("%p: failing due to connection having been invalidated: %s",
2044 c, tp_proxy_get_invalidated (c->connection)->message);
2045 contacts_context_fail (c, tp_proxy_get_invalidated (c->connection));
2046 }
2047 else
2048 {
2049 DEBUG ("%p: on to the next thing", c);
2050 next (c);
2051 }
2052 }
2053 }
2054
2055 static gboolean
contacts_context_idle_continue(gpointer data)2056 contacts_context_idle_continue (gpointer data)
2057 {
2058 contacts_context_continue (data);
2059 return FALSE;
2060 }
2061
2062 static void
contacts_held_one(TpConnection * connection,TpHandleType handle_type,guint n_handles,const TpHandle * handles,const GError * error,gpointer user_data,GObject * weak_object)2063 contacts_held_one (TpConnection *connection,
2064 TpHandleType handle_type,
2065 guint n_handles,
2066 const TpHandle *handles,
2067 const GError *error,
2068 gpointer user_data,
2069 GObject *weak_object)
2070 {
2071 ContactsContext *c = user_data;
2072
2073 g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
2074 g_assert (c->next_index < c->handles->len);
2075
2076 if (error == NULL)
2077 {
2078 /* I have a handle of my very own. Just what I always wanted! */
2079 TpContact *contact;
2080
2081 g_assert (n_handles == 1);
2082 g_assert (handles[0] != 0);
2083 g_debug ("%u vs %u", g_array_index (c->handles, TpHandle, c->next_index),
2084 handles[0]);
2085 g_assert (g_array_index (c->handles, TpHandle, c->next_index)
2086 == handles[0]);
2087
2088 contact = tp_contact_ensure (connection, handles[0]);
2089 g_ptr_array_add (c->contacts, contact);
2090 c->next_index++;
2091 }
2092 else if (error->domain == TP_ERROR &&
2093 error->code == TP_ERROR_INVALID_HANDLE)
2094 {
2095 g_array_append_val (c->invalid,
2096 g_array_index (c->handles, TpHandle, c->next_index));
2097 /* ignore the bad handle - we just won't return a TpContact for it */
2098 g_array_remove_index_fast (c->handles, c->next_index);
2099 /* do not increment next_index - another handle has been moved into that
2100 * position */
2101 }
2102 else
2103 {
2104 /* the connection fell down a well or something */
2105 contacts_context_fail (c, error);
2106 return;
2107 }
2108
2109 /* Either continue to hold handles, or proceed along the slow path. */
2110 contacts_context_continue (c);
2111 }
2112
2113
2114 static void
contacts_hold_one(ContactsContext * c)2115 contacts_hold_one (ContactsContext *c)
2116 {
2117 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
2118 c->refcount++;
2119 tp_connection_hold_handles (c->connection, -1,
2120 TP_HANDLE_TYPE_CONTACT, 1,
2121 &g_array_index (c->handles, TpHandle, c->next_index),
2122 contacts_held_one, c, contacts_context_unref, c->weak_object);
2123 G_GNUC_END_IGNORE_DEPRECATIONS
2124 }
2125
2126
2127 static void
contacts_held_handles(TpConnection * connection,TpHandleType handle_type,guint n_handles,const TpHandle * handles,const GError * error,gpointer user_data,GObject * weak_object)2128 contacts_held_handles (TpConnection *connection,
2129 TpHandleType handle_type,
2130 guint n_handles,
2131 const TpHandle *handles,
2132 const GError *error,
2133 gpointer user_data,
2134 GObject *weak_object)
2135 {
2136 ContactsContext *c = user_data;
2137
2138 g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
2139 g_assert (weak_object == c->weak_object);
2140
2141 if (error == NULL)
2142 {
2143 /* I now own all n handles. It's like Christmas morning! */
2144 guint i;
2145
2146 g_assert (n_handles == c->handles->len);
2147 g_assert (c->contacts->len == 0);
2148
2149 for (i = 0; i < c->handles->len; i++)
2150 {
2151 g_ptr_array_add (c->contacts,
2152 tp_contact_ensure (connection,
2153 g_array_index (c->handles, TpHandle, i)));
2154 }
2155 }
2156 else if (error->domain == TP_ERROR &&
2157 error->code == TP_ERROR_INVALID_HANDLE)
2158 {
2159 /* One of the handles is bad. We don't know which one :-( so split
2160 * the batch into a chain of calls. */
2161 guint i;
2162
2163 for (i = 0; i < c->handles->len; i++)
2164 {
2165 g_queue_push_head (&c->todo, contacts_hold_one);
2166 }
2167
2168 g_assert (c->next_index == 0);
2169 }
2170 else
2171 {
2172 /* the connection fell down a well or something */
2173 contacts_context_fail (c, error);
2174 return;
2175 }
2176
2177 /* Either hold the handles individually, or proceed along the slow path. */
2178 contacts_context_continue (c);
2179 }
2180
2181
2182 static void
contacts_inspected(TpConnection * connection,const gchar ** ids,const GError * error,gpointer user_data,GObject * weak_object)2183 contacts_inspected (TpConnection *connection,
2184 const gchar **ids,
2185 const GError *error,
2186 gpointer user_data,
2187 GObject *weak_object)
2188 {
2189 ContactsContext *c = user_data;
2190
2191 g_assert (weak_object == c->weak_object);
2192 g_assert (c->handles->len == c->contacts->len);
2193
2194 if (error != NULL)
2195 {
2196 /* the connection fell down a well or something */
2197 contacts_context_fail (c, error);
2198 return;
2199 }
2200 else if (G_UNLIKELY (g_strv_length ((GStrv) ids) != c->handles->len))
2201 {
2202 GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
2203 "Connection manager %s is broken: we inspected %u "
2204 "handles but InspectHandles returned %u strings",
2205 tp_proxy_get_bus_name (connection), c->handles->len,
2206 g_strv_length ((GStrv) ids));
2207
2208 WARNING ("%s", e->message);
2209 contacts_context_fail (c, e);
2210 g_error_free (e);
2211 return;
2212 }
2213 else
2214 {
2215 guint i;
2216
2217 DEBUG ("%p: inspected %u handles", c, c->contacts->len);
2218
2219 for (i = 0; i < c->contacts->len; i++)
2220 {
2221 TpContact *contact = g_ptr_array_index (c->contacts, i);
2222
2223 g_assert (ids[i] != NULL);
2224
2225 DEBUG ("- #%u: \"%s\"", contact->priv->handle, ids[i]);
2226
2227 if (contact->priv->identifier == NULL)
2228 {
2229 contact->priv->identifier = g_strdup (ids[i]);
2230 }
2231 else if (tp_strdiff (contact->priv->identifier, ids[i]))
2232 {
2233 GError *e = g_error_new (TP_DBUS_ERRORS,
2234 TP_DBUS_ERROR_INCONSISTENT,
2235 "Connection manager %s is broken: contact handle %u "
2236 "identifier changed from %s to %s",
2237 tp_proxy_get_bus_name (connection), contact->priv->handle,
2238 contact->priv->identifier, ids[i]);
2239
2240 WARNING ("%s", e->message);
2241 contacts_context_fail (c, e);
2242 g_error_free (e);
2243 return;
2244 }
2245 }
2246 }
2247
2248 contacts_context_continue (c);
2249 }
2250
2251
2252 static void
contacts_inspect(ContactsContext * c)2253 contacts_inspect (ContactsContext *c)
2254 {
2255 guint i;
2256
2257 g_assert (c->handles->len == c->contacts->len);
2258
2259 for (i = 0; i < c->contacts->len; i++)
2260 {
2261 TpContact *contact = g_ptr_array_index (c->contacts, i);
2262
2263 if (contact->priv->identifier == NULL)
2264 {
2265 c->refcount++;
2266 tp_cli_connection_call_inspect_handles (c->connection, -1,
2267 TP_HANDLE_TYPE_CONTACT, c->handles, contacts_inspected,
2268 c, contacts_context_unref, c->weak_object);
2269 return;
2270 }
2271 }
2272
2273 /* else there's no need to inspect the contacts' handles, because we already
2274 * know all their identifiers */
2275 contacts_context_continue (c);
2276 }
2277
2278
2279 static void
contacts_requested_aliases(TpConnection * connection,const gchar ** aliases,const GError * error,gpointer user_data,GObject * weak_object)2280 contacts_requested_aliases (TpConnection *connection,
2281 const gchar **aliases,
2282 const GError *error,
2283 gpointer user_data,
2284 GObject *weak_object)
2285 {
2286 ContactsContext *c = user_data;
2287
2288 g_assert (c->handles->len == c->contacts->len);
2289
2290 if (error == NULL)
2291 {
2292 guint i;
2293
2294 if (G_UNLIKELY (g_strv_length ((GStrv) aliases) != c->contacts->len))
2295 {
2296 WARNING ("Connection manager %s is broken: we requested %u "
2297 "handles' aliases but got %u strings back",
2298 tp_proxy_get_bus_name (connection), c->contacts->len,
2299 g_strv_length ((GStrv) aliases));
2300
2301 /* give up on the possibility of getting aliases, and just
2302 * move on */
2303 contacts_context_continue (c);
2304 return;
2305 }
2306
2307 for (i = 0; i < c->contacts->len; i++)
2308 {
2309 TpContact *contact = g_ptr_array_index (c->contacts, i);
2310 const gchar *alias = aliases[i];
2311
2312 contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
2313 g_free (contact->priv->alias);
2314 contact->priv->alias = g_strdup (alias);
2315 g_object_notify ((GObject *) contact, "alias");
2316 }
2317 }
2318 else
2319 {
2320 /* never mind, we can live without aliases */
2321 DEBUG ("GetAliases failed with %s %u: %s",
2322 g_quark_to_string (error->domain), error->code, error->message);
2323 }
2324
2325 contacts_context_continue (c);
2326 }
2327
2328
2329 static void
contacts_got_aliases(TpConnection * connection,GHashTable * handle_to_alias,const GError * error,gpointer user_data,GObject * weak_object)2330 contacts_got_aliases (TpConnection *connection,
2331 GHashTable *handle_to_alias,
2332 const GError *error,
2333 gpointer user_data,
2334 GObject *weak_object)
2335 {
2336 ContactsContext *c = user_data;
2337
2338 if (error == NULL)
2339 {
2340 guint i;
2341
2342 for (i = 0; i < c->contacts->len; i++)
2343 {
2344 TpContact *contact = g_ptr_array_index (c->contacts, i);
2345 const gchar *alias = g_hash_table_lookup (handle_to_alias,
2346 GUINT_TO_POINTER (contact->priv->handle));
2347
2348 contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
2349 g_free (contact->priv->alias);
2350 contact->priv->alias = NULL;
2351
2352 if (alias != NULL)
2353 {
2354 contact->priv->alias = g_strdup (alias);
2355 }
2356 else
2357 {
2358 WARNING ("No alias returned for %u, will use ID instead",
2359 contact->priv->handle);
2360 }
2361
2362 g_object_notify ((GObject *) contact, "alias");
2363 }
2364 }
2365 else if ((error->domain == TP_ERROR &&
2366 error->code == TP_ERROR_NOT_IMPLEMENTED) ||
2367 (error->domain == DBUS_GERROR &&
2368 error->code == DBUS_GERROR_UNKNOWN_METHOD))
2369 {
2370 /* GetAliases not implemented, fall back to (slow?) RequestAliases */
2371 c->refcount++;
2372 tp_cli_connection_interface_aliasing_call_request_aliases (connection,
2373 -1, c->handles, contacts_requested_aliases,
2374 c, contacts_context_unref, weak_object);
2375 return;
2376 }
2377 else
2378 {
2379 /* never mind, we can live without aliases */
2380 DEBUG ("GetAliases failed with %s %u: %s",
2381 g_quark_to_string (error->domain), error->code, error->message);
2382 }
2383
2384 contacts_context_continue (c);
2385 }
2386
2387
2388 static void
contacts_aliases_changed(TpConnection * connection,const GPtrArray * alias_structs,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)2389 contacts_aliases_changed (TpConnection *connection,
2390 const GPtrArray *alias_structs,
2391 gpointer user_data G_GNUC_UNUSED,
2392 GObject *weak_object G_GNUC_UNUSED)
2393 {
2394 guint i;
2395
2396 for (i = 0; i < alias_structs->len; i++)
2397 {
2398 GValueArray *pair = g_ptr_array_index (alias_structs, i);
2399 TpHandle handle = g_value_get_uint (pair->values + 0);
2400 const gchar *alias = g_value_get_string (pair->values + 1);
2401 TpContact *contact = _tp_connection_lookup_contact (connection, handle);
2402
2403 if (contact != NULL)
2404 {
2405 contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
2406 DEBUG ("Contact \"%s\" alias changed from \"%s\" to \"%s\"",
2407 contact->priv->identifier, contact->priv->alias, alias);
2408 g_free (contact->priv->alias);
2409 contact->priv->alias = g_strdup (alias);
2410 g_object_notify ((GObject *) contact, "alias");
2411 }
2412 }
2413 }
2414
2415
2416 static void
contacts_bind_to_aliases_changed(TpConnection * connection)2417 contacts_bind_to_aliases_changed (TpConnection *connection)
2418 {
2419 if (!connection->priv->tracking_aliases_changed)
2420 {
2421 connection->priv->tracking_aliases_changed = TRUE;
2422
2423 tp_cli_connection_interface_aliasing_connect_to_aliases_changed (
2424 connection, contacts_aliases_changed, NULL, NULL, NULL, NULL);
2425 }
2426 }
2427
2428
2429 static void
contacts_get_aliases(ContactsContext * c)2430 contacts_get_aliases (ContactsContext *c)
2431 {
2432 guint i;
2433
2434 g_assert (c->handles->len == c->contacts->len);
2435
2436 contacts_bind_to_aliases_changed (c->connection);
2437
2438 for (i = 0; i < c->contacts->len; i++)
2439 {
2440 TpContact *contact = g_ptr_array_index (c->contacts, i);
2441
2442 if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_ALIAS) == 0)
2443 {
2444 c->refcount++;
2445 tp_cli_connection_interface_aliasing_call_get_aliases (c->connection,
2446 -1, c->handles, contacts_got_aliases, c, contacts_context_unref,
2447 c->weak_object);
2448 return;
2449 }
2450 }
2451
2452 /* else there's no need to get the contacts' aliases, because we already
2453 * know them all */
2454 contacts_context_continue (c);
2455 }
2456
2457
2458 static void
contact_maybe_set_simple_presence(TpContact * contact,GValueArray * presence)2459 contact_maybe_set_simple_presence (TpContact *contact,
2460 GValueArray *presence)
2461 {
2462 guint type;
2463 const gchar *status;
2464 const gchar *message;
2465
2466 if (contact == NULL)
2467 return;
2468
2469 g_return_if_fail (presence != NULL);
2470 contact->priv->has_features |= CONTACT_FEATURE_FLAG_PRESENCE;
2471
2472 tp_value_array_unpack (presence, 3, &type, &status, &message);
2473
2474 contact->priv->presence_type = type;
2475
2476 g_free (contact->priv->presence_status);
2477 contact->priv->presence_status = g_strdup (status);
2478
2479 g_free (contact->priv->presence_message);
2480 contact->priv->presence_message = g_strdup (message);
2481
2482 g_object_notify ((GObject *) contact, "presence-type");
2483 g_object_notify ((GObject *) contact, "presence-status");
2484 g_object_notify ((GObject *) contact, "presence-message");
2485
2486 g_signal_emit (contact, signals[SIGNAL_PRESENCE_CHANGED], 0,
2487 contact->priv->presence_type,
2488 contact->priv->presence_status,
2489 contact->priv->presence_message);
2490 }
2491
2492 static void
contact_maybe_set_location(TpContact * self,GHashTable * location)2493 contact_maybe_set_location (TpContact *self,
2494 GHashTable *location)
2495 {
2496 if (self == NULL)
2497 return;
2498
2499 if (self->priv->location != NULL)
2500 g_hash_table_unref (self->priv->location);
2501
2502 /* We guarantee that, if we've fetched a location for a contact, the
2503 * :location property is non-NULL. This is mainly because Empathy assumed
2504 * this and would crash if not.
2505 */
2506 if (location == NULL)
2507 location = tp_asv_new (NULL, NULL);
2508 else
2509 g_hash_table_ref (location);
2510
2511 self->priv->has_features |= CONTACT_FEATURE_FLAG_LOCATION;
2512 self->priv->location = location;
2513 g_object_notify ((GObject *) self, "location");
2514 g_object_notify ((GObject *) self, "location-vardict");
2515 }
2516
2517 static void
contact_set_capabilities(TpContact * self,TpCapabilities * capabilities)2518 contact_set_capabilities (TpContact *self,
2519 TpCapabilities *capabilities)
2520 {
2521 tp_clear_object (&self->priv->capabilities);
2522
2523 self->priv->has_features |= CONTACT_FEATURE_FLAG_CAPABILITIES;
2524 self->priv->capabilities = g_object_ref (capabilities);
2525 g_object_notify ((GObject *) self, "capabilities");
2526 }
2527
2528 static void
contact_maybe_set_capabilities(TpContact * self,GPtrArray * arr)2529 contact_maybe_set_capabilities (TpContact *self,
2530 GPtrArray *arr)
2531 {
2532 TpCapabilities *capabilities;
2533
2534 if (self == NULL || arr == NULL)
2535 return;
2536
2537 capabilities = _tp_capabilities_new (arr, TRUE);
2538 contact_set_capabilities (self, capabilities);
2539 g_object_unref (capabilities);
2540 }
2541
2542
2543 static void
contacts_presences_changed(TpConnection * connection,GHashTable * presences,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)2544 contacts_presences_changed (TpConnection *connection,
2545 GHashTable *presences,
2546 gpointer user_data G_GNUC_UNUSED,
2547 GObject *weak_object G_GNUC_UNUSED)
2548 {
2549 GHashTableIter iter;
2550 gpointer key, value;
2551
2552 g_hash_table_iter_init (&iter, presences);
2553
2554 while (g_hash_table_iter_next (&iter, &key, &value))
2555 {
2556 TpContact *contact = _tp_connection_lookup_contact (connection,
2557 GPOINTER_TO_UINT (key));
2558
2559 contact_maybe_set_simple_presence (contact, value);
2560 }
2561 }
2562
2563
2564 static void
contacts_got_simple_presence(TpConnection * connection,GHashTable * presences,const GError * error,gpointer user_data,GObject * weak_object)2565 contacts_got_simple_presence (TpConnection *connection,
2566 GHashTable *presences,
2567 const GError *error,
2568 gpointer user_data,
2569 GObject *weak_object)
2570 {
2571 ContactsContext *c = user_data;
2572
2573 if (error == NULL)
2574 {
2575 contacts_presences_changed (connection, presences, NULL, NULL);
2576 }
2577 else
2578 {
2579 /* never mind, we can live without presences */
2580 DEBUG ("GetPresences failed with %s %u: %s",
2581 g_quark_to_string (error->domain), error->code, error->message);
2582 }
2583
2584 contacts_context_continue (c);
2585 }
2586
2587
2588 static void
contacts_bind_to_presences_changed(TpConnection * connection)2589 contacts_bind_to_presences_changed (TpConnection *connection)
2590 {
2591 if (!connection->priv->tracking_presences_changed)
2592 {
2593 connection->priv->tracking_presences_changed = TRUE;
2594
2595 tp_cli_connection_interface_simple_presence_connect_to_presences_changed
2596 (connection, contacts_presences_changed, NULL, NULL, NULL, NULL);
2597 }
2598 }
2599
2600 static void
contacts_get_simple_presence(ContactsContext * c)2601 contacts_get_simple_presence (ContactsContext *c)
2602 {
2603 guint i;
2604
2605 g_assert (c->handles->len == c->contacts->len);
2606
2607 contacts_bind_to_presences_changed (c->connection);
2608
2609 for (i = 0; i < c->contacts->len; i++)
2610 {
2611 TpContact *contact = g_ptr_array_index (c->contacts, i);
2612
2613 if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_PRESENCE) == 0)
2614 {
2615 c->refcount++;
2616 tp_cli_connection_interface_simple_presence_call_get_presences (
2617 c->connection, -1,
2618 c->handles, contacts_got_simple_presence,
2619 c, contacts_context_unref, c->weak_object);
2620 return;
2621 }
2622 }
2623
2624 contacts_context_continue (c);
2625 }
2626
2627 static void
contacts_location_updated(TpConnection * connection,guint handle,GHashTable * location,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)2628 contacts_location_updated (TpConnection *connection,
2629 guint handle,
2630 GHashTable *location,
2631 gpointer user_data G_GNUC_UNUSED,
2632 GObject *weak_object G_GNUC_UNUSED)
2633 {
2634 TpContact *contact = _tp_connection_lookup_contact (connection,
2635 GPOINTER_TO_UINT (handle));
2636
2637 contact_maybe_set_location (contact, location);
2638 }
2639
2640 static void
contacts_bind_to_location_updated(TpConnection * connection)2641 contacts_bind_to_location_updated (TpConnection *connection)
2642 {
2643 if (!connection->priv->tracking_location_changed)
2644 {
2645 connection->priv->tracking_location_changed = TRUE;
2646
2647 tp_cli_connection_interface_location_connect_to_location_updated
2648 (connection, contacts_location_updated, NULL, NULL, NULL, NULL);
2649
2650 tp_connection_add_client_interest (connection,
2651 TP_IFACE_CONNECTION_INTERFACE_LOCATION);
2652 }
2653 }
2654
2655 static void
contact_maybe_set_client_types(TpContact * self,const gchar * const * types)2656 contact_maybe_set_client_types (TpContact *self,
2657 const gchar * const *types)
2658 {
2659 if (self == NULL)
2660 return;
2661
2662 if (self->priv->client_types != NULL)
2663 g_strfreev (self->priv->client_types);
2664
2665 self->priv->has_features |= CONTACT_FEATURE_FLAG_CLIENT_TYPES;
2666 self->priv->client_types = g_strdupv ((gchar **) types);
2667 g_object_notify ((GObject *) self, "client-types");
2668 }
2669
2670 static void
contacts_client_types_updated(TpConnection * connection,guint handle,const gchar ** types,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)2671 contacts_client_types_updated (TpConnection *connection,
2672 guint handle,
2673 const gchar **types,
2674 gpointer user_data G_GNUC_UNUSED,
2675 GObject *weak_object G_GNUC_UNUSED)
2676 {
2677 TpContact *contact = _tp_connection_lookup_contact (connection,
2678 GPOINTER_TO_UINT (handle));
2679
2680 contact_maybe_set_client_types (contact, types);
2681 }
2682
2683 static void
contacts_bind_to_client_types_updated(TpConnection * connection)2684 contacts_bind_to_client_types_updated (TpConnection *connection)
2685 {
2686 if (!connection->priv->tracking_client_types_updated)
2687 {
2688 connection->priv->tracking_client_types_updated = TRUE;
2689
2690 tp_cli_connection_interface_client_types_connect_to_client_types_updated
2691 (connection, contacts_client_types_updated, NULL, NULL, NULL, NULL);
2692 }
2693 }
2694
2695 static void
set_conn_capabilities_on_contacts(GPtrArray * contacts,TpConnection * connection)2696 set_conn_capabilities_on_contacts (GPtrArray *contacts,
2697 TpConnection *connection)
2698 {
2699 guint i;
2700 TpCapabilities *conn_caps = tp_connection_get_capabilities (connection);
2701 GPtrArray *rcc;
2702
2703 /* If the connection has no capabilities then don't bother setting them on
2704 * the contact and pretend we just don't know.. In practise this will only
2705 * happen if there was an error in getting the connections capabilities so
2706 * claiming ignorance seems the most sensible thing to do */
2707 if (conn_caps == NULL)
2708 return;
2709
2710 rcc = tp_capabilities_get_channel_classes (conn_caps);
2711 if (rcc == NULL || rcc->len == 0)
2712 return;
2713
2714 for (i = 0; i < contacts->len; i++)
2715 {
2716 TpContact *contact = g_ptr_array_index (contacts, i);
2717
2718 contact_set_capabilities (contact, conn_caps);
2719 }
2720 }
2721
2722 static void
connection_capabilities_fetched_cb(GObject * object,GAsyncResult * res,gpointer user_data)2723 connection_capabilities_fetched_cb (GObject *object,
2724 GAsyncResult *res,
2725 gpointer user_data)
2726 {
2727 ContactsContext *c = user_data;
2728
2729 DEBUG ("Connection capabilities prepared");
2730
2731 set_conn_capabilities_on_contacts (c->contacts, c->connection);
2732 contacts_context_continue (c);
2733 contacts_context_unref (c);
2734 }
2735
2736 static void
contacts_get_conn_capabilities(ContactsContext * c)2737 contacts_get_conn_capabilities (ContactsContext *c)
2738 {
2739 g_assert (c->handles->len == c->contacts->len);
2740
2741 DEBUG ("Getting connection capabilities");
2742
2743 c->refcount++;
2744 _tp_connection_get_capabilities_async (c->connection,
2745 connection_capabilities_fetched_cb, c);
2746 }
2747
2748 static void
contacts_capabilities_updated(TpConnection * connection,GHashTable * capabilities,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)2749 contacts_capabilities_updated (TpConnection *connection,
2750 GHashTable *capabilities,
2751 gpointer user_data G_GNUC_UNUSED,
2752 GObject *weak_object G_GNUC_UNUSED)
2753 {
2754 GHashTableIter iter;
2755 gpointer handle, value;
2756
2757 g_hash_table_iter_init (&iter, capabilities);
2758 while (g_hash_table_iter_next (&iter, &handle, &value))
2759 {
2760 TpContact *contact = _tp_connection_lookup_contact (connection,
2761 GPOINTER_TO_UINT (handle));
2762
2763 contact_maybe_set_capabilities (contact, value);
2764 }
2765 }
2766
2767 static void
contacts_bind_to_capabilities_updated(TpConnection * connection)2768 contacts_bind_to_capabilities_updated (TpConnection *connection)
2769 {
2770 if (!connection->priv->tracking_contact_caps_changed)
2771 {
2772 connection->priv->tracking_contact_caps_changed = TRUE;
2773
2774 tp_cli_connection_interface_contact_capabilities_connect_to_contact_capabilities_changed
2775 (connection, contacts_capabilities_updated, NULL, NULL, NULL, NULL);
2776 }
2777 }
2778
2779 static gboolean
build_avatar_filename(TpConnection * connection,const gchar * avatar_token,gboolean create_dir,gchar ** ret_filename,gchar ** ret_mime_filename)2780 build_avatar_filename (TpConnection *connection,
2781 const gchar *avatar_token,
2782 gboolean create_dir,
2783 gchar **ret_filename,
2784 gchar **ret_mime_filename)
2785 {
2786 gchar *dir;
2787 gchar *token_escaped;
2788 gboolean success = TRUE;
2789
2790 token_escaped = tp_escape_as_identifier (avatar_token);
2791 dir = g_build_filename (g_get_user_cache_dir (),
2792 "telepathy", "avatars",
2793 tp_connection_get_cm_name (connection),
2794 tp_connection_get_protocol_name (connection),
2795 NULL);
2796
2797 if (create_dir)
2798 {
2799 if (g_mkdir_with_parents (dir, 0700) == -1)
2800 {
2801 DEBUG ("Error creating avatar cache dir: %s", g_strerror (errno));
2802 success = FALSE;
2803 goto out;
2804 }
2805 }
2806
2807 if (ret_filename != NULL)
2808 *ret_filename = g_strconcat (dir, G_DIR_SEPARATOR_S, token_escaped, NULL);
2809
2810 if (ret_mime_filename != NULL)
2811 *ret_mime_filename = g_strconcat (dir, G_DIR_SEPARATOR_S, token_escaped,
2812 ".mime", NULL);
2813
2814 out:
2815
2816 g_free (dir);
2817 g_free (token_escaped);
2818
2819 return success;
2820 }
2821
2822 static void contact_set_avatar_token (TpContact *self, const gchar *new_token,
2823 gboolean request);
2824
2825 typedef struct {
2826 GWeakRef contact;
2827 TpConnection *connection;
2828 gchar *token;
2829 GFile *file;
2830 GBytes *data;
2831 GFile *mime_file;
2832 gchar *mime_type;
2833 } WriteAvatarData;
2834
2835 static void
write_avatar_data_free(WriteAvatarData * avatar_data)2836 write_avatar_data_free (WriteAvatarData *avatar_data)
2837 {
2838 g_weak_ref_clear (&avatar_data->contact);
2839 g_clear_object (&avatar_data->connection);
2840 tp_clear_pointer (&avatar_data->token, g_free);
2841 g_clear_object (&avatar_data->file);
2842 tp_clear_pointer (&avatar_data->data, g_bytes_unref);
2843 g_clear_object (&avatar_data->mime_file);
2844 tp_clear_pointer (&avatar_data->mime_type, g_free);
2845
2846 g_slice_free (WriteAvatarData, avatar_data);
2847 }
2848
2849 static void
mime_file_written(GObject * source_object,GAsyncResult * res,gpointer user_data)2850 mime_file_written (GObject *source_object,
2851 GAsyncResult *res,
2852 gpointer user_data)
2853 {
2854 GError *error = NULL;
2855 WriteAvatarData *avatar_data = user_data;
2856 GFile *file = G_FILE (source_object);
2857 TpContact *self;
2858 gchar *path = g_file_get_path (file);
2859
2860 g_assert (file == avatar_data->mime_file);
2861
2862 if (!g_file_replace_contents_finish (file, res, NULL, &error))
2863 {
2864 DEBUG ("Failed to store MIME type in cache (%s): %s", path,
2865 error->message);
2866 g_clear_error (&error);
2867 }
2868 else
2869 {
2870 DEBUG ("Contact avatar MIME type stored in cache: %s", path);
2871 }
2872
2873 g_free (path);
2874
2875 self = g_weak_ref_get (&avatar_data->contact);
2876
2877 if (self == NULL)
2878 {
2879 DEBUG ("No relevant TpContact");
2880 }
2881 else if (tp_strdiff (avatar_data->token, self->priv->avatar_token))
2882 {
2883 DEBUG ("Contact's avatar token has changed from %s to %s, "
2884 "this avatar is no longer relevant",
2885 avatar_data->token, nonnull (self->priv->avatar_token));
2886 }
2887 else
2888 {
2889 gchar *data_path = g_file_get_path (avatar_data->file);
2890
2891 DEBUG ("Saved avatar '%s' of MIME type '%s' still used by '%s' to '%s'",
2892 avatar_data->token, avatar_data->mime_type,
2893 self->priv->identifier, data_path);
2894 g_clear_object (&self->priv->avatar_file);
2895 self->priv->avatar_file = g_object_ref (avatar_data->file);
2896
2897 g_free (self->priv->avatar_mime_type);
2898 self->priv->avatar_mime_type = g_strdup (avatar_data->mime_type);
2899
2900 /* Notify both property changes together once both files have been
2901 * written */
2902 g_object_notify ((GObject *) self, "avatar-mime-type");
2903 g_object_notify ((GObject *) self, "avatar-file");
2904
2905 g_object_unref (self);
2906 g_free (data_path);
2907 }
2908
2909 write_avatar_data_free (avatar_data);
2910 }
2911
2912 static void
avatar_file_written(GObject * source_object,GAsyncResult * res,gpointer user_data)2913 avatar_file_written (GObject *source_object,
2914 GAsyncResult *res,
2915 gpointer user_data)
2916 {
2917 GError *error = NULL;
2918 WriteAvatarData *avatar_data = user_data;
2919 GFile *file = G_FILE (source_object);
2920 gchar *path = g_file_get_path (file);
2921
2922 g_assert (file == avatar_data->file);
2923
2924 if (!g_file_replace_contents_finish (file, res, NULL, &error))
2925 {
2926 DEBUG ("Failed to store avatar in cache (%s): %s",
2927 path, error->message);
2928 DEBUG ("Storing the MIME type anyway");
2929 g_clear_error (&error);
2930 }
2931 else
2932 {
2933 DEBUG ("Contact avatar stored in cache: %s",
2934 path);
2935 }
2936
2937 g_file_replace_contents_async (avatar_data->mime_file,
2938 avatar_data->mime_type, strlen (avatar_data->mime_type),
2939 NULL, FALSE, G_FILE_CREATE_PRIVATE|G_FILE_CREATE_REPLACE_DESTINATION,
2940 NULL, mime_file_written, avatar_data);
2941
2942 g_free (path);
2943 }
2944
2945 static void
contact_avatar_retrieved(TpConnection * connection,guint handle,const gchar * token,const GArray * avatar,const gchar * mime_type,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)2946 contact_avatar_retrieved (TpConnection *connection,
2947 guint handle,
2948 const gchar *token,
2949 const GArray *avatar,
2950 const gchar *mime_type,
2951 gpointer user_data G_GNUC_UNUSED,
2952 GObject *weak_object G_GNUC_UNUSED)
2953 {
2954 TpContact *self = _tp_connection_lookup_contact (connection, handle);
2955 gchar *filename;
2956 gchar *mime_filename;
2957 WriteAvatarData *avatar_data;
2958
2959 DEBUG ("token '%s', %u bytes, MIME type '%s'",
2960 token, avatar->len, mime_type);
2961
2962 if (self == NULL)
2963 DEBUG ("handle #%u is not associated with any TpContact", handle);
2964 else
2965 DEBUG ("used by contact #%u '%s'", handle,
2966 tp_contact_get_identifier (self));
2967
2968 if (self != NULL)
2969 {
2970 /* Update the avatar token if a newer one is given
2971 * (this emits notify::avatar-token if needed) */
2972 contact_set_avatar_token (self, token, FALSE);
2973 }
2974
2975 if (!build_avatar_filename (connection, token, TRUE, &filename,
2976 &mime_filename))
2977 {
2978 DEBUG ("failed to set up cache");
2979 return;
2980 }
2981
2982 /* Save avatar in cache, even if the contact is unknown, to avoid as much as
2983 * possible future avatar requests */
2984 avatar_data = g_slice_new0 (WriteAvatarData);
2985 avatar_data->connection = g_object_ref (connection);
2986 g_weak_ref_set (&avatar_data->contact, self);
2987 avatar_data->token = g_strdup (token);
2988 avatar_data->file = g_file_new_for_path (filename);
2989 /* g_file_replace_contents_async() doesn't copy its argument, see
2990 * <https://bugzilla.gnome.org/show_bug.cgi?id=690525>, so we have
2991 * to keep a copy around */
2992 avatar_data->data = g_bytes_new (avatar->data, avatar->len);
2993 avatar_data->mime_file = g_file_new_for_path (mime_filename);
2994 avatar_data->mime_type = g_strdup (mime_type);
2995
2996 g_file_replace_contents_async (avatar_data->file,
2997 g_bytes_get_data (avatar_data->data, NULL), avatar->len,
2998 NULL, FALSE, G_FILE_CREATE_PRIVATE|G_FILE_CREATE_REPLACE_DESTINATION,
2999 NULL, avatar_file_written, avatar_data);
3000
3001 g_free (filename);
3002 g_free (mime_filename);
3003 }
3004
3005 static gboolean
connection_avatar_request_idle_cb(gpointer user_data)3006 connection_avatar_request_idle_cb (gpointer user_data)
3007 {
3008 TpConnection *connection = user_data;
3009
3010 DEBUG ("Request %d avatars", connection->priv->avatar_request_queue->len);
3011
3012 tp_cli_connection_interface_avatars_call_request_avatars (connection, -1,
3013 connection->priv->avatar_request_queue, NULL, NULL, NULL, NULL);
3014
3015 g_array_unref (connection->priv->avatar_request_queue);
3016 connection->priv->avatar_request_queue = NULL;
3017 connection->priv->avatar_request_idle_id = 0;
3018
3019 return FALSE;
3020 }
3021
3022 static void
contact_update_avatar_data(TpContact * self)3023 contact_update_avatar_data (TpContact *self)
3024 {
3025 TpConnection *connection;
3026 gchar *filename = NULL;
3027 gchar *mime_filename = NULL;
3028
3029 /* If token is NULL, it means that CM doesn't know the token. In that case we
3030 * have to request the avatar data to get the token. This happens with XMPP
3031 * for offline contacts. We don't want to bypass the avatar cache, so we won't
3032 * update avatar. */
3033 if (self->priv->avatar_token == NULL)
3034 return;
3035
3036 /* If token is empty (""), it means the contact has no avatar. */
3037 if (tp_str_empty (self->priv->avatar_token))
3038 {
3039 tp_clear_object (&self->priv->avatar_file);
3040
3041 g_free (self->priv->avatar_mime_type);
3042 self->priv->avatar_mime_type = NULL;
3043
3044 DEBUG ("contact#%u has no avatar", self->priv->handle);
3045
3046 g_object_notify ((GObject *) self, "avatar-file");
3047 g_object_notify ((GObject *) self, "avatar-mime-type");
3048
3049 return;
3050 }
3051
3052 /* We have a token, search in cache... */
3053 if (build_avatar_filename (self->priv->connection, self->priv->avatar_token,
3054 FALSE, &filename, &mime_filename))
3055 {
3056 if (g_file_test (filename, G_FILE_TEST_EXISTS))
3057 {
3058 GError *error = NULL;
3059
3060 tp_clear_object (&self->priv->avatar_file);
3061 self->priv->avatar_file = g_file_new_for_path (filename);
3062
3063 g_free (self->priv->avatar_mime_type);
3064 if (!g_file_get_contents (mime_filename, &self->priv->avatar_mime_type,
3065 NULL, &error))
3066 {
3067 DEBUG ("Error reading avatar MIME type (%s): %s", mime_filename,
3068 error ? error->message : "No error message");
3069 self->priv->avatar_mime_type = NULL;
3070 g_clear_error (&error);
3071 }
3072
3073 DEBUG ("contact#%u avatar found in cache: %s, %s",
3074 self->priv->handle, filename, self->priv->avatar_mime_type);
3075
3076 g_object_notify ((GObject *) self, "avatar-file");
3077 g_object_notify ((GObject *) self, "avatar-mime_type");
3078
3079 goto out;
3080 }
3081 }
3082
3083 /* Not found in cache, queue this contact. We do this to group contacts
3084 * for the AvatarRequest call */
3085 connection = self->priv->connection;
3086 if (connection->priv->avatar_request_queue == NULL)
3087 connection->priv->avatar_request_queue = g_array_new (FALSE, FALSE,
3088 sizeof (TpHandle));
3089
3090 g_array_append_val (connection->priv->avatar_request_queue,
3091 self->priv->handle);
3092
3093 if (connection->priv->avatar_request_idle_id == 0)
3094 connection->priv->avatar_request_idle_id = g_idle_add (
3095 connection_avatar_request_idle_cb, connection);
3096
3097 out:
3098
3099 g_free (filename);
3100 g_free (mime_filename);
3101 }
3102
3103 static void
contact_maybe_update_avatar_data(TpContact * self)3104 contact_maybe_update_avatar_data (TpContact *self)
3105 {
3106 if ((self->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_DATA) == 0 &&
3107 (self->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0)
3108 {
3109 self->priv->has_features |= CONTACT_FEATURE_FLAG_AVATAR_DATA;
3110 contact_update_avatar_data (self);
3111 }
3112 }
3113
3114 static void
contacts_bind_to_avatar_retrieved(TpConnection * connection)3115 contacts_bind_to_avatar_retrieved (TpConnection *connection)
3116 {
3117 if (!connection->priv->tracking_avatar_retrieved)
3118 {
3119 connection->priv->tracking_avatar_retrieved = TRUE;
3120
3121 tp_cli_connection_interface_avatars_connect_to_avatar_retrieved
3122 (connection, contact_avatar_retrieved, NULL, NULL, NULL, NULL);
3123 }
3124 }
3125
3126 static void
contacts_get_avatar_data(ContactsContext * c)3127 contacts_get_avatar_data (ContactsContext *c)
3128 {
3129 guint i;
3130
3131 g_assert (c->handles->len == c->contacts->len);
3132
3133 contacts_bind_to_avatar_retrieved (c->connection);
3134
3135 for (i = 0; i < c->contacts->len; i++)
3136 contact_maybe_update_avatar_data (g_ptr_array_index (c->contacts, i));
3137
3138 contacts_context_continue (c);
3139 }
3140
3141 static void
contact_set_avatar_token(TpContact * self,const gchar * new_token,gboolean request)3142 contact_set_avatar_token (TpContact *self, const gchar *new_token,
3143 gboolean request)
3144 {
3145 /* A no-op change (specifically from NULL to NULL) is still interesting if we
3146 * don't have the AVATAR_TOKEN feature yet: it indicates that we've
3147 * discovered it.
3148 */
3149 if ((self->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) &&
3150 !tp_strdiff (self->priv->avatar_token, new_token))
3151 return;
3152
3153 DEBUG ("contact#%u token is %s", self->priv->handle, new_token);
3154
3155 self->priv->has_features |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
3156 g_free (self->priv->avatar_token);
3157 self->priv->avatar_token = g_strdup (new_token);
3158 g_object_notify ((GObject *) self, "avatar-token");
3159
3160 if (request && tp_contact_has_feature (self, TP_CONTACT_FEATURE_AVATAR_DATA))
3161 contact_update_avatar_data (self);
3162 }
3163
3164 static void
contacts_avatar_updated(TpConnection * connection,TpHandle handle,const gchar * new_token,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)3165 contacts_avatar_updated (TpConnection *connection,
3166 TpHandle handle,
3167 const gchar *new_token,
3168 gpointer user_data G_GNUC_UNUSED,
3169 GObject *weak_object G_GNUC_UNUSED)
3170 {
3171 TpContact *contact = _tp_connection_lookup_contact (connection, handle);
3172
3173 if (contact != NULL)
3174 contact_set_avatar_token (contact, new_token, TRUE);
3175 }
3176
3177
3178 static void
contacts_got_known_avatar_tokens(TpConnection * connection,GHashTable * handle_to_token,const GError * error,gpointer user_data,GObject * weak_object)3179 contacts_got_known_avatar_tokens (TpConnection *connection,
3180 GHashTable *handle_to_token,
3181 const GError *error,
3182 gpointer user_data,
3183 GObject *weak_object)
3184 {
3185 ContactsContext *c = user_data;
3186 GHashTableIter iter;
3187 gpointer key, value;
3188
3189 if (error == NULL)
3190 {
3191 g_hash_table_iter_init (&iter, handle_to_token);
3192
3193 while (g_hash_table_iter_next (&iter, &key, &value))
3194 {
3195 contacts_avatar_updated (connection, GPOINTER_TO_UINT (key), value,
3196 NULL, NULL);
3197 }
3198
3199 }
3200 /* FIXME: perhaps we could fall back to GetAvatarTokens (which should have
3201 * been called RequestAvatarTokens, because it blocks on network traffic)
3202 * if GetKnownAvatarTokens doesn't work? */
3203 else
3204 {
3205 /* never mind, we can live without avatar tokens */
3206 DEBUG ("GetKnownAvatarTokens failed with %s %u: %s",
3207 g_quark_to_string (error->domain), error->code, error->message);
3208 }
3209
3210 contacts_context_continue (c);
3211 }
3212
3213
3214 static void
contacts_bind_to_avatar_updated(TpConnection * connection)3215 contacts_bind_to_avatar_updated (TpConnection *connection)
3216 {
3217 if (!connection->priv->tracking_avatar_updated)
3218 {
3219 connection->priv->tracking_avatar_updated = TRUE;
3220
3221 tp_cli_connection_interface_avatars_connect_to_avatar_updated
3222 (connection, contacts_avatar_updated, NULL, NULL, NULL, NULL);
3223 }
3224 }
3225
3226
3227 static void
contacts_get_avatar_tokens(ContactsContext * c)3228 contacts_get_avatar_tokens (ContactsContext *c)
3229 {
3230 guint i;
3231
3232 g_assert (c->handles->len == c->contacts->len);
3233
3234 contacts_bind_to_avatar_updated (c->connection);
3235
3236 for (i = 0; i < c->contacts->len; i++)
3237 {
3238 TpContact *contact = g_ptr_array_index (c->contacts, i);
3239
3240 if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_AVATAR_TOKEN)
3241 == 0)
3242 {
3243 c->refcount++;
3244 tp_cli_connection_interface_avatars_call_get_known_avatar_tokens (
3245 c->connection, -1,
3246 c->handles, contacts_got_known_avatar_tokens,
3247 c, contacts_context_unref, c->weak_object);
3248 return;
3249 }
3250 }
3251
3252 contacts_context_continue (c);
3253 }
3254
3255 static void
contact_maybe_set_info(TpContact * self,const GPtrArray * contact_info)3256 contact_maybe_set_info (TpContact *self,
3257 const GPtrArray *contact_info)
3258 {
3259 guint i;
3260
3261 if (self == NULL)
3262 return;
3263
3264 tp_contact_info_list_free (self->priv->contact_info);
3265 self->priv->contact_info = NULL;
3266
3267 self->priv->has_features |= CONTACT_FEATURE_FLAG_CONTACT_INFO;
3268
3269 if (contact_info != NULL)
3270 {
3271 for (i = contact_info->len; i > 0; i--)
3272 {
3273 GValueArray *va = g_ptr_array_index (contact_info, i - 1);
3274 const gchar *field_name;
3275 GStrv parameters;
3276 GStrv field_value;
3277
3278 tp_value_array_unpack (va, 3, &field_name, ¶meters, &field_value);
3279 self->priv->contact_info = g_list_prepend (self->priv->contact_info,
3280 tp_contact_info_field_new (field_name, parameters, field_value));
3281 }
3282 }
3283 /* else we don't know, but an empty list is perfectly valid. */
3284
3285 g_object_notify ((GObject *) self, "contact-info");
3286 }
3287
3288 static void
contact_info_changed(TpConnection * connection,guint handle,const GPtrArray * contact_info,gpointer user_data G_GNUC_UNUSED,GObject * weak_object G_GNUC_UNUSED)3289 contact_info_changed (TpConnection *connection,
3290 guint handle,
3291 const GPtrArray *contact_info,
3292 gpointer user_data G_GNUC_UNUSED,
3293 GObject *weak_object G_GNUC_UNUSED)
3294 {
3295 TpContact *self = _tp_connection_lookup_contact (connection, handle);
3296
3297 contact_maybe_set_info (self, contact_info);
3298 }
3299
3300 static void
contacts_got_contact_info(TpConnection * connection,GHashTable * info,const GError * error,gpointer user_data,GObject * weak_object)3301 contacts_got_contact_info (TpConnection *connection,
3302 GHashTable *info,
3303 const GError *error,
3304 gpointer user_data,
3305 GObject *weak_object)
3306 {
3307 ContactsContext *c = user_data;
3308
3309 if (error != NULL)
3310 {
3311 DEBUG ("GetContactInfo failed with %s %u: %s",
3312 g_quark_to_string (error->domain), error->code, error->message);
3313 }
3314 else
3315 {
3316 GHashTableIter iter;
3317 gpointer key, value;
3318
3319 g_hash_table_iter_init (&iter, info);
3320 while (g_hash_table_iter_next (&iter, &key, &value))
3321 {
3322 contact_info_changed (connection, GPOINTER_TO_UINT (key),
3323 value, NULL, NULL);
3324 }
3325 }
3326
3327 contacts_context_continue (c);
3328 }
3329
3330 static void
contacts_bind_to_contact_info_changed(TpConnection * connection)3331 contacts_bind_to_contact_info_changed (TpConnection *connection)
3332 {
3333 if (!connection->priv->tracking_contact_info_changed)
3334 {
3335 connection->priv->tracking_contact_info_changed = TRUE;
3336
3337 tp_cli_connection_interface_contact_info_connect_to_contact_info_changed (
3338 connection, contact_info_changed, NULL, NULL, NULL, NULL);
3339 }
3340 }
3341
3342 static void
contacts_get_contact_info(ContactsContext * c)3343 contacts_get_contact_info (ContactsContext *c)
3344 {
3345 guint i;
3346
3347 g_assert (c->handles->len == c->contacts->len);
3348
3349 contacts_bind_to_contact_info_changed (c->connection);
3350
3351 for (i = 0; i < c->contacts->len; i++)
3352 {
3353 TpContact *contact = g_ptr_array_index (c->contacts, i);
3354
3355 if ((contact->priv->has_features & CONTACT_FEATURE_FLAG_CONTACT_INFO) == 0)
3356 {
3357 c->refcount++;
3358 tp_cli_connection_interface_contact_info_call_get_contact_info (
3359 c->connection, -1, c->handles, contacts_got_contact_info,
3360 c, contacts_context_unref, c->weak_object);
3361 return;
3362 }
3363 }
3364
3365 contacts_context_continue (c);
3366 }
3367
3368 typedef struct
3369 {
3370 TpContact *contact;
3371 GSimpleAsyncResult *result;
3372 TpProxyPendingCall *call;
3373 GCancellable *cancellable;
3374 gulong cancelled_id;
3375 } ContactInfoRequestData;
3376
3377 static void
contact_info_request_data_free(ContactInfoRequestData * data)3378 contact_info_request_data_free (ContactInfoRequestData *data)
3379 {
3380 if (data != NULL)
3381 {
3382 g_object_unref (data->result);
3383
3384 if (data->cancellable != NULL)
3385 g_object_unref (data->cancellable);
3386
3387 g_slice_free (ContactInfoRequestData, data);
3388 }
3389 }
3390
3391 static void
contact_info_request_cb(TpConnection * connection,const GPtrArray * contact_info,const GError * error,gpointer user_data,GObject * weak_object)3392 contact_info_request_cb (TpConnection *connection,
3393 const GPtrArray *contact_info,
3394 const GError *error,
3395 gpointer user_data,
3396 GObject *weak_object)
3397 {
3398 ContactInfoRequestData *data = user_data;
3399 TpContact *self = data->contact;
3400
3401 if (data->cancellable != NULL)
3402 {
3403 /* At this point it's too late to cancel the operation. This will block
3404 * until the signal handler has finished if it's already running, so
3405 * we're guaranteed to never be in a partially-cancelled state after
3406 * this call. */
3407 g_cancellable_disconnect (data->cancellable, data->cancelled_id);
3408
3409 /* If this is true, the cancelled callback has already run and completed the
3410 * async result, so just bail. */
3411 if (data->cancelled_id == 0)
3412 return;
3413
3414 data->cancelled_id = 0;
3415 }
3416
3417 if (error != NULL)
3418 {
3419 DEBUG ("Failed to request ContactInfo: %s", error->message);
3420 g_simple_async_result_set_from_error (data->result, error);
3421 }
3422 else
3423 {
3424 contact_maybe_set_info (self, contact_info);
3425 }
3426
3427 g_simple_async_result_complete_in_idle (data->result);
3428 data->call = NULL;
3429 }
3430
3431 static void
contact_info_request_cancelled_cb(GCancellable * cancellable,ContactInfoRequestData * data)3432 contact_info_request_cancelled_cb (GCancellable *cancellable,
3433 ContactInfoRequestData *data)
3434 {
3435 GError *error = NULL;
3436 gboolean was_cancelled;
3437
3438 /* We disconnect from the signal manually; since we're in the cancelled
3439 * callback, we hold the cancellable's lock so calling this instead of
3440 * g_cancellable_disconnect() is fine. We do this here so that
3441 * g_cancellable_disconnect() isn't called by contact_info_request_data_free()
3442 * which is called by tp_proxy_pending_call_cancel().
3443 * cancelled_id might already be 0 if the cancellable was cancelled before
3444 * we connected to it. */
3445 if (data->cancelled_id != 0)
3446 g_signal_handler_disconnect (data->cancellable, data->cancelled_id);
3447 data->cancelled_id = 0;
3448
3449 was_cancelled = g_cancellable_set_error_if_cancelled (data->cancellable,
3450 &error);
3451 g_assert (was_cancelled);
3452
3453 DEBUG ("Request ContactInfo cancelled");
3454
3455 g_simple_async_result_set_from_error (data->result, error);
3456 g_simple_async_result_complete_in_idle (data->result);
3457 g_clear_error (&error);
3458
3459 if (data->call != NULL)
3460 tp_proxy_pending_call_cancel (data->call);
3461 }
3462
3463 /**
3464 * tp_contact_request_contact_info_async:
3465 * @self: a #TpContact
3466 * @cancellable: optional #GCancellable object, %NULL to ignore.
3467 * @callback: a callback to call when the request is satisfied
3468 * @user_data: data to pass to @callback
3469 *
3470 * Requests an asynchronous request of the contact info of @self. When
3471 * the operation is finished, @callback will be called. You can then call
3472 * tp_contact_request_contact_info_finish() to get the result of the operation.
3473 *
3474 * If the operation is successful, the #TpContact:contact-info property will be
3475 * updated (emitting "notify::contact-info" signal) before @callback is called.
3476 * That means you can call tp_contact_get_contact_info() to get the new vCard
3477 * inside @callback.
3478 *
3479 * Note that requesting the vCard from the network can take significant time, so
3480 * a bigger timeout is set on the underlying D-Bus call. @cancellable can be
3481 * cancelled to free resources used in the D-Bus call if the caller is no longer
3482 * interested in the vCard.
3483 *
3484 * If %TP_CONTACT_FEATURE_CONTACT_INFO is not yet set on @self, it will be
3485 * set before its property gets updated and @callback is called.
3486 *
3487 * Since: 0.11.7
3488 */
3489 void
tp_contact_request_contact_info_async(TpContact * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3490 tp_contact_request_contact_info_async (TpContact *self,
3491 GCancellable *cancellable,
3492 GAsyncReadyCallback callback,
3493 gpointer user_data)
3494 {
3495 ContactInfoRequestData *data;
3496
3497 g_return_if_fail (TP_IS_CONTACT (self));
3498
3499 contacts_bind_to_contact_info_changed (self->priv->connection);
3500
3501 data = g_slice_new0 (ContactInfoRequestData);
3502
3503 data->contact = self;
3504 data->result = g_simple_async_result_new (G_OBJECT (self), callback,
3505 user_data, tp_contact_request_contact_info_finish);
3506
3507 if (cancellable != NULL)
3508 {
3509 data->cancellable = g_object_ref (cancellable);
3510 data->cancelled_id = g_cancellable_connect (data->cancellable,
3511 G_CALLBACK (contact_info_request_cancelled_cb), data, NULL);
3512
3513 /* Return early if the cancellable has already been cancelled */
3514 if (data->cancelled_id == 0)
3515 return;
3516 }
3517
3518 data->call = tp_cli_connection_interface_contact_info_call_request_contact_info (
3519 self->priv->connection, 60*60*1000, self->priv->handle,
3520 contact_info_request_cb,
3521 data, (GDestroyNotify) contact_info_request_data_free,
3522 NULL);
3523 }
3524
3525 /**
3526 * tp_contact_request_contact_info_finish:
3527 * @self: a #TpContact
3528 * @result: a #GAsyncResult
3529 * @error: a #GError to be filled
3530 *
3531 * Finishes an async request of @self info. If the operation was successful,
3532 * the contact's vCard can be accessed using tp_contact_get_contact_info().
3533 *
3534 * Returns: %TRUE if the request call was successful, otherwise %FALSE
3535 *
3536 * Since: 0.11.7
3537 */
3538 gboolean
tp_contact_request_contact_info_finish(TpContact * self,GAsyncResult * result,GError ** error)3539 tp_contact_request_contact_info_finish (TpContact *self,
3540 GAsyncResult *result,
3541 GError **error)
3542 {
3543 _tp_implement_finish_void (self, tp_contact_request_contact_info_finish);
3544 }
3545
3546 /**
3547 * tp_connection_refresh_contact_info:
3548 * @self: a #TpConnection
3549 * @n_contacts: The number of contacts in @contacts (must be at least 1)
3550 * @contacts: (array length=n_contacts): An array of #TpContact objects
3551 * associated with @self
3552 *
3553 * Requests to refresh the #TpContact:contact-info property on each contact from
3554 * @contacts, requesting it from the network if an up-to-date version is not
3555 * cached locally. "notify::contact-info" will be emitted when the contact's
3556 * information are updated.
3557 *
3558 * If %TP_CONTACT_FEATURE_CONTACT_INFO is not yet set on a contact, it will be
3559 * set before its property gets updated.
3560 *
3561 * Since: 0.11.7
3562 */
3563 void
tp_connection_refresh_contact_info(TpConnection * self,guint n_contacts,TpContact * const * contacts)3564 tp_connection_refresh_contact_info (TpConnection *self,
3565 guint n_contacts,
3566 TpContact * const *contacts)
3567 {
3568 GArray *handles;
3569 guint i;
3570
3571 g_return_if_fail (TP_IS_CONNECTION (self));
3572 g_return_if_fail (n_contacts >= 1);
3573 g_return_if_fail (contacts != NULL);
3574
3575 for (i = 0; i < n_contacts; i++)
3576 {
3577 g_return_if_fail (TP_IS_CONTACT (contacts[i]));
3578 g_return_if_fail (contacts[i]->priv->connection == self);
3579 }
3580
3581 contacts_bind_to_contact_info_changed (self);
3582
3583 handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), n_contacts);
3584 for (i = 0; i < n_contacts; i++)
3585 g_array_append_val (handles, contacts[i]->priv->handle);
3586
3587 tp_cli_connection_interface_contact_info_call_refresh_contact_info (self, -1,
3588 handles, NULL, NULL, NULL, NULL);
3589
3590 g_array_unref (handles);
3591 }
3592
3593 static void
contact_set_subscription_states(TpContact * self,TpSubscriptionState subscribe,TpSubscriptionState publish,const gchar * publish_request)3594 contact_set_subscription_states (TpContact *self,
3595 TpSubscriptionState subscribe,
3596 TpSubscriptionState publish,
3597 const gchar *publish_request)
3598 {
3599 if (publish_request == NULL)
3600 publish_request = "";
3601
3602 DEBUG ("contact#%u state changed: subscribe=%c publish=%c '%s'",
3603 self->priv->handle,
3604 _tp_base_contact_list_presence_state_to_letter (subscribe),
3605 _tp_base_contact_list_presence_state_to_letter (publish),
3606 publish_request);
3607
3608 self->priv->has_features |= CONTACT_FEATURE_FLAG_STATES;
3609
3610 g_free (self->priv->publish_request);
3611
3612 self->priv->subscribe = subscribe;
3613 self->priv->publish = publish;
3614 self->priv->publish_request = g_strdup (publish_request);
3615
3616 g_object_notify ((GObject *) self, "subscribe-state");
3617 g_object_notify ((GObject *) self, "publish-state");
3618 g_object_notify ((GObject *) self, "publish-request");
3619
3620 g_signal_emit (self, signals[SIGNAL_SUBSCRIPTION_STATES_CHANGED], 0,
3621 self->priv->subscribe, self->priv->publish, self->priv->publish_request);
3622 }
3623
3624 void
_tp_contact_set_subscription_states(TpContact * self,GValueArray * value_array)3625 _tp_contact_set_subscription_states (TpContact *self,
3626 GValueArray *value_array)
3627 {
3628 TpSubscriptionState subscribe;
3629 TpSubscriptionState publish;
3630 const gchar *publish_request;
3631
3632 tp_value_array_unpack (value_array, 3,
3633 &subscribe, &publish, &publish_request);
3634
3635 contact_set_subscription_states (self, subscribe, publish, publish_request);
3636 }
3637
3638 static void
contacts_changed_cb(TpConnection * connection,GHashTable * changes,const GArray * removals,gpointer user_data,GObject * weak_object)3639 contacts_changed_cb (TpConnection *connection,
3640 GHashTable *changes,
3641 const GArray *removals,
3642 gpointer user_data,
3643 GObject *weak_object)
3644 {
3645 GHashTableIter iter;
3646 gpointer key, value;
3647 guint i;
3648
3649 g_hash_table_iter_init (&iter, changes);
3650 while (g_hash_table_iter_next (&iter, &key, &value))
3651 {
3652 TpHandle handle = GPOINTER_TO_UINT (key);
3653 TpContact *contact = _tp_connection_lookup_contact (connection, handle);
3654
3655 if (contact != NULL)
3656 _tp_contact_set_subscription_states (contact, value);
3657 }
3658
3659 for (i = 0; i < removals->len; i++)
3660 {
3661 TpHandle handle = g_array_index (removals, TpHandle, i);
3662 TpContact *contact = _tp_connection_lookup_contact (connection, handle);
3663
3664 if (contact == NULL)
3665 continue;
3666
3667 contact_set_subscription_states (contact, TP_SUBSCRIPTION_STATE_NO,
3668 TP_SUBSCRIPTION_STATE_NO, NULL);
3669 }
3670 }
3671
3672 static void
contacts_bind_to_contacts_changed(TpConnection * connection)3673 contacts_bind_to_contacts_changed (TpConnection *connection)
3674 {
3675 if (!connection->priv->tracking_contacts_changed)
3676 {
3677 connection->priv->tracking_contacts_changed = TRUE;
3678
3679 tp_cli_connection_interface_contact_list_connect_to_contacts_changed
3680 (connection, contacts_changed_cb, NULL, NULL, NULL, NULL);
3681 }
3682 }
3683
3684 static void
contact_maybe_set_contact_groups(TpContact * self,GStrv contact_groups)3685 contact_maybe_set_contact_groups (TpContact *self,
3686 GStrv contact_groups)
3687 {
3688 gchar **iter;
3689
3690 if (self == NULL || contact_groups == NULL)
3691 return;
3692
3693 self->priv->has_features |= CONTACT_FEATURE_FLAG_CONTACT_GROUPS;
3694
3695 tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref);
3696 self->priv->contact_groups = g_ptr_array_new_full (
3697 g_strv_length (contact_groups) + 1, g_free);
3698
3699 for (iter = contact_groups; *iter != NULL; iter++)
3700 g_ptr_array_add (self->priv->contact_groups, g_strdup (*iter));
3701 g_ptr_array_add (self->priv->contact_groups, NULL);
3702
3703 g_object_notify ((GObject *) self, "contact-groups");
3704 }
3705
3706 static void
contact_groups_changed_cb(TpConnection * connection,const GArray * contacts,const gchar ** added,const gchar ** removed,gpointer user_data,GObject * weak_object)3707 contact_groups_changed_cb (TpConnection *connection,
3708 const GArray *contacts,
3709 const gchar **added,
3710 const gchar **removed,
3711 gpointer user_data,
3712 GObject *weak_object)
3713 {
3714 guint i;
3715
3716 for (i = 0; i < contacts->len; i++)
3717 {
3718 TpHandle handle = g_array_index (contacts, TpHandle, i);
3719 TpContact *contact = _tp_connection_lookup_contact (connection, handle);
3720 const gchar **iter;
3721 guint j;
3722
3723 if (contact == NULL || contact->priv->contact_groups == NULL)
3724 continue;
3725
3726 /* Remove the ending NULL */
3727 g_ptr_array_remove_index_fast (contact->priv->contact_groups,
3728 contact->priv->contact_groups->len - 1);
3729
3730 /* Remove old groups */
3731 for (iter = removed; *iter != NULL; iter++)
3732 {
3733 for (j = 0; j < contact->priv->contact_groups->len; j++)
3734 {
3735 const gchar *str;
3736
3737 str = g_ptr_array_index (contact->priv->contact_groups, j);
3738 if (!tp_strdiff (str, *iter))
3739 {
3740 g_ptr_array_remove_index_fast (contact->priv->contact_groups, j);
3741 break;
3742 }
3743 }
3744 }
3745
3746 /* Add new groups */
3747 for (iter = added; *iter != NULL; iter++)
3748 g_ptr_array_add (contact->priv->contact_groups, g_strdup (*iter));
3749
3750 /* Add back the ending NULL */
3751 g_ptr_array_add (contact->priv->contact_groups, NULL);
3752
3753 g_object_notify ((GObject *) contact, "contact-groups");
3754 g_signal_emit (contact, signals[SIGNAL_CONTACT_GROUPS_CHANGED], 0,
3755 added, removed);
3756 }
3757 }
3758
3759 static void
contacts_bind_to_contact_groups_changed(TpConnection * connection)3760 contacts_bind_to_contact_groups_changed (TpConnection *connection)
3761 {
3762 if (!connection->priv->tracking_contact_groups_changed)
3763 {
3764 connection->priv->tracking_contact_groups_changed = TRUE;
3765
3766 tp_cli_connection_interface_contact_groups_connect_to_groups_changed
3767 (connection, contact_groups_changed_cb, NULL, NULL, NULL, NULL);
3768 }
3769 }
3770
3771 static gboolean
contacts_context_supports_iface(ContactsContext * context,GQuark iface)3772 contacts_context_supports_iface (ContactsContext *context,
3773 GQuark iface)
3774 {
3775 GArray *contact_attribute_interfaces =
3776 context->connection->priv->contact_attribute_interfaces;
3777 guint i;
3778
3779 if (!tp_proxy_has_interface_by_id (context->connection,
3780 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
3781 return FALSE;
3782
3783 if (contact_attribute_interfaces == NULL)
3784 return FALSE;
3785
3786 for (i = 0; i < contact_attribute_interfaces->len; i++)
3787 {
3788 GQuark q = g_array_index (contact_attribute_interfaces, GQuark, i);
3789
3790 if (q == iface)
3791 return TRUE;
3792 }
3793
3794 return FALSE;
3795 }
3796
3797 static void
contacts_context_queue_features(ContactsContext * context)3798 contacts_context_queue_features (ContactsContext *context)
3799 {
3800 ContactFeatureFlags feature_flags = context->wanted;
3801
3802 /* Start slow path for requested features that are not in
3803 * ContactAttributeInterfaces */
3804
3805 if ((feature_flags & CONTACT_FEATURE_FLAG_ALIAS) != 0 &&
3806 !contacts_context_supports_iface (context,
3807 TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING) &&
3808 tp_proxy_has_interface_by_id (context->connection,
3809 TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING))
3810 {
3811 g_queue_push_tail (&context->todo, contacts_get_aliases);
3812 }
3813
3814 if ((feature_flags & CONTACT_FEATURE_FLAG_PRESENCE) != 0 &&
3815 !contacts_context_supports_iface (context,
3816 TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE))
3817 {
3818 if (tp_proxy_has_interface_by_id (context->connection,
3819 TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE))
3820 {
3821 g_queue_push_tail (&context->todo, contacts_get_simple_presence);
3822 }
3823 #if 0
3824 /* FIXME: Before doing this for the first time, we'd need to download
3825 * from the CM the definition of what each status actually *means* */
3826 else if (tp_proxy_has_interface_by_id (context->connection,
3827 TP_IFACE_QUARK_CONNECTION_INTERFACE_PRESENCE))
3828 {
3829 g_queue_push_tail (&context->todo, contacts_get_complex_presence);
3830 }
3831 #endif
3832 }
3833
3834 if ((feature_flags & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0 &&
3835 !contacts_context_supports_iface (context,
3836 TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS) &&
3837 tp_proxy_has_interface_by_id (context->connection,
3838 TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
3839 {
3840 g_queue_push_tail (&context->todo, contacts_get_avatar_tokens);
3841 }
3842
3843 /* There is no contact attribute for avatar data, always use slow path */
3844 if ((feature_flags & CONTACT_FEATURE_FLAG_AVATAR_DATA) != 0 &&
3845 tp_proxy_has_interface_by_id (context->connection,
3846 TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
3847 {
3848 g_queue_push_tail (&context->todo, contacts_get_avatar_data);
3849 }
3850
3851 if ((feature_flags & CONTACT_FEATURE_FLAG_LOCATION) != 0 &&
3852 !contacts_context_supports_iface (context,
3853 TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION) &&
3854 tp_proxy_has_interface_by_id (context->connection,
3855 TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION))
3856 {
3857 WARNING ("%s supports Location but not Contacts! Where did you find "
3858 "this CM? TP_CONTACT_FEATURE_LOCATION is not gonna work",
3859 tp_proxy_get_object_path (context->connection));
3860 }
3861
3862 /* Don't implement slow path for ContactCapabilities as Contacts is now
3863 * mandatory so any CM supporting ContactCapabilities will implement
3864 * Contacts as well.
3865 *
3866 * But if ContactCapabilities is NOT supported, we fallback to connection
3867 * capabilities.
3868 * */
3869
3870 if ((feature_flags & CONTACT_FEATURE_FLAG_CAPABILITIES) != 0 &&
3871 !tp_proxy_has_interface_by_id (context->connection,
3872 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES))
3873 {
3874 DEBUG ("Connection doesn't support ContactCapabilities; fallback to "
3875 "connection capabilities");
3876
3877 g_queue_push_tail (&context->todo, contacts_get_conn_capabilities);
3878 }
3879
3880 if ((feature_flags & CONTACT_FEATURE_FLAG_CONTACT_INFO) != 0 &&
3881 !contacts_context_supports_iface (context,
3882 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO) &&
3883 tp_proxy_has_interface_by_id (context->connection,
3884 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
3885 {
3886 g_queue_push_tail (&context->todo, contacts_get_contact_info);
3887 }
3888 }
3889
3890 static gboolean
tp_contact_set_attributes(TpContact * contact,GHashTable * asv,ContactFeatureFlags wanted,ContactFeatureFlags getting,GError ** error)3891 tp_contact_set_attributes (TpContact *contact,
3892 GHashTable *asv,
3893 ContactFeatureFlags wanted,
3894 ContactFeatureFlags getting,
3895 GError **error)
3896 {
3897 TpConnection *connection = tp_contact_get_connection (contact);
3898 const gchar *s;
3899 gpointer boxed;
3900
3901 /* Identifier */
3902 s = tp_asv_get_string (asv, TP_TOKEN_CONNECTION_CONTACT_ID);
3903
3904 if (s == NULL)
3905 {
3906 g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
3907 "Connection manager %s is broken: contact #%u in the "
3908 "GetContactAttributes result has no contact-id",
3909 tp_proxy_get_bus_name (connection), contact->priv->handle);
3910
3911 return FALSE;
3912 }
3913
3914 DEBUG ("#%u: \"%s\"", contact->priv->handle, s);
3915
3916 {
3917 GHashTableIter iter;
3918 gpointer k, v;
3919
3920 g_hash_table_iter_init (&iter, asv);
3921
3922 while (g_hash_table_iter_next (&iter, &k, &v))
3923 {
3924 gchar *str = g_strdup_value_contents (v);
3925
3926 DEBUG ("- %s => %s", (const gchar *) k, str);
3927 g_free (str);
3928 }
3929 }
3930
3931 if (contact->priv->identifier == NULL)
3932 {
3933 contact->priv->identifier = g_strdup (s);
3934 }
3935 else if (tp_strdiff (contact->priv->identifier, s))
3936 {
3937 g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
3938 "Connection manager %s is broken: contact #%u identifier "
3939 "changed from %s to %s",
3940 tp_proxy_get_bus_name (connection), contact->priv->handle,
3941 contact->priv->identifier, s);
3942
3943 return FALSE;
3944 }
3945
3946 /* Alias */
3947 if (wanted & CONTACT_FEATURE_FLAG_ALIAS)
3948 {
3949 s = tp_asv_get_string (asv,
3950 TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS);
3951
3952 if (s == NULL)
3953 {
3954 if (getting & CONTACT_FEATURE_FLAG_ALIAS)
3955 {
3956 WARNING ("%s supposedly implements Contacts and Aliasing, but "
3957 "omitted " TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
3958 tp_proxy_get_object_path (connection));
3959 }
3960 }
3961 else
3962 {
3963 contact->priv->has_features |= CONTACT_FEATURE_FLAG_ALIAS;
3964 g_free (contact->priv->alias);
3965 contact->priv->alias = g_strdup (s);
3966 g_object_notify ((GObject *) contact, "alias");
3967 }
3968 }
3969
3970 /* Avatar */
3971 if (wanted & CONTACT_FEATURE_FLAG_AVATAR_TOKEN)
3972 {
3973 s = tp_asv_get_string (asv,
3974 TP_TOKEN_CONNECTION_INTERFACE_AVATARS_TOKEN);
3975 contact_set_avatar_token (contact, s, TRUE);
3976 }
3977
3978 if (wanted & CONTACT_FEATURE_FLAG_AVATAR_DATA)
3979 {
3980 /* There is no attribute for the avatar data, this will set the avatar
3981 * from cache or start the avatar request if its missing from cache. */
3982 contact_maybe_update_avatar_data (contact);
3983 }
3984
3985 /* Presence */
3986 if (wanted & CONTACT_FEATURE_FLAG_PRESENCE)
3987 {
3988 boxed = tp_asv_get_boxed (asv,
3989 TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE,
3990 TP_STRUCT_TYPE_SIMPLE_PRESENCE);
3991
3992 if (boxed == NULL)
3993 {
3994 if (getting & CONTACT_FEATURE_FLAG_PRESENCE)
3995 {
3996 WARNING ("%s supposedly implements Contacts and SimplePresence, "
3997 "but omitted the mandatory "
3998 TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE
3999 " attribute",
4000 tp_proxy_get_object_path (connection));
4001 }
4002 }
4003 else
4004 {
4005 contact_maybe_set_simple_presence (contact, boxed);
4006 }
4007 }
4008
4009 /* Location */
4010 if (wanted & CONTACT_FEATURE_FLAG_LOCATION)
4011 {
4012 boxed = tp_asv_get_boxed (asv,
4013 TP_TOKEN_CONNECTION_INTERFACE_LOCATION_LOCATION,
4014 TP_HASH_TYPE_LOCATION);
4015 contact_maybe_set_location (contact, boxed);
4016 }
4017
4018 /* Capabilities */
4019 if (wanted & CONTACT_FEATURE_FLAG_CAPABILITIES)
4020 {
4021 boxed = tp_asv_get_boxed (asv,
4022 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_CAPABILITIES_CAPABILITIES,
4023 TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
4024 contact_maybe_set_capabilities (contact, boxed);
4025 }
4026
4027 /* ContactInfo */
4028 if (wanted & CONTACT_FEATURE_FLAG_CONTACT_INFO)
4029 {
4030 boxed = tp_asv_get_boxed (asv,
4031 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_INFO_INFO,
4032 TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
4033 contact_maybe_set_info (contact, boxed);
4034 }
4035
4036 /* ClientTypes */
4037 if (wanted & CONTACT_FEATURE_FLAG_CLIENT_TYPES)
4038 {
4039 boxed = tp_asv_get_boxed (asv,
4040 TP_TOKEN_CONNECTION_INTERFACE_CLIENT_TYPES_CLIENT_TYPES,
4041 G_TYPE_STRV);
4042 contact_maybe_set_client_types (contact, boxed);
4043 }
4044
4045 /* ContactList subscription states */
4046 if (wanted & CONTACT_FEATURE_FLAG_STATES)
4047 {
4048 TpSubscriptionState subscribe;
4049 TpSubscriptionState publish;
4050 const gchar *publish_request;
4051 gboolean subscribe_valid = FALSE;
4052 gboolean publish_valid = FALSE;
4053
4054 subscribe = tp_asv_get_uint32 (asv,
4055 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_SUBSCRIBE,
4056 &subscribe_valid);
4057 publish = tp_asv_get_uint32 (asv,
4058 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_PUBLISH,
4059 &publish_valid);
4060 publish_request = tp_asv_get_string (asv,
4061 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_PUBLISH_REQUEST);
4062
4063 if (subscribe_valid && publish_valid)
4064 {
4065 contact_set_subscription_states (contact, subscribe, publish,
4066 publish_request);
4067 }
4068 }
4069
4070 /* ContactGroups */
4071 if (wanted & CONTACT_FEATURE_FLAG_CONTACT_GROUPS)
4072 {
4073 boxed = tp_asv_get_boxed (asv,
4074 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_GROUPS_GROUPS,
4075 G_TYPE_STRV);
4076 contact_maybe_set_contact_groups (contact, boxed);
4077 }
4078
4079 /* ContactBlocking */
4080 if (wanted & CONTACT_FEATURE_FLAG_CONTACT_BLOCKING)
4081 {
4082 gboolean is_blocked, valid;
4083
4084 is_blocked = tp_asv_get_boolean (asv,
4085 TP_TOKEN_CONNECTION_INTERFACE_CONTACT_BLOCKING_BLOCKED, &valid);
4086
4087 if (valid)
4088 _tp_contact_set_is_blocked (contact, is_blocked);
4089 }
4090
4091 return TRUE;
4092 }
4093
4094 static gboolean get_feature_flags (guint n_features,
4095 const TpContactFeature *features, ContactFeatureFlags *flags);
4096
4097 gboolean
_tp_contact_set_attributes(TpContact * contact,GHashTable * asv,guint n_features,const TpContactFeature * features,GError ** error)4098 _tp_contact_set_attributes (TpContact *contact,
4099 GHashTable *asv,
4100 guint n_features,
4101 const TpContactFeature *features,
4102 GError **error)
4103 {
4104 ContactFeatureFlags feature_flags = 0;
4105
4106 if (!get_feature_flags (n_features, features, &feature_flags))
4107 return FALSE;
4108
4109 return tp_contact_set_attributes (contact, asv, feature_flags,
4110 0 /* can't know what we expected to get */, error);
4111 }
4112
4113 static void
contacts_got_attributes(TpConnection * connection,GHashTable * attributes,const GError * error,gpointer user_data,GObject * weak_object)4114 contacts_got_attributes (TpConnection *connection,
4115 GHashTable *attributes,
4116 const GError *error,
4117 gpointer user_data,
4118 GObject *weak_object)
4119 {
4120 ContactsContext *c = user_data;
4121 guint i;
4122
4123 DEBUG ("%p: reply from GetContactAttributes: %s",
4124 c, (error == NULL ? "OK" : error->message));
4125
4126 if (error != NULL)
4127 {
4128 contacts_context_fail (c, error);
4129 return;
4130 }
4131
4132 i = 0;
4133
4134 if (c->signature == CB_BY_HANDLE && c->contacts->len == 0)
4135 {
4136 while (i < c->handles->len)
4137 {
4138 TpHandle handle = g_array_index (c->handles, guint, i);
4139 GHashTable *asv = g_hash_table_lookup (attributes,
4140 GUINT_TO_POINTER (handle));
4141
4142 if (asv == NULL)
4143 {
4144 /* not in the hash table => not valid */
4145 g_array_append_val (c->invalid, handle);
4146 g_array_remove_index_fast (c->handles, i);
4147 }
4148 else
4149 {
4150 TpContact *contact = tp_contact_ensure (connection, handle);
4151
4152 g_ptr_array_add (c->contacts, contact);
4153 i++;
4154 }
4155 }
4156 }
4157
4158 g_assert (c->contacts->len == c->handles->len);
4159
4160 for (i = 0; i < c->handles->len; i++)
4161 {
4162 TpContact *contact = g_ptr_array_index (c->contacts, i);
4163 GHashTable *asv = g_hash_table_lookup (attributes,
4164 GUINT_TO_POINTER (contact->priv->handle));
4165 GError *e = NULL;
4166
4167 if (asv == NULL)
4168 {
4169 g_set_error (&e, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
4170 "We hold a ref to handle #%u but it appears to be invalid",
4171 contact->priv->handle);
4172 }
4173 else
4174 {
4175 /* set up the contact with its attributes */
4176 tp_contact_set_attributes (contact, asv, c->wanted, c->getting, &e);
4177 }
4178
4179 if (e != NULL)
4180 {
4181 contacts_context_fail (c, e);
4182 g_error_free (e);
4183 return;
4184 }
4185 }
4186
4187 contacts_context_continue (c);
4188 }
4189
4190 static const gchar **
contacts_bind_to_signals(TpConnection * connection,ContactFeatureFlags wanted,ContactFeatureFlags * getting)4191 contacts_bind_to_signals (TpConnection *connection,
4192 ContactFeatureFlags wanted,
4193 ContactFeatureFlags *getting)
4194 {
4195 GArray *contact_attribute_interfaces =
4196 connection->priv->contact_attribute_interfaces;
4197 GPtrArray *array;
4198 guint i;
4199 guint len = 0;
4200
4201 if (getting != NULL)
4202 *getting = 0;
4203
4204 if (contact_attribute_interfaces != NULL)
4205 len = contact_attribute_interfaces->len;
4206
4207 g_assert (tp_proxy_has_interface_by_id (connection,
4208 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS));
4209
4210 array = g_ptr_array_sized_new (len);
4211
4212 for (i = 0; i < len; i++)
4213 {
4214 GQuark q = g_array_index (contact_attribute_interfaces, GQuark, i);
4215
4216 if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)
4217 {
4218 if ((wanted & CONTACT_FEATURE_FLAG_ALIAS) != 0)
4219 {
4220 g_ptr_array_add (array,
4221 TP_IFACE_CONNECTION_INTERFACE_ALIASING);
4222 contacts_bind_to_aliases_changed (connection);
4223
4224 if (getting != NULL)
4225 *getting |= CONTACT_FEATURE_FLAG_ALIAS;
4226 }
4227 }
4228 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS)
4229 {
4230 if ((wanted & CONTACT_FEATURE_FLAG_AVATAR_TOKEN) != 0)
4231 {
4232 g_ptr_array_add (array,
4233 TP_IFACE_CONNECTION_INTERFACE_AVATARS);
4234 contacts_bind_to_avatar_updated (connection);
4235
4236 if (getting != NULL)
4237 *getting |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
4238 }
4239
4240 if ((wanted & CONTACT_FEATURE_FLAG_AVATAR_DATA) != 0)
4241 {
4242 contacts_bind_to_avatar_retrieved (connection);
4243 }
4244 }
4245 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE)
4246 {
4247 if ((wanted & CONTACT_FEATURE_FLAG_PRESENCE) != 0)
4248 {
4249 g_ptr_array_add (array,
4250 TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE);
4251 contacts_bind_to_presences_changed (connection);
4252
4253 if (getting != NULL)
4254 *getting |= CONTACT_FEATURE_FLAG_PRESENCE;
4255 }
4256 }
4257 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION)
4258 {
4259 if ((wanted & CONTACT_FEATURE_FLAG_LOCATION) != 0)
4260 {
4261 g_ptr_array_add (array,
4262 TP_IFACE_CONNECTION_INTERFACE_LOCATION);
4263 contacts_bind_to_location_updated (connection);
4264
4265 if (getting != NULL)
4266 *getting |= CONTACT_FEATURE_FLAG_LOCATION;
4267 }
4268 }
4269 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES)
4270 {
4271 if ((wanted & CONTACT_FEATURE_FLAG_CAPABILITIES) != 0)
4272 {
4273 g_ptr_array_add (array,
4274 TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES);
4275 contacts_bind_to_capabilities_updated (connection);
4276
4277 if (getting != NULL)
4278 *getting |= CONTACT_FEATURE_FLAG_CAPABILITIES;
4279 }
4280 }
4281 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO)
4282 {
4283 if ((wanted & CONTACT_FEATURE_FLAG_CONTACT_INFO) != 0)
4284 {
4285 g_ptr_array_add (array,
4286 TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO);
4287 contacts_bind_to_contact_info_changed (connection);
4288
4289 if (getting != NULL)
4290 *getting |= CONTACT_FEATURE_FLAG_CONTACT_INFO;
4291 }
4292 }
4293 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CLIENT_TYPES)
4294 {
4295 if ((wanted & CONTACT_FEATURE_FLAG_CLIENT_TYPES) != 0)
4296 {
4297 g_ptr_array_add (array,
4298 TP_IFACE_CONNECTION_INTERFACE_CLIENT_TYPES);
4299 contacts_bind_to_client_types_updated (connection);
4300
4301 if (getting != NULL)
4302 *getting |= CONTACT_FEATURE_FLAG_CLIENT_TYPES;
4303 }
4304 }
4305 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_LIST)
4306 {
4307 if ((wanted & CONTACT_FEATURE_FLAG_STATES) != 0)
4308 {
4309 g_ptr_array_add (array,
4310 TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST);
4311 contacts_bind_to_contacts_changed (connection);
4312
4313 if (getting != NULL)
4314 *getting |= CONTACT_FEATURE_FLAG_STATES;
4315 }
4316 }
4317 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_GROUPS)
4318 {
4319 if ((wanted & CONTACT_FEATURE_FLAG_CONTACT_GROUPS) != 0)
4320 {
4321 g_ptr_array_add (array,
4322 TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS);
4323 contacts_bind_to_contact_groups_changed (connection);
4324
4325 if (getting != NULL)
4326 *getting |= CONTACT_FEATURE_FLAG_CONTACT_GROUPS;
4327 }
4328 }
4329 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING)
4330 {
4331 if ((wanted & CONTACT_FEATURE_FLAG_CONTACT_BLOCKING) != 0)
4332 {
4333 GQuark features[] = { TP_CONNECTION_FEATURE_CONTACT_BLOCKING, 0 };
4334
4335 g_ptr_array_add (array,
4336 TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING);
4337
4338 /* The BlockedContactsChanged signal is already handled by
4339 * connection-contact-list.c so we just have to prepare
4340 * TP_CONNECTION_FEATURE_CONTACT_BLOCKING to make sure it's
4341 * connected. */
4342 if (!tp_proxy_is_prepared (connection,
4343 TP_CONNECTION_FEATURE_CONTACT_BLOCKING))
4344 {
4345 tp_proxy_prepare_async (connection, features, NULL, NULL);
4346 }
4347
4348 if (getting != NULL)
4349 *getting |= CONTACT_FEATURE_FLAG_CONTACT_BLOCKING;
4350 }
4351 }
4352 }
4353
4354 g_ptr_array_add (array, NULL);
4355 return (const gchar **) g_ptr_array_free (array, FALSE);
4356 }
4357
4358 /*
4359 * The connection must implement Contacts.
4360 */
4361 const gchar **
_tp_contacts_bind_to_signals(TpConnection * connection,guint n_features,const TpContactFeature * features)4362 _tp_contacts_bind_to_signals (TpConnection *connection,
4363 guint n_features,
4364 const TpContactFeature *features)
4365 {
4366 ContactFeatureFlags feature_flags = 0;
4367
4368 if (!get_feature_flags (n_features, features, &feature_flags))
4369 return NULL;
4370
4371 return contacts_bind_to_signals (connection, feature_flags, NULL);
4372 }
4373
4374 static void
contacts_get_attributes(ContactsContext * context)4375 contacts_get_attributes (ContactsContext *context)
4376 {
4377 const gchar **supported_interfaces;
4378 guint i;
4379
4380 /* tp_connection_get_contact_attributes insists that you have at least one
4381 * handle; skip it if we don't (can only happen if we started from IDs) */
4382 if (context->handles->len == 0)
4383 {
4384 contacts_context_continue (context);
4385 return;
4386 }
4387
4388 supported_interfaces = contacts_bind_to_signals (context->connection,
4389 context->wanted, &context->getting);
4390
4391 if (supported_interfaces[0] == NULL &&
4392 !(context->signature == CB_BY_HANDLE && context->contacts->len == 0) &&
4393 context->contacts_have_ids)
4394 {
4395 /* We're not going to do anything useful: we're not holding/inspecting
4396 * the handles, and we're not inspecting any extended interfaces
4397 * either. Skip it. */
4398 g_free (supported_interfaces);
4399 contacts_context_continue (context);
4400 return;
4401 }
4402
4403 /* The Hold parameter is only true if we started from handles, and we don't
4404 * already have all the contacts we need. */
4405 context->refcount++;
4406 DEBUG ("calling GetContactAttributes");
4407
4408 for (i = 0; supported_interfaces[i] != NULL; i++)
4409 DEBUG ("- %s", supported_interfaces[i]);
4410
4411 tp_cli_connection_interface_contacts_call_get_contact_attributes (
4412 context->connection, -1, context->handles, supported_interfaces,
4413 (context->signature == CB_BY_HANDLE && context->contacts->len == 0),
4414 contacts_got_attributes,
4415 context, contacts_context_unref, context->weak_object);
4416 g_free (supported_interfaces);
4417 }
4418
4419 /*
4420 * Returns a new GPtrArray of borrowed references to TpContacts,
4421 * or NULL if any contacts could not be found.
4422 */
4423 static GPtrArray *
lookup_all_contacts(ContactsContext * context)4424 lookup_all_contacts (ContactsContext *context)
4425 {
4426 GPtrArray *contacts = g_ptr_array_new ();
4427 guint i;
4428
4429 for (i = 0; i < context->handles->len; i++)
4430 {
4431 TpContact *contact = _tp_connection_lookup_contact (context->connection,
4432 g_array_index (context->handles, TpHandle, i));
4433 if (contact != NULL)
4434 {
4435 g_ptr_array_add (contacts, contact);
4436 }
4437 else
4438 {
4439 g_ptr_array_unref (contacts);
4440 contacts = NULL;
4441 break;
4442 }
4443 }
4444
4445 return contacts;
4446 }
4447
4448 static gboolean
get_feature_flags(guint n_features,const TpContactFeature * features,ContactFeatureFlags * flags)4449 get_feature_flags (guint n_features,
4450 const TpContactFeature *features,
4451 ContactFeatureFlags *flags)
4452 {
4453 ContactFeatureFlags feature_flags = 0;
4454 guint i;
4455
4456 for (i = 0; i < n_features; i++)
4457 {
4458 g_return_val_if_fail (features[i] < TP_NUM_CONTACT_FEATURES, FALSE);
4459 feature_flags |= (1 << features[i]);
4460 }
4461
4462 /* Force AVATAR_TOKEN if we have AVATAR_DATA */
4463 if ((feature_flags & CONTACT_FEATURE_FLAG_AVATAR_DATA) != 0)
4464 feature_flags |= CONTACT_FEATURE_FLAG_AVATAR_TOKEN;
4465
4466 *flags = feature_flags;
4467
4468 return TRUE;
4469 }
4470
4471 static void
contacts_context_remove_common_features(ContactsContext * context)4472 contacts_context_remove_common_features (ContactsContext *context)
4473 {
4474 ContactFeatureFlags minimal_feature_flags = 0xFFFFFFFF;
4475 guint i;
4476
4477 context->contacts_have_ids = TRUE;
4478
4479 for (i = 0; i < context->contacts->len; i++)
4480 {
4481 TpContact *contact = g_ptr_array_index (context->contacts, i);
4482
4483 minimal_feature_flags &= contact->priv->has_features;
4484
4485 if (contact->priv->identifier == NULL)
4486 context->contacts_have_ids = FALSE;
4487 }
4488
4489 context->wanted &= (~minimal_feature_flags);
4490 }
4491
4492
4493 /**
4494 * tp_connection_get_contacts_by_handle:
4495 * @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
4496 * feature prepared
4497 * @n_handles: The number of handles in @handles (must be at least 1)
4498 * @handles: (array length=n_handles) (element-type uint): An array of handles
4499 * of type %TP_HANDLE_TYPE_CONTACT representing the desired contacts
4500 * @n_features: The number of features in @features (may be 0)
4501 * @features: (array length=n_features) (allow-none) (element-type uint): An array of features that
4502 * must be ready for use (if supported) before the callback is called (may
4503 * be %NULL if @n_features is 0)
4504 * @callback: A user callback to call when the contacts are ready
4505 * @user_data: Data to pass to the callback
4506 * @destroy: Called to destroy @user_data either after @callback has been
4507 * called, or if the operation is cancelled
4508 * @weak_object: (allow-none): An object to pass to the callback, which will be
4509 * weakly referenced; if this object is destroyed, the operation will be
4510 * cancelled
4511 *
4512 * Create a number of #TpContact objects and make asynchronous method calls
4513 * to hold their handles and ensure that all the features specified in
4514 * @features are ready for use (if they are supported at all).
4515 *
4516 * It is not an error to put features in @features even if the connection
4517 * manager doesn't support them - users of this method should have a static
4518 * list of features they would like to use if possible, and use it for all
4519 * connection managers.
4520 *
4521 * Since: 0.7.18
4522 * Deprecated: Use tp_simple_client_factory_ensure_contact() instead.
4523 */
4524 void
tp_connection_get_contacts_by_handle(TpConnection * self,guint n_handles,const TpHandle * handles,guint n_features,const TpContactFeature * features,TpConnectionContactsByHandleCb callback,gpointer user_data,GDestroyNotify destroy,GObject * weak_object)4525 tp_connection_get_contacts_by_handle (TpConnection *self,
4526 guint n_handles,
4527 const TpHandle *handles,
4528 guint n_features,
4529 const TpContactFeature *features,
4530 TpConnectionContactsByHandleCb callback,
4531 gpointer user_data,
4532 GDestroyNotify destroy,
4533 GObject *weak_object)
4534 {
4535 ContactFeatureFlags feature_flags = 0;
4536 ContactsContext *context;
4537 GPtrArray *contacts;
4538
4539 /* As an implementation detail, this method actually starts working slightly
4540 * before we're officially ready. We use this to get the TpContact for the
4541 * Connection. */
4542 g_return_if_fail (self->priv->ready_enough_for_contacts);
4543
4544 g_return_if_fail (tp_proxy_get_invalidated (self) == NULL);
4545 g_return_if_fail (n_handles >= 1);
4546 g_return_if_fail (handles != NULL);
4547 g_return_if_fail (n_features == 0 || features != NULL);
4548 g_return_if_fail (callback != NULL);
4549
4550 if (!get_feature_flags (n_features, features, &feature_flags))
4551 return;
4552
4553 context = contacts_context_new (self, n_handles, feature_flags,
4554 CB_BY_HANDLE, user_data, destroy, weak_object);
4555 context->callback.by_handle = callback;
4556
4557 g_array_append_vals (context->handles, handles, n_handles);
4558
4559 contacts = lookup_all_contacts (context);
4560
4561 if (contacts != NULL)
4562 {
4563 /* We have already held (and possibly inspected) handles, so we can
4564 * skip that. */
4565
4566 g_ptr_array_foreach (contacts, (GFunc) g_object_ref, NULL);
4567 tp_g_ptr_array_extend (context->contacts, contacts);
4568
4569 contacts_context_remove_common_features (context);
4570
4571 /* We do need to retrieve any features that aren't there yet, though. */
4572 if (tp_proxy_has_interface_by_id (self,
4573 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
4574 {
4575 g_queue_push_head (&context->todo, contacts_get_attributes);
4576 }
4577
4578 contacts_context_queue_features (context);
4579
4580 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
4581 contacts_context_idle_continue, context, contacts_context_unref);
4582
4583 g_ptr_array_unref (contacts);
4584 return;
4585 }
4586
4587 if (tp_proxy_has_interface_by_id (self,
4588 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
4589 {
4590 /* we support the Contacts interface, so we can hold the handles and
4591 * simultaneously inspect them. After that, we'll fill in any
4592 * features that are necessary (this becomes a no-op if Contacts
4593 * will give us everything). */
4594 g_queue_push_head (&context->todo, contacts_get_attributes);
4595 contacts_context_queue_features (context);
4596 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
4597 contacts_context_idle_continue, context, contacts_context_unref);
4598 return;
4599 }
4600
4601 /* if we haven't already returned, we're on the slow path */
4602 DEBUG ("slow path");
4603
4604 /* Before we return anything we'll want to inspect the handles */
4605 g_queue_push_head (&context->todo, contacts_inspect);
4606
4607 /* After that we'll get the features */
4608 contacts_context_queue_features (context);
4609
4610 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4611 /* but first, we need to hold onto them */
4612 tp_connection_hold_handles (self, -1,
4613 TP_HANDLE_TYPE_CONTACT, n_handles, handles,
4614 contacts_held_handles, context, contacts_context_unref, weak_object);
4615 G_GNUC_END_IGNORE_DEPRECATIONS
4616 }
4617
4618
4619 /**
4620 * tp_connection_upgrade_contacts:
4621 * @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
4622 * feature prepared
4623 * @n_contacts: The number of contacts in @contacts (must be at least 1)
4624 * @contacts: (array length=n_contacts): An array of #TpContact objects
4625 * associated with @self
4626 * @n_features: The number of features in @features (must be at least 1)
4627 * @features: (array length=n_features): An array of features that must be
4628 * ready for use (if supported) before the callback is called
4629 * @callback: A user callback to call when the contacts are ready
4630 * @user_data: Data to pass to the callback
4631 * @destroy: Called to destroy @user_data either after @callback has been
4632 * called, or if the operation is cancelled
4633 * @weak_object: (allow-none): An object to pass to the callback, which will be
4634 * weakly referenced; if this object is destroyed, the operation will be
4635 * cancelled
4636 *
4637 * Given several #TpContact objects, make asynchronous method calls
4638 * ensure that all the features specified in @features are ready for use
4639 * (if they are supported at all).
4640 *
4641 * It is not an error to put features in @features even if the connection
4642 * manager doesn't support them - users of this method should have a static
4643 * list of features they would like to use if possible, and use it for all
4644 * connection managers.
4645 *
4646 * Since: 0.7.18
4647 * Deprecated: Use tp_connection_upgrade_contacts_async() instead.
4648 */
4649 void
tp_connection_upgrade_contacts(TpConnection * self,guint n_contacts,TpContact * const * contacts,guint n_features,const TpContactFeature * features,TpConnectionUpgradeContactsCb callback,gpointer user_data,GDestroyNotify destroy,GObject * weak_object)4650 tp_connection_upgrade_contacts (TpConnection *self,
4651 guint n_contacts,
4652 TpContact * const *contacts,
4653 guint n_features,
4654 const TpContactFeature *features,
4655 TpConnectionUpgradeContactsCb callback,
4656 gpointer user_data,
4657 GDestroyNotify destroy,
4658 GObject *weak_object)
4659 {
4660 ContactFeatureFlags feature_flags = 0;
4661 ContactsContext *context;
4662 guint i;
4663
4664 /* As an implementation detail, this method actually starts working slightly
4665 * before we're officially ready. We use this to get the TpContact for the
4666 * Connection. */
4667 g_return_if_fail (self->priv->ready_enough_for_contacts);
4668 g_return_if_fail (n_contacts >= 1);
4669 g_return_if_fail (contacts != NULL);
4670 g_return_if_fail (n_features == 0 || features != NULL);
4671 g_return_if_fail (callback != NULL);
4672
4673 for (i = 0; i < n_contacts; i++)
4674 {
4675 g_return_if_fail (contacts[i]->priv->connection == self);
4676 g_return_if_fail (contacts[i]->priv->identifier != NULL);
4677 }
4678
4679 if (!get_feature_flags (n_features, features, &feature_flags))
4680 return;
4681
4682 context = contacts_context_new (self, n_contacts, feature_flags,
4683 CB_UPGRADE, user_data, destroy, weak_object);
4684 context->callback.upgrade = callback;
4685
4686 for (i = 0; i < n_contacts; i++)
4687 {
4688 g_ptr_array_add (context->contacts, g_object_ref (contacts[i]));
4689 g_array_append_val (context->handles, contacts[i]->priv->handle);
4690 }
4691
4692 g_assert (context->handles->len == n_contacts);
4693
4694 contacts_context_remove_common_features (context);
4695
4696 if (tp_proxy_has_interface_by_id (self,
4697 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
4698 {
4699 g_queue_push_head (&context->todo, contacts_get_attributes);
4700 }
4701
4702 contacts_context_queue_features (context);
4703
4704 /* use an idle to make sure the callback is called after we return,
4705 * even if all the contacts actually have all the features, just to be
4706 * consistent */
4707 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
4708 contacts_context_idle_continue, context, contacts_context_unref);
4709 }
4710
4711
4712 static void
contacts_requested_one_handle(TpConnection * connection,TpHandleType handle_type,guint n_handles,const TpHandle * handles,const gchar * const * ids,const GError * error,gpointer user_data,GObject * weak_object)4713 contacts_requested_one_handle (TpConnection *connection,
4714 TpHandleType handle_type,
4715 guint n_handles,
4716 const TpHandle *handles,
4717 const gchar * const *ids,
4718 const GError *error,
4719 gpointer user_data,
4720 GObject *weak_object)
4721 {
4722 ContactsContext *c = user_data;
4723
4724 if (error == NULL)
4725 {
4726 TpContact *contact;
4727
4728 g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
4729 /* -1 because NULL terminator is explicit */
4730 g_assert (c->next_index < c->request_ids->len - 1);
4731
4732 g_assert (n_handles == 1);
4733 g_assert (handles[0] != 0);
4734
4735 contact = tp_contact_ensure (connection, handles[0]);
4736 g_array_append_val (c->handles, handles[0]);
4737 g_ptr_array_add (c->contacts, contact);
4738 c->next_index++;
4739 }
4740 else if (error->domain == TP_ERROR &&
4741 (error->code == TP_ERROR_INVALID_HANDLE ||
4742 error->code == TP_ERROR_NOT_AVAILABLE ||
4743 error->code == TP_ERROR_INVALID_ARGUMENT))
4744 {
4745 g_hash_table_insert (c->request_errors,
4746 g_ptr_array_index (c->request_ids, c->next_index),
4747 g_error_copy (error));
4748 /* shift the rest of the IDs down one and do not increment next_index */
4749 g_ptr_array_remove_index (c->request_ids, c->next_index);
4750 }
4751 else
4752 {
4753 contacts_context_fail (c, error);
4754 return;
4755 }
4756
4757 contacts_context_continue (c);
4758 }
4759
4760
4761 static void
contacts_request_one_handle(ContactsContext * c)4762 contacts_request_one_handle (ContactsContext *c)
4763 {
4764 const gchar *ids[] = { NULL, NULL };
4765
4766 ids[0] = g_ptr_array_index (c->request_ids, c->next_index);
4767 g_assert (ids[0] != NULL);
4768
4769 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4770 c->refcount++;
4771 tp_connection_request_handles (c->connection, -1,
4772 TP_HANDLE_TYPE_CONTACT, ids,
4773 contacts_requested_one_handle, c, contacts_context_unref,
4774 c->weak_object);
4775 G_GNUC_END_IGNORE_DEPRECATIONS
4776 }
4777
4778
4779 static void
contacts_requested_handles(TpConnection * connection,TpHandleType handle_type,guint n_handles,const TpHandle * handles,const gchar * const * ids,const GError * error,gpointer user_data,GObject * weak_object)4780 contacts_requested_handles (TpConnection *connection,
4781 TpHandleType handle_type,
4782 guint n_handles,
4783 const TpHandle *handles,
4784 const gchar * const *ids,
4785 const GError *error,
4786 gpointer user_data,
4787 GObject *weak_object)
4788 {
4789 ContactsContext *c = user_data;
4790
4791 g_assert (handle_type == TP_HANDLE_TYPE_CONTACT);
4792 g_assert (weak_object == c->weak_object);
4793
4794 if (error == NULL)
4795 {
4796 guint i;
4797
4798 for (i = 0; i < n_handles; i++)
4799 {
4800 TpContact *contact = tp_contact_ensure (connection, handles[i]);
4801
4802 g_array_append_val (c->handles, handles[i]);
4803 g_ptr_array_add (c->contacts, contact);
4804 }
4805 }
4806 else if (error->domain == TP_ERROR &&
4807 (error->code == TP_ERROR_INVALID_HANDLE ||
4808 error->code == TP_ERROR_NOT_AVAILABLE ||
4809 error->code == TP_ERROR_INVALID_ARGUMENT))
4810 {
4811 /* One of the strings is bad. We don't know which, so split them. */
4812 guint i;
4813
4814 DEBUG ("A handle was bad, trying to recover: %s %u: %s",
4815 g_quark_to_string (error->domain), error->code, error->message);
4816
4817 /* -1 because NULL terminator is explicit */
4818 for (i = 0; i < c->request_ids->len - 1; i++)
4819 {
4820 g_queue_push_head (&c->todo, contacts_request_one_handle);
4821 }
4822
4823 g_assert (c->next_index == 0);
4824 }
4825 else
4826 {
4827 DEBUG ("RequestHandles failed: %s %u: %s",
4828 g_quark_to_string (error->domain), error->code, error->message);
4829 contacts_context_fail (c, error);
4830 return;
4831 }
4832
4833 contacts_context_continue (c);
4834 }
4835
4836
4837 /**
4838 * tp_connection_get_contacts_by_id:
4839 * @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
4840 * feature prepared
4841 * @n_ids: The number of IDs in @ids (must be at least 1)
4842 * @ids: (array length=n_ids) (transfer none): An array of strings representing
4843 * the desired contacts by their
4844 * identifiers in the IM protocol (XMPP JIDs, SIP URIs, MSN Passports,
4845 * AOL screen-names etc.)
4846 * @n_features: The number of features in @features (may be 0)
4847 * @features: (array length=n_features) (allow-none): An array of features
4848 * that must be ready for use (if supported)
4849 * before the callback is called (may be %NULL if @n_features is 0)
4850 * @callback: A user callback to call when the contacts are ready
4851 * @user_data: Data to pass to the callback
4852 * @destroy: Called to destroy @user_data either after @callback has been
4853 * called, or if the operation is cancelled
4854 * @weak_object: (allow-none): An object to pass to the callback, which will
4855 * be weakly referenced; if this object is destroyed, the operation will be
4856 * cancelled
4857 *
4858 * Create a number of #TpContact objects and make asynchronous method calls
4859 * to obtain their handles and ensure that all the features specified in
4860 * @features are ready for use (if they are supported at all).
4861 *
4862 * It is not an error to put features in @features even if the connection
4863 * manager doesn't support them - users of this method should have a static
4864 * list of features they would like to use if possible, and use it for all
4865 * connection managers.
4866 *
4867 * Since: 0.7.18
4868 * Deprecated: Use tp_connection_dup_contact_by_id_async() instead.
4869 */
4870 void
tp_connection_get_contacts_by_id(TpConnection * self,guint n_ids,const gchar * const * ids,guint n_features,const TpContactFeature * features,TpConnectionContactsByIdCb callback,gpointer user_data,GDestroyNotify destroy,GObject * weak_object)4871 tp_connection_get_contacts_by_id (TpConnection *self,
4872 guint n_ids,
4873 const gchar * const *ids,
4874 guint n_features,
4875 const TpContactFeature *features,
4876 TpConnectionContactsByIdCb callback,
4877 gpointer user_data,
4878 GDestroyNotify destroy,
4879 GObject *weak_object)
4880 {
4881 ContactFeatureFlags feature_flags = 0;
4882 ContactsContext *context;
4883 guint i;
4884
4885 g_return_if_fail (tp_proxy_is_prepared (self,
4886 TP_CONNECTION_FEATURE_CONNECTED));
4887 g_return_if_fail (n_ids >= 1);
4888 g_return_if_fail (ids != NULL);
4889 g_return_if_fail (ids[0] != NULL);
4890 g_return_if_fail (n_features == 0 || features != NULL);
4891 g_return_if_fail (callback != NULL);
4892
4893 if (!get_feature_flags (n_features, features, &feature_flags))
4894 return;
4895
4896 context = contacts_context_new (self, n_ids, feature_flags,
4897 CB_BY_ID, user_data, destroy, weak_object);
4898 context->callback.by_id = callback;
4899 context->request_errors = g_hash_table_new_full (g_str_hash, g_str_equal,
4900 g_free, (GDestroyNotify) g_error_free);
4901
4902 context->request_ids = g_ptr_array_sized_new (n_ids);
4903
4904 for (i = 0; i < n_ids; i++)
4905 {
4906 g_return_if_fail (ids[i] != NULL);
4907 g_ptr_array_add (context->request_ids, g_strdup (ids[i]));
4908 }
4909
4910 g_ptr_array_add (context->request_ids, NULL);
4911
4912 /* set up the queue of feature introspection */
4913
4914 if (tp_proxy_has_interface_by_id (self,
4915 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
4916 {
4917 g_queue_push_head (&context->todo, contacts_get_attributes);
4918 }
4919 else
4920 {
4921 g_queue_push_head (&context->todo, contacts_inspect);
4922 }
4923
4924 contacts_context_queue_features (context);
4925
4926 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4927 /* but first, we need to get the handles in the first place */
4928 tp_connection_request_handles (self, -1,
4929 TP_HANDLE_TYPE_CONTACT,
4930 (const gchar * const *) context->request_ids->pdata,
4931 contacts_requested_handles, context, contacts_context_unref,
4932 weak_object);
4933 G_GNUC_END_IGNORE_DEPRECATIONS
4934 }
4935
4936 static void
got_contact_by_id_fallback_cb(TpConnection * self,guint n_contacts,TpContact * const * contacts,const gchar * const * requested_ids,GHashTable * failed_id_errors,const GError * error,gpointer user_data,GObject * weak_object)4937 got_contact_by_id_fallback_cb (TpConnection *self,
4938 guint n_contacts,
4939 TpContact * const *contacts,
4940 const gchar * const *requested_ids,
4941 GHashTable *failed_id_errors,
4942 const GError *error,
4943 gpointer user_data,
4944 GObject *weak_object)
4945 {
4946 const gchar *id = user_data;
4947 GSimpleAsyncResult *result = (GSimpleAsyncResult *) weak_object;
4948 GError *e = NULL;
4949
4950 if (error != NULL)
4951 {
4952 g_simple_async_result_set_from_error (result, error);
4953 }
4954 else if (g_hash_table_size (failed_id_errors) > 0)
4955 {
4956 e = g_hash_table_lookup (failed_id_errors, id);
4957
4958 if (e == NULL)
4959 {
4960 g_set_error (&e, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
4961 "We requested 1 id, and got an error for another id - Broken CM");
4962 g_simple_async_result_take_error (result, e);
4963 }
4964 else
4965 {
4966 g_simple_async_result_set_from_error (result, e);
4967 }
4968 }
4969 else if (n_contacts != 1 || contacts[0] == NULL)
4970 {
4971 g_set_error (&e, TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
4972 "We requested 1 id, but no contacts and no error - Broken CM");
4973 g_simple_async_result_take_error (result, e);
4974 }
4975 else
4976 {
4977 g_simple_async_result_set_op_res_gpointer (result,
4978 g_object_ref (contacts[0]), g_object_unref);
4979 }
4980
4981 g_simple_async_result_complete_in_idle (result);
4982 g_object_unref (result);
4983 }
4984
4985 /**
4986 * tp_connection_dup_contact_by_id_async:
4987 * @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
4988 * feature prepared
4989 * @id: A strings representing the desired contact by its
4990 * identifier in the IM protocol (an XMPP JID, SIP URI, MSN Passport,
4991 * AOL screen-name etc.)
4992 * @n_features: The number of features in @features (may be 0)
4993 * @features: (array length=n_features) (allow-none): An array of features
4994 * that must be ready for use (if supported)
4995 * before the callback is called (may be %NULL if @n_features is 0)
4996 * @callback: A user callback to call when the contact is ready
4997 * @user_data: Data to pass to the callback
4998 *
4999 * Create a #TpContact object and make any asynchronous method calls necessary
5000 * to ensure that all the features specified in @features are ready for use
5001 * (if they are supported at all).
5002 *
5003 * It is not an error to put features in @features even if the connection
5004 * manager doesn't support them - users of this method should have a static
5005 * list of features they would like to use if possible, and use it for all
5006 * connection managers.
5007 *
5008 * Since: 0.19.0
5009 */
5010 void
tp_connection_dup_contact_by_id_async(TpConnection * self,const gchar * id,guint n_features,const TpContactFeature * features,GAsyncReadyCallback callback,gpointer user_data)5011 tp_connection_dup_contact_by_id_async (TpConnection *self,
5012 const gchar *id,
5013 guint n_features,
5014 const TpContactFeature *features,
5015 GAsyncReadyCallback callback,
5016 gpointer user_data)
5017 {
5018 GSimpleAsyncResult *result;
5019
5020 result = g_simple_async_result_new ((GObject *) self, callback, user_data,
5021 tp_connection_dup_contact_by_id_async);
5022
5023 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
5024 tp_connection_get_contacts_by_id (self,
5025 1, &id,
5026 n_features, features,
5027 got_contact_by_id_fallback_cb,
5028 g_strdup (id), g_free, G_OBJECT (result));
5029 G_GNUC_END_IGNORE_DEPRECATIONS
5030 }
5031
5032 /**
5033 * tp_connection_dup_contact_by_id_finish:
5034 * @self: a #TpConnection
5035 * @result: a #GAsyncResult
5036 * @error: a #GError to fill
5037 *
5038 * Finishes tp_connection_dup_contact_by_id_async().
5039 *
5040 * Returns: (transfer full): a #TpContact or %NULL on error.
5041 * Since: 0.19.0
5042 */
5043 TpContact *
tp_connection_dup_contact_by_id_finish(TpConnection * self,GAsyncResult * result,GError ** error)5044 tp_connection_dup_contact_by_id_finish (TpConnection *self,
5045 GAsyncResult *result,
5046 GError **error)
5047 {
5048 _tp_implement_finish_return_copy_pointer (self,
5049 tp_connection_dup_contact_by_id_async, g_object_ref);
5050 }
5051
5052 static void
upgrade_contacts_fallback_cb(TpConnection * connection,guint n_contacts,TpContact * const * contacts,const GError * error,gpointer user_data,GObject * weak_object)5053 upgrade_contacts_fallback_cb (TpConnection *connection,
5054 guint n_contacts,
5055 TpContact * const *contacts,
5056 const GError *error,
5057 gpointer user_data,
5058 GObject *weak_object)
5059 {
5060 GSimpleAsyncResult *result = user_data;
5061 GPtrArray *contacts_array;
5062 guint i;
5063
5064 contacts_array = g_ptr_array_new_full (n_contacts, g_object_unref);
5065 for (i = 0; i < n_contacts; i++)
5066 g_ptr_array_add (contacts_array, g_object_ref (contacts[i]));
5067
5068 g_simple_async_result_set_op_res_gpointer (result, contacts_array,
5069 (GDestroyNotify) g_ptr_array_unref);
5070
5071 if (error != NULL)
5072 g_simple_async_result_set_from_error (result, error);
5073
5074 g_simple_async_result_complete_in_idle (result);
5075 }
5076
5077 /**
5078 * tp_connection_upgrade_contacts_async:
5079 * @self: A connection, which must have the %TP_CONNECTION_FEATURE_CONNECTED
5080 * feature prepared
5081 * @n_contacts: The number of contacts in @contacts (must be at least 1)
5082 * @contacts: (array length=n_contacts): An array of #TpContact objects
5083 * associated with @self
5084 * @n_features: The number of features in @features (must be at least 1)
5085 * @features: (array length=n_features): An array of features that must be
5086 * ready for use (if supported) before the callback is called
5087 * @callback: A user callback to call when the contacts are ready
5088 * @user_data: Data to pass to the callback
5089 *
5090 * Given several #TpContact objects, make asynchronous method calls
5091 * ensure that all the features specified in @features are ready for use
5092 * (if they are supported at all).
5093 *
5094 * It is not an error to put features in @features even if the connection
5095 * manager doesn't support them - users of this method should have a static
5096 * list of features they would like to use if possible, and use it for all
5097 * connection managers.
5098 *
5099 * Since: 0.19.0
5100 */
5101 void
tp_connection_upgrade_contacts_async(TpConnection * self,guint n_contacts,TpContact * const * contacts,guint n_features,const TpContactFeature * features,GAsyncReadyCallback callback,gpointer user_data)5102 tp_connection_upgrade_contacts_async (TpConnection *self,
5103 guint n_contacts,
5104 TpContact * const *contacts,
5105 guint n_features,
5106 const TpContactFeature *features,
5107 GAsyncReadyCallback callback,
5108 gpointer user_data)
5109 {
5110 GSimpleAsyncResult *result;
5111
5112 result = g_simple_async_result_new ((GObject *) self, callback, user_data,
5113 tp_connection_upgrade_contacts_async);
5114
5115 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
5116 tp_connection_upgrade_contacts (self,
5117 n_contacts, contacts,
5118 n_features, features,
5119 upgrade_contacts_fallback_cb,
5120 result, g_object_unref, NULL);
5121 G_GNUC_END_IGNORE_DEPRECATIONS
5122 }
5123
5124 /**
5125 * tp_connection_upgrade_contacts_finish:
5126 * @self: a #TpConnection
5127 * @result: a #GAsyncResult
5128 * @contacts: (element-type TelepathyGLib.Contact) (transfer container) (out) (allow-none):
5129 * a location to set a #GPtrArray of upgraded #TpContact, or %NULL.
5130 * @error: a #GError to fill
5131 *
5132 * Finishes tp_connection_upgrade_contacts_async().
5133 *
5134 * Returns: %TRUE on success, %FALSE otherwise.
5135 * Since: 0.19.0
5136 */
5137 gboolean
tp_connection_upgrade_contacts_finish(TpConnection * self,GAsyncResult * result,GPtrArray ** contacts,GError ** error)5138 tp_connection_upgrade_contacts_finish (TpConnection *self,
5139 GAsyncResult *result,
5140 GPtrArray **contacts,
5141 GError **error)
5142 {
5143 _tp_implement_finish_copy_pointer (self,
5144 tp_connection_upgrade_contacts_async, g_ptr_array_ref, contacts);
5145 }
5146
5147 void
_tp_contact_set_is_blocked(TpContact * self,gboolean is_blocked)5148 _tp_contact_set_is_blocked (TpContact *self,
5149 gboolean is_blocked)
5150 {
5151 if (self == NULL)
5152 return;
5153
5154 self->priv->has_features |= CONTACT_FEATURE_FLAG_CONTACT_BLOCKING;
5155
5156 if (self->priv->is_blocked == is_blocked)
5157 return;
5158
5159 self->priv->is_blocked = is_blocked;
5160
5161 g_object_notify ((GObject *) self, "is-blocked");
5162 }
5163
5164 /**
5165 * tp_contact_is_blocked:
5166 * @self: a #TpContact
5167 *
5168 * <!-- -->
5169
5170 * Returns: the value of #TpContact:is-blocked.
5171 *
5172 * Since: 0.17.0
5173 */
5174 gboolean
tp_contact_is_blocked(TpContact * self)5175 tp_contact_is_blocked (TpContact *self)
5176 {
5177 g_return_val_if_fail (TP_IS_CONTACT (self), FALSE);
5178
5179 return self->priv->is_blocked;
5180 }
5181