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