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