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