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