]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact.c
Make clear that contacts_table doesn't keep any ref
[empathy.git] / libempathy / empathy-contact.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /*
3  * Copyright (C) 2007-2009 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25
26 #include <glib/gi18n-lib.h>
27
28 #include <telepathy-glib/account-manager.h>
29 #include <telepathy-glib/interfaces.h>
30 #include <telepathy-glib/util.h>
31 #ifdef ENABEL_TPL
32 #include <telepathy-logger/contact.h>
33 #endif /* ENABLE_TPL */
34
35 #if HAVE_GEOCLUE
36 #include <geoclue/geoclue-geocode.h>
37 #endif
38
39 #include "empathy-contact.h"
40 #include "empathy-utils.h"
41 #include "empathy-enum-types.h"
42 #include "empathy-marshal.h"
43 #include "empathy-location.h"
44
45 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
46 #include "empathy-debug.h"
47
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContact)
49 typedef struct {
50   TpContact *tp_contact;
51   TpAccount *account;
52   gchar *id;
53   gchar *name;
54   EmpathyAvatar *avatar;
55   TpConnectionPresenceType presence;
56   gchar *presence_message;
57   guint handle;
58   EmpathyCapabilities capabilities;
59   gboolean is_user;
60   guint hash;
61   /* Location is composed of string keys and GValues.
62    * Example: a "city" key would have "Helsinki" as string GValue,
63    *          a "latitude" would have 65.0 as double GValue.
64    *
65    * This is a super set of the location stored in TpContact as we can try add
66    * more fields by searching the address using geoclue.
67    */
68   GHashTable *location;
69 } EmpathyContactPriv;
70
71 static void contact_finalize (GObject *object);
72 static void contact_get_property (GObject *object, guint param_id,
73     GValue *value, GParamSpec *pspec);
74 static void contact_set_property (GObject *object, guint param_id,
75     const GValue *value, GParamSpec *pspec);
76
77 #if HAVE_GEOCLUE
78 static void update_geocode (EmpathyContact *contact);
79 #endif
80
81 static void empathy_contact_set_location (EmpathyContact *contact,
82     GHashTable *location);
83
84 static void set_capabilities_from_tp_caps (EmpathyContact *self,
85     TpCapabilities *caps);
86
87 static void contact_set_avatar_from_tp_contact (EmpathyContact *contact);
88
89 G_DEFINE_TYPE (EmpathyContact, empathy_contact, G_TYPE_OBJECT);
90
91 enum
92 {
93   PROP_0,
94   PROP_TP_CONTACT,
95   PROP_ACCOUNT,
96   PROP_ID,
97   PROP_NAME,
98   PROP_AVATAR,
99   PROP_PRESENCE,
100   PROP_PRESENCE_MESSAGE,
101   PROP_HANDLE,
102   PROP_CAPABILITIES,
103   PROP_IS_USER,
104   PROP_LOCATION
105 };
106
107 enum {
108   PRESENCE_CHANGED,
109   LAST_SIGNAL
110 };
111
112 static guint signals[LAST_SIGNAL];
113
114 /* TpContact* -> EmpathyContact*, both borrowed ref */
115 static GHashTable *contacts_table = NULL;
116
117 static void
118 tp_contact_notify_cb (TpContact *tp_contact,
119                       GParamSpec *param,
120                       GObject *contact)
121 {
122   EmpathyContactPriv *priv = GET_PRIV (contact);
123
124   /* Forward property notifications */
125   if (!tp_strdiff (param->name, "alias"))
126     g_object_notify (contact, "name");
127   else if (!tp_strdiff (param->name, "presence-type")) {
128     TpConnectionPresenceType presence;
129
130     presence = empathy_contact_get_presence (EMPATHY_CONTACT (contact));
131     g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence,
132       priv->presence);
133     priv->presence = presence;
134     g_object_notify (contact, "presence");
135   }
136   else if (!tp_strdiff (param->name, "presence-message"))
137     g_object_notify (contact, "presence-message");
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 }
160
161 static void
162 contact_dispose (GObject *object)
163 {
164   EmpathyContactPriv *priv = GET_PRIV (object);
165
166   if (priv->tp_contact)
167     {
168       g_hash_table_remove (contacts_table, priv->tp_contact);
169       g_signal_handlers_disconnect_by_func (priv->tp_contact,
170           tp_contact_notify_cb, object);
171       g_object_unref (priv->tp_contact);
172     }
173   priv->tp_contact = NULL;
174
175   if (priv->account)
176     g_object_unref (priv->account);
177   priv->account = NULL;
178
179   if (priv->avatar != NULL)
180     {
181       empathy_avatar_unref (priv->avatar);
182       priv->avatar = NULL;
183     }
184
185   if (priv->location != NULL)
186     {
187       g_hash_table_unref (priv->location);
188       priv->location = NULL;
189     }
190
191   G_OBJECT_CLASS (empathy_contact_parent_class)->dispose (object);
192 }
193
194 static void
195 empathy_contact_class_init (EmpathyContactClass *class)
196 {
197   GObjectClass *object_class;
198
199   object_class = G_OBJECT_CLASS (class);
200
201   object_class->finalize = contact_finalize;
202   object_class->dispose = contact_dispose;
203   object_class->get_property = contact_get_property;
204   object_class->set_property = contact_set_property;
205
206   g_object_class_install_property (object_class,
207       PROP_TP_CONTACT,
208       g_param_spec_object ("tp-contact",
209         "TpContact",
210         "The TpContact associated with the contact",
211         TP_TYPE_CONTACT,
212         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
213
214   g_object_class_install_property (object_class,
215       PROP_ACCOUNT,
216       g_param_spec_object ("account",
217         "The account",
218         "The account associated with the contact",
219         TP_TYPE_ACCOUNT,
220         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
221
222   g_object_class_install_property (object_class,
223       PROP_ID,
224       g_param_spec_string ("id",
225         "Contact id",
226         "String identifying contact",
227         NULL,
228         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
229
230   g_object_class_install_property (object_class,
231       PROP_NAME,
232       g_param_spec_string ("name",
233         "Contact Name",
234         "The name of the contact",
235         NULL,
236         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
237
238   g_object_class_install_property (object_class,
239       PROP_AVATAR,
240       g_param_spec_boxed ("avatar",
241         "Avatar image",
242         "The avatar image",
243         EMPATHY_TYPE_AVATAR,
244         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
245
246   g_object_class_install_property (object_class,
247       PROP_PRESENCE,
248       g_param_spec_uint ("presence",
249         "Contact presence",
250         "Presence of contact",
251         TP_CONNECTION_PRESENCE_TYPE_UNSET,
252         NUM_TP_CONNECTION_PRESENCE_TYPES,
253         TP_CONNECTION_PRESENCE_TYPE_UNSET,
254         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
255
256   g_object_class_install_property (object_class,
257       PROP_PRESENCE_MESSAGE,
258       g_param_spec_string ("presence-message",
259         "Contact presence message",
260         "Presence message of contact",
261         NULL,
262         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
263
264   g_object_class_install_property (object_class,
265       PROP_HANDLE,
266       g_param_spec_uint ("handle",
267         "Contact Handle",
268         "The handle of the contact",
269         0,
270         G_MAXUINT,
271         0,
272         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
273
274   g_object_class_install_property (object_class,
275       PROP_CAPABILITIES,
276       g_param_spec_flags ("capabilities",
277         "Contact Capabilities",
278         "Capabilities of the contact",
279         EMPATHY_TYPE_CAPABILITIES,
280         EMPATHY_CAPABILITIES_UNKNOWN,
281         G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
282
283   g_object_class_install_property (object_class,
284       PROP_IS_USER,
285       g_param_spec_boolean ("is-user",
286         "Contact is-user",
287         "Is contact the user",
288         FALSE,
289         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
290
291
292   g_object_class_install_property (object_class,
293       PROP_LOCATION,
294       g_param_spec_boxed ("location",
295         "Contact location",
296         "Physical location of the contact",
297         G_TYPE_HASH_TABLE,
298         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
299
300   signals[PRESENCE_CHANGED] =
301     g_signal_new ("presence-changed",
302                   G_TYPE_FROM_CLASS (class),
303                   G_SIGNAL_RUN_LAST,
304                   0,
305                   NULL, NULL,
306                   _empathy_marshal_VOID__UINT_UINT,
307                   G_TYPE_NONE,
308                   2, G_TYPE_UINT,
309                   G_TYPE_UINT);
310
311   g_type_class_add_private (object_class, sizeof (EmpathyContactPriv));
312 }
313
314 static void
315 empathy_contact_init (EmpathyContact *contact)
316 {
317   EmpathyContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
318     EMPATHY_TYPE_CONTACT, EmpathyContactPriv);
319
320   contact->priv = priv;
321
322   priv->location = NULL;
323 }
324
325 static void
326 contact_finalize (GObject *object)
327 {
328   EmpathyContactPriv *priv;
329
330   priv = GET_PRIV (object);
331
332   DEBUG ("finalize: %p", object);
333
334   g_free (priv->name);
335   g_free (priv->id);
336   g_free (priv->presence_message);
337
338   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
339 }
340
341 static void
342 set_tp_contact (EmpathyContact *contact,
343                 TpContact *tp_contact)
344 {
345   EmpathyContactPriv *priv = GET_PRIV (contact);
346   GHashTable *location;
347   TpHandle self_handle;
348   TpHandle handle;
349
350   if (tp_contact == NULL)
351     return;
352
353   g_assert (priv->tp_contact == NULL);
354   priv->tp_contact = g_object_ref (tp_contact);
355   priv->presence = empathy_contact_get_presence (contact);
356
357   location = tp_contact_get_location (tp_contact);
358   if (location != NULL)
359     empathy_contact_set_location (contact, location);
360
361   set_capabilities_from_tp_caps (contact,
362       tp_contact_get_capabilities (tp_contact));
363
364   contact_set_avatar_from_tp_contact (contact);
365
366   /* Set is-user property. Note that it could still be the handle is
367    * different from the connection's self handle, in the case the handle
368    * comes from a group interface. */
369   self_handle = tp_connection_get_self_handle (
370       tp_contact_get_connection (tp_contact));
371   handle = tp_contact_get_handle (tp_contact);
372   empathy_contact_set_is_user (contact, self_handle == handle);
373
374   g_signal_connect (priv->tp_contact, "notify",
375     G_CALLBACK (tp_contact_notify_cb), contact);
376 }
377
378 static void
379 contact_get_property (GObject *object,
380                       guint param_id,
381                       GValue *value,
382                       GParamSpec *pspec)
383 {
384   EmpathyContact *contact = EMPATHY_CONTACT (object);
385
386   switch (param_id)
387     {
388       case PROP_TP_CONTACT:
389         g_value_set_object (value, empathy_contact_get_tp_contact (contact));
390         break;
391       case PROP_ACCOUNT:
392         g_value_set_object (value, empathy_contact_get_account (contact));
393         break;
394       case PROP_ID:
395         g_value_set_string (value, empathy_contact_get_id (contact));
396         break;
397       case PROP_NAME:
398         g_value_set_string (value, empathy_contact_get_name (contact));
399         break;
400       case PROP_AVATAR:
401         g_value_set_boxed (value, empathy_contact_get_avatar (contact));
402         break;
403       case PROP_PRESENCE:
404         g_value_set_uint (value, empathy_contact_get_presence (contact));
405         break;
406       case PROP_PRESENCE_MESSAGE:
407         g_value_set_string (value, empathy_contact_get_presence_message (contact));
408         break;
409       case PROP_HANDLE:
410         g_value_set_uint (value, empathy_contact_get_handle (contact));
411         break;
412       case PROP_CAPABILITIES:
413         g_value_set_flags (value, empathy_contact_get_capabilities (contact));
414         break;
415       case PROP_IS_USER:
416         g_value_set_boolean (value, empathy_contact_is_user (contact));
417         break;
418       default:
419         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
420         break;
421     };
422 }
423
424 static void
425 contact_set_property (GObject *object,
426                       guint param_id,
427                       const GValue *value,
428                       GParamSpec *pspec)
429 {
430   EmpathyContact *contact = EMPATHY_CONTACT (object);
431   EmpathyContactPriv *priv = GET_PRIV (object);
432
433   switch (param_id)
434     {
435       case PROP_TP_CONTACT:
436         set_tp_contact (contact, g_value_get_object (value));
437         break;
438       case PROP_ACCOUNT:
439         g_assert (priv->account == NULL);
440         priv->account = g_value_dup_object (value);
441         break;
442       case PROP_ID:
443         empathy_contact_set_id (contact, g_value_get_string (value));
444         break;
445       case PROP_NAME:
446         empathy_contact_set_name (contact, g_value_get_string (value));
447         break;
448       case PROP_AVATAR:
449         empathy_contact_set_avatar (contact, g_value_get_boxed (value));
450         break;
451       case PROP_PRESENCE:
452         empathy_contact_set_presence (contact, g_value_get_uint (value));
453         break;
454       case PROP_PRESENCE_MESSAGE:
455         empathy_contact_set_presence_message (contact, g_value_get_string (value));
456         break;
457       case PROP_HANDLE:
458         empathy_contact_set_handle (contact, g_value_get_uint (value));
459         break;
460       case PROP_CAPABILITIES:
461         empathy_contact_set_capabilities (contact, g_value_get_flags (value));
462         break;
463       case PROP_IS_USER:
464         empathy_contact_set_is_user (contact, g_value_get_boolean (value));
465         break;
466       default:
467         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
468         break;
469     };
470 }
471
472 EmpathyContact *
473 empathy_contact_new (TpContact *tp_contact)
474 {
475   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
476
477   return g_object_new (EMPATHY_TYPE_CONTACT,
478       "tp-contact", tp_contact,
479       NULL);
480 }
481
482 #ifdef ENABLE_TPL
483 EmpathyContact *
484 empathy_contact_from_tpl_contact (TpAccount *account,
485     TplContact *tpl_contact)
486 {
487   EmpathyContact *retval;
488   gboolean is_user;
489
490   g_return_val_if_fail (TPL_IS_CONTACT (tpl_contact), NULL);
491
492   is_user = (TPL_CONTACT_USER == tpl_contact_get_contact_type (tpl_contact));
493
494   retval = g_object_new (EMPATHY_TYPE_CONTACT,
495       "id", tpl_contact_get_alias (tpl_contact),
496       "name", tpl_contact_get_identifier (tpl_contact),
497       "account", account,
498       "is-user", is_user,
499       NULL);
500
501   if (!EMP_STR_EMPTY (tpl_contact_get_avatar_token (tpl_contact)))
502     empathy_contact_load_avatar_cache (retval,
503         tpl_contact_get_avatar_token (tpl_contact));
504
505   return retval;
506 }
507 #endif /* ENABLE_TPL */
508
509 EmpathyContact *
510 empathy_contact_new_for_log (TpAccount *account,
511                              const gchar *id,
512                              const gchar *name,
513                              gboolean is_user)
514 {
515   g_return_val_if_fail (id != NULL, NULL);
516   g_assert (account != NULL);
517
518   return g_object_new (EMPATHY_TYPE_CONTACT,
519       "account", account,
520       "id", id,
521       "name", name,
522       "is-user", is_user,
523       NULL);
524 }
525
526 TpContact *
527 empathy_contact_get_tp_contact (EmpathyContact *contact)
528 {
529   EmpathyContactPriv *priv;
530
531   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
532
533   priv = GET_PRIV (contact);
534
535   return priv->tp_contact;
536 }
537
538 const gchar *
539 empathy_contact_get_id (EmpathyContact *contact)
540 {
541   EmpathyContactPriv *priv;
542
543   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
544
545   priv = GET_PRIV (contact);
546
547   if (priv->tp_contact != NULL)
548     return tp_contact_get_identifier (priv->tp_contact);
549
550   return priv->id;
551 }
552
553 void
554 empathy_contact_set_id (EmpathyContact *contact,
555                         const gchar *id)
556 {
557   EmpathyContactPriv *priv;
558
559   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
560   g_return_if_fail (id != NULL);
561
562   priv = GET_PRIV (contact);
563
564   /* We temporally ref the contact because it could be destroyed
565    * during the signal emition */
566   g_object_ref (contact);
567   if (tp_strdiff (id, priv->id))
568     {
569       g_free (priv->id);
570       priv->id = g_strdup (id);
571
572       g_object_notify (G_OBJECT (contact), "id");
573       if (EMP_STR_EMPTY (priv->name))
574           g_object_notify (G_OBJECT (contact), "name");
575     }
576
577   g_object_unref (contact);
578 }
579
580 const gchar *
581 empathy_contact_get_name (EmpathyContact *contact)
582 {
583   EmpathyContactPriv *priv;
584   const gchar        *alias;
585
586   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
587
588   priv = GET_PRIV (contact);
589
590   if (priv->tp_contact != NULL)
591     alias = tp_contact_get_alias (priv->tp_contact);
592   else
593     alias = priv->name;
594
595   if (!EMP_STR_EMPTY (alias))
596     return alias;
597   else
598     return empathy_contact_get_id (contact);
599 }
600
601 void
602 empathy_contact_set_name (EmpathyContact *contact,
603                           const gchar *name)
604 {
605   EmpathyContactPriv *priv;
606
607   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
608
609   priv = GET_PRIV (contact);
610
611   g_object_ref (contact);
612   if (tp_strdiff (name, priv->name))
613     {
614       g_free (priv->name);
615       priv->name = g_strdup (name);
616       g_object_notify (G_OBJECT (contact), "name");
617     }
618   g_object_unref (contact);
619 }
620
621 EmpathyAvatar *
622 empathy_contact_get_avatar (EmpathyContact *contact)
623 {
624   EmpathyContactPriv *priv;
625
626   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
627
628   priv = GET_PRIV (contact);
629
630   return priv->avatar;
631 }
632
633 void
634 empathy_contact_set_avatar (EmpathyContact *contact,
635                             EmpathyAvatar *avatar)
636 {
637   EmpathyContactPriv *priv;
638
639   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
640
641   priv = GET_PRIV (contact);
642
643   if (priv->avatar == avatar)
644     return;
645
646   if (priv->avatar)
647     {
648       empathy_avatar_unref (priv->avatar);
649       priv->avatar = NULL;
650     }
651
652   if (avatar)
653       priv->avatar = empathy_avatar_ref (avatar);
654
655   g_object_notify (G_OBJECT (contact), "avatar");
656 }
657
658 TpAccount *
659 empathy_contact_get_account (EmpathyContact *contact)
660 {
661   EmpathyContactPriv *priv;
662
663   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
664
665   priv = GET_PRIV (contact);
666
667   if (priv->account == NULL && priv->tp_contact != NULL)
668     {
669       TpConnection *connection;
670
671       /* FIXME: This assume the account manager already exists */
672       connection = tp_contact_get_connection (priv->tp_contact);
673       priv->account =
674         g_object_ref (empathy_get_account_for_connection (connection));
675     }
676
677   return priv->account;
678 }
679
680 TpConnection *
681 empathy_contact_get_connection (EmpathyContact *contact)
682 {
683   EmpathyContactPriv *priv;
684
685   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
686
687   priv = GET_PRIV (contact);
688
689   if (priv->tp_contact != NULL)
690     return tp_contact_get_connection (priv->tp_contact);
691
692   return NULL;
693 }
694
695 TpConnectionPresenceType
696 empathy_contact_get_presence (EmpathyContact *contact)
697 {
698   EmpathyContactPriv *priv;
699
700   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
701     TP_CONNECTION_PRESENCE_TYPE_UNSET);
702
703   priv = GET_PRIV (contact);
704
705   if (priv->tp_contact != NULL)
706     return tp_contact_get_presence_type (priv->tp_contact);
707
708   return priv->presence;
709 }
710
711 void
712 empathy_contact_set_presence (EmpathyContact *contact,
713                               TpConnectionPresenceType presence)
714 {
715   EmpathyContactPriv *priv;
716   TpConnectionPresenceType old_presence;
717
718   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
719
720   priv = GET_PRIV (contact);
721
722   if (presence == priv->presence)
723     return;
724
725   old_presence = priv->presence;
726   priv->presence = presence;
727
728   g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
729
730   g_object_notify (G_OBJECT (contact), "presence");
731 }
732
733 const gchar *
734 empathy_contact_get_presence_message (EmpathyContact *contact)
735 {
736   EmpathyContactPriv *priv;
737
738   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
739
740   priv = GET_PRIV (contact);
741
742   if (priv->tp_contact != NULL)
743     return tp_contact_get_presence_message (priv->tp_contact);
744
745   return priv->presence_message;
746 }
747
748 void
749 empathy_contact_set_presence_message (EmpathyContact *contact,
750                                       const gchar *message)
751 {
752   EmpathyContactPriv *priv = GET_PRIV (contact);
753
754   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
755
756   if (!tp_strdiff (message, priv->presence_message))
757     return;
758
759   g_free (priv->presence_message);
760   priv->presence_message = g_strdup (message);
761
762   g_object_notify (G_OBJECT (contact), "presence-message");
763 }
764
765 guint
766 empathy_contact_get_handle (EmpathyContact *contact)
767 {
768   EmpathyContactPriv *priv;
769
770   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
771
772   priv = GET_PRIV (contact);
773
774   if (priv->tp_contact != NULL)
775     return tp_contact_get_handle (priv->tp_contact);
776
777   return priv->handle;
778 }
779
780 void
781 empathy_contact_set_handle (EmpathyContact *contact,
782                             guint handle)
783 {
784   EmpathyContactPriv *priv;
785
786   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
787
788   priv = GET_PRIV (contact);
789
790   g_object_ref (contact);
791   if (handle != priv->handle)
792     {
793       priv->handle = handle;
794       g_object_notify (G_OBJECT (contact), "handle");
795     }
796   g_object_unref (contact);
797 }
798
799 EmpathyCapabilities
800 empathy_contact_get_capabilities (EmpathyContact *contact)
801 {
802   EmpathyContactPriv *priv;
803
804   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
805
806   priv = GET_PRIV (contact);
807
808   return priv->capabilities;
809 }
810
811 void
812 empathy_contact_set_capabilities (EmpathyContact *contact,
813                                   EmpathyCapabilities capabilities)
814 {
815   EmpathyContactPriv *priv;
816
817   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
818
819   priv = GET_PRIV (contact);
820
821   if (priv->capabilities == capabilities)
822     return;
823
824   priv->capabilities = capabilities;
825
826   g_object_notify (G_OBJECT (contact), "capabilities");
827 }
828
829 gboolean
830 empathy_contact_is_user (EmpathyContact *contact)
831 {
832   EmpathyContactPriv *priv;
833
834   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
835
836   priv = GET_PRIV (contact);
837
838   return priv->is_user;
839 }
840
841 void
842 empathy_contact_set_is_user (EmpathyContact *contact,
843                              gboolean is_user)
844 {
845   EmpathyContactPriv *priv;
846
847   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
848
849   priv = GET_PRIV (contact);
850
851   if (priv->is_user == is_user)
852     return;
853
854   priv->is_user = is_user;
855
856   g_object_notify (G_OBJECT (contact), "is-user");
857 }
858
859 gboolean
860 empathy_contact_is_online (EmpathyContact *contact)
861 {
862   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
863
864   switch (empathy_contact_get_presence (contact))
865     {
866       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
867       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
868       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
869         return FALSE;
870       default:
871         return TRUE;
872     }
873 }
874
875 const gchar *
876 empathy_contact_get_status (EmpathyContact *contact)
877 {
878   const gchar *message;
879
880   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), "");
881
882   message = empathy_contact_get_presence_message (contact);
883   if (!EMP_STR_EMPTY (message))
884     return message;
885
886   return empathy_presence_get_default_message (
887       empathy_contact_get_presence (contact));
888 }
889
890 gboolean
891 empathy_contact_can_voip (EmpathyContact *contact)
892 {
893   EmpathyContactPriv *priv;
894
895   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
896
897   priv = GET_PRIV (contact);
898
899   return priv->capabilities & (EMPATHY_CAPABILITIES_AUDIO |
900       EMPATHY_CAPABILITIES_VIDEO);
901 }
902
903 gboolean
904 empathy_contact_can_voip_audio (EmpathyContact *contact)
905 {
906   EmpathyContactPriv *priv;
907
908   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
909
910   priv = GET_PRIV (contact);
911
912   return priv->capabilities & EMPATHY_CAPABILITIES_AUDIO;
913 }
914
915 gboolean
916 empathy_contact_can_voip_video (EmpathyContact *contact)
917 {
918   EmpathyContactPriv *priv;
919
920   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
921
922   priv = GET_PRIV (contact);
923
924   return priv->capabilities & EMPATHY_CAPABILITIES_VIDEO;
925 }
926
927 gboolean
928 empathy_contact_can_send_files (EmpathyContact *contact)
929 {
930   EmpathyContactPriv *priv;
931
932   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
933
934   priv = GET_PRIV (contact);
935
936   return priv->capabilities & EMPATHY_CAPABILITIES_FT;
937 }
938
939 gboolean
940 empathy_contact_can_use_stream_tube (EmpathyContact *contact)
941 {
942   EmpathyContactPriv *priv;
943
944   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
945
946   priv = GET_PRIV (contact);
947
948   return priv->capabilities & EMPATHY_CAPABILITIES_STREAM_TUBE;
949 }
950
951 static gchar *
952 contact_get_avatar_filename (EmpathyContact *contact,
953                              const gchar *token)
954 {
955   TpAccount *account;
956   gchar *avatar_path;
957   gchar *avatar_file;
958   gchar *token_escaped;
959
960   if (EMP_STR_EMPTY (empathy_contact_get_id (contact)))
961     return NULL;
962
963   token_escaped = tp_escape_as_identifier (token);
964   account = empathy_contact_get_account (contact);
965
966   avatar_path = g_build_filename (g_get_user_cache_dir (),
967       "telepathy",
968       "avatars",
969       tp_account_get_connection_manager (account),
970       tp_account_get_protocol (account),
971       NULL);
972   g_mkdir_with_parents (avatar_path, 0700);
973
974   avatar_file = g_build_filename (avatar_path, token_escaped, NULL);
975
976   g_free (token_escaped);
977   g_free (avatar_path);
978
979   return avatar_file;
980 }
981
982 gboolean
983 empathy_contact_load_avatar_cache (EmpathyContact *contact,
984                                    const gchar *token)
985 {
986   EmpathyAvatar *avatar = NULL;
987   gchar *filename;
988   gchar *data = NULL;
989   gsize len;
990   GError *error = NULL;
991
992   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
993   g_return_val_if_fail (!EMP_STR_EMPTY (token), FALSE);
994
995   /* Load the avatar from file if it exists */
996   filename = contact_get_avatar_filename (contact, token);
997   if (filename && g_file_test (filename, G_FILE_TEST_EXISTS))
998     {
999       if (!g_file_get_contents (filename, &data, &len, &error))
1000         {
1001           DEBUG ("Failed to load avatar from cache: %s",
1002             error ? error->message : "No error given");
1003           g_clear_error (&error);
1004         }
1005     }
1006
1007   if (data)
1008     {
1009       DEBUG ("Avatar loaded from %s", filename);
1010       avatar = empathy_avatar_new ((guchar *) data, len, NULL, g_strdup (token),
1011           filename);
1012       empathy_contact_set_avatar (contact, avatar);
1013       empathy_avatar_unref (avatar);
1014     }
1015   else
1016     {
1017       g_free (filename);
1018     }
1019
1020   return data != NULL;
1021 }
1022
1023 GType
1024 empathy_avatar_get_type (void)
1025 {
1026   static GType type_id = 0;
1027
1028   if (!type_id)
1029     {
1030       type_id = g_boxed_type_register_static ("EmpathyAvatar",
1031           (GBoxedCopyFunc) empathy_avatar_ref,
1032           (GBoxedFreeFunc) empathy_avatar_unref);
1033     }
1034
1035   return type_id;
1036 }
1037
1038 /**
1039  * empathy_avatar_new:
1040  * @data: the avatar data
1041  * @len: the size of avatar data
1042  * @format: the mime type of the avatar image
1043  * @token: the token of the avatar
1044  * @filename: the filename where the avatar is stored in cache
1045  *
1046  * Create a #EmpathyAvatar from the provided data. This function takes the
1047  * ownership of @data, @format, @token and @filename.
1048  *
1049  * Returns: a new #EmpathyAvatar
1050  */
1051 EmpathyAvatar *
1052 empathy_avatar_new (guchar *data,
1053                     gsize len,
1054                     gchar *format,
1055                     gchar *token,
1056                     gchar *filename)
1057 {
1058   EmpathyAvatar *avatar;
1059
1060   avatar = g_slice_new0 (EmpathyAvatar);
1061   avatar->data = data;
1062   avatar->len = len;
1063   avatar->format = format;
1064   avatar->token = token;
1065   avatar->filename = filename;
1066   avatar->refcount = 1;
1067
1068   return avatar;
1069 }
1070
1071 void
1072 empathy_avatar_unref (EmpathyAvatar *avatar)
1073 {
1074   g_return_if_fail (avatar != NULL);
1075
1076   avatar->refcount--;
1077   if (avatar->refcount == 0)
1078     {
1079       g_free (avatar->data);
1080       g_free (avatar->format);
1081       g_free (avatar->token);
1082       g_slice_free (EmpathyAvatar, avatar);
1083     }
1084 }
1085
1086 EmpathyAvatar *
1087 empathy_avatar_ref (EmpathyAvatar *avatar)
1088 {
1089   g_return_val_if_fail (avatar != NULL, NULL);
1090
1091   avatar->refcount++;
1092
1093   return avatar;
1094 }
1095
1096 /**
1097  * empathy_avatar_save_to_file:
1098  * @avatar: the avatar
1099  * @filename: name of a file to write avatar to
1100  * @error: return location for a GError, or NULL
1101  *
1102  * Save the avatar to a file named filename
1103  *
1104  * Returns: %TRUE on success, %FALSE if an error occurred
1105  */
1106 gboolean
1107 empathy_avatar_save_to_file (EmpathyAvatar *self,
1108                              const gchar *filename,
1109                              GError **error)
1110 {
1111   return g_file_set_contents (filename, (const gchar *) self->data, self->len,
1112       error);
1113 }
1114
1115 /**
1116  * empathy_contact_get_location:
1117  * @contact: an #EmpathyContact
1118  *
1119  * Returns the user's location if available.  The keys are defined in
1120  * empathy-location.h. If the contact doesn't have location
1121  * information, the GHashTable will be empthy. Use #g_hash_table_unref when
1122  * you are done with the #GHashTable.
1123  *
1124  * It is composed of string keys and GValues.  Keys are
1125  * defined in empathy-location.h such as #EMPATHY_LOCATION_COUNTRY.
1126  * Example: a "city" key would have "Helsinki" as string GValue,
1127  *          a "latitude" would have 65.0 as double GValue.
1128  *
1129  * Returns: a #GHashTable of location values
1130  */
1131 GHashTable *
1132 empathy_contact_get_location (EmpathyContact *contact)
1133 {
1134   EmpathyContactPriv *priv;
1135
1136   g_return_val_if_fail (EMPATHY_CONTACT (contact), NULL);
1137
1138   priv = GET_PRIV (contact);
1139
1140   return priv->location;
1141 }
1142
1143 /**
1144  * empathy_contact_set_location:
1145  * @contact: an #EmpathyContact
1146  * @location: a #GHashTable of the location
1147  *
1148  * Sets the user's location based on the location #GHashTable passed.
1149  * It is composed of string keys and GValues.  Keys are
1150  * defined in empathy-location.h such as #EMPATHY_LOCATION_COUNTRY.
1151  * Example: a "city" key would have "Helsinki" as string GValue,
1152  *          a "latitude" would have 65.0 as double GValue.
1153  */
1154 static void
1155 empathy_contact_set_location (EmpathyContact *contact,
1156     GHashTable *location)
1157 {
1158   EmpathyContactPriv *priv;
1159
1160   g_return_if_fail (EMPATHY_CONTACT (contact));
1161   g_return_if_fail (location != NULL);
1162
1163   priv = GET_PRIV (contact);
1164
1165   if (priv->location != NULL)
1166     g_hash_table_unref (priv->location);
1167
1168   priv->location = g_hash_table_ref (location);
1169 #if HAVE_GEOCLUE
1170   update_geocode (contact);
1171 #endif
1172   g_object_notify (G_OBJECT (contact), "location");
1173 }
1174
1175 /**
1176  * empathy_contact_equal:
1177  * @contact1: an #EmpathyContact
1178  * @contact2: an #EmpathyContact
1179  *
1180  * Returns FALSE if one of the contacts is NULL but the other is not.
1181  * Otherwise returns TRUE if both pointer are equal or if they bith
1182  * refer to the same id.
1183  * It's only necessary to call this function if your contact objects
1184  * come from logs where contacts are created dynamically and comparing
1185  * pointers is not enough.
1186  */
1187 gboolean
1188 empathy_contact_equal (gconstpointer contact1,
1189                        gconstpointer contact2)
1190 {
1191   EmpathyContact *c1;
1192   EmpathyContact *c2;
1193   const gchar *id1;
1194   const gchar *id2;
1195
1196   if ((contact1 == NULL) != (contact2 == NULL)) {
1197     return FALSE;
1198   }
1199   if (contact1 == contact2) {
1200     return TRUE;
1201   }
1202   c1 = EMPATHY_CONTACT (contact1);
1203   c2 = EMPATHY_CONTACT (contact2);
1204   id1 = empathy_contact_get_id (c1);
1205   id2 = empathy_contact_get_id (c2);
1206   if (!tp_strdiff (id1, id2)) {
1207     return TRUE;
1208   }
1209   return FALSE;
1210 }
1211
1212 #if HAVE_GEOCLUE
1213 #define GEOCODE_SERVICE "org.freedesktop.Geoclue.Providers.Yahoo"
1214 #define GEOCODE_PATH "/org/freedesktop/Geoclue/Providers/Yahoo"
1215
1216 /* This callback is called by geoclue when it found a position
1217  * for the given address.  A position is necessary for a contact
1218  * to show up on the map
1219  */
1220 static void
1221 geocode_cb (GeoclueGeocode *geocode,
1222     GeocluePositionFields fields,
1223     double latitude,
1224     double longitude,
1225     double altitude,
1226     GeoclueAccuracy *accuracy,
1227     GError *error,
1228     gpointer contact)
1229 {
1230   EmpathyContactPriv *priv = GET_PRIV (contact);
1231   GHashTable *new_location;
1232
1233   if (priv->location == NULL)
1234     goto out;
1235
1236   if (error != NULL)
1237     {
1238       DEBUG ("Error geocoding location : %s", error->message);
1239       goto out;
1240     }
1241
1242   /* No need to change location if we didn't find the position */
1243   if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE))
1244     goto out;
1245
1246   if (!(fields & GEOCLUE_POSITION_FIELDS_LONGITUDE))
1247     goto out;
1248
1249   new_location = tp_asv_new (
1250       EMPATHY_LOCATION_LAT, G_TYPE_DOUBLE, latitude,
1251       EMPATHY_LOCATION_LON, G_TYPE_DOUBLE, longitude,
1252       NULL);
1253
1254   DEBUG ("\t - Latitude: %f", latitude);
1255   DEBUG ("\t - Longitude: %f", longitude);
1256
1257   /* Copy remaning fields. LAT and LON were not defined so we won't overwrite
1258    * the values we just set. */
1259   tp_g_hash_table_update (new_location, priv->location,
1260       (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup);
1261
1262   /* Set the altitude only if it wasn't defined before */
1263   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE &&
1264       g_hash_table_lookup (new_location, EMPATHY_LOCATION_ALT) == NULL)
1265     {
1266       tp_asv_set_double (new_location, g_strdup (EMPATHY_LOCATION_ALT),
1267           altitude);
1268       DEBUG ("\t - Altitude: %f", altitude);
1269     }
1270
1271   /* Don't change the accuracy as we used an address to get this position */
1272   g_hash_table_unref (priv->location);
1273   priv->location = new_location;
1274   g_object_notify (contact, "location");
1275 out:
1276   g_object_unref (geocode);
1277   g_object_unref (contact);
1278 }
1279
1280 static gchar *
1281 get_dup_string (GHashTable *location,
1282     gchar *key)
1283 {
1284   GValue *value;
1285
1286   value = g_hash_table_lookup (location, key);
1287   if (value != NULL)
1288     return g_value_dup_string (value);
1289
1290   return NULL;
1291 }
1292
1293 static void
1294 update_geocode (EmpathyContact *contact)
1295 {
1296   static GeoclueGeocode *geocode;
1297   gchar *str;
1298   GHashTable *address;
1299   GHashTable *location;
1300
1301   location = empathy_contact_get_location (contact);
1302   if (location == NULL)
1303     return;
1304
1305   /* No need to search for position if contact published it */
1306   if (g_hash_table_lookup (location, EMPATHY_LOCATION_LAT) != NULL ||
1307       g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL)
1308     return;
1309
1310   if (geocode == NULL)
1311     {
1312       geocode = geoclue_geocode_new (GEOCODE_SERVICE, GEOCODE_PATH);
1313       g_object_add_weak_pointer (G_OBJECT (geocode), (gpointer *) &geocode);
1314     }
1315   else
1316     {
1317       g_object_ref (geocode);
1318     }
1319
1320   address = geoclue_address_details_new ();
1321
1322   str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY_CODE);
1323   if (str != NULL)
1324     {
1325       g_hash_table_insert (address,
1326         g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRYCODE), str);
1327       DEBUG ("\t - countrycode: %s", str);
1328     }
1329
1330   str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY);
1331   if (str != NULL)
1332     {
1333       g_hash_table_insert (address,
1334         g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRY), str);
1335       DEBUG ("\t - country: %s", str);
1336     }
1337
1338   str = get_dup_string (location, EMPATHY_LOCATION_POSTAL_CODE);
1339   if (str != NULL)
1340     {
1341       g_hash_table_insert (address,
1342         g_strdup (GEOCLUE_ADDRESS_KEY_POSTALCODE), str);
1343       DEBUG ("\t - postalcode: %s", str);
1344     }
1345
1346   str = get_dup_string (location, EMPATHY_LOCATION_REGION);
1347   if (str != NULL)
1348     {
1349       g_hash_table_insert (address,
1350         g_strdup (GEOCLUE_ADDRESS_KEY_REGION), str);
1351       DEBUG ("\t - region: %s", str);
1352     }
1353
1354   str = get_dup_string (location, EMPATHY_LOCATION_LOCALITY);
1355   if (str != NULL)
1356     {
1357       g_hash_table_insert (address,
1358         g_strdup (GEOCLUE_ADDRESS_KEY_LOCALITY), str);
1359       DEBUG ("\t - locality: %s", str);
1360     }
1361
1362   str = get_dup_string (location, EMPATHY_LOCATION_STREET);
1363   if (str != NULL)
1364     {
1365       g_hash_table_insert (address,
1366         g_strdup (GEOCLUE_ADDRESS_KEY_STREET), str);
1367       DEBUG ("\t - street: %s", str);
1368     }
1369
1370   if (g_hash_table_size (address) > 0)
1371     {
1372       g_object_ref (contact);
1373
1374       geoclue_geocode_address_to_position_async (geocode, address,
1375           geocode_cb, contact);
1376     }
1377
1378   g_hash_table_unref (address);
1379 }
1380 #endif
1381
1382 static EmpathyCapabilities
1383 tp_caps_to_capabilities (TpCapabilities *caps)
1384 {
1385   EmpathyCapabilities capabilities = 0;
1386   guint i;
1387   GPtrArray *classes;
1388
1389   classes = tp_capabilities_get_channel_classes (caps);
1390
1391   for (i = 0; i < classes->len; i++)
1392     {
1393       GValueArray *class_struct;
1394       GHashTable *fixed_prop;
1395       GStrv allowed_prop;
1396       TpHandleType handle_type;
1397       const gchar *chan_type;
1398
1399       class_struct = g_ptr_array_index (classes, i);
1400       tp_value_array_unpack (class_struct, 2,
1401           &fixed_prop,
1402           &allowed_prop);
1403
1404       handle_type = tp_asv_get_uint32 (fixed_prop,
1405           TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
1406       if (handle_type != TP_HANDLE_TYPE_CONTACT)
1407         continue;
1408
1409       chan_type = tp_asv_get_string (fixed_prop,
1410           TP_PROP_CHANNEL_CHANNEL_TYPE);
1411
1412       if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1413         {
1414           capabilities |= EMPATHY_CAPABILITIES_FT;
1415         }
1416       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
1417         {
1418           capabilities |= EMPATHY_CAPABILITIES_STREAM_TUBE;
1419         }
1420       else if (!tp_strdiff (chan_type,
1421         TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
1422         {
1423           guint j;
1424
1425           for (j = 0; allowed_prop[j] != NULL; j++)
1426             {
1427               if (!tp_strdiff (allowed_prop[j],
1428                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO))
1429                 capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1430               else if (!tp_strdiff (allowed_prop[j],
1431                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO))
1432                 capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1433             }
1434         }
1435     }
1436
1437   return capabilities;
1438 }
1439
1440 static void
1441 set_capabilities_from_tp_caps (EmpathyContact *self,
1442     TpCapabilities *caps)
1443 {
1444   EmpathyCapabilities capabilities;
1445
1446   if (caps == NULL)
1447     return;
1448
1449   capabilities = tp_caps_to_capabilities (caps);
1450   empathy_contact_set_capabilities (self, capabilities);
1451 }
1452
1453 static void
1454 contact_set_avatar_from_tp_contact (EmpathyContact *contact)
1455 {
1456   EmpathyContactPriv *priv = GET_PRIV (contact);
1457   const gchar *mime;
1458   const gchar *token;
1459   GFile *file;
1460
1461   token = tp_contact_get_avatar_token (priv->tp_contact);
1462   mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
1463   file = tp_contact_get_avatar_file (priv->tp_contact);
1464
1465   if (file != NULL)
1466     {
1467       EmpathyAvatar *avatar;
1468       gchar *data;
1469       gsize len;
1470
1471       g_file_load_contents (file, NULL, &data, &len, NULL, NULL);
1472       avatar = empathy_avatar_new ((guchar *) data, len, g_strdup (mime), g_strdup (token),
1473           g_file_get_path (file));
1474       empathy_contact_set_avatar (contact, avatar);
1475       empathy_avatar_unref (avatar);
1476     }
1477   else
1478     {
1479       empathy_contact_set_avatar (contact, NULL);
1480     }
1481 }
1482
1483 EmpathyContact *
1484 empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
1485 {
1486   EmpathyContact *contact = NULL;
1487
1488   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
1489
1490   if (contacts_table == NULL)
1491     contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
1492   else
1493     contact = g_hash_table_lookup (contacts_table, tp_contact);
1494
1495   if (contact == NULL)
1496     {
1497       contact = empathy_contact_new (tp_contact);
1498
1499       /* The hash table does not keep any ref.
1500        * contact keeps a ref to tp_contact, and is removed from the table in
1501        * contact_dispose() */
1502       g_hash_table_insert (contacts_table, tp_contact, contact);
1503     }
1504   else
1505     {
1506       g_object_ref (contact);
1507     }
1508
1509   return contact;
1510 }
1511