]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact.c
Adjust for Folks PresenceOwner -> PresenceDetails rename
[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_details_set_presence_message (
514           FOLKS_PRESENCE_DETAILS (priv->persona), 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_ALIAS_DETAILS (persona))
733     {
734       DEBUG ("Setting alias for contact %s to %s",
735           empathy_contact_get_id (contact), alias);
736
737       folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (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   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
756   GError *error = NULL;
757
758   folks_group_details_change_group_finish (group_details, 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_GROUP_DETAILS (persona))
783         folks_group_details_change_group (FOLKS_GROUP_DETAILS (persona), group,
784             is_member, 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 (empathy_folks_persona_is_interesting (FOLKS_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_group_details_set_groups (FOLKS_GROUP_DETAILS (persona),
948           priv->groups);
949       g_hash_table_destroy (priv->groups);
950       priv->groups = NULL;
951     }
952 }
953
954 TpConnection *
955 empathy_contact_get_connection (EmpathyContact *contact)
956 {
957   EmpathyContactPriv *priv;
958
959   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
960
961   priv = GET_PRIV (contact);
962
963   if (priv->tp_contact != NULL)
964     return tp_contact_get_connection (priv->tp_contact);
965
966   return NULL;
967 }
968
969 TpConnectionPresenceType
970 empathy_contact_get_presence (EmpathyContact *contact)
971 {
972   EmpathyContactPriv *priv;
973
974   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
975     TP_CONNECTION_PRESENCE_TYPE_UNSET);
976
977   priv = GET_PRIV (contact);
978
979   if (priv->tp_contact != NULL)
980     return tp_contact_get_presence_type (priv->tp_contact);
981
982   return priv->presence;
983 }
984
985 const gchar *
986 empathy_contact_get_presence_message (EmpathyContact *contact)
987 {
988   EmpathyContactPriv *priv;
989
990   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
991
992   priv = GET_PRIV (contact);
993
994   if (priv->persona != NULL)
995     return folks_presence_details_get_presence_message (
996         FOLKS_PRESENCE_DETAILS (priv->persona));
997
998   if (priv->tp_contact != NULL)
999     return tp_contact_get_presence_message (priv->tp_contact);
1000
1001   return NULL;
1002 }
1003
1004 guint
1005 empathy_contact_get_handle (EmpathyContact *contact)
1006 {
1007   EmpathyContactPriv *priv;
1008
1009   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
1010
1011   priv = GET_PRIV (contact);
1012
1013   if (priv->tp_contact != NULL)
1014     return tp_contact_get_handle (priv->tp_contact);
1015
1016   return priv->handle;
1017 }
1018
1019 EmpathyCapabilities
1020 empathy_contact_get_capabilities (EmpathyContact *contact)
1021 {
1022   EmpathyContactPriv *priv;
1023
1024   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
1025
1026   priv = GET_PRIV (contact);
1027
1028   return priv->capabilities;
1029 }
1030
1031 gboolean
1032 empathy_contact_is_user (EmpathyContact *contact)
1033 {
1034   EmpathyContactPriv *priv;
1035
1036   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1037
1038   priv = GET_PRIV (contact);
1039
1040   return priv->is_user;
1041 }
1042
1043 void
1044 empathy_contact_set_is_user (EmpathyContact *contact,
1045                              gboolean is_user)
1046 {
1047   EmpathyContactPriv *priv;
1048
1049   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1050
1051   priv = GET_PRIV (contact);
1052
1053   if (priv->is_user == is_user)
1054     return;
1055
1056   priv->is_user = is_user;
1057
1058   g_object_notify (G_OBJECT (contact), "is-user");
1059 }
1060
1061 gboolean
1062 empathy_contact_is_online (EmpathyContact *contact)
1063 {
1064   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1065
1066   switch (empathy_contact_get_presence (contact))
1067     {
1068       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
1069       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
1070       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
1071         return FALSE;
1072       /* Contacts without presence are considered online so we can display IRC
1073        * contacts in rooms. */
1074       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
1075       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
1076       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
1077       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
1078       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
1079       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
1080       default:
1081         return TRUE;
1082     }
1083 }
1084
1085 const gchar *
1086 empathy_contact_get_status (EmpathyContact *contact)
1087 {
1088   const gchar *message;
1089
1090   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), "");
1091
1092   message = empathy_contact_get_presence_message (contact);
1093   if (!EMP_STR_EMPTY (message))
1094     return message;
1095
1096   return empathy_presence_get_default_message (
1097       empathy_contact_get_presence (contact));
1098 }
1099
1100 gboolean
1101 empathy_contact_can_voip (EmpathyContact *contact)
1102 {
1103   EmpathyContactPriv *priv;
1104
1105   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1106
1107   priv = GET_PRIV (contact);
1108
1109   return priv->capabilities & (EMPATHY_CAPABILITIES_AUDIO |
1110       EMPATHY_CAPABILITIES_VIDEO);
1111 }
1112
1113 gboolean
1114 empathy_contact_can_voip_audio (EmpathyContact *contact)
1115 {
1116   EmpathyContactPriv *priv;
1117
1118   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1119
1120   priv = GET_PRIV (contact);
1121
1122   return priv->capabilities & EMPATHY_CAPABILITIES_AUDIO;
1123 }
1124
1125 gboolean
1126 empathy_contact_can_voip_video (EmpathyContact *contact)
1127 {
1128   EmpathyContactPriv *priv;
1129
1130   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1131
1132   priv = GET_PRIV (contact);
1133
1134   return priv->capabilities & EMPATHY_CAPABILITIES_VIDEO;
1135 }
1136
1137 gboolean
1138 empathy_contact_can_send_files (EmpathyContact *contact)
1139 {
1140   EmpathyContactPriv *priv;
1141
1142   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1143
1144   priv = GET_PRIV (contact);
1145
1146   return priv->capabilities & EMPATHY_CAPABILITIES_FT;
1147 }
1148
1149 gboolean
1150 empathy_contact_can_use_rfb_stream_tube (EmpathyContact *contact)
1151 {
1152   EmpathyContactPriv *priv;
1153
1154   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1155
1156   priv = GET_PRIV (contact);
1157
1158   return priv->capabilities & EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
1159 }
1160
1161 static gboolean
1162 contact_has_log (EmpathyContact *contact)
1163 {
1164   TplLogManager *manager;
1165   TplEntity *entity;
1166   gboolean have_log;
1167
1168   manager = tpl_log_manager_dup_singleton ();
1169   entity = tpl_entity_new (empathy_contact_get_id (contact),
1170       TPL_ENTITY_CONTACT, NULL, NULL);
1171
1172   have_log = tpl_log_manager_exists (manager,
1173       empathy_contact_get_account (contact), entity, TPL_EVENT_MASK_TEXT);
1174
1175   g_object_unref (entity);
1176   g_object_unref (manager);
1177
1178   return have_log;
1179 }
1180
1181 gboolean
1182 empathy_contact_can_do_action (EmpathyContact *self,
1183     EmpathyActionType action_type)
1184 {
1185   gboolean sensitivity = FALSE;
1186
1187   switch (action_type)
1188     {
1189       case EMPATHY_ACTION_CHAT:
1190         sensitivity = TRUE;
1191         break;
1192       case EMPATHY_ACTION_AUDIO_CALL:
1193         sensitivity = empathy_contact_can_voip_audio (self);
1194         break;
1195       case EMPATHY_ACTION_VIDEO_CALL:
1196         sensitivity = empathy_contact_can_voip_video (self);
1197         break;
1198       case EMPATHY_ACTION_VIEW_LOGS:
1199         sensitivity = contact_has_log (self);
1200         break;
1201       case EMPATHY_ACTION_SEND_FILE:
1202         sensitivity = empathy_contact_can_send_files (self);
1203         break;
1204       case EMPATHY_ACTION_SHARE_MY_DESKTOP:
1205         sensitivity = empathy_contact_can_use_rfb_stream_tube (self);
1206         break;
1207       default:
1208         g_assert_not_reached ();
1209     }
1210
1211   return (sensitivity ? TRUE : FALSE);
1212 }
1213
1214 static gchar *
1215 contact_get_avatar_filename (EmpathyContact *contact,
1216                              const gchar *token)
1217 {
1218   TpAccount *account;
1219   gchar *avatar_path;
1220   gchar *avatar_file;
1221   gchar *token_escaped;
1222
1223   if (EMP_STR_EMPTY (empathy_contact_get_id (contact)))
1224     return NULL;
1225
1226   token_escaped = tp_escape_as_identifier (token);
1227   account = empathy_contact_get_account (contact);
1228
1229   avatar_path = g_build_filename (g_get_user_cache_dir (),
1230       "telepathy",
1231       "avatars",
1232       tp_account_get_connection_manager (account),
1233       tp_account_get_protocol (account),
1234       NULL);
1235   g_mkdir_with_parents (avatar_path, 0700);
1236
1237   avatar_file = g_build_filename (avatar_path, token_escaped, NULL);
1238
1239   g_free (token_escaped);
1240   g_free (avatar_path);
1241
1242   return avatar_file;
1243 }
1244
1245 static gboolean
1246 contact_load_avatar_cache (EmpathyContact *contact,
1247                            const gchar *token)
1248 {
1249   EmpathyAvatar *avatar = NULL;
1250   gchar *filename;
1251   gchar *data = NULL;
1252   gsize len;
1253   GError *error = NULL;
1254
1255   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
1256   g_return_val_if_fail (!EMP_STR_EMPTY (token), FALSE);
1257
1258   /* Load the avatar from file if it exists */
1259   filename = contact_get_avatar_filename (contact, token);
1260   if (filename && g_file_test (filename, G_FILE_TEST_EXISTS))
1261     {
1262       if (!g_file_get_contents (filename, &data, &len, &error))
1263         {
1264           DEBUG ("Failed to load avatar from cache: %s",
1265             error ? error->message : "No error given");
1266           g_clear_error (&error);
1267         }
1268     }
1269
1270   if (data)
1271     {
1272       DEBUG ("Avatar loaded from %s", filename);
1273       avatar = empathy_avatar_new ((guchar *) data, len, NULL, filename);
1274       contact_set_avatar (contact, avatar);
1275       empathy_avatar_unref (avatar);
1276     }
1277   else
1278     {
1279       g_free (filename);
1280     }
1281
1282   return data != NULL;
1283 }
1284
1285 GType
1286 empathy_avatar_get_type (void)
1287 {
1288   static GType type_id = 0;
1289
1290   if (!type_id)
1291     {
1292       type_id = g_boxed_type_register_static ("EmpathyAvatar",
1293           (GBoxedCopyFunc) empathy_avatar_ref,
1294           (GBoxedFreeFunc) empathy_avatar_unref);
1295     }
1296
1297   return type_id;
1298 }
1299
1300 /**
1301  * empathy_avatar_new:
1302  * @data: the avatar data
1303  * @len: the size of avatar data
1304  * @format: the mime type of the avatar image
1305  * @filename: the filename where the avatar is stored in cache
1306  *
1307  * Create a #EmpathyAvatar from the provided data. This function takes the
1308  * ownership of @data, @format and @filename.
1309  *
1310  * Returns: a new #EmpathyAvatar
1311  */
1312 EmpathyAvatar *
1313 empathy_avatar_new (guchar *data,
1314                     gsize len,
1315                     gchar *format,
1316                     gchar *filename)
1317 {
1318   EmpathyAvatar *avatar;
1319
1320   avatar = g_slice_new0 (EmpathyAvatar);
1321   avatar->data = data;
1322   avatar->len = len;
1323   avatar->format = format;
1324   avatar->filename = filename;
1325   avatar->refcount = 1;
1326
1327   return avatar;
1328 }
1329
1330 void
1331 empathy_avatar_unref (EmpathyAvatar *avatar)
1332 {
1333   g_return_if_fail (avatar != NULL);
1334
1335   avatar->refcount--;
1336   if (avatar->refcount == 0)
1337     {
1338       g_free (avatar->data);
1339       g_free (avatar->format);
1340       g_free (avatar->filename);
1341       g_slice_free (EmpathyAvatar, avatar);
1342     }
1343 }
1344
1345 EmpathyAvatar *
1346 empathy_avatar_ref (EmpathyAvatar *avatar)
1347 {
1348   g_return_val_if_fail (avatar != NULL, NULL);
1349
1350   avatar->refcount++;
1351
1352   return avatar;
1353 }
1354
1355 /**
1356  * empathy_avatar_save_to_file:
1357  * @avatar: the avatar
1358  * @filename: name of a file to write avatar to
1359  * @error: return location for a GError, or NULL
1360  *
1361  * Save the avatar to a file named filename
1362  *
1363  * Returns: %TRUE on success, %FALSE if an error occurred
1364  */
1365 gboolean
1366 empathy_avatar_save_to_file (EmpathyAvatar *self,
1367                              const gchar *filename,
1368                              GError **error)
1369 {
1370   return g_file_set_contents (filename, (const gchar *) self->data, self->len,
1371       error);
1372 }
1373
1374 /**
1375  * empathy_contact_get_location:
1376  * @contact: an #EmpathyContact
1377  *
1378  * Returns the user's location if available.  The keys are defined in
1379  * empathy-location.h. If the contact doesn't have location
1380  * information, the GHashTable will be empthy. Use #g_hash_table_unref when
1381  * you are done with the #GHashTable.
1382  *
1383  * It is composed of string keys and GValues.  Keys are
1384  * defined in empathy-location.h such as #EMPATHY_LOCATION_COUNTRY.
1385  * Example: a "city" key would have "Helsinki" as string GValue,
1386  *          a "latitude" would have 65.0 as double GValue.
1387  *
1388  * Returns: a #GHashTable of location values
1389  */
1390 GHashTable *
1391 empathy_contact_get_location (EmpathyContact *contact)
1392 {
1393   EmpathyContactPriv *priv;
1394
1395   g_return_val_if_fail (EMPATHY_CONTACT (contact), NULL);
1396
1397   priv = GET_PRIV (contact);
1398
1399   return priv->location;
1400 }
1401
1402 /**
1403  * empathy_contact_set_location:
1404  * @contact: an #EmpathyContact
1405  * @location: a #GHashTable of the location
1406  *
1407  * Sets the user's location based on the location #GHashTable passed.
1408  * It is composed of string keys and GValues.  Keys are
1409  * defined in empathy-location.h such as #EMPATHY_LOCATION_COUNTRY.
1410  * Example: a "city" key would have "Helsinki" as string GValue,
1411  *          a "latitude" would have 65.0 as double GValue.
1412  */
1413 static void
1414 empathy_contact_set_location (EmpathyContact *contact,
1415     GHashTable *location)
1416 {
1417   EmpathyContactPriv *priv;
1418
1419   g_return_if_fail (EMPATHY_CONTACT (contact));
1420   g_return_if_fail (location != NULL);
1421
1422   priv = GET_PRIV (contact);
1423
1424   if (priv->location != NULL)
1425     g_hash_table_unref (priv->location);
1426
1427   priv->location = g_hash_table_ref (location);
1428 #ifdef HAVE_GEOCLUE
1429   update_geocode (contact);
1430 #endif
1431   g_object_notify (G_OBJECT (contact), "location");
1432 }
1433
1434 const gchar * const *
1435 empathy_contact_get_client_types (EmpathyContact *contact)
1436 {
1437   EmpathyContactPriv *priv;
1438
1439   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1440
1441   priv = GET_PRIV (contact);
1442
1443   return (const gchar * const *) priv->client_types;
1444 }
1445
1446 static void
1447 contact_set_client_types (EmpathyContact *contact,
1448     const gchar * const *client_types)
1449 {
1450   EmpathyContactPriv *priv = GET_PRIV (contact);
1451
1452   if (priv->client_types != NULL)
1453     g_strfreev (priv->client_types);
1454
1455   priv->client_types = g_strdupv ((gchar **) client_types);
1456   g_object_notify (G_OBJECT (contact), "client-types");
1457 }
1458
1459 /**
1460  * empathy_contact_equal:
1461  * @contact1: an #EmpathyContact
1462  * @contact2: an #EmpathyContact
1463  *
1464  * Returns FALSE if one of the contacts is NULL but the other is not.
1465  * Otherwise returns TRUE if both pointer are equal or if they bith
1466  * refer to the same id.
1467  * It's only necessary to call this function if your contact objects
1468  * come from logs where contacts are created dynamically and comparing
1469  * pointers is not enough.
1470  */
1471 gboolean
1472 empathy_contact_equal (gconstpointer contact1,
1473                        gconstpointer contact2)
1474 {
1475   EmpathyContact *c1;
1476   EmpathyContact *c2;
1477   const gchar *id1;
1478   const gchar *id2;
1479
1480   if ((contact1 == NULL) != (contact2 == NULL)) {
1481     return FALSE;
1482   }
1483   if (contact1 == contact2) {
1484     return TRUE;
1485   }
1486   c1 = EMPATHY_CONTACT (contact1);
1487   c2 = EMPATHY_CONTACT (contact2);
1488   id1 = empathy_contact_get_id (c1);
1489   id2 = empathy_contact_get_id (c2);
1490   if (!tp_strdiff (id1, id2)) {
1491     return TRUE;
1492   }
1493   return FALSE;
1494 }
1495
1496 #ifdef HAVE_GEOCLUE
1497 #define GEOCODE_SERVICE "org.freedesktop.Geoclue.Providers.Yahoo"
1498 #define GEOCODE_PATH "/org/freedesktop/Geoclue/Providers/Yahoo"
1499
1500 /* This callback is called by geoclue when it found a position
1501  * for the given address.  A position is necessary for a contact
1502  * to show up on the map
1503  */
1504 static void
1505 geocode_cb (GeoclueGeocode *geocode,
1506     GeocluePositionFields fields,
1507     double latitude,
1508     double longitude,
1509     double altitude,
1510     GeoclueAccuracy *accuracy,
1511     GError *error,
1512     gpointer contact)
1513 {
1514   EmpathyContactPriv *priv = GET_PRIV (contact);
1515   GHashTable *new_location;
1516
1517   if (priv->location == NULL)
1518     goto out;
1519
1520   if (error != NULL)
1521     {
1522       DEBUG ("Error geocoding location : %s", error->message);
1523       goto out;
1524     }
1525
1526   /* No need to change location if we didn't find the position */
1527   if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE))
1528     goto out;
1529
1530   if (!(fields & GEOCLUE_POSITION_FIELDS_LONGITUDE))
1531     goto out;
1532
1533   new_location = tp_asv_new (
1534       EMPATHY_LOCATION_LAT, G_TYPE_DOUBLE, latitude,
1535       EMPATHY_LOCATION_LON, G_TYPE_DOUBLE, longitude,
1536       NULL);
1537
1538   DEBUG ("\t - Latitude: %f", latitude);
1539   DEBUG ("\t - Longitude: %f", longitude);
1540
1541   /* Copy remaning fields. LAT and LON were not defined so we won't overwrite
1542    * the values we just set. */
1543   tp_g_hash_table_update (new_location, priv->location,
1544       (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup);
1545
1546   /* Set the altitude only if it wasn't defined before */
1547   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE &&
1548       g_hash_table_lookup (new_location, EMPATHY_LOCATION_ALT) == NULL)
1549     {
1550       tp_asv_set_double (new_location, g_strdup (EMPATHY_LOCATION_ALT),
1551           altitude);
1552       DEBUG ("\t - Altitude: %f", altitude);
1553     }
1554
1555   /* Don't change the accuracy as we used an address to get this position */
1556   g_hash_table_unref (priv->location);
1557   priv->location = new_location;
1558   g_object_notify (contact, "location");
1559 out:
1560   g_object_unref (geocode);
1561   g_object_unref (contact);
1562 }
1563
1564 static gchar *
1565 get_dup_string (GHashTable *location,
1566     gchar *key)
1567 {
1568   GValue *value;
1569
1570   value = g_hash_table_lookup (location, key);
1571   if (value != NULL)
1572     return g_value_dup_string (value);
1573
1574   return NULL;
1575 }
1576
1577 static void
1578 update_geocode (EmpathyContact *contact)
1579 {
1580   static GeoclueGeocode *geocode;
1581   gchar *str;
1582   GHashTable *address;
1583   GHashTable *location;
1584
1585   location = empathy_contact_get_location (contact);
1586   if (location == NULL)
1587     return;
1588
1589   /* No need to search for position if contact published it */
1590   if (g_hash_table_lookup (location, EMPATHY_LOCATION_LAT) != NULL ||
1591       g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL)
1592     return;
1593
1594   if (geocode == NULL)
1595     {
1596       geocode = geoclue_geocode_new (GEOCODE_SERVICE, GEOCODE_PATH);
1597       g_object_add_weak_pointer (G_OBJECT (geocode), (gpointer *) &geocode);
1598     }
1599   else
1600     {
1601       g_object_ref (geocode);
1602     }
1603
1604   address = geoclue_address_details_new ();
1605
1606   str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY_CODE);
1607   if (str != NULL)
1608     {
1609       g_hash_table_insert (address,
1610         g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRYCODE), str);
1611       DEBUG ("\t - countrycode: %s", str);
1612     }
1613
1614   str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY);
1615   if (str != NULL)
1616     {
1617       g_hash_table_insert (address,
1618         g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRY), str);
1619       DEBUG ("\t - country: %s", str);
1620     }
1621
1622   str = get_dup_string (location, EMPATHY_LOCATION_POSTAL_CODE);
1623   if (str != NULL)
1624     {
1625       g_hash_table_insert (address,
1626         g_strdup (GEOCLUE_ADDRESS_KEY_POSTALCODE), str);
1627       DEBUG ("\t - postalcode: %s", str);
1628     }
1629
1630   str = get_dup_string (location, EMPATHY_LOCATION_REGION);
1631   if (str != NULL)
1632     {
1633       g_hash_table_insert (address,
1634         g_strdup (GEOCLUE_ADDRESS_KEY_REGION), str);
1635       DEBUG ("\t - region: %s", str);
1636     }
1637
1638   str = get_dup_string (location, EMPATHY_LOCATION_LOCALITY);
1639   if (str != NULL)
1640     {
1641       g_hash_table_insert (address,
1642         g_strdup (GEOCLUE_ADDRESS_KEY_LOCALITY), str);
1643       DEBUG ("\t - locality: %s", str);
1644     }
1645
1646   str = get_dup_string (location, EMPATHY_LOCATION_STREET);
1647   if (str != NULL)
1648     {
1649       g_hash_table_insert (address,
1650         g_strdup (GEOCLUE_ADDRESS_KEY_STREET), str);
1651       DEBUG ("\t - street: %s", str);
1652     }
1653
1654   if (g_hash_table_size (address) > 0)
1655     {
1656       g_object_ref (contact);
1657
1658       geoclue_geocode_address_to_position_async (geocode, address,
1659           geocode_cb, contact);
1660     }
1661
1662   g_hash_table_unref (address);
1663 }
1664 #endif
1665
1666 static EmpathyCapabilities
1667 tp_caps_to_capabilities (TpCapabilities *caps)
1668 {
1669   EmpathyCapabilities capabilities = 0;
1670   guint i;
1671   GPtrArray *classes;
1672
1673   classes = tp_capabilities_get_channel_classes (caps);
1674
1675   for (i = 0; i < classes->len; i++)
1676     {
1677       GValueArray *class_struct;
1678       GHashTable *fixed_prop;
1679       GStrv allowed_prop;
1680       TpHandleType handle_type;
1681       const gchar *chan_type;
1682
1683       class_struct = g_ptr_array_index (classes, i);
1684       tp_value_array_unpack (class_struct, 2,
1685           &fixed_prop,
1686           &allowed_prop);
1687
1688       handle_type = tp_asv_get_uint32 (fixed_prop,
1689           TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
1690       if (handle_type != TP_HANDLE_TYPE_CONTACT)
1691         continue;
1692
1693       chan_type = tp_asv_get_string (fixed_prop,
1694           TP_PROP_CHANNEL_CHANNEL_TYPE);
1695
1696       if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1697         {
1698           capabilities |= EMPATHY_CAPABILITIES_FT;
1699         }
1700       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
1701         {
1702           const gchar *service;
1703
1704           service = tp_asv_get_string (fixed_prop,
1705               TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE);
1706
1707           if (!tp_strdiff (service, "rfb"))
1708             capabilities |= EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
1709         }
1710       else if (!tp_strdiff (chan_type,
1711         TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
1712         {
1713           guint j;
1714
1715           for (j = 0; allowed_prop[j] != NULL; j++)
1716             {
1717               if (!tp_strdiff (allowed_prop[j],
1718                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO))
1719                 capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1720               else if (!tp_strdiff (allowed_prop[j],
1721                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO))
1722                 capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1723             }
1724         }
1725     }
1726
1727   return capabilities;
1728 }
1729
1730 static void
1731 set_capabilities_from_tp_caps (EmpathyContact *self,
1732     TpCapabilities *caps)
1733 {
1734   EmpathyCapabilities capabilities;
1735
1736   if (caps == NULL)
1737     return;
1738
1739   capabilities = tp_caps_to_capabilities (caps);
1740   empathy_contact_set_capabilities (self, capabilities);
1741 }
1742
1743 static void
1744 contact_set_avatar_from_tp_contact (EmpathyContact *contact)
1745 {
1746   EmpathyContactPriv *priv = GET_PRIV (contact);
1747   const gchar *mime;
1748   GFile *file;
1749
1750   mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
1751   file = tp_contact_get_avatar_file (priv->tp_contact);
1752
1753   if (file != NULL)
1754     {
1755       EmpathyAvatar *avatar;
1756       gchar *data;
1757       gsize len;
1758
1759       g_file_load_contents (file, NULL, &data, &len, NULL, NULL);
1760       avatar = empathy_avatar_new ((guchar *) data, len, g_strdup (mime),
1761           g_file_get_path (file));
1762       contact_set_avatar (contact, avatar);
1763       empathy_avatar_unref (avatar);
1764     }
1765   else
1766     {
1767       contact_set_avatar (contact, NULL);
1768     }
1769 }
1770
1771 EmpathyContact *
1772 empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
1773 {
1774   EmpathyContact *contact = NULL;
1775
1776   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
1777
1778   if (contacts_table == NULL)
1779     contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
1780   else
1781     contact = g_hash_table_lookup (contacts_table, tp_contact);
1782
1783   if (contact == NULL)
1784     {
1785       contact = empathy_contact_new (tp_contact);
1786
1787       /* The hash table does not keep any ref.
1788        * contact keeps a ref to tp_contact, and is removed from the table in
1789        * contact_dispose() */
1790       g_hash_table_insert (contacts_table, tp_contact, contact);
1791     }
1792   else
1793     {
1794       g_object_ref (contact);
1795     }
1796
1797   return contact;
1798 }
1799
1800 static int
1801 presence_cmp_func (EmpathyContact *a,
1802     EmpathyContact *b)
1803 {
1804   FolksPresenceDetails *presence_a, *presence_b;
1805
1806   presence_a = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (a));
1807   presence_b = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (b));
1808
1809   /* We negate the result because we're sorting in reverse order (i.e. such that
1810    * the Personas with the highest presence are at the beginning of the list. */
1811   return -folks_presence_details_typecmp (
1812       folks_presence_details_get_presence_type (presence_a),
1813       folks_presence_details_get_presence_type (presence_b));
1814 }
1815
1816 static gint
1817 voip_cmp_func (EmpathyContact *a,
1818     EmpathyContact *b)
1819 {
1820   gboolean has_audio_a, has_audio_b;
1821   gboolean has_video_a, has_video_b;
1822
1823   has_audio_a = empathy_contact_can_voip_audio (a);
1824   has_audio_b = empathy_contact_can_voip_audio (b);
1825   has_video_a = empathy_contact_can_voip_video (a);
1826   has_video_b = empathy_contact_can_voip_video (b);
1827
1828   /* First check video */
1829   if (has_video_a == has_video_b)
1830     {
1831       /* Use audio to to break tie */
1832       if (has_audio_a == has_audio_b)
1833         return 0;
1834       else if (has_audio_a)
1835         return -1;
1836       else
1837         return 1;
1838     }
1839   else if (has_video_a)
1840     return -1;
1841   else
1842     return 1;
1843 }
1844
1845 static gint
1846 ft_cmp_func (EmpathyContact *a,
1847     EmpathyContact *b)
1848 {
1849   gboolean can_send_files_a, can_send_files_b;
1850
1851   can_send_files_a = empathy_contact_can_send_files (a);
1852   can_send_files_b = empathy_contact_can_send_files (b);
1853
1854   if (can_send_files_a == can_send_files_b)
1855     return 0;
1856   else if (can_send_files_a)
1857     return -1;
1858   else
1859     return 1;
1860 }
1861
1862 static gint
1863 rfb_stream_tube_cmp_func (EmpathyContact *a,
1864     EmpathyContact *b)
1865 {
1866   gboolean rfb_a, rfb_b;
1867
1868   rfb_a = empathy_contact_can_use_rfb_stream_tube (a);
1869   rfb_b = empathy_contact_can_use_rfb_stream_tube (b);
1870
1871   if (rfb_a == rfb_b)
1872     return 0;
1873   else if (rfb_a)
1874     return -1;
1875   else
1876     return 1;
1877 }
1878
1879 /* Sort by presence as with presence_cmp_func(), but if the two contacts have
1880  * the same presence, prefer the one which can do both audio *and* video calls,
1881  * over the one which can only do one of the two. */
1882 static int
1883 voip_sort_func (EmpathyContact *a, EmpathyContact *b)
1884 {
1885   gint presence_sort = presence_cmp_func (a, b);
1886
1887   if (presence_sort != 0)
1888     return presence_sort;
1889
1890   return voip_cmp_func (a, b);
1891 }
1892
1893 /* Sort by presence as with presence_cmp_func() and then break ties using the
1894  * most "capable" individual. So users will be able to pick more actions on
1895  * the contact in the "Contact" menu of the chat window. */
1896 static gint
1897 chat_sort_func (EmpathyContact *a,
1898     EmpathyContact *b)
1899 {
1900   gint result;
1901
1902   result = presence_cmp_func (a, b);
1903   if (result != 0)
1904     return result;
1905
1906   /* Prefer individual supporting file transfer */
1907   result = ft_cmp_func (a, b);
1908   if (result != 0)
1909     return result;
1910
1911   /* Check audio/video capabilities */
1912   result = voip_cmp_func (a, b);
1913   if (result != 0)
1914     return result;
1915
1916   /* Check 'Share my destop' feature */
1917   return rfb_stream_tube_cmp_func (a, b);
1918 }
1919
1920 static GCompareFunc
1921 get_sort_func_for_action (EmpathyActionType action_type)
1922 {
1923   switch (action_type)
1924     {
1925       case EMPATHY_ACTION_AUDIO_CALL:
1926       case EMPATHY_ACTION_VIDEO_CALL:
1927         return (GCompareFunc) voip_sort_func;
1928       case EMPATHY_ACTION_CHAT:
1929         return (GCompareFunc) chat_sort_func;
1930       case EMPATHY_ACTION_VIEW_LOGS:
1931       case EMPATHY_ACTION_SEND_FILE:
1932       case EMPATHY_ACTION_SHARE_MY_DESKTOP:
1933       default:
1934         return (GCompareFunc) presence_cmp_func;
1935     }
1936 }
1937
1938 /**
1939  * empathy_contact_dup_best_for_action:
1940  * @individual: a #FolksIndividual
1941  * @action_type: the type of action to be performed on the contact
1942  *
1943  * Chooses a #FolksPersona from the given @individual which is best-suited for
1944  * the given @action_type. "Best-suited" is determined by choosing the persona
1945  * with the highest presence out of all the personas which can perform the given
1946  * @action_type (e.g. are capable of video calling).
1947  *
1948  * Return value: an #EmpathyContact for the best persona, or %NULL;
1949  * unref with g_object_unref()
1950  */
1951 EmpathyContact *
1952 empathy_contact_dup_best_for_action (FolksIndividual *individual,
1953     EmpathyActionType action_type)
1954 {
1955   GList *personas, *contacts, *l;
1956   EmpathyContact *best_contact = NULL;
1957
1958   /* Build a list of EmpathyContacts that we can sort */
1959   personas = folks_individual_get_personas (individual);
1960   contacts = NULL;
1961
1962   for (l = personas; l != NULL; l = l->next)
1963     {
1964       TpContact *tp_contact;
1965       EmpathyContact *contact;
1966
1967       if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1968         continue;
1969
1970       tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
1971       contact = empathy_contact_dup_from_tp_contact (tp_contact);
1972       empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
1973
1974       /* Only choose the contact if they're actually capable of the specified
1975        * action. */
1976       if (!empathy_contact_can_do_action (contact, action_type))
1977         {
1978           g_object_unref (contact);
1979           continue;
1980         }
1981
1982       contacts = g_list_prepend (contacts, contact);
1983     }
1984
1985   /* Sort the contacts by some heuristic based on the action type, then take
1986    * the top contact. */
1987   if (contacts != NULL)
1988     {
1989       contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
1990       best_contact = g_object_ref (contacts->data);
1991     }
1992
1993   g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
1994   g_list_free (contacts);
1995
1996   return best_contact;
1997 }