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