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