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