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