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