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