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