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, &parameters, &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