]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact.c
Merge remote-tracking branch 'origin/gnome-3-8'
[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 #include "empathy-contact.h"
24
25 #ifdef HAVE_GEOCODE
26 #include <geocode-glib/geocode-glib.h>
27 #endif
28
29 #include "empathy-utils.h"
30 #include "empathy-enum-types.h"
31
32 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
33 #include "empathy-debug.h"
34
35 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContact)
36 typedef struct {
37   TpContact *tp_contact;
38   TpAccount *account;
39   FolksPersona *persona;
40   gchar *id;
41   gchar *alias;
42   gchar *logged_alias;
43   EmpathyAvatar *avatar;
44   TpConnectionPresenceType presence;
45   guint handle;
46   EmpathyCapabilities capabilities;
47   gboolean is_user;
48   /* Location is composed of string keys and GValues.
49    * Example: a "city" key would have "Helsinki" as string GValue,
50    *          a "latitude" would have 65.0 as double GValue.
51    *
52    * This is a super set of the location stored in TpContact as we can try add
53    * more fields by searching the address using geoclue.
54    */
55   GHashTable *location;
56   GeeHashSet *groups;
57   gchar **client_types;
58 } EmpathyContactPriv;
59
60 static void contact_finalize (GObject *object);
61 static void contact_get_property (GObject *object, guint param_id,
62     GValue *value, GParamSpec *pspec);
63 static void contact_set_property (GObject *object, guint param_id,
64     const GValue *value, GParamSpec *pspec);
65
66 #ifdef HAVE_GEOCODE
67 static void update_geocode (EmpathyContact *contact);
68 #endif
69
70 static void empathy_contact_set_location (EmpathyContact *contact,
71     GHashTable *location);
72
73 static void contact_set_client_types (EmpathyContact *contact,
74     const gchar * const *types);
75
76 static void set_capabilities_from_tp_caps (EmpathyContact *self,
77     TpCapabilities *caps);
78
79 static void contact_set_avatar (EmpathyContact *contact,
80     EmpathyAvatar *avatar);
81 static void contact_set_avatar_from_tp_contact (EmpathyContact *contact);
82 static gboolean contact_load_avatar_cache (EmpathyContact *contact,
83     const gchar *token);
84
85 G_DEFINE_TYPE (EmpathyContact, empathy_contact, G_TYPE_OBJECT);
86
87 enum
88 {
89   PROP_0,
90   PROP_TP_CONTACT,
91   PROP_ACCOUNT,
92   PROP_PERSONA,
93   PROP_ID,
94   PROP_ALIAS,
95   PROP_LOGGED_ALIAS,
96   PROP_AVATAR,
97   PROP_PRESENCE,
98   PROP_PRESENCE_MESSAGE,
99   PROP_HANDLE,
100   PROP_CAPABILITIES,
101   PROP_IS_USER,
102   PROP_LOCATION,
103   PROP_CLIENT_TYPES
104 };
105
106 enum {
107   PRESENCE_CHANGED,
108   LAST_SIGNAL
109 };
110
111 static guint signals[LAST_SIGNAL];
112
113 /* TpContact* -> EmpathyContact*, both borrowed ref */
114 static GHashTable *contacts_table = NULL;
115
116 static void
117 tp_contact_notify_cb (TpContact *tp_contact,
118                       GParamSpec *param,
119                       GObject *contact)
120 {
121   EmpathyContactPriv *priv = GET_PRIV (contact);
122
123   /* Forward property notifications */
124   if (!tp_strdiff (param->name, "alias"))
125     g_object_notify (contact, "alias");
126   else if (!tp_strdiff (param->name, "presence-type")) {
127     TpConnectionPresenceType presence;
128
129     presence = empathy_contact_get_presence (EMPATHY_CONTACT (contact));
130     g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence,
131       priv->presence);
132     priv->presence = presence;
133     g_object_notify (contact, "presence");
134   }
135   else if (!tp_strdiff (param->name, "identifier"))
136     g_object_notify (contact, "id");
137   else if (!tp_strdiff (param->name, "handle"))
138     g_object_notify (contact, "handle");
139   else if (!tp_strdiff (param->name, "location"))
140     {
141       GHashTable *location;
142
143       location = tp_contact_get_location (tp_contact);
144       /* This will start a geoclue search to find the address if needed */
145       empathy_contact_set_location (EMPATHY_CONTACT (contact), location);
146     }
147   else if (!tp_strdiff (param->name, "capabilities"))
148     {
149       set_capabilities_from_tp_caps (EMPATHY_CONTACT (contact),
150           tp_contact_get_capabilities (tp_contact));
151     }
152   else if (!tp_strdiff (param->name, "avatar-file"))
153     {
154       contact_set_avatar_from_tp_contact (EMPATHY_CONTACT (contact));
155     }
156   else if (!tp_strdiff (param->name, "client-types"))
157     {
158       contact_set_client_types (EMPATHY_CONTACT (contact),
159           tp_contact_get_client_types (tp_contact));
160     }
161 }
162
163 static void
164 folks_persona_notify_cb (FolksPersona *folks_persona,
165                          GParamSpec *param,
166                          GObject *contact)
167 {
168   if (!tp_strdiff (param->name, "presence-message"))
169     g_object_notify (contact, "presence-message");
170 }
171
172 static void
173 contact_dispose (GObject *object)
174 {
175   EmpathyContactPriv *priv = GET_PRIV (object);
176
177   if (priv->tp_contact != NULL)
178     {
179       g_signal_handlers_disconnect_by_func (priv->tp_contact,
180           tp_contact_notify_cb, object);
181     }
182   tp_clear_object (&priv->tp_contact);
183
184   if (priv->account)
185     g_object_unref (priv->account);
186   priv->account = NULL;
187
188   if (priv->persona)
189     {
190       g_signal_handlers_disconnect_by_func (priv->persona,
191           folks_persona_notify_cb, object);
192       g_object_unref (priv->persona);
193     }
194   priv->persona = NULL;
195
196   if (priv->avatar != NULL)
197     {
198       empathy_avatar_unref (priv->avatar);
199       priv->avatar = NULL;
200     }
201
202   if (priv->location != NULL)
203     {
204       g_hash_table_unref (priv->location);
205       priv->location = NULL;
206     }
207
208   G_OBJECT_CLASS (empathy_contact_parent_class)->dispose (object);
209 }
210
211 static void
212 contact_constructed (GObject *object)
213 {
214   EmpathyContact *contact = (EmpathyContact *) object;
215   EmpathyContactPriv *priv = GET_PRIV (contact);
216   GHashTable *location;
217   TpContact *self_contact;
218   const gchar * const *client_types;
219
220   if (priv->tp_contact == NULL)
221     return;
222
223   priv->presence = empathy_contact_get_presence (contact);
224
225   location = tp_contact_get_location (priv->tp_contact);
226   if (location != NULL)
227     empathy_contact_set_location (contact, location);
228
229   client_types = tp_contact_get_client_types (priv->tp_contact);
230   if (client_types != NULL)
231     contact_set_client_types (contact, client_types);
232
233   set_capabilities_from_tp_caps (contact,
234       tp_contact_get_capabilities (priv->tp_contact));
235
236   contact_set_avatar_from_tp_contact (contact);
237
238   /* Set is-user property. Note that it could still be the handle is
239    * different from the connection's self handle, in the case the handle
240    * comes from a group interface. */
241   self_contact = tp_connection_get_self_contact (
242       tp_contact_get_connection (priv->tp_contact));
243   empathy_contact_set_is_user (contact, self_contact == priv->tp_contact);
244
245   g_signal_connect (priv->tp_contact, "notify",
246     G_CALLBACK (tp_contact_notify_cb), contact);
247 }
248
249 static void
250 empathy_contact_class_init (EmpathyContactClass *class)
251 {
252   GObjectClass *object_class;
253
254   object_class = G_OBJECT_CLASS (class);
255
256   object_class->finalize = contact_finalize;
257   object_class->dispose = contact_dispose;
258   object_class->get_property = contact_get_property;
259   object_class->set_property = contact_set_property;
260   object_class->constructed = contact_constructed;
261
262   g_object_class_install_property (object_class,
263       PROP_TP_CONTACT,
264       g_param_spec_object ("tp-contact",
265         "TpContact",
266         "The TpContact associated with the contact",
267         TP_TYPE_CONTACT,
268         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
269
270   g_object_class_install_property (object_class,
271       PROP_ACCOUNT,
272       g_param_spec_object ("account",
273         "The account",
274         "The account associated with the contact",
275         TP_TYPE_ACCOUNT,
276         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
277
278   g_object_class_install_property (object_class,
279       PROP_PERSONA,
280       g_param_spec_object ("persona",
281         "Persona",
282         "The FolksPersona associated with the contact",
283         FOLKS_TYPE_PERSONA,
284         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
285
286   g_object_class_install_property (object_class,
287       PROP_ID,
288       g_param_spec_string ("id",
289         "Contact id",
290         "String identifying contact",
291         NULL,
292         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
293
294   g_object_class_install_property (object_class,
295       PROP_ALIAS,
296       g_param_spec_string ("alias",
297         "Contact alias",
298         "An alias for the contact",
299         NULL,
300         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
301
302   g_object_class_install_property (object_class,
303       PROP_LOGGED_ALIAS,
304       g_param_spec_string ("logged-alias",
305         "Logged alias",
306         "The alias the user had when a message was logged, "
307         "only set when using empathy_contact_from_tpl_contact()",
308         NULL,
309         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
310
311   g_object_class_install_property (object_class,
312       PROP_AVATAR,
313       g_param_spec_boxed ("avatar",
314         "Avatar image",
315         "The avatar image",
316         EMPATHY_TYPE_AVATAR,
317         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
318
319   g_object_class_install_property (object_class,
320       PROP_PRESENCE,
321       g_param_spec_uint ("presence",
322         "Contact presence",
323         "Presence of contact",
324         TP_CONNECTION_PRESENCE_TYPE_UNSET,
325         NUM_TP_CONNECTION_PRESENCE_TYPES,
326         TP_CONNECTION_PRESENCE_TYPE_UNSET,
327         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
328
329   g_object_class_install_property (object_class,
330       PROP_PRESENCE_MESSAGE,
331       g_param_spec_string ("presence-message",
332         "Contact presence message",
333         "Presence message of contact",
334         NULL,
335         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
336
337   g_object_class_install_property (object_class,
338       PROP_HANDLE,
339       g_param_spec_uint ("handle",
340         "Contact Handle",
341         "The handle of the contact",
342         0,
343         G_MAXUINT,
344         0,
345         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
346
347   g_object_class_install_property (object_class,
348       PROP_CAPABILITIES,
349       g_param_spec_flags ("capabilities",
350         "Contact Capabilities",
351         "Capabilities of the contact",
352         EMPATHY_TYPE_CAPABILITIES,
353         EMPATHY_CAPABILITIES_UNKNOWN,
354         G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
355
356   g_object_class_install_property (object_class,
357       PROP_IS_USER,
358       g_param_spec_boolean ("is-user",
359         "Contact is-user",
360         "Is contact the user",
361         FALSE,
362         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
363
364
365   g_object_class_install_property (object_class,
366       PROP_LOCATION,
367       g_param_spec_boxed ("location",
368         "Contact location",
369         "Physical location of the contact",
370         G_TYPE_HASH_TABLE,
371         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
372
373   g_object_class_install_property (object_class,
374       PROP_CLIENT_TYPES,
375       g_param_spec_boxed ("client-types",
376         "Contact client types",
377         "Client types of the contact",
378         G_TYPE_STRV,
379         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
380
381   signals[PRESENCE_CHANGED] =
382     g_signal_new ("presence-changed",
383                   G_TYPE_FROM_CLASS (class),
384                   G_SIGNAL_RUN_LAST,
385                   0,
386                   NULL, NULL,
387                   g_cclosure_marshal_generic,
388                   G_TYPE_NONE,
389                   2, G_TYPE_UINT,
390                   G_TYPE_UINT);
391
392   g_type_class_add_private (object_class, sizeof (EmpathyContactPriv));
393 }
394
395 static void
396 empathy_contact_init (EmpathyContact *contact)
397 {
398   EmpathyContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
399     EMPATHY_TYPE_CONTACT, EmpathyContactPriv);
400
401   contact->priv = priv;
402
403   priv->location = NULL;
404   priv->client_types = NULL;
405   priv->groups = NULL;
406 }
407
408 static void
409 contact_finalize (GObject *object)
410 {
411   EmpathyContactPriv *priv;
412
413   priv = GET_PRIV (object);
414
415   DEBUG ("finalize: %p", object);
416
417   g_clear_object (&priv->groups);
418   g_free (priv->alias);
419   g_free (priv->logged_alias);
420   g_free (priv->id);
421   g_strfreev (priv->client_types);
422
423   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
424 }
425
426 static void
427 empathy_contact_set_capabilities (EmpathyContact *contact,
428                                   EmpathyCapabilities capabilities)
429 {
430   EmpathyContactPriv *priv;
431
432   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
433
434   priv = GET_PRIV (contact);
435
436   if (priv->capabilities == capabilities)
437     return;
438
439   priv->capabilities = capabilities;
440
441   g_object_notify (G_OBJECT (contact), "capabilities");
442 }
443
444 static void
445 empathy_contact_set_id (EmpathyContact *contact,
446                         const gchar *id)
447 {
448   EmpathyContactPriv *priv;
449
450   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
451   g_return_if_fail (id != NULL);
452
453   priv = GET_PRIV (contact);
454
455   /* We temporally ref the contact because it could be destroyed
456    * during the signal emition */
457   g_object_ref (contact);
458   if (tp_strdiff (id, priv->id))
459     {
460       g_free (priv->id);
461       priv->id = g_strdup (id);
462
463       g_object_notify (G_OBJECT (contact), "id");
464       if (EMP_STR_EMPTY (priv->alias))
465           g_object_notify (G_OBJECT (contact), "alias");
466     }
467
468   g_object_unref (contact);
469 }
470
471 static void
472 empathy_contact_set_presence (EmpathyContact *contact,
473                               TpConnectionPresenceType presence)
474 {
475   EmpathyContactPriv *priv;
476   TpConnectionPresenceType old_presence;
477
478   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
479
480   priv = GET_PRIV (contact);
481
482   if (presence == priv->presence)
483     return;
484
485   old_presence = priv->presence;
486   priv->presence = presence;
487
488   g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
489
490   g_object_notify (G_OBJECT (contact), "presence");
491 }
492
493 static void
494 empathy_contact_set_presence_message (EmpathyContact *contact,
495                                       const gchar *message)
496 {
497   EmpathyContactPriv *priv = GET_PRIV (contact);
498
499   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
500
501   if (priv->persona != NULL)
502     {
503       folks_presence_details_set_presence_message (
504           FOLKS_PRESENCE_DETAILS (priv->persona), message);
505     }
506 }
507
508 static void
509 empathy_contact_set_handle (EmpathyContact *contact,
510                             guint handle)
511 {
512   EmpathyContactPriv *priv;
513
514   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
515
516   priv = GET_PRIV (contact);
517
518   g_object_ref (contact);
519   if (handle != priv->handle)
520     {
521       priv->handle = handle;
522       g_object_notify (G_OBJECT (contact), "handle");
523     }
524   g_object_unref (contact);
525 }
526
527 static void
528 contact_get_property (GObject *object,
529                       guint param_id,
530                       GValue *value,
531                       GParamSpec *pspec)
532 {
533   EmpathyContact *contact = EMPATHY_CONTACT (object);
534
535   switch (param_id)
536     {
537       case PROP_TP_CONTACT:
538         g_value_set_object (value, empathy_contact_get_tp_contact (contact));
539         break;
540       case PROP_ACCOUNT:
541         g_value_set_object (value, empathy_contact_get_account (contact));
542         break;
543       case PROP_PERSONA:
544         g_value_set_object (value, empathy_contact_get_persona (contact));
545         break;
546       case PROP_ID:
547         g_value_set_string (value, empathy_contact_get_id (contact));
548         break;
549       case PROP_ALIAS:
550         g_value_set_string (value, empathy_contact_get_alias (contact));
551         break;
552       case PROP_LOGGED_ALIAS:
553         g_value_set_string (value, empathy_contact_get_logged_alias (contact));
554         break;
555       case PROP_AVATAR:
556         g_value_set_boxed (value, empathy_contact_get_avatar (contact));
557         break;
558       case PROP_PRESENCE:
559         g_value_set_uint (value, empathy_contact_get_presence (contact));
560         break;
561       case PROP_PRESENCE_MESSAGE:
562         g_value_set_string (value, empathy_contact_get_presence_message (contact));
563         break;
564       case PROP_HANDLE:
565         g_value_set_uint (value, empathy_contact_get_handle (contact));
566         break;
567       case PROP_CAPABILITIES:
568         g_value_set_flags (value, empathy_contact_get_capabilities (contact));
569         break;
570       case PROP_IS_USER:
571         g_value_set_boolean (value, empathy_contact_is_user (contact));
572         break;
573       default:
574         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
575         break;
576     };
577 }
578
579 static void
580 contact_set_property (GObject *object,
581                       guint param_id,
582                       const GValue *value,
583                       GParamSpec *pspec)
584 {
585   EmpathyContact *contact = EMPATHY_CONTACT (object);
586   EmpathyContactPriv *priv = GET_PRIV (object);
587
588   switch (param_id)
589     {
590       case PROP_TP_CONTACT:
591         priv->tp_contact = g_value_dup_object (value);
592         break;
593       case PROP_ACCOUNT:
594         g_assert (priv->account == NULL);
595         priv->account = g_value_dup_object (value);
596         break;
597       case PROP_PERSONA:
598         empathy_contact_set_persona (contact, g_value_get_object (value));
599         break;
600       case PROP_ID:
601         empathy_contact_set_id (contact, g_value_get_string (value));
602         break;
603       case PROP_ALIAS:
604         empathy_contact_set_alias (contact, g_value_get_string (value));
605         break;
606       case PROP_LOGGED_ALIAS:
607         g_assert (priv->logged_alias == NULL);
608         priv->logged_alias = g_value_dup_string (value);
609         break;
610       case PROP_PRESENCE:
611         empathy_contact_set_presence (contact, g_value_get_uint (value));
612         break;
613       case PROP_PRESENCE_MESSAGE:
614         empathy_contact_set_presence_message (contact, g_value_get_string (value));
615         break;
616       case PROP_HANDLE:
617         empathy_contact_set_handle (contact, g_value_get_uint (value));
618         break;
619       case PROP_CAPABILITIES:
620         empathy_contact_set_capabilities (contact, g_value_get_flags (value));
621         break;
622       case PROP_IS_USER:
623         empathy_contact_set_is_user (contact, g_value_get_boolean (value));
624         break;
625       default:
626         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
627         break;
628     };
629 }
630
631 static void
632 remove_tp_contact (gpointer data,
633     GObject *object)
634 {
635   g_hash_table_remove (contacts_table, data);
636 }
637
638 static EmpathyContact *
639 empathy_contact_new (TpContact *tp_contact)
640 {
641   EmpathyContact *retval;
642
643   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
644
645   retval = g_object_new (EMPATHY_TYPE_CONTACT,
646       "tp-contact", tp_contact,
647       NULL);
648
649   g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact);
650
651   return retval;
652 }
653
654 typedef struct
655 {
656   TplEntity *entity;
657   TpAccount *account;
658 } FindContactData;
659
660 static gboolean
661 contact_is_tpl_entity (gpointer key,
662     gpointer value,
663     gpointer user_data)
664 {
665   EmpathyContact *contact = value;
666   FindContactData *data = user_data;
667   TpAccount *account = empathy_contact_get_account (contact);
668   const gchar *path = NULL;
669
670   if (account != NULL)
671     path = tp_proxy_get_object_path (account);
672
673   return !tp_strdiff (empathy_contact_get_id (contact),
674               tpl_entity_get_identifier (data->entity)) &&
675          !tp_strdiff (tp_proxy_get_object_path (data->account), path);
676 }
677
678 static void
679 get_contacts_cb (GObject *source,
680     GAsyncResult *result,
681     gpointer user_data)
682 {
683   TpWeakRef *wr = user_data;
684   EmpathyContactPriv *priv;
685   EmpathyContact *self;
686
687   self = tp_weak_ref_dup_object (wr);
688   if (self == NULL)
689     goto out;
690
691   priv = GET_PRIV (self);
692
693   g_return_if_fail (priv->tp_contact == NULL);
694
695   priv->tp_contact = tp_connection_dup_contact_by_id_finish (
696       TP_CONNECTION (source), result, NULL);
697   if (priv->tp_contact == NULL)
698     goto out;
699
700   g_object_notify (G_OBJECT (self), "tp-contact");
701
702   /* Update capabilities now that we have a TpContact */
703   set_capabilities_from_tp_caps (self,
704       tp_contact_get_capabilities (priv->tp_contact));
705
706 out:
707   g_clear_object (&self);
708   tp_weak_ref_destroy (wr);
709 }
710
711 EmpathyContact *
712 empathy_contact_from_tpl_contact (TpAccount *account,
713     TplEntity *tpl_entity)
714 {
715   EmpathyContact *retval;
716   gboolean is_user;
717   EmpathyContact *existing_contact = NULL;
718
719   g_return_val_if_fail (TPL_IS_ENTITY (tpl_entity), NULL);
720
721   if (contacts_table != NULL)
722     {
723       FindContactData data;
724
725       data.entity = tpl_entity;
726       data.account = account;
727
728       existing_contact = g_hash_table_find (contacts_table,
729         contact_is_tpl_entity, &data);
730     }
731
732   if (existing_contact != NULL)
733     {
734       retval = g_object_new (EMPATHY_TYPE_CONTACT,
735           "tp-contact", empathy_contact_get_tp_contact (existing_contact),
736           "logged-alias", tpl_entity_get_alias (tpl_entity),
737           NULL);
738     }
739   else
740     {
741       TpConnection *conn;
742       const gchar *id;
743
744       is_user = (TPL_ENTITY_SELF == tpl_entity_get_entity_type (tpl_entity));
745
746       id = tpl_entity_get_identifier (tpl_entity);
747
748       retval = g_object_new (EMPATHY_TYPE_CONTACT,
749           "id", id,
750           "alias", tpl_entity_get_alias (tpl_entity),
751           "account", account,
752           "is-user", is_user,
753           NULL);
754
755       /* Try to get a TpContact associated to have at least contact
756        * capabilities if possible. This is useful for CM supporting calling
757        * offline contacts for example. */
758       conn = tp_account_get_connection (account);
759       if (conn != NULL)
760         {
761           TpContactFeature features[] = { TP_CONTACT_FEATURE_CAPABILITIES };
762           conn = tp_account_get_connection (account);
763
764           tp_connection_dup_contact_by_id_async (conn, id,
765               G_N_ELEMENTS (features), features, get_contacts_cb,
766               tp_weak_ref_new (retval, NULL, NULL));
767         }
768     }
769
770   if (!EMP_STR_EMPTY (tpl_entity_get_avatar_token (tpl_entity)))
771     contact_load_avatar_cache (retval,
772         tpl_entity_get_avatar_token (tpl_entity));
773
774   return retval;
775 }
776
777 TpContact *
778 empathy_contact_get_tp_contact (EmpathyContact *contact)
779 {
780   EmpathyContactPriv *priv;
781
782   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
783
784   priv = GET_PRIV (contact);
785
786   return priv->tp_contact;
787 }
788
789 const gchar *
790 empathy_contact_get_id (EmpathyContact *contact)
791 {
792   EmpathyContactPriv *priv;
793
794   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
795
796   priv = GET_PRIV (contact);
797
798   if (priv->tp_contact != NULL)
799     return tp_contact_get_identifier (priv->tp_contact);
800
801   return priv->id;
802 }
803
804 const gchar *
805 empathy_contact_get_alias (EmpathyContact *contact)
806 {
807   EmpathyContactPriv *priv;
808   const gchar        *alias = NULL;
809
810   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
811
812   priv = GET_PRIV (contact);
813
814   if (!EMP_STR_EMPTY (priv->alias))
815     alias = priv->alias;
816   else if (priv->tp_contact != NULL)
817     alias = tp_contact_get_alias (priv->tp_contact);
818
819   if (!EMP_STR_EMPTY (alias))
820     return alias;
821   else
822     return empathy_contact_get_id (contact);
823 }
824
825 const gchar *
826 empathy_contact_get_logged_alias (EmpathyContact *contact)
827 {
828   EmpathyContactPriv *priv;
829
830   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
831
832   priv = GET_PRIV (contact);
833
834   if (priv->logged_alias != NULL)
835     return priv->logged_alias;
836   else
837     return empathy_contact_get_alias (contact);
838 }
839
840 void
841 empathy_contact_set_alias (EmpathyContact *contact,
842                           const gchar *alias)
843 {
844   EmpathyContactPriv *priv;
845   FolksPersona *persona;
846
847   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
848
849   priv = GET_PRIV (contact);
850
851   g_object_ref (contact);
852
853   /* Set the alias on the persona if possible */
854   persona = empathy_contact_get_persona (contact);
855   if (persona != NULL && FOLKS_IS_ALIAS_DETAILS (persona))
856     {
857       DEBUG ("Setting alias for contact %s to %s",
858           empathy_contact_get_id (contact), alias);
859
860       folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (persona), alias);
861     }
862
863   if (tp_strdiff (alias, priv->alias))
864     {
865       g_free (priv->alias);
866       priv->alias = g_strdup (alias);
867       g_object_notify (G_OBJECT (contact), "alias");
868     }
869
870   g_object_unref (contact);
871 }
872
873 static void
874 groups_change_group_cb (GObject *source,
875     GAsyncResult *result,
876     gpointer user_data)
877 {
878   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
879   GError *error = NULL;
880
881   folks_group_details_change_group_finish (group_details, result, &error);
882   if (error != NULL)
883     {
884       g_warning ("failed to change group: %s", error->message);
885       g_clear_error (&error);
886     }
887 }
888
889 void
890 empathy_contact_change_group (EmpathyContact *contact, const gchar *group,
891     gboolean is_member)
892 {
893   EmpathyContactPriv *priv;
894   FolksPersona *persona;
895
896   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
897   g_return_if_fail (group != NULL);
898
899   priv = GET_PRIV (contact);
900
901   /* Normally pass through the changes to the persona */
902   persona = empathy_contact_get_persona (contact);
903   if (persona != NULL)
904     {
905       if (FOLKS_IS_GROUP_DETAILS (persona))
906         folks_group_details_change_group (FOLKS_GROUP_DETAILS (persona), group,
907             is_member, groups_change_group_cb, contact);
908       return;
909     }
910
911   /* If the persona doesn't exist yet, we have to cache the changes until it
912    * does */
913   if (priv->groups == NULL)
914     {
915       priv->groups = gee_hash_set_new (G_TYPE_STRING, (GBoxedCopyFunc) g_strdup,
916           g_free, NULL, NULL, NULL, NULL, NULL, NULL);
917     }
918
919   gee_collection_add (GEE_COLLECTION (priv->groups), group);
920 }
921
922 EmpathyAvatar *
923 empathy_contact_get_avatar (EmpathyContact *contact)
924 {
925   EmpathyContactPriv *priv;
926
927   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
928
929   priv = GET_PRIV (contact);
930
931   return priv->avatar;
932 }
933
934 static void
935 contact_set_avatar (EmpathyContact *contact,
936                     EmpathyAvatar *avatar)
937 {
938   EmpathyContactPriv *priv;
939
940   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
941
942   priv = GET_PRIV (contact);
943
944   if (priv->avatar == avatar)
945     return;
946
947   if (priv->avatar)
948     {
949       empathy_avatar_unref (priv->avatar);
950       priv->avatar = NULL;
951     }
952
953   if (avatar)
954       priv->avatar = empathy_avatar_ref (avatar);
955
956   g_object_notify (G_OBJECT (contact), "avatar");
957 }
958
959 TpAccount *
960 empathy_contact_get_account (EmpathyContact *contact)
961 {
962   EmpathyContactPriv *priv;
963
964   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
965
966   priv = GET_PRIV (contact);
967
968   if (priv->account == NULL && priv->tp_contact != NULL)
969     {
970       TpConnection *connection;
971
972       /* FIXME: This assume the account manager already exists */
973       connection = tp_contact_get_connection (priv->tp_contact);
974       priv->account =
975         g_object_ref (tp_connection_get_account (connection));
976     }
977
978   return priv->account;
979 }
980
981 FolksPersona *
982 empathy_contact_get_persona (EmpathyContact *contact)
983 {
984   EmpathyContactPriv *priv;
985
986   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
987
988   priv = GET_PRIV (contact);
989
990   if (priv->persona == NULL && priv->tp_contact != NULL)
991     {
992       TpfPersona *persona;
993
994       persona = tpf_persona_dup_for_contact (priv->tp_contact);
995       if (persona != NULL)
996         {
997           empathy_contact_set_persona (contact, (FolksPersona *) persona);
998           g_object_unref (persona);
999         }
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_cm_name (account),
1342       tp_account_get_protocol_name (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   GList *res;
1617   GeocodeLocation *loc;
1618   GHashTable *new_location;
1619   GHashTable *resolved = NULL;
1620   gdouble latitude, longitude;
1621
1622   if (priv->location == NULL)
1623     goto out;
1624
1625   res = geocode_forward_search_finish (GEOCODE_FORWARD (source), result,
1626       &error);
1627
1628   if (res == NULL)
1629     {
1630       DEBUG ("Failed to resolve geocode: %s", error->message);
1631       g_error_free (error);
1632       goto out;
1633     }
1634
1635   loc = res->data;
1636
1637   new_location = tp_asv_new (
1638       EMPATHY_LOCATION_LAT, G_TYPE_DOUBLE, loc->latitude,
1639       EMPATHY_LOCATION_LON, G_TYPE_DOUBLE, loc->longitude,
1640       NULL);
1641
1642   DEBUG ("\t - Latitude: %f", loc->latitude);
1643   DEBUG ("\t - Longitude: %f", loc->longitude);
1644
1645   g_list_free_full (res, (GDestroyNotify) geocode_location_free);
1646
1647   /* Copy remaning fields. LAT and LON were not defined so we won't overwrite
1648    * the values we just set. */
1649   tp_g_hash_table_update (new_location, priv->location,
1650       (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup);
1651
1652   /* Don't change the accuracy as we used an address to get this position */
1653   g_hash_table_unref (priv->location);
1654   priv->location = new_location;
1655   g_object_notify ((GObject *) contact, "location");
1656
1657 out:
1658   tp_clear_pointer (&resolved, g_hash_table_unref);
1659   g_object_unref (contact);
1660 }
1661
1662 static void
1663 update_geocode (EmpathyContact *contact)
1664 {
1665   GeocodeForward *geocode;
1666   GHashTable *location;
1667
1668   location = empathy_contact_get_location (contact);
1669   if (location == NULL ||
1670       g_hash_table_size (location) == 0)
1671     return;
1672
1673   /* No need to search for position if contact published it */
1674   if (g_hash_table_lookup (location, EMPATHY_LOCATION_LAT) != NULL ||
1675       g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL)
1676     return;
1677
1678   geocode = geocode_forward_new_for_params (location);
1679   if (geocode == NULL)
1680     return;
1681
1682   geocode_forward_search_async (geocode, NULL, geocode_cb,
1683       g_object_ref (contact));
1684
1685   g_object_unref (geocode);
1686 }
1687 #endif
1688
1689 static EmpathyCapabilities
1690 tp_caps_to_capabilities (TpCapabilities *caps)
1691 {
1692   EmpathyCapabilities capabilities = 0;
1693
1694   if (tp_capabilities_supports_file_transfer (caps))
1695     capabilities |= EMPATHY_CAPABILITIES_FT;
1696
1697   if (tp_capabilities_supports_stream_tubes (caps, TP_HANDLE_TYPE_CONTACT,
1698         "rfb"))
1699     capabilities |= EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
1700
1701   if (tp_capabilities_supports_audio_video_call (caps, TP_HANDLE_TYPE_CONTACT))
1702     {
1703       capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1704       capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1705     }
1706   else if (tp_capabilities_supports_audio_call (caps, TP_HANDLE_TYPE_CONTACT))
1707     {
1708       capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1709     }
1710
1711   if (tp_capabilities_supports_sms (caps))
1712     capabilities |= EMPATHY_CAPABILITIES_SMS;
1713
1714   return capabilities;
1715 }
1716
1717 static void
1718 set_capabilities_from_tp_caps (EmpathyContact *self,
1719     TpCapabilities *caps)
1720 {
1721   EmpathyCapabilities capabilities;
1722
1723   if (caps == NULL)
1724     return;
1725
1726   capabilities = tp_caps_to_capabilities (caps);
1727   empathy_contact_set_capabilities (self, capabilities);
1728 }
1729
1730 static void
1731 contact_set_avatar_from_tp_contact (EmpathyContact *contact)
1732 {
1733   EmpathyContactPriv *priv = GET_PRIV (contact);
1734   const gchar *mime;
1735   GFile *file;
1736
1737   mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
1738   file = tp_contact_get_avatar_file (priv->tp_contact);
1739
1740   if (file != NULL)
1741     {
1742       EmpathyAvatar *avatar;
1743       gchar *data;
1744       gsize len;
1745       gchar *path;
1746       GError *error = NULL;
1747
1748       if (!g_file_load_contents (file, NULL, &data, &len, NULL, &error))
1749         {
1750           DEBUG ("Failed to load avatar: %s", error->message);
1751
1752           g_error_free (error);
1753           contact_set_avatar (contact, NULL);
1754           return;
1755         }
1756
1757       path = g_file_get_path (file);
1758
1759       avatar = empathy_avatar_new ((guchar *) data, len, mime, path);
1760
1761       contact_set_avatar (contact, avatar);
1762       empathy_avatar_unref (avatar);
1763       g_free (path);
1764       g_free (data);
1765     }
1766   else
1767     {
1768       contact_set_avatar (contact, NULL);
1769     }
1770 }
1771
1772 EmpathyContact *
1773 empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
1774 {
1775   EmpathyContact *contact = NULL;
1776
1777   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
1778
1779   if (contacts_table == NULL)
1780     contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
1781   else
1782     contact = g_hash_table_lookup (contacts_table, tp_contact);
1783
1784   if (contact == NULL)
1785     {
1786       contact = empathy_contact_new (tp_contact);
1787
1788       /* The hash table does not keep any ref.
1789        * contact keeps a ref to tp_contact, and is removed from the table in
1790        * contact_dispose() */
1791       g_hash_table_insert (contacts_table, tp_contact, contact);
1792     }
1793   else
1794     {
1795       g_object_ref (contact);
1796     }
1797
1798   return contact;
1799 }
1800
1801 static int
1802 presence_cmp_func (EmpathyContact *a,
1803     EmpathyContact *b)
1804 {
1805   FolksPresenceDetails *presence_a, *presence_b;
1806
1807   presence_a = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (a));
1808   presence_b = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (b));
1809
1810   /* We negate the result because we're sorting in reverse order (i.e. such that
1811    * the Personas with the highest presence are at the beginning of the list. */
1812   return -folks_presence_details_typecmp (
1813       folks_presence_details_get_presence_type (presence_a),
1814       folks_presence_details_get_presence_type (presence_b));
1815 }
1816
1817 static gint
1818 voip_cmp_func (EmpathyContact *a,
1819     EmpathyContact *b)
1820 {
1821   gboolean has_audio_a, has_audio_b;
1822   gboolean has_video_a, has_video_b;
1823
1824   has_audio_a = empathy_contact_can_voip_audio (a);
1825   has_audio_b = empathy_contact_can_voip_audio (b);
1826   has_video_a = empathy_contact_can_voip_video (a);
1827   has_video_b = empathy_contact_can_voip_video (b);
1828
1829   /* First check video */
1830   if (has_video_a == has_video_b)
1831     {
1832       /* Use audio to to break tie */
1833       if (has_audio_a == has_audio_b)
1834         return 0;
1835       else if (has_audio_a)
1836         return -1;
1837       else
1838         return 1;
1839     }
1840   else if (has_video_a)
1841     return -1;
1842   else
1843     return 1;
1844 }
1845
1846 static gint
1847 ft_cmp_func (EmpathyContact *a,
1848     EmpathyContact *b)
1849 {
1850   gboolean can_send_files_a, can_send_files_b;
1851
1852   can_send_files_a = empathy_contact_can_send_files (a);
1853   can_send_files_b = empathy_contact_can_send_files (b);
1854
1855   if (can_send_files_a == can_send_files_b)
1856     return 0;
1857   else if (can_send_files_a)
1858     return -1;
1859   else
1860     return 1;
1861 }
1862
1863 static gint
1864 rfb_stream_tube_cmp_func (EmpathyContact *a,
1865     EmpathyContact *b)
1866 {
1867   gboolean rfb_a, rfb_b;
1868
1869   rfb_a = empathy_contact_can_use_rfb_stream_tube (a);
1870   rfb_b = empathy_contact_can_use_rfb_stream_tube (b);
1871
1872   if (rfb_a == rfb_b)
1873     return 0;
1874   else if (rfb_a)
1875     return -1;
1876   else
1877     return 1;
1878 }
1879
1880 /* Sort by presence as with presence_cmp_func(), but if the two contacts have
1881  * the same presence, prefer the one which can do both audio *and* video calls,
1882  * over the one which can only do one of the two. */
1883 static int
1884 voip_sort_func (EmpathyContact *a, EmpathyContact *b)
1885 {
1886   gint presence_sort = presence_cmp_func (a, b);
1887
1888   if (presence_sort != 0)
1889     return presence_sort;
1890
1891   return voip_cmp_func (a, b);
1892 }
1893
1894 /* Sort by presence as with presence_cmp_func() and then break ties using the
1895  * most "capable" individual. So users will be able to pick more actions on
1896  * the contact in the "Contact" menu of the chat window. */
1897 static gint
1898 chat_sort_func (EmpathyContact *a,
1899     EmpathyContact *b)
1900 {
1901   gint result;
1902
1903   result = presence_cmp_func (a, b);
1904   if (result != 0)
1905     return result;
1906
1907   /* Prefer individual supporting file transfer */
1908   result = ft_cmp_func (a, b);
1909   if (result != 0)
1910     return result;
1911
1912   /* Check audio/video capabilities */
1913   result = voip_cmp_func (a, b);
1914   if (result != 0)
1915     return result;
1916
1917   /* Check 'Share my destop' feature */
1918   return rfb_stream_tube_cmp_func (a, b);
1919 }
1920
1921 static GCompareFunc
1922 get_sort_func_for_action (EmpathyActionType action_type)
1923 {
1924   switch (action_type)
1925     {
1926       case EMPATHY_ACTION_AUDIO_CALL:
1927       case EMPATHY_ACTION_VIDEO_CALL:
1928         return (GCompareFunc) voip_sort_func;
1929       case EMPATHY_ACTION_CHAT:
1930         return (GCompareFunc) chat_sort_func;
1931       case EMPATHY_ACTION_VIEW_LOGS:
1932       case EMPATHY_ACTION_SEND_FILE:
1933       case EMPATHY_ACTION_SHARE_MY_DESKTOP:
1934       default:
1935         return (GCompareFunc) presence_cmp_func;
1936     }
1937 }
1938
1939 /**
1940  * empathy_contact_dup_best_for_action:
1941  * @individual: a #FolksIndividual
1942  * @action_type: the type of action to be performed on the contact
1943  *
1944  * Chooses a #FolksPersona from the given @individual which is best-suited for
1945  * the given @action_type. "Best-suited" is determined by choosing the persona
1946  * with the highest presence out of all the personas which can perform the given
1947  * @action_type (e.g. are capable of video calling).
1948  *
1949  * Return value: an #EmpathyContact for the best persona, or %NULL;
1950  * unref with g_object_unref()
1951  */
1952 EmpathyContact *
1953 empathy_contact_dup_best_for_action (FolksIndividual *individual,
1954     EmpathyActionType action_type)
1955 {
1956   GeeSet *personas;
1957   GeeIterator *iter;
1958   GList *contacts;
1959   EmpathyContact *best_contact = NULL;
1960
1961   /* Build a list of EmpathyContacts that we can sort */
1962   personas = folks_individual_get_personas (individual);
1963   contacts = NULL;
1964
1965   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1966   while (gee_iterator_next (iter))
1967     {
1968       FolksPersona *persona = gee_iterator_get (iter);
1969       TpContact *tp_contact;
1970       EmpathyContact *contact = NULL;
1971
1972       if (!empathy_folks_persona_is_interesting (persona))
1973         goto while_finish;
1974
1975       tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1976       if (tp_contact == NULL)
1977         goto while_finish;
1978
1979       contact = empathy_contact_dup_from_tp_contact (tp_contact);
1980       empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
1981
1982       /* Only choose the contact if they're actually capable of the specified
1983        * action. */
1984       if (empathy_contact_can_do_action (contact, action_type))
1985         contacts = g_list_prepend (contacts, g_object_ref (contact));
1986
1987 while_finish:
1988       g_clear_object (&contact);
1989       g_clear_object (&persona);
1990     }
1991   g_clear_object (&iter);
1992
1993   /* Sort the contacts by some heuristic based on the action type, then take
1994    * the top contact. */
1995   if (contacts != NULL)
1996     {
1997       contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
1998       best_contact = g_object_ref (contacts->data);
1999     }
2000
2001   g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2002   g_list_free (contacts);
2003
2004   return best_contact;
2005 }
2006
2007 #define declare_contact_cb(name) \
2008 static void \
2009 contact_##name##_cb (GObject *source, \
2010     GAsyncResult *result, \
2011     gpointer user_data) \
2012 { \
2013   TpContact *contact = (TpContact *) source; \
2014   GError *error = NULL; \
2015   \
2016   if (!tp_contact_##name##_finish (contact, result, &error)) \
2017     { \
2018       DEBUG ("Failed to ##name## on %s\n", \
2019           tp_contact_get_identifier (contact)); \
2020       g_error_free (error); \
2021     } \
2022 }
2023
2024 declare_contact_cb (request_subscription)
2025 declare_contact_cb (authorize_publication)
2026 declare_contact_cb (unblock)
2027
2028 void
2029 empathy_contact_add_to_contact_list (EmpathyContact *self,
2030     const gchar *message)
2031 {
2032   EmpathyContactPriv *priv = GET_PRIV (self);
2033
2034   g_return_if_fail (priv->tp_contact != NULL);
2035
2036   tp_contact_request_subscription_async (priv->tp_contact, message,
2037       contact_request_subscription_cb, NULL);
2038
2039   tp_contact_authorize_publication_async (priv->tp_contact,
2040       contact_authorize_publication_cb, NULL);
2041
2042   tp_contact_unblock_async (priv->tp_contact, contact_unblock_cb, NULL);
2043 }
2044
2045 declare_contact_cb (remove)
2046
2047 void
2048 empathy_contact_remove_from_contact_list (EmpathyContact *self)
2049 {
2050   EmpathyContactPriv *priv = GET_PRIV (self);
2051
2052   g_return_if_fail (priv->tp_contact != NULL);
2053
2054   tp_contact_remove_async (priv->tp_contact, contact_remove_cb, NULL);
2055 }