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