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