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