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