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