]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact.c
auth-client: add some more debugging
[empathy.git] / libempathy / empathy-contact.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /*
3  * Copyright (C) 2007-2009 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25
26 #include <glib/gi18n-lib.h>
27
28 #include <telepathy-glib/account-manager.h>
29 #include <telepathy-glib/interfaces.h>
30 #include <telepathy-glib/util.h>
31 #include <telepathy-yell/telepathy-yell.h>
32
33 #include <telepathy-logger/log-manager.h>
34
35 #include <folks/folks.h>
36 #include <folks/folks-telepathy.h>
37
38 #ifdef HAVE_GEOCODE
39 #include <geocode-glib/geocode-glib.h>
40 #endif
41
42 #include "empathy-contact.h"
43 #include "empathy-camera-monitor.h"
44 #include "empathy-individual-manager.h"
45 #include "empathy-utils.h"
46 #include "empathy-enum-types.h"
47 #include "empathy-location.h"
48
49 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
50 #include "empathy-debug.h"
51
52 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContact)
53 typedef struct {
54   TpContact *tp_contact;
55   TpAccount *account;
56   FolksPersona *persona;
57   gchar *id;
58   gchar *alias;
59   gchar *logged_alias;
60   EmpathyAvatar *avatar;
61   TpConnectionPresenceType presence;
62   guint handle;
63   EmpathyCapabilities capabilities;
64   gboolean is_user;
65   guint hash;
66   /* Location is composed of string keys and GValues.
67    * Example: a "city" key would have "Helsinki" as string GValue,
68    *          a "latitude" would have 65.0 as double GValue.
69    *
70    * This is a super set of the location stored in TpContact as we can try add
71    * more fields by searching the address using geoclue.
72    */
73   GHashTable *location;
74   GeeHashSet *groups;
75   gchar **client_types;
76 } EmpathyContactPriv;
77
78 static void contact_finalize (GObject *object);
79 static void contact_get_property (GObject *object, guint param_id,
80     GValue *value, GParamSpec *pspec);
81 static void contact_set_property (GObject *object, guint param_id,
82     const GValue *value, GParamSpec *pspec);
83
84 #ifdef HAVE_GEOCODE
85 static void update_geocode (EmpathyContact *contact);
86 #endif
87
88 static void empathy_contact_set_location (EmpathyContact *contact,
89     GHashTable *location);
90
91 static void contact_set_client_types (EmpathyContact *contact,
92     const gchar * const *types);
93
94 static void set_capabilities_from_tp_caps (EmpathyContact *self,
95     TpCapabilities *caps);
96
97 static void contact_set_avatar (EmpathyContact *contact,
98     EmpathyAvatar *avatar);
99 static void contact_set_avatar_from_tp_contact (EmpathyContact *contact);
100 static gboolean contact_load_avatar_cache (EmpathyContact *contact,
101     const gchar *token);
102
103 G_DEFINE_TYPE (EmpathyContact, empathy_contact, G_TYPE_OBJECT);
104
105 enum
106 {
107   PROP_0,
108   PROP_TP_CONTACT,
109   PROP_ACCOUNT,
110   PROP_PERSONA,
111   PROP_ID,
112   PROP_ALIAS,
113   PROP_LOGGED_ALIAS,
114   PROP_AVATAR,
115   PROP_PRESENCE,
116   PROP_PRESENCE_MESSAGE,
117   PROP_HANDLE,
118   PROP_CAPABILITIES,
119   PROP_IS_USER,
120   PROP_LOCATION,
121   PROP_CLIENT_TYPES
122 };
123
124 enum {
125   PRESENCE_CHANGED,
126   LAST_SIGNAL
127 };
128
129 static guint signals[LAST_SIGNAL];
130
131 /* TpContact* -> EmpathyContact*, both borrowed ref */
132 static GHashTable *contacts_table = NULL;
133
134 static void
135 tp_contact_notify_cb (TpContact *tp_contact,
136                       GParamSpec *param,
137                       GObject *contact)
138 {
139   EmpathyContactPriv *priv = GET_PRIV (contact);
140
141   /* Forward property notifications */
142   if (!tp_strdiff (param->name, "alias"))
143     g_object_notify (contact, "alias");
144   else if (!tp_strdiff (param->name, "presence-type")) {
145     TpConnectionPresenceType presence;
146
147     presence = empathy_contact_get_presence (EMPATHY_CONTACT (contact));
148     g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence,
149       priv->presence);
150     priv->presence = presence;
151     g_object_notify (contact, "presence");
152   }
153   else if (!tp_strdiff (param->name, "identifier"))
154     g_object_notify (contact, "id");
155   else if (!tp_strdiff (param->name, "handle"))
156     g_object_notify (contact, "handle");
157   else if (!tp_strdiff (param->name, "location"))
158     {
159       GHashTable *location;
160
161       location = tp_contact_get_location (tp_contact);
162       /* This will start a geoclue search to find the address if needed */
163       empathy_contact_set_location (EMPATHY_CONTACT (contact), location);
164     }
165   else if (!tp_strdiff (param->name, "capabilities"))
166     {
167       set_capabilities_from_tp_caps (EMPATHY_CONTACT (contact),
168           tp_contact_get_capabilities (tp_contact));
169     }
170   else if (!tp_strdiff (param->name, "avatar-file"))
171     {
172       contact_set_avatar_from_tp_contact (EMPATHY_CONTACT (contact));
173     }
174   else if (!tp_strdiff (param->name, "client-types"))
175     {
176       contact_set_client_types (EMPATHY_CONTACT (contact),
177           tp_contact_get_client_types (tp_contact));
178     }
179 }
180
181 static void
182 folks_persona_notify_cb (FolksPersona *folks_persona,
183                          GParamSpec *param,
184                          GObject *contact)
185 {
186   if (!tp_strdiff (param->name, "presence-message"))
187     g_object_notify (contact, "presence-message");
188 }
189
190 static void
191 contact_dispose (GObject *object)
192 {
193   EmpathyContactPriv *priv = GET_PRIV (object);
194
195   if (priv->tp_contact != NULL)
196     {
197       g_signal_handlers_disconnect_by_func (priv->tp_contact,
198           tp_contact_notify_cb, object);
199     }
200   tp_clear_object (&priv->tp_contact);
201
202   if (priv->account)
203     g_object_unref (priv->account);
204   priv->account = NULL;
205
206   if (priv->persona)
207     {
208       g_signal_handlers_disconnect_by_func (priv->persona,
209           folks_persona_notify_cb, object);
210       g_object_unref (priv->persona);
211     }
212   priv->persona = NULL;
213
214   if (priv->avatar != NULL)
215     {
216       empathy_avatar_unref (priv->avatar);
217       priv->avatar = NULL;
218     }
219
220   if (priv->location != NULL)
221     {
222       g_hash_table_unref (priv->location);
223       priv->location = NULL;
224     }
225
226   G_OBJECT_CLASS (empathy_contact_parent_class)->dispose (object);
227 }
228
229 static void
230 contact_constructed (GObject *object)
231 {
232   EmpathyContact *contact = (EmpathyContact *) object;
233   EmpathyContactPriv *priv = GET_PRIV (contact);
234   GHashTable *location;
235   TpHandle self_handle;
236   TpHandle handle;
237   const gchar * const *client_types;
238
239   if (priv->tp_contact == NULL)
240     return;
241
242   priv->presence = empathy_contact_get_presence (contact);
243
244   location = tp_contact_get_location (priv->tp_contact);
245   if (location != NULL)
246     empathy_contact_set_location (contact, location);
247
248   client_types = tp_contact_get_client_types (priv->tp_contact);
249   if (client_types != NULL)
250     contact_set_client_types (contact, client_types);
251
252   set_capabilities_from_tp_caps (contact,
253       tp_contact_get_capabilities (priv->tp_contact));
254
255   contact_set_avatar_from_tp_contact (contact);
256
257   /* Set is-user property. Note that it could still be the handle is
258    * different from the connection's self handle, in the case the handle
259    * comes from a group interface. */
260   self_handle = tp_connection_get_self_handle (
261       tp_contact_get_connection (priv->tp_contact));
262   handle = tp_contact_get_handle (priv->tp_contact);
263   empathy_contact_set_is_user (contact, self_handle == handle);
264
265   g_signal_connect (priv->tp_contact, "notify",
266     G_CALLBACK (tp_contact_notify_cb), contact);
267 }
268
269 static void
270 empathy_contact_class_init (EmpathyContactClass *class)
271 {
272   GObjectClass *object_class;
273
274   object_class = G_OBJECT_CLASS (class);
275
276   object_class->finalize = contact_finalize;
277   object_class->dispose = contact_dispose;
278   object_class->get_property = contact_get_property;
279   object_class->set_property = contact_set_property;
280   object_class->constructed = contact_constructed;
281
282   g_object_class_install_property (object_class,
283       PROP_TP_CONTACT,
284       g_param_spec_object ("tp-contact",
285         "TpContact",
286         "The TpContact associated with the contact",
287         TP_TYPE_CONTACT,
288         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
289
290   g_object_class_install_property (object_class,
291       PROP_ACCOUNT,
292       g_param_spec_object ("account",
293         "The account",
294         "The account associated with the contact",
295         TP_TYPE_ACCOUNT,
296         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
297
298   g_object_class_install_property (object_class,
299       PROP_PERSONA,
300       g_param_spec_object ("persona",
301         "Persona",
302         "The FolksPersona associated with the contact",
303         FOLKS_TYPE_PERSONA,
304         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
305
306   g_object_class_install_property (object_class,
307       PROP_ID,
308       g_param_spec_string ("id",
309         "Contact id",
310         "String identifying contact",
311         NULL,
312         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
313
314   g_object_class_install_property (object_class,
315       PROP_ALIAS,
316       g_param_spec_string ("alias",
317         "Contact alias",
318         "An alias for the contact",
319         NULL,
320         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
321
322   g_object_class_install_property (object_class,
323       PROP_LOGGED_ALIAS,
324       g_param_spec_string ("logged-alias",
325         "Logged alias",
326         "The alias the user had when a message was logged, "
327         "only set when using empathy_contact_from_tpl_contact()",
328         NULL,
329         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
330
331   g_object_class_install_property (object_class,
332       PROP_AVATAR,
333       g_param_spec_boxed ("avatar",
334         "Avatar image",
335         "The avatar image",
336         EMPATHY_TYPE_AVATAR,
337         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
338
339   g_object_class_install_property (object_class,
340       PROP_PRESENCE,
341       g_param_spec_uint ("presence",
342         "Contact presence",
343         "Presence of contact",
344         TP_CONNECTION_PRESENCE_TYPE_UNSET,
345         NUM_TP_CONNECTION_PRESENCE_TYPES,
346         TP_CONNECTION_PRESENCE_TYPE_UNSET,
347         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
348
349   g_object_class_install_property (object_class,
350       PROP_PRESENCE_MESSAGE,
351       g_param_spec_string ("presence-message",
352         "Contact presence message",
353         "Presence message of contact",
354         NULL,
355         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
356
357   g_object_class_install_property (object_class,
358       PROP_HANDLE,
359       g_param_spec_uint ("handle",
360         "Contact Handle",
361         "The handle of the contact",
362         0,
363         G_MAXUINT,
364         0,
365         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
366
367   g_object_class_install_property (object_class,
368       PROP_CAPABILITIES,
369       g_param_spec_flags ("capabilities",
370         "Contact Capabilities",
371         "Capabilities of the contact",
372         EMPATHY_TYPE_CAPABILITIES,
373         EMPATHY_CAPABILITIES_UNKNOWN,
374         G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
375
376   g_object_class_install_property (object_class,
377       PROP_IS_USER,
378       g_param_spec_boolean ("is-user",
379         "Contact is-user",
380         "Is contact the user",
381         FALSE,
382         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
383
384
385   g_object_class_install_property (object_class,
386       PROP_LOCATION,
387       g_param_spec_boxed ("location",
388         "Contact location",
389         "Physical location of the contact",
390         G_TYPE_HASH_TABLE,
391         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
392
393   g_object_class_install_property (object_class,
394       PROP_CLIENT_TYPES,
395       g_param_spec_boxed ("client-types",
396         "Contact client types",
397         "Client types of the contact",
398         G_TYPE_STRV,
399         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
400
401   signals[PRESENCE_CHANGED] =
402     g_signal_new ("presence-changed",
403                   G_TYPE_FROM_CLASS (class),
404                   G_SIGNAL_RUN_LAST,
405                   0,
406                   NULL, NULL,
407                   g_cclosure_marshal_generic,
408                   G_TYPE_NONE,
409                   2, G_TYPE_UINT,
410                   G_TYPE_UINT);
411
412   g_type_class_add_private (object_class, sizeof (EmpathyContactPriv));
413 }
414
415 static void
416 empathy_contact_init (EmpathyContact *contact)
417 {
418   EmpathyContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
419     EMPATHY_TYPE_CONTACT, EmpathyContactPriv);
420
421   contact->priv = priv;
422
423   priv->location = NULL;
424   priv->client_types = NULL;
425   priv->groups = NULL;
426 }
427
428 static void
429 contact_finalize (GObject *object)
430 {
431   EmpathyContactPriv *priv;
432
433   priv = GET_PRIV (object);
434
435   DEBUG ("finalize: %p", object);
436
437   g_clear_object (&priv->groups);
438   g_free (priv->alias);
439   g_free (priv->id);
440   g_strfreev (priv->client_types);
441
442   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
443 }
444
445 static void
446 empathy_contact_set_capabilities (EmpathyContact *contact,
447                                   EmpathyCapabilities capabilities)
448 {
449   EmpathyContactPriv *priv;
450
451   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
452
453   priv = GET_PRIV (contact);
454
455   if (priv->capabilities == capabilities)
456     return;
457
458   priv->capabilities = capabilities;
459
460   g_object_notify (G_OBJECT (contact), "capabilities");
461 }
462
463 static void
464 empathy_contact_set_id (EmpathyContact *contact,
465                         const gchar *id)
466 {
467   EmpathyContactPriv *priv;
468
469   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
470   g_return_if_fail (id != NULL);
471
472   priv = GET_PRIV (contact);
473
474   /* We temporally ref the contact because it could be destroyed
475    * during the signal emition */
476   g_object_ref (contact);
477   if (tp_strdiff (id, priv->id))
478     {
479       g_free (priv->id);
480       priv->id = g_strdup (id);
481
482       g_object_notify (G_OBJECT (contact), "id");
483       if (EMP_STR_EMPTY (priv->alias))
484           g_object_notify (G_OBJECT (contact), "alias");
485     }
486
487   g_object_unref (contact);
488 }
489
490 static void
491 empathy_contact_set_presence (EmpathyContact *contact,
492                               TpConnectionPresenceType presence)
493 {
494   EmpathyContactPriv *priv;
495   TpConnectionPresenceType old_presence;
496
497   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
498
499   priv = GET_PRIV (contact);
500
501   if (presence == priv->presence)
502     return;
503
504   old_presence = priv->presence;
505   priv->presence = presence;
506
507   g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
508
509   g_object_notify (G_OBJECT (contact), "presence");
510 }
511
512 static void
513 empathy_contact_set_presence_message (EmpathyContact *contact,
514                                       const gchar *message)
515 {
516   EmpathyContactPriv *priv = GET_PRIV (contact);
517
518   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
519
520   if (priv->persona != NULL)
521     {
522       folks_presence_details_set_presence_message (
523           FOLKS_PRESENCE_DETAILS (priv->persona), message);
524     }
525 }
526
527 static void
528 empathy_contact_set_handle (EmpathyContact *contact,
529                             guint handle)
530 {
531   EmpathyContactPriv *priv;
532
533   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
534
535   priv = GET_PRIV (contact);
536
537   g_object_ref (contact);
538   if (handle != priv->handle)
539     {
540       priv->handle = handle;
541       g_object_notify (G_OBJECT (contact), "handle");
542     }
543   g_object_unref (contact);
544 }
545
546 static void
547 contact_get_property (GObject *object,
548                       guint param_id,
549                       GValue *value,
550                       GParamSpec *pspec)
551 {
552   EmpathyContact *contact = EMPATHY_CONTACT (object);
553
554   switch (param_id)
555     {
556       case PROP_TP_CONTACT:
557         g_value_set_object (value, empathy_contact_get_tp_contact (contact));
558         break;
559       case PROP_ACCOUNT:
560         g_value_set_object (value, empathy_contact_get_account (contact));
561         break;
562       case PROP_PERSONA:
563         g_value_set_object (value, empathy_contact_get_persona (contact));
564         break;
565       case PROP_ID:
566         g_value_set_string (value, empathy_contact_get_id (contact));
567         break;
568       case PROP_ALIAS:
569         g_value_set_string (value, empathy_contact_get_alias (contact));
570         break;
571       case PROP_LOGGED_ALIAS:
572         g_value_set_string (value, empathy_contact_get_logged_alias (contact));
573         break;
574       case PROP_AVATAR:
575         g_value_set_boxed (value, empathy_contact_get_avatar (contact));
576         break;
577       case PROP_PRESENCE:
578         g_value_set_uint (value, empathy_contact_get_presence (contact));
579         break;
580       case PROP_PRESENCE_MESSAGE:
581         g_value_set_string (value, empathy_contact_get_presence_message (contact));
582         break;
583       case PROP_HANDLE:
584         g_value_set_uint (value, empathy_contact_get_handle (contact));
585         break;
586       case PROP_CAPABILITIES:
587         g_value_set_flags (value, empathy_contact_get_capabilities (contact));
588         break;
589       case PROP_IS_USER:
590         g_value_set_boolean (value, empathy_contact_is_user (contact));
591         break;
592       default:
593         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
594         break;
595     };
596 }
597
598 static void
599 contact_set_property (GObject *object,
600                       guint param_id,
601                       const GValue *value,
602                       GParamSpec *pspec)
603 {
604   EmpathyContact *contact = EMPATHY_CONTACT (object);
605   EmpathyContactPriv *priv = GET_PRIV (object);
606
607   switch (param_id)
608     {
609       case PROP_TP_CONTACT:
610         priv->tp_contact = g_value_dup_object (value);
611         break;
612       case PROP_ACCOUNT:
613         g_assert (priv->account == NULL);
614         priv->account = g_value_dup_object (value);
615         break;
616       case PROP_PERSONA:
617         empathy_contact_set_persona (contact, g_value_get_object (value));
618         break;
619       case PROP_ID:
620         empathy_contact_set_id (contact, g_value_get_string (value));
621         break;
622       case PROP_ALIAS:
623         empathy_contact_set_alias (contact, g_value_get_string (value));
624         break;
625       case PROP_LOGGED_ALIAS:
626         g_assert (priv->logged_alias == NULL);
627         priv->logged_alias = g_value_dup_string (value);
628         break;
629       case PROP_PRESENCE:
630         empathy_contact_set_presence (contact, g_value_get_uint (value));
631         break;
632       case PROP_PRESENCE_MESSAGE:
633         empathy_contact_set_presence_message (contact, g_value_get_string (value));
634         break;
635       case PROP_HANDLE:
636         empathy_contact_set_handle (contact, g_value_get_uint (value));
637         break;
638       case PROP_CAPABILITIES:
639         empathy_contact_set_capabilities (contact, g_value_get_flags (value));
640         break;
641       case PROP_IS_USER:
642         empathy_contact_set_is_user (contact, g_value_get_boolean (value));
643         break;
644       default:
645         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
646         break;
647     };
648 }
649
650 static void
651 remove_tp_contact (gpointer data,
652     GObject *object)
653 {
654   g_hash_table_remove (contacts_table, data);
655 }
656
657 static EmpathyContact *
658 empathy_contact_new (TpContact *tp_contact)
659 {
660   EmpathyContact *retval;
661
662   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
663
664   retval = g_object_new (EMPATHY_TYPE_CONTACT,
665       "tp-contact", tp_contact,
666       NULL);
667
668   g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact);
669
670   return retval;
671 }
672
673 typedef struct
674 {
675   TplEntity *entity;
676   TpAccount *account;
677 } FindContactData;
678
679 static gboolean
680 contact_is_tpl_entity (gpointer key,
681     gpointer value,
682     gpointer user_data)
683 {
684   EmpathyContact *contact = value;
685   FindContactData *data = user_data;
686   TpAccount *account = empathy_contact_get_account (contact);
687   const gchar *path = NULL;
688
689   if (account != NULL)
690     path = tp_proxy_get_object_path (account);
691
692   return !tp_strdiff (empathy_contact_get_id (contact),
693               tpl_entity_get_identifier (data->entity)) &&
694          !tp_strdiff (tp_proxy_get_object_path (data->account), path);
695 }
696
697 static void
698 get_contacts_cb (TpConnection *connection,
699     guint n_contacts,
700     TpContact * const *contacts,
701     const gchar * const *requested_ids,
702     GHashTable *failed_id_errors,
703     const GError *error,
704     gpointer user_data,
705     GObject *weak_object)
706 {
707   EmpathyContact *self = (EmpathyContact *) weak_object;
708   EmpathyContactPriv *priv = GET_PRIV (self);
709   TpContact *tp_contact;
710
711   if (n_contacts != 1)
712     return;
713
714   tp_contact = contacts[0];
715
716   g_return_if_fail (priv->tp_contact == NULL);
717   priv->tp_contact = g_object_ref (tp_contact);
718   g_object_notify (G_OBJECT (self), "tp-contact");
719
720   /* Update capabilities now that we have a TpContact */
721   set_capabilities_from_tp_caps (self,
722       tp_contact_get_capabilities (tp_contact));
723 }
724
725 EmpathyContact *
726 empathy_contact_from_tpl_contact (TpAccount *account,
727     TplEntity *tpl_entity)
728 {
729   EmpathyContact *retval;
730   gboolean is_user;
731   EmpathyContact *existing_contact = NULL;
732
733   g_return_val_if_fail (TPL_IS_ENTITY (tpl_entity), NULL);
734
735   if (contacts_table != NULL)
736     {
737       FindContactData data;
738
739       data.entity = tpl_entity;
740       data.account = account;
741
742       existing_contact = g_hash_table_find (contacts_table,
743         contact_is_tpl_entity, &data);
744     }
745
746   if (existing_contact != NULL)
747     {
748       retval = g_object_new (EMPATHY_TYPE_CONTACT,
749           "tp-contact", empathy_contact_get_tp_contact (existing_contact),
750           "logged-alias", tpl_entity_get_alias (tpl_entity),
751           NULL);
752     }
753   else
754     {
755       TpConnection *conn;
756       const gchar *id;
757
758       is_user = (TPL_ENTITY_SELF == tpl_entity_get_entity_type (tpl_entity));
759
760       id = tpl_entity_get_identifier (tpl_entity);
761
762       retval = g_object_new (EMPATHY_TYPE_CONTACT,
763           "id", id,
764           "alias", tpl_entity_get_alias (tpl_entity),
765           "account", account,
766           "is-user", is_user,
767           NULL);
768
769       /* Try to get a TpContact associated to have at least contact
770        * capabilities if possible. This is useful for CM supporting calling
771        * offline contacts for example. */
772       conn = tp_account_get_connection (account);
773       if (conn != NULL)
774         {
775           TpContactFeature features[] = { TP_CONTACT_FEATURE_CAPABILITIES };
776           conn = tp_account_get_connection (account);
777
778           tp_connection_get_contacts_by_id (conn, 1, &id,
779               G_N_ELEMENTS (features), features, get_contacts_cb,
780               NULL, NULL, G_OBJECT (retval));
781         }
782     }
783
784   if (!EMP_STR_EMPTY (tpl_entity_get_avatar_token (tpl_entity)))
785     contact_load_avatar_cache (retval,
786         tpl_entity_get_avatar_token (tpl_entity));
787
788   return retval;
789 }
790
791 TpContact *
792 empathy_contact_get_tp_contact (EmpathyContact *contact)
793 {
794   EmpathyContactPriv *priv;
795
796   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
797
798   priv = GET_PRIV (contact);
799
800   return priv->tp_contact;
801 }
802
803 const gchar *
804 empathy_contact_get_id (EmpathyContact *contact)
805 {
806   EmpathyContactPriv *priv;
807
808   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
809
810   priv = GET_PRIV (contact);
811
812   if (priv->tp_contact != NULL)
813     return tp_contact_get_identifier (priv->tp_contact);
814
815   return priv->id;
816 }
817
818 const gchar *
819 empathy_contact_get_alias (EmpathyContact *contact)
820 {
821   EmpathyContactPriv *priv;
822   const gchar        *alias = NULL;
823
824   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
825
826   priv = GET_PRIV (contact);
827
828   if (!EMP_STR_EMPTY (priv->alias))
829     alias = priv->alias;
830   else if (priv->tp_contact != NULL)
831     alias = tp_contact_get_alias (priv->tp_contact);
832
833   if (!EMP_STR_EMPTY (alias))
834     return alias;
835   else
836     return empathy_contact_get_id (contact);
837 }
838
839 const gchar *
840 empathy_contact_get_logged_alias (EmpathyContact *contact)
841 {
842   EmpathyContactPriv *priv;
843
844   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
845
846   priv = GET_PRIV (contact);
847
848   if (priv->logged_alias != NULL)
849     return priv->logged_alias;
850   else
851     return empathy_contact_get_alias (contact);
852 }
853
854 void
855 empathy_contact_set_alias (EmpathyContact *contact,
856                           const gchar *alias)
857 {
858   EmpathyContactPriv *priv;
859   FolksPersona *persona;
860
861   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
862
863   priv = GET_PRIV (contact);
864
865   g_object_ref (contact);
866
867   /* Set the alias on the persona if possible */
868   persona = empathy_contact_get_persona (contact);
869   if (persona != NULL && FOLKS_IS_ALIAS_DETAILS (persona))
870     {
871       DEBUG ("Setting alias for contact %s to %s",
872           empathy_contact_get_id (contact), alias);
873
874       folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (persona), alias);
875     }
876
877   if (tp_strdiff (alias, priv->alias))
878     {
879       g_free (priv->alias);
880       priv->alias = g_strdup (alias);
881       g_object_notify (G_OBJECT (contact), "alias");
882     }
883
884   g_object_unref (contact);
885 }
886
887 static void
888 groups_change_group_cb (GObject *source,
889     GAsyncResult *result,
890     gpointer user_data)
891 {
892   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
893   GError *error = NULL;
894
895   folks_group_details_change_group_finish (group_details, result, &error);
896   if (error != NULL)
897     {
898       g_warning ("failed to change group: %s", error->message);
899       g_clear_error (&error);
900     }
901 }
902
903 void
904 empathy_contact_change_group (EmpathyContact *contact, const gchar *group,
905     gboolean is_member)
906 {
907   EmpathyContactPriv *priv;
908   FolksPersona *persona;
909
910   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
911   g_return_if_fail (group != NULL);
912
913   priv = GET_PRIV (contact);
914
915   /* Normally pass through the changes to the persona */
916   persona = empathy_contact_get_persona (contact);
917   if (persona != NULL)
918     {
919       if (FOLKS_IS_GROUP_DETAILS (persona))
920         folks_group_details_change_group (FOLKS_GROUP_DETAILS (persona), group,
921             is_member, groups_change_group_cb, contact);
922       return;
923     }
924
925   /* If the persona doesn't exist yet, we have to cache the changes until it
926    * does */
927   if (priv->groups == NULL)
928     {
929       priv->groups = gee_hash_set_new (G_TYPE_STRING, (GBoxedCopyFunc) g_strdup,
930           g_free, g_str_hash, g_str_equal);
931     }
932
933   gee_collection_add (GEE_COLLECTION (priv->groups), group);
934 }
935
936 EmpathyAvatar *
937 empathy_contact_get_avatar (EmpathyContact *contact)
938 {
939   EmpathyContactPriv *priv;
940
941   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
942
943   priv = GET_PRIV (contact);
944
945   return priv->avatar;
946 }
947
948 static void
949 contact_set_avatar (EmpathyContact *contact,
950                     EmpathyAvatar *avatar)
951 {
952   EmpathyContactPriv *priv;
953
954   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
955
956   priv = GET_PRIV (contact);
957
958   if (priv->avatar == avatar)
959     return;
960
961   if (priv->avatar)
962     {
963       empathy_avatar_unref (priv->avatar);
964       priv->avatar = NULL;
965     }
966
967   if (avatar)
968       priv->avatar = empathy_avatar_ref (avatar);
969
970   g_object_notify (G_OBJECT (contact), "avatar");
971 }
972
973 TpAccount *
974 empathy_contact_get_account (EmpathyContact *contact)
975 {
976   EmpathyContactPriv *priv;
977
978   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
979
980   priv = GET_PRIV (contact);
981
982   if (priv->account == NULL && priv->tp_contact != NULL)
983     {
984       TpConnection *connection;
985
986       /* FIXME: This assume the account manager already exists */
987       connection = tp_contact_get_connection (priv->tp_contact);
988       priv->account =
989         g_object_ref (tp_connection_get_account (connection));
990     }
991
992   return priv->account;
993 }
994
995 FolksPersona *
996 empathy_contact_get_persona (EmpathyContact *contact)
997 {
998   EmpathyContactPriv *priv;
999
1000   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1001
1002   priv = GET_PRIV (contact);
1003
1004   if (priv->persona == NULL && priv->tp_contact != NULL)
1005     {
1006       /* FIXME: This is disgustingly slow */
1007       /* Query for the persona */
1008       EmpathyIndividualManager *manager;
1009       GList *individuals, *l;
1010
1011       manager = empathy_individual_manager_dup_singleton ();
1012       individuals = empathy_individual_manager_get_members (manager);
1013
1014       for (l = individuals; l != NULL; l = l->next)
1015         {
1016           FolksIndividual *individual = FOLKS_INDIVIDUAL (l->data);
1017           GeeSet *personas;
1018           GeeIterator *iter;
1019           gboolean persona_found = FALSE;
1020
1021           personas = folks_individual_get_personas (individual);
1022           iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1023           while (!persona_found && gee_iterator_next (iter))
1024             {
1025               TpfPersona *persona = gee_iterator_get (iter);
1026
1027               if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1028                 {
1029                   TpContact *tp_contact = tpf_persona_get_contact (persona);
1030
1031                   if (tp_contact == priv->tp_contact)
1032                     {
1033                       /* Found the right persona */
1034                       empathy_contact_set_persona (contact,
1035                           (FolksPersona *) persona);
1036                       persona_found = TRUE;
1037                     }
1038                   g_clear_object (&persona);
1039                 }
1040             }
1041           g_clear_object (&iter);
1042         }
1043
1044       g_list_free (individuals);
1045       g_object_unref (manager);
1046     }
1047
1048   return priv->persona;
1049 }
1050
1051 void
1052 empathy_contact_set_persona (EmpathyContact *contact,
1053     FolksPersona *persona)
1054 {
1055   EmpathyContactPriv *priv;
1056
1057   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1058   g_return_if_fail (TPF_IS_PERSONA (persona));
1059
1060   priv = GET_PRIV (contact);
1061
1062   if (persona == priv->persona)
1063     return;
1064
1065   if (priv->persona != NULL)
1066     {
1067       g_signal_handlers_disconnect_by_func (priv->persona,
1068           folks_persona_notify_cb, contact);
1069       g_object_unref (priv->persona);
1070     }
1071   priv->persona = g_object_ref (persona);
1072
1073   g_signal_connect (priv->persona, "notify",
1074     G_CALLBACK (folks_persona_notify_cb), contact);
1075
1076   g_object_notify (G_OBJECT (contact), "persona");
1077
1078   /* Set the persona's alias, since ours could've been set using
1079    * empathy_contact_set_alias() before we had a persona; this happens when
1080    * adding a contact. */
1081   if (priv->alias != NULL)
1082     empathy_contact_set_alias (contact, priv->alias);
1083
1084   /* Set the persona's groups */
1085   if (priv->groups != NULL)
1086     {
1087       folks_group_details_set_groups (FOLKS_GROUP_DETAILS (persona),
1088           GEE_SET (priv->groups));
1089       g_object_unref (priv->groups);
1090       priv->groups = NULL;
1091     }
1092 }
1093
1094 TpConnection *
1095 empathy_contact_get_connection (EmpathyContact *contact)
1096 {
1097   EmpathyContactPriv *priv;
1098
1099   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1100
1101   priv = GET_PRIV (contact);
1102
1103   if (priv->tp_contact != NULL)
1104     return tp_contact_get_connection (priv->tp_contact);
1105
1106   return NULL;
1107 }
1108
1109 TpConnectionPresenceType
1110 empathy_contact_get_presence (EmpathyContact *contact)
1111 {
1112   EmpathyContactPriv *priv;
1113
1114   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
1115     TP_CONNECTION_PRESENCE_TYPE_UNSET);
1116
1117   priv = GET_PRIV (contact);
1118
1119   if (priv->tp_contact != NULL)
1120     return tp_contact_get_presence_type (priv->tp_contact);
1121
1122   return priv->presence;
1123 }
1124
1125 const gchar *
1126 empathy_contact_get_presence_message (EmpathyContact *contact)
1127 {
1128   EmpathyContactPriv *priv;
1129
1130   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1131
1132   priv = GET_PRIV (contact);
1133
1134   if (priv->persona != NULL)
1135     return folks_presence_details_get_presence_message (
1136         FOLKS_PRESENCE_DETAILS (priv->persona));
1137
1138   if (priv->tp_contact != NULL)
1139     return tp_contact_get_presence_message (priv->tp_contact);
1140
1141   return NULL;
1142 }
1143
1144 guint
1145 empathy_contact_get_handle (EmpathyContact *contact)
1146 {
1147   EmpathyContactPriv *priv;
1148
1149   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
1150
1151   priv = GET_PRIV (contact);
1152
1153   if (priv->tp_contact != NULL)
1154     return tp_contact_get_handle (priv->tp_contact);
1155
1156   return priv->handle;
1157 }
1158
1159 EmpathyCapabilities
1160 empathy_contact_get_capabilities (EmpathyContact *contact)
1161 {
1162   EmpathyContactPriv *priv;
1163
1164   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
1165
1166   priv = GET_PRIV (contact);
1167
1168   return priv->capabilities;
1169 }
1170
1171 gboolean
1172 empathy_contact_is_user (EmpathyContact *contact)
1173 {
1174   EmpathyContactPriv *priv;
1175
1176   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1177
1178   priv = GET_PRIV (contact);
1179
1180   return priv->is_user;
1181 }
1182
1183 void
1184 empathy_contact_set_is_user (EmpathyContact *contact,
1185                              gboolean is_user)
1186 {
1187   EmpathyContactPriv *priv;
1188
1189   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1190
1191   priv = GET_PRIV (contact);
1192
1193   if (priv->is_user == is_user)
1194     return;
1195
1196   priv->is_user = is_user;
1197
1198   g_object_notify (G_OBJECT (contact), "is-user");
1199 }
1200
1201 gboolean
1202 empathy_contact_is_online (EmpathyContact *contact)
1203 {
1204   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1205
1206   switch (empathy_contact_get_presence (contact))
1207     {
1208       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
1209       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
1210       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
1211         return FALSE;
1212       /* Contacts without presence are considered online so we can display IRC
1213        * contacts in rooms. */
1214       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
1215       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
1216       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
1217       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
1218       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
1219       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
1220       default:
1221         return TRUE;
1222     }
1223 }
1224
1225 const gchar *
1226 empathy_contact_get_status (EmpathyContact *contact)
1227 {
1228   const gchar *message;
1229
1230   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), "");
1231
1232   message = empathy_contact_get_presence_message (contact);
1233   if (!EMP_STR_EMPTY (message))
1234     return message;
1235
1236   return empathy_presence_get_default_message (
1237       empathy_contact_get_presence (contact));
1238 }
1239
1240 gboolean
1241 empathy_contact_can_sms (EmpathyContact *contact)
1242 {
1243   EmpathyContactPriv *priv;
1244
1245   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1246
1247   priv = GET_PRIV (contact);
1248
1249   return priv->capabilities & EMPATHY_CAPABILITIES_SMS;
1250 }
1251
1252 gboolean
1253 empathy_contact_can_voip (EmpathyContact *contact)
1254 {
1255   EmpathyContactPriv *priv;
1256
1257   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1258
1259   priv = GET_PRIV (contact);
1260
1261   return priv->capabilities & (EMPATHY_CAPABILITIES_AUDIO |
1262       EMPATHY_CAPABILITIES_VIDEO);
1263 }
1264
1265 gboolean
1266 empathy_contact_can_voip_audio (EmpathyContact *contact)
1267 {
1268   EmpathyContactPriv *priv;
1269
1270   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1271
1272   priv = GET_PRIV (contact);
1273
1274   return priv->capabilities & EMPATHY_CAPABILITIES_AUDIO;
1275 }
1276
1277 gboolean
1278 empathy_contact_can_voip_video (EmpathyContact *contact)
1279 {
1280   EmpathyContactPriv *priv;
1281
1282   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1283
1284   priv = GET_PRIV (contact);
1285
1286   return priv->capabilities & EMPATHY_CAPABILITIES_VIDEO;
1287 }
1288
1289 gboolean
1290 empathy_contact_can_send_files (EmpathyContact *contact)
1291 {
1292   EmpathyContactPriv *priv;
1293
1294   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1295
1296   priv = GET_PRIV (contact);
1297
1298   return priv->capabilities & EMPATHY_CAPABILITIES_FT;
1299 }
1300
1301 gboolean
1302 empathy_contact_can_use_rfb_stream_tube (EmpathyContact *contact)
1303 {
1304   EmpathyContactPriv *priv;
1305
1306   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1307
1308   priv = GET_PRIV (contact);
1309
1310   return priv->capabilities & EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
1311 }
1312
1313 static gboolean
1314 contact_has_log (EmpathyContact *contact)
1315 {
1316   TplLogManager *manager;
1317   TplEntity *entity;
1318   gboolean have_log;
1319
1320   manager = tpl_log_manager_dup_singleton ();
1321   entity = tpl_entity_new (empathy_contact_get_id (contact),
1322       TPL_ENTITY_CONTACT, NULL, NULL);
1323
1324   have_log = tpl_log_manager_exists (manager,
1325       empathy_contact_get_account (contact), entity, TPL_EVENT_MASK_TEXT);
1326
1327   g_object_unref (entity);
1328   g_object_unref (manager);
1329
1330   return have_log;
1331 }
1332
1333 gboolean
1334 empathy_contact_can_do_action (EmpathyContact *self,
1335     EmpathyActionType action_type)
1336 {
1337   gboolean sensitivity = FALSE;
1338
1339   switch (action_type)
1340     {
1341       case EMPATHY_ACTION_CHAT:
1342         sensitivity = TRUE;
1343         break;
1344       case EMPATHY_ACTION_SMS:
1345         sensitivity = empathy_contact_can_sms (self);
1346         break;
1347       case EMPATHY_ACTION_AUDIO_CALL:
1348         sensitivity = empathy_contact_can_voip_audio (self);
1349         break;
1350       case EMPATHY_ACTION_VIDEO_CALL:
1351         sensitivity = empathy_contact_can_voip_video (self);
1352         break;
1353       case EMPATHY_ACTION_VIEW_LOGS:
1354         sensitivity = contact_has_log (self);
1355         break;
1356       case EMPATHY_ACTION_SEND_FILE:
1357         sensitivity = empathy_contact_can_send_files (self);
1358         break;
1359       case EMPATHY_ACTION_SHARE_MY_DESKTOP:
1360         sensitivity = empathy_contact_can_use_rfb_stream_tube (self);
1361         break;
1362       default:
1363         g_assert_not_reached ();
1364     }
1365
1366   return (sensitivity ? TRUE : FALSE);
1367 }
1368
1369 static gchar *
1370 contact_get_avatar_filename (EmpathyContact *contact,
1371                              const gchar *token)
1372 {
1373   TpAccount *account;
1374   gchar *avatar_path;
1375   gchar *avatar_file;
1376   gchar *token_escaped;
1377
1378   if (EMP_STR_EMPTY (empathy_contact_get_id (contact)))
1379     return NULL;
1380
1381   token_escaped = tp_escape_as_identifier (token);
1382   account = empathy_contact_get_account (contact);
1383
1384   avatar_path = g_build_filename (g_get_user_cache_dir (),
1385       "telepathy",
1386       "avatars",
1387       tp_account_get_connection_manager (account),
1388       tp_account_get_protocol (account),
1389       NULL);
1390   g_mkdir_with_parents (avatar_path, 0700);
1391
1392   avatar_file = g_build_filename (avatar_path, token_escaped, NULL);
1393
1394   g_free (token_escaped);
1395   g_free (avatar_path);
1396
1397   return avatar_file;
1398 }
1399
1400 static gboolean
1401 contact_load_avatar_cache (EmpathyContact *contact,
1402                            const gchar *token)
1403 {
1404   EmpathyAvatar *avatar = NULL;
1405   gchar *filename;
1406   gchar *data = NULL;
1407   gsize len;
1408   GError *error = NULL;
1409
1410   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1411   g_return_val_if_fail (!EMP_STR_EMPTY (token), FALSE);
1412
1413   /* Load the avatar from file if it exists */
1414   filename = contact_get_avatar_filename (contact, token);
1415   if (filename && g_file_test (filename, G_FILE_TEST_EXISTS))
1416     {
1417       if (!g_file_get_contents (filename, &data, &len, &error))
1418         {
1419           DEBUG ("Failed to load avatar from cache: %s",
1420             error ? error->message : "No error given");
1421           g_clear_error (&error);
1422         }
1423     }
1424
1425   if (data != NULL)
1426     {
1427       DEBUG ("Avatar loaded from %s", filename);
1428       avatar = empathy_avatar_new ((guchar *) data, len, NULL, filename);
1429       contact_set_avatar (contact, avatar);
1430       empathy_avatar_unref (avatar);
1431     }
1432
1433   g_free (data);
1434   g_free (filename);
1435
1436   return data != NULL;
1437 }
1438
1439 GType
1440 empathy_avatar_get_type (void)
1441 {
1442   static GType type_id = 0;
1443
1444   if (!type_id)
1445     {
1446       type_id = g_boxed_type_register_static ("EmpathyAvatar",
1447           (GBoxedCopyFunc) empathy_avatar_ref,
1448           (GBoxedFreeFunc) empathy_avatar_unref);
1449     }
1450
1451   return type_id;
1452 }
1453
1454 /**
1455  * empathy_avatar_new:
1456  * @data: the avatar data
1457  * @len: the size of avatar data
1458  * @format: the mime type of the avatar image
1459  * @filename: the filename where the avatar is stored in cache
1460  *
1461  * Create a #EmpathyAvatar from the provided data.
1462  *
1463  * Returns: a new #EmpathyAvatar
1464  */
1465 EmpathyAvatar *
1466 empathy_avatar_new (const guchar *data,
1467                     gsize len,
1468                     const gchar *format,
1469                     const gchar *filename)
1470 {
1471   EmpathyAvatar *avatar;
1472
1473   avatar = g_slice_new0 (EmpathyAvatar);
1474   avatar->data = g_memdup (data, len);
1475   avatar->len = len;
1476   avatar->format = g_strdup (format);
1477   avatar->filename = g_strdup (filename);
1478   avatar->refcount = 1;
1479
1480   return avatar;
1481 }
1482
1483 void
1484 empathy_avatar_unref (EmpathyAvatar *avatar)
1485 {
1486   g_return_if_fail (avatar != NULL);
1487
1488   avatar->refcount--;
1489   if (avatar->refcount == 0)
1490     {
1491       g_free (avatar->data);
1492       g_free (avatar->format);
1493       g_free (avatar->filename);
1494       g_slice_free (EmpathyAvatar, avatar);
1495     }
1496 }
1497
1498 EmpathyAvatar *
1499 empathy_avatar_ref (EmpathyAvatar *avatar)
1500 {
1501   g_return_val_if_fail (avatar != NULL, NULL);
1502
1503   avatar->refcount++;
1504
1505   return avatar;
1506 }
1507
1508 /**
1509  * empathy_avatar_save_to_file:
1510  * @avatar: the avatar
1511  * @filename: name of a file to write avatar to
1512  * @error: return location for a GError, or NULL
1513  *
1514  * Save the avatar to a file named filename
1515  *
1516  * Returns: %TRUE on success, %FALSE if an error occurred
1517  */
1518 gboolean
1519 empathy_avatar_save_to_file (EmpathyAvatar *self,
1520                              const gchar *filename,
1521                              GError **error)
1522 {
1523   return g_file_set_contents (filename, (const gchar *) self->data, self->len,
1524       error);
1525 }
1526
1527 /**
1528  * empathy_contact_get_location:
1529  * @contact: an #EmpathyContact
1530  *
1531  * Returns the user's location if available.  The keys are defined in
1532  * empathy-location.h. If the contact doesn't have location
1533  * information, the GHashTable will be empthy. Use #g_hash_table_unref when
1534  * you are done with the #GHashTable.
1535  *
1536  * It is composed of string keys and GValues.  Keys are
1537  * defined in empathy-location.h such as #EMPATHY_LOCATION_COUNTRY.
1538  * Example: a "city" key would have "Helsinki" as string GValue,
1539  *          a "latitude" would have 65.0 as double GValue.
1540  *
1541  * Returns: a #GHashTable of location values
1542  */
1543 GHashTable *
1544 empathy_contact_get_location (EmpathyContact *contact)
1545 {
1546   EmpathyContactPriv *priv;
1547
1548   g_return_val_if_fail (EMPATHY_CONTACT (contact), NULL);
1549
1550   priv = GET_PRIV (contact);
1551
1552   return priv->location;
1553 }
1554
1555 /**
1556  * empathy_contact_set_location:
1557  * @contact: an #EmpathyContact
1558  * @location: a #GHashTable of the location
1559  *
1560  * Sets the user's location based on the location #GHashTable passed.
1561  * It is composed of string keys and GValues.  Keys are
1562  * defined in empathy-location.h such as #EMPATHY_LOCATION_COUNTRY.
1563  * Example: a "city" key would have "Helsinki" as string GValue,
1564  *          a "latitude" would have 65.0 as double GValue.
1565  */
1566 static void
1567 empathy_contact_set_location (EmpathyContact *contact,
1568     GHashTable *location)
1569 {
1570   EmpathyContactPriv *priv;
1571
1572   g_return_if_fail (EMPATHY_CONTACT (contact));
1573   g_return_if_fail (location != NULL);
1574
1575   priv = GET_PRIV (contact);
1576
1577   if (priv->location != NULL)
1578     g_hash_table_unref (priv->location);
1579
1580   priv->location = g_hash_table_ref (location);
1581 #ifdef HAVE_GEOCODE
1582   update_geocode (contact);
1583 #endif
1584   g_object_notify (G_OBJECT (contact), "location");
1585 }
1586
1587 const gchar * const *
1588 empathy_contact_get_client_types (EmpathyContact *contact)
1589 {
1590   EmpathyContactPriv *priv;
1591
1592   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1593
1594   priv = GET_PRIV (contact);
1595
1596   return (const gchar * const *) priv->client_types;
1597 }
1598
1599 static void
1600 contact_set_client_types (EmpathyContact *contact,
1601     const gchar * const *client_types)
1602 {
1603   EmpathyContactPriv *priv = GET_PRIV (contact);
1604
1605   if (priv->client_types != NULL)
1606     g_strfreev (priv->client_types);
1607
1608   priv->client_types = g_strdupv ((gchar **) client_types);
1609   g_object_notify (G_OBJECT (contact), "client-types");
1610 }
1611
1612 /**
1613  * empathy_contact_equal:
1614  * @contact1: an #EmpathyContact
1615  * @contact2: an #EmpathyContact
1616  *
1617  * Returns FALSE if one of the contacts is NULL but the other is not.
1618  * Otherwise returns TRUE if both pointer are equal or if they bith
1619  * refer to the same id.
1620  * It's only necessary to call this function if your contact objects
1621  * come from logs where contacts are created dynamically and comparing
1622  * pointers is not enough.
1623  */
1624 gboolean
1625 empathy_contact_equal (gconstpointer contact1,
1626                        gconstpointer contact2)
1627 {
1628   EmpathyContact *c1;
1629   EmpathyContact *c2;
1630   const gchar *id1;
1631   const gchar *id2;
1632
1633   if ((contact1 == NULL) != (contact2 == NULL)) {
1634     return FALSE;
1635   }
1636   if (contact1 == contact2) {
1637     return TRUE;
1638   }
1639   c1 = EMPATHY_CONTACT (contact1);
1640   c2 = EMPATHY_CONTACT (contact2);
1641   id1 = empathy_contact_get_id (c1);
1642   id2 = empathy_contact_get_id (c2);
1643   if (!tp_strdiff (id1, id2)) {
1644     return TRUE;
1645   }
1646   return FALSE;
1647 }
1648
1649 #ifdef HAVE_GEOCODE
1650 /* This callback is called by geocode-glib when it found a position
1651  * for the given address.  A position is necessary for a contact
1652  * to show up on the map
1653  */
1654 static void
1655 geocode_cb (GObject *source,
1656     GAsyncResult *result,
1657     gpointer user_data)
1658 {
1659   EmpathyContact *contact = user_data;
1660   EmpathyContactPriv *priv = GET_PRIV (contact);
1661   GError *error = NULL;
1662   GHashTable *new_location;
1663   GHashTable *resolved = NULL;
1664   gdouble latitude, longitude;
1665
1666   if (priv->location == NULL)
1667     goto out;
1668
1669   resolved = geocode_object_resolve_finish (GEOCODE_OBJECT (source), result,
1670       &error);
1671
1672   if (resolved == NULL)
1673     {
1674       DEBUG ("Failed to resolve geocode: %s", error->message);
1675       g_error_free (error);
1676       goto out;
1677     }
1678
1679   if (!geocode_object_get_coords (resolved, &longitude, &latitude))
1680     goto out;
1681
1682   new_location = tp_asv_new (
1683       EMPATHY_LOCATION_LAT, G_TYPE_DOUBLE, latitude,
1684       EMPATHY_LOCATION_LON, G_TYPE_DOUBLE, longitude,
1685       NULL);
1686
1687   DEBUG ("\t - Latitude: %f", latitude);
1688   DEBUG ("\t - Longitude: %f", longitude);
1689
1690   /* Copy remaning fields. LAT and LON were not defined so we won't overwrite
1691    * the values we just set. */
1692   tp_g_hash_table_update (new_location, priv->location,
1693       (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup);
1694
1695   /* Don't change the accuracy as we used an address to get this position */
1696   g_hash_table_unref (priv->location);
1697   priv->location = new_location;
1698   g_object_notify ((GObject *) contact, "location");
1699
1700 out:
1701   tp_clear_pointer (&resolved, g_hash_table_unref);
1702   g_object_unref (contact);
1703 }
1704
1705 static void
1706 update_geocode (EmpathyContact *contact)
1707 {
1708   GeocodeObject *geocode;
1709   GHashTable *location;
1710
1711   location = empathy_contact_get_location (contact);
1712   if (location == NULL ||
1713       g_hash_table_size (location) == 0)
1714     return;
1715
1716   /* No need to search for position if contact published it */
1717   if (g_hash_table_lookup (location, EMPATHY_LOCATION_LAT) != NULL ||
1718       g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL)
1719     return;
1720
1721   geocode = geocode_object_new_for_params (location);
1722   if (geocode == NULL)
1723     return;
1724
1725   geocode_object_resolve_async (geocode, NULL, geocode_cb,
1726       g_object_ref (contact));
1727
1728   g_object_unref (geocode);
1729 }
1730 #endif
1731
1732 static EmpathyCapabilities
1733 tp_caps_to_capabilities (TpCapabilities *caps)
1734 {
1735   EmpathyCapabilities capabilities = 0;
1736   guint i;
1737   GPtrArray *classes;
1738
1739   classes = tp_capabilities_get_channel_classes (caps);
1740
1741   for (i = 0; i < classes->len; i++)
1742     {
1743       GValueArray *class_struct;
1744       GHashTable *fixed_prop;
1745       GStrv allowed_prop;
1746       TpHandleType handle_type;
1747       const gchar *chan_type;
1748
1749       class_struct = g_ptr_array_index (classes, i);
1750       tp_value_array_unpack (class_struct, 2,
1751           &fixed_prop,
1752           &allowed_prop);
1753
1754       handle_type = tp_asv_get_uint32 (fixed_prop,
1755           TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
1756       if (handle_type != TP_HANDLE_TYPE_CONTACT)
1757         continue;
1758
1759       chan_type = tp_asv_get_string (fixed_prop,
1760           TP_PROP_CHANNEL_CHANNEL_TYPE);
1761
1762       if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1763         {
1764           capabilities |= EMPATHY_CAPABILITIES_FT;
1765         }
1766       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
1767         {
1768           const gchar *service;
1769
1770           service = tp_asv_get_string (fixed_prop,
1771               TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE);
1772
1773           if (!tp_strdiff (service, "rfb"))
1774             capabilities |= EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
1775         }
1776       else if (!tp_strdiff (chan_type,
1777         TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
1778         {
1779           guint j;
1780
1781           for (j = 0; allowed_prop[j] != NULL; j++)
1782             {
1783               if (!tp_strdiff (allowed_prop[j],
1784                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO))
1785                 capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1786               else if (!tp_strdiff (allowed_prop[j],
1787                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO))
1788                 capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1789             }
1790
1791           if (tp_asv_get_boolean (fixed_prop,
1792                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, NULL))
1793             capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1794           if (tp_asv_get_boolean (fixed_prop,
1795                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, NULL))
1796             capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1797         }
1798       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT))
1799         {
1800           if (tp_asv_get_boolean (fixed_prop,
1801                 TP_PROP_CHANNEL_INTERFACE_SMS_SMS_CHANNEL, NULL))
1802             capabilities |= EMPATHY_CAPABILITIES_SMS;
1803         }
1804       else if (!tp_strdiff (chan_type,
1805         TPY_IFACE_CHANNEL_TYPE_CALL))
1806         {
1807           guint j;
1808
1809           if (tp_asv_get_boolean (fixed_prop,
1810               TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL))
1811             capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1812
1813           if (tp_asv_get_boolean (fixed_prop,
1814               TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL))
1815             capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1816
1817           for (j = 0; allowed_prop[j] != NULL; j++)
1818             {
1819               if (!tp_strdiff (allowed_prop[j],
1820                     TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO))
1821                 capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1822               else if (!tp_strdiff (allowed_prop[j],
1823                     TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO))
1824                 capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1825             }
1826         }
1827     }
1828
1829   return capabilities;
1830 }
1831
1832 static void
1833 set_capabilities_from_tp_caps (EmpathyContact *self,
1834     TpCapabilities *caps)
1835 {
1836   EmpathyCapabilities capabilities;
1837
1838   if (caps == NULL)
1839     return;
1840
1841   capabilities = tp_caps_to_capabilities (caps);
1842   empathy_contact_set_capabilities (self, capabilities);
1843 }
1844
1845 static void
1846 contact_set_avatar_from_tp_contact (EmpathyContact *contact)
1847 {
1848   EmpathyContactPriv *priv = GET_PRIV (contact);
1849   const gchar *mime;
1850   GFile *file;
1851
1852   mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
1853   file = tp_contact_get_avatar_file (priv->tp_contact);
1854
1855   if (file != NULL)
1856     {
1857       EmpathyAvatar *avatar;
1858       gchar *data;
1859       gsize len;
1860       gchar *path;
1861       GError *error = NULL;
1862
1863       if (!g_file_load_contents (file, NULL, &data, &len, NULL, &error))
1864         {
1865           DEBUG ("Failed to load avatar: %s", error->message);
1866
1867           g_error_free (error);
1868           contact_set_avatar (contact, NULL);
1869           return;
1870         }
1871
1872       path = g_file_get_path (file);
1873
1874       avatar = empathy_avatar_new ((guchar *) data, len, mime, path);
1875
1876       contact_set_avatar (contact, avatar);
1877       empathy_avatar_unref (avatar);
1878       g_free (path);
1879       g_free (data);
1880     }
1881   else
1882     {
1883       contact_set_avatar (contact, NULL);
1884     }
1885 }
1886
1887 EmpathyContact *
1888 empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
1889 {
1890   EmpathyContact *contact = NULL;
1891
1892   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
1893
1894   if (contacts_table == NULL)
1895     contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
1896   else
1897     contact = g_hash_table_lookup (contacts_table, tp_contact);
1898
1899   if (contact == NULL)
1900     {
1901       contact = empathy_contact_new (tp_contact);
1902
1903       /* The hash table does not keep any ref.
1904        * contact keeps a ref to tp_contact, and is removed from the table in
1905        * contact_dispose() */
1906       g_hash_table_insert (contacts_table, tp_contact, contact);
1907     }
1908   else
1909     {
1910       g_object_ref (contact);
1911     }
1912
1913   return contact;
1914 }
1915
1916 static int
1917 presence_cmp_func (EmpathyContact *a,
1918     EmpathyContact *b)
1919 {
1920   FolksPresenceDetails *presence_a, *presence_b;
1921
1922   presence_a = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (a));
1923   presence_b = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (b));
1924
1925   /* We negate the result because we're sorting in reverse order (i.e. such that
1926    * the Personas with the highest presence are at the beginning of the list. */
1927   return -folks_presence_details_typecmp (
1928       folks_presence_details_get_presence_type (presence_a),
1929       folks_presence_details_get_presence_type (presence_b));
1930 }
1931
1932 static gint
1933 voip_cmp_func (EmpathyContact *a,
1934     EmpathyContact *b)
1935 {
1936   gboolean has_audio_a, has_audio_b;
1937   gboolean has_video_a, has_video_b;
1938
1939   has_audio_a = empathy_contact_can_voip_audio (a);
1940   has_audio_b = empathy_contact_can_voip_audio (b);
1941   has_video_a = empathy_contact_can_voip_video (a);
1942   has_video_b = empathy_contact_can_voip_video (b);
1943
1944   /* First check video */
1945   if (has_video_a == has_video_b)
1946     {
1947       /* Use audio to to break tie */
1948       if (has_audio_a == has_audio_b)
1949         return 0;
1950       else if (has_audio_a)
1951         return -1;
1952       else
1953         return 1;
1954     }
1955   else if (has_video_a)
1956     return -1;
1957   else
1958     return 1;
1959 }
1960
1961 static gint
1962 ft_cmp_func (EmpathyContact *a,
1963     EmpathyContact *b)
1964 {
1965   gboolean can_send_files_a, can_send_files_b;
1966
1967   can_send_files_a = empathy_contact_can_send_files (a);
1968   can_send_files_b = empathy_contact_can_send_files (b);
1969
1970   if (can_send_files_a == can_send_files_b)
1971     return 0;
1972   else if (can_send_files_a)
1973     return -1;
1974   else
1975     return 1;
1976 }
1977
1978 static gint
1979 rfb_stream_tube_cmp_func (EmpathyContact *a,
1980     EmpathyContact *b)
1981 {
1982   gboolean rfb_a, rfb_b;
1983
1984   rfb_a = empathy_contact_can_use_rfb_stream_tube (a);
1985   rfb_b = empathy_contact_can_use_rfb_stream_tube (b);
1986
1987   if (rfb_a == rfb_b)
1988     return 0;
1989   else if (rfb_a)
1990     return -1;
1991   else
1992     return 1;
1993 }
1994
1995 /* Sort by presence as with presence_cmp_func(), but if the two contacts have
1996  * the same presence, prefer the one which can do both audio *and* video calls,
1997  * over the one which can only do one of the two. */
1998 static int
1999 voip_sort_func (EmpathyContact *a, EmpathyContact *b)
2000 {
2001   gint presence_sort = presence_cmp_func (a, b);
2002
2003   if (presence_sort != 0)
2004     return presence_sort;
2005
2006   return voip_cmp_func (a, b);
2007 }
2008
2009 /* Sort by presence as with presence_cmp_func() and then break ties using the
2010  * most "capable" individual. So users will be able to pick more actions on
2011  * the contact in the "Contact" menu of the chat window. */
2012 static gint
2013 chat_sort_func (EmpathyContact *a,
2014     EmpathyContact *b)
2015 {
2016   gint result;
2017
2018   result = presence_cmp_func (a, b);
2019   if (result != 0)
2020     return result;
2021
2022   /* Prefer individual supporting file transfer */
2023   result = ft_cmp_func (a, b);
2024   if (result != 0)
2025     return result;
2026
2027   /* Check audio/video capabilities */
2028   result = voip_cmp_func (a, b);
2029   if (result != 0)
2030     return result;
2031
2032   /* Check 'Share my destop' feature */
2033   return rfb_stream_tube_cmp_func (a, b);
2034 }
2035
2036 static GCompareFunc
2037 get_sort_func_for_action (EmpathyActionType action_type)
2038 {
2039   switch (action_type)
2040     {
2041       case EMPATHY_ACTION_AUDIO_CALL:
2042       case EMPATHY_ACTION_VIDEO_CALL:
2043         return (GCompareFunc) voip_sort_func;
2044       case EMPATHY_ACTION_CHAT:
2045         return (GCompareFunc) chat_sort_func;
2046       case EMPATHY_ACTION_VIEW_LOGS:
2047       case EMPATHY_ACTION_SEND_FILE:
2048       case EMPATHY_ACTION_SHARE_MY_DESKTOP:
2049       default:
2050         return (GCompareFunc) presence_cmp_func;
2051     }
2052 }
2053
2054 /**
2055  * empathy_contact_dup_best_for_action:
2056  * @individual: a #FolksIndividual
2057  * @action_type: the type of action to be performed on the contact
2058  *
2059  * Chooses a #FolksPersona from the given @individual which is best-suited for
2060  * the given @action_type. "Best-suited" is determined by choosing the persona
2061  * with the highest presence out of all the personas which can perform the given
2062  * @action_type (e.g. are capable of video calling).
2063  *
2064  * Return value: an #EmpathyContact for the best persona, or %NULL;
2065  * unref with g_object_unref()
2066  */
2067 EmpathyContact *
2068 empathy_contact_dup_best_for_action (FolksIndividual *individual,
2069     EmpathyActionType action_type)
2070 {
2071   GeeSet *personas;
2072   GeeIterator *iter;
2073   GList *contacts;
2074   EmpathyContact *best_contact = NULL;
2075
2076   /* Build a list of EmpathyContacts that we can sort */
2077   personas = folks_individual_get_personas (individual);
2078   contacts = NULL;
2079
2080   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2081   while (gee_iterator_next (iter))
2082     {
2083       FolksPersona *persona = gee_iterator_get (iter);
2084       TpContact *tp_contact;
2085       EmpathyContact *contact = NULL;
2086
2087       if (!empathy_folks_persona_is_interesting (persona))
2088         goto while_finish;
2089
2090       tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
2091       if (tp_contact == NULL)
2092         goto while_finish;
2093
2094       contact = empathy_contact_dup_from_tp_contact (tp_contact);
2095       empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
2096
2097       /* Only choose the contact if they're actually capable of the specified
2098        * action. */
2099       if (empathy_contact_can_do_action (contact, action_type))
2100         contacts = g_list_prepend (contacts, g_object_ref (contact));
2101
2102 while_finish:
2103       g_clear_object (&contact);
2104       g_clear_object (&persona);
2105     }
2106   g_clear_object (&iter);
2107
2108   /* Sort the contacts by some heuristic based on the action type, then take
2109    * the top contact. */
2110   if (contacts != NULL)
2111     {
2112       contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
2113       best_contact = g_object_ref (contacts->data);
2114     }
2115
2116   g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2117   g_list_free (contacts);
2118
2119   return best_contact;
2120 }
2121
2122 #define declare_contact_cb(name) \
2123 static void \
2124 contact_##name##_cb (GObject *source, \
2125     GAsyncResult *result, \
2126     gpointer user_data) \
2127 { \
2128   TpContact *contact = (TpContact *) source; \
2129   GError *error = NULL; \
2130   \
2131   if (!tp_contact_##name##_finish (contact, result, &error)) \
2132     { \
2133       DEBUG ("Failed to ##name## on %s\n", \
2134           tp_contact_get_identifier (contact)); \
2135       g_error_free (error); \
2136     } \
2137 }
2138
2139 declare_contact_cb (request_subscription)
2140 declare_contact_cb (authorize_publication)
2141 declare_contact_cb (unblock)
2142
2143 void
2144 empathy_contact_add_to_contact_list (EmpathyContact *self,
2145     const gchar *message)
2146 {
2147   EmpathyContactPriv *priv = GET_PRIV (self);
2148
2149   g_return_if_fail (priv->tp_contact != NULL);
2150
2151   tp_contact_request_subscription_async (priv->tp_contact, message,
2152       contact_request_subscription_cb, NULL);
2153
2154   tp_contact_authorize_publication_async (priv->tp_contact,
2155       contact_authorize_publication_cb, NULL);
2156
2157   tp_contact_unblock_async (priv->tp_contact, contact_unblock_cb, NULL);
2158 }
2159
2160 declare_contact_cb (remove)
2161
2162 void
2163 empathy_contact_remove_from_contact_list (EmpathyContact *self)
2164 {
2165   EmpathyContactPriv *priv = GET_PRIV (self);
2166
2167   g_return_if_fail (priv->tp_contact != NULL);
2168
2169   tp_contact_remove_async (priv->tp_contact, contact_remove_cb, NULL);
2170 }