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