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