]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact.c
Merge remote-tracking branch 'glassrose/debug-window-send-to-pastebin-button-658724'
[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 <telepathy-logger/log-manager.h>
33
34 #include <folks/folks.h>
35 #include <folks/folks-telepathy.h>
36
37 #ifdef HAVE_GEOCODE
38 #include <geocode-glib/geocode-glib.h>
39 #endif
40
41 #include "empathy-contact.h"
42 #include "empathy-camera-monitor.h"
43 #include "empathy-individual-manager.h"
44 #include "empathy-utils.h"
45 #include "empathy-enum-types.h"
46 #include "empathy-location.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
49 #include "empathy-debug.h"
50
51 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContact)
52 typedef struct {
53   TpContact *tp_contact;
54   TpAccount *account;
55   FolksPersona *persona;
56   gchar *id;
57   gchar *alias;
58   gchar *logged_alias;
59   EmpathyAvatar *avatar;
60   TpConnectionPresenceType presence;
61   guint handle;
62   EmpathyCapabilities capabilities;
63   gboolean is_user;
64   guint hash;
65   /* Location is composed of string keys and GValues.
66    * Example: a "city" key would have "Helsinki" as string GValue,
67    *          a "latitude" would have 65.0 as double GValue.
68    *
69    * This is a super set of the location stored in TpContact as we can try add
70    * more fields by searching the address using geoclue.
71    */
72   GHashTable *location;
73   GeeHashSet *groups;
74   gchar **client_types;
75 } EmpathyContactPriv;
76
77 static void contact_finalize (GObject *object);
78 static void contact_get_property (GObject *object, guint param_id,
79     GValue *value, GParamSpec *pspec);
80 static void contact_set_property (GObject *object, guint param_id,
81     const GValue *value, GParamSpec *pspec);
82
83 #ifdef HAVE_GEOCODE
84 static void update_geocode (EmpathyContact *contact);
85 #endif
86
87 static void empathy_contact_set_location (EmpathyContact *contact,
88     GHashTable *location);
89
90 static void contact_set_client_types (EmpathyContact *contact,
91     const gchar * const *types);
92
93 static void set_capabilities_from_tp_caps (EmpathyContact *self,
94     TpCapabilities *caps);
95
96 static void contact_set_avatar (EmpathyContact *contact,
97     EmpathyAvatar *avatar);
98 static void contact_set_avatar_from_tp_contact (EmpathyContact *contact);
99 static gboolean contact_load_avatar_cache (EmpathyContact *contact,
100     const gchar *token);
101
102 G_DEFINE_TYPE (EmpathyContact, empathy_contact, G_TYPE_OBJECT);
103
104 enum
105 {
106   PROP_0,
107   PROP_TP_CONTACT,
108   PROP_ACCOUNT,
109   PROP_PERSONA,
110   PROP_ID,
111   PROP_ALIAS,
112   PROP_LOGGED_ALIAS,
113   PROP_AVATAR,
114   PROP_PRESENCE,
115   PROP_PRESENCE_MESSAGE,
116   PROP_HANDLE,
117   PROP_CAPABILITIES,
118   PROP_IS_USER,
119   PROP_LOCATION,
120   PROP_CLIENT_TYPES
121 };
122
123 enum {
124   PRESENCE_CHANGED,
125   LAST_SIGNAL
126 };
127
128 static guint signals[LAST_SIGNAL];
129
130 /* TpContact* -> EmpathyContact*, both borrowed ref */
131 static GHashTable *contacts_table = NULL;
132
133 static void
134 tp_contact_notify_cb (TpContact *tp_contact,
135                       GParamSpec *param,
136                       GObject *contact)
137 {
138   EmpathyContactPriv *priv = GET_PRIV (contact);
139
140   /* Forward property notifications */
141   if (!tp_strdiff (param->name, "alias"))
142     g_object_notify (contact, "alias");
143   else if (!tp_strdiff (param->name, "presence-type")) {
144     TpConnectionPresenceType presence;
145
146     presence = empathy_contact_get_presence (EMPATHY_CONTACT (contact));
147     g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence,
148       priv->presence);
149     priv->presence = presence;
150     g_object_notify (contact, "presence");
151   }
152   else if (!tp_strdiff (param->name, "identifier"))
153     g_object_notify (contact, "id");
154   else if (!tp_strdiff (param->name, "handle"))
155     g_object_notify (contact, "handle");
156   else if (!tp_strdiff (param->name, "location"))
157     {
158       GHashTable *location;
159
160       location = tp_contact_get_location (tp_contact);
161       /* This will start a geoclue search to find the address if needed */
162       empathy_contact_set_location (EMPATHY_CONTACT (contact), location);
163     }
164   else if (!tp_strdiff (param->name, "capabilities"))
165     {
166       set_capabilities_from_tp_caps (EMPATHY_CONTACT (contact),
167           tp_contact_get_capabilities (tp_contact));
168     }
169   else if (!tp_strdiff (param->name, "avatar-file"))
170     {
171       contact_set_avatar_from_tp_contact (EMPATHY_CONTACT (contact));
172     }
173   else if (!tp_strdiff (param->name, "client-types"))
174     {
175       contact_set_client_types (EMPATHY_CONTACT (contact),
176           tp_contact_get_client_types (tp_contact));
177     }
178 }
179
180 static void
181 folks_persona_notify_cb (FolksPersona *folks_persona,
182                          GParamSpec *param,
183                          GObject *contact)
184 {
185   if (!tp_strdiff (param->name, "presence-message"))
186     g_object_notify (contact, "presence-message");
187 }
188
189 static void
190 contact_dispose (GObject *object)
191 {
192   EmpathyContactPriv *priv = GET_PRIV (object);
193
194   if (priv->tp_contact != NULL)
195     {
196       g_signal_handlers_disconnect_by_func (priv->tp_contact,
197           tp_contact_notify_cb, object);
198     }
199   tp_clear_object (&priv->tp_contact);
200
201   if (priv->account)
202     g_object_unref (priv->account);
203   priv->account = NULL;
204
205   if (priv->persona)
206     {
207       g_signal_handlers_disconnect_by_func (priv->persona,
208           folks_persona_notify_cb, object);
209       g_object_unref (priv->persona);
210     }
211   priv->persona = NULL;
212
213   if (priv->avatar != NULL)
214     {
215       empathy_avatar_unref (priv->avatar);
216       priv->avatar = NULL;
217     }
218
219   if (priv->location != NULL)
220     {
221       g_hash_table_unref (priv->location);
222       priv->location = NULL;
223     }
224
225   G_OBJECT_CLASS (empathy_contact_parent_class)->dispose (object);
226 }
227
228 static void
229 contact_constructed (GObject *object)
230 {
231   EmpathyContact *contact = (EmpathyContact *) object;
232   EmpathyContactPriv *priv = GET_PRIV (contact);
233   GHashTable *location;
234   TpHandle self_handle;
235   TpHandle handle;
236   const gchar * const *client_types;
237
238   if (priv->tp_contact == NULL)
239     return;
240
241   priv->presence = empathy_contact_get_presence (contact);
242
243   location = tp_contact_get_location (priv->tp_contact);
244   if (location != NULL)
245     empathy_contact_set_location (contact, location);
246
247   client_types = tp_contact_get_client_types (priv->tp_contact);
248   if (client_types != NULL)
249     contact_set_client_types (contact, client_types);
250
251   set_capabilities_from_tp_caps (contact,
252       tp_contact_get_capabilities (priv->tp_contact));
253
254   contact_set_avatar_from_tp_contact (contact);
255
256   /* Set is-user property. Note that it could still be the handle is
257    * different from the connection's self handle, in the case the handle
258    * comes from a group interface. */
259   self_handle = tp_connection_get_self_handle (
260       tp_contact_get_connection (priv->tp_contact));
261   handle = tp_contact_get_handle (priv->tp_contact);
262   empathy_contact_set_is_user (contact, self_handle == handle);
263
264   g_signal_connect (priv->tp_contact, "notify",
265     G_CALLBACK (tp_contact_notify_cb), contact);
266 }
267
268 static void
269 empathy_contact_class_init (EmpathyContactClass *class)
270 {
271   GObjectClass *object_class;
272
273   object_class = G_OBJECT_CLASS (class);
274
275   object_class->finalize = contact_finalize;
276   object_class->dispose = contact_dispose;
277   object_class->get_property = contact_get_property;
278   object_class->set_property = contact_set_property;
279   object_class->constructed = contact_constructed;
280
281   g_object_class_install_property (object_class,
282       PROP_TP_CONTACT,
283       g_param_spec_object ("tp-contact",
284         "TpContact",
285         "The TpContact associated with the contact",
286         TP_TYPE_CONTACT,
287         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
288
289   g_object_class_install_property (object_class,
290       PROP_ACCOUNT,
291       g_param_spec_object ("account",
292         "The account",
293         "The account associated with the contact",
294         TP_TYPE_ACCOUNT,
295         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
296
297   g_object_class_install_property (object_class,
298       PROP_PERSONA,
299       g_param_spec_object ("persona",
300         "Persona",
301         "The FolksPersona associated with the contact",
302         FOLKS_TYPE_PERSONA,
303         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
304
305   g_object_class_install_property (object_class,
306       PROP_ID,
307       g_param_spec_string ("id",
308         "Contact id",
309         "String identifying contact",
310         NULL,
311         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
312
313   g_object_class_install_property (object_class,
314       PROP_ALIAS,
315       g_param_spec_string ("alias",
316         "Contact alias",
317         "An alias for the contact",
318         NULL,
319         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
320
321   g_object_class_install_property (object_class,
322       PROP_LOGGED_ALIAS,
323       g_param_spec_string ("logged-alias",
324         "Logged alias",
325         "The alias the user had when a message was logged, "
326         "only set when using empathy_contact_from_tpl_contact()",
327         NULL,
328         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
329
330   g_object_class_install_property (object_class,
331       PROP_AVATAR,
332       g_param_spec_boxed ("avatar",
333         "Avatar image",
334         "The avatar image",
335         EMPATHY_TYPE_AVATAR,
336         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
337
338   g_object_class_install_property (object_class,
339       PROP_PRESENCE,
340       g_param_spec_uint ("presence",
341         "Contact presence",
342         "Presence of contact",
343         TP_CONNECTION_PRESENCE_TYPE_UNSET,
344         NUM_TP_CONNECTION_PRESENCE_TYPES,
345         TP_CONNECTION_PRESENCE_TYPE_UNSET,
346         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
347
348   g_object_class_install_property (object_class,
349       PROP_PRESENCE_MESSAGE,
350       g_param_spec_string ("presence-message",
351         "Contact presence message",
352         "Presence message of contact",
353         NULL,
354         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
355
356   g_object_class_install_property (object_class,
357       PROP_HANDLE,
358       g_param_spec_uint ("handle",
359         "Contact Handle",
360         "The handle of the contact",
361         0,
362         G_MAXUINT,
363         0,
364         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
365
366   g_object_class_install_property (object_class,
367       PROP_CAPABILITIES,
368       g_param_spec_flags ("capabilities",
369         "Contact Capabilities",
370         "Capabilities of the contact",
371         EMPATHY_TYPE_CAPABILITIES,
372         EMPATHY_CAPABILITIES_UNKNOWN,
373         G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
374
375   g_object_class_install_property (object_class,
376       PROP_IS_USER,
377       g_param_spec_boolean ("is-user",
378         "Contact is-user",
379         "Is contact the user",
380         FALSE,
381         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
382
383
384   g_object_class_install_property (object_class,
385       PROP_LOCATION,
386       g_param_spec_boxed ("location",
387         "Contact location",
388         "Physical location of the contact",
389         G_TYPE_HASH_TABLE,
390         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
391
392   g_object_class_install_property (object_class,
393       PROP_CLIENT_TYPES,
394       g_param_spec_boxed ("client-types",
395         "Contact client types",
396         "Client types of the contact",
397         G_TYPE_STRV,
398         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
399
400   signals[PRESENCE_CHANGED] =
401     g_signal_new ("presence-changed",
402                   G_TYPE_FROM_CLASS (class),
403                   G_SIGNAL_RUN_LAST,
404                   0,
405                   NULL, NULL,
406                   g_cclosure_marshal_generic,
407                   G_TYPE_NONE,
408                   2, G_TYPE_UINT,
409                   G_TYPE_UINT);
410
411   g_type_class_add_private (object_class, sizeof (EmpathyContactPriv));
412 }
413
414 static void
415 empathy_contact_init (EmpathyContact *contact)
416 {
417   EmpathyContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
418     EMPATHY_TYPE_CONTACT, EmpathyContactPriv);
419
420   contact->priv = priv;
421
422   priv->location = NULL;
423   priv->client_types = NULL;
424   priv->groups = NULL;
425 }
426
427 static void
428 contact_finalize (GObject *object)
429 {
430   EmpathyContactPriv *priv;
431
432   priv = GET_PRIV (object);
433
434   DEBUG ("finalize: %p", object);
435
436   g_clear_object (&priv->groups);
437   g_free (priv->alias);
438   g_free (priv->id);
439   g_strfreev (priv->client_types);
440
441   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
442 }
443
444 static void
445 empathy_contact_set_capabilities (EmpathyContact *contact,
446                                   EmpathyCapabilities capabilities)
447 {
448   EmpathyContactPriv *priv;
449
450   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
451
452   priv = GET_PRIV (contact);
453
454   if (priv->capabilities == capabilities)
455     return;
456
457   priv->capabilities = capabilities;
458
459   g_object_notify (G_OBJECT (contact), "capabilities");
460 }
461
462 static void
463 empathy_contact_set_id (EmpathyContact *contact,
464                         const gchar *id)
465 {
466   EmpathyContactPriv *priv;
467
468   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
469   g_return_if_fail (id != NULL);
470
471   priv = GET_PRIV (contact);
472
473   /* We temporally ref the contact because it could be destroyed
474    * during the signal emition */
475   g_object_ref (contact);
476   if (tp_strdiff (id, priv->id))
477     {
478       g_free (priv->id);
479       priv->id = g_strdup (id);
480
481       g_object_notify (G_OBJECT (contact), "id");
482       if (EMP_STR_EMPTY (priv->alias))
483           g_object_notify (G_OBJECT (contact), "alias");
484     }
485
486   g_object_unref (contact);
487 }
488
489 static void
490 empathy_contact_set_presence (EmpathyContact *contact,
491                               TpConnectionPresenceType presence)
492 {
493   EmpathyContactPriv *priv;
494   TpConnectionPresenceType old_presence;
495
496   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
497
498   priv = GET_PRIV (contact);
499
500   if (presence == priv->presence)
501     return;
502
503   old_presence = priv->presence;
504   priv->presence = presence;
505
506   g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
507
508   g_object_notify (G_OBJECT (contact), "presence");
509 }
510
511 static void
512 empathy_contact_set_presence_message (EmpathyContact *contact,
513                                       const gchar *message)
514 {
515   EmpathyContactPriv *priv = GET_PRIV (contact);
516
517   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
518
519   if (priv->persona != NULL)
520     {
521       folks_presence_details_set_presence_message (
522           FOLKS_PRESENCE_DETAILS (priv->persona), message);
523     }
524 }
525
526 static void
527 empathy_contact_set_handle (EmpathyContact *contact,
528                             guint handle)
529 {
530   EmpathyContactPriv *priv;
531
532   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
533
534   priv = GET_PRIV (contact);
535
536   g_object_ref (contact);
537   if (handle != priv->handle)
538     {
539       priv->handle = handle;
540       g_object_notify (G_OBJECT (contact), "handle");
541     }
542   g_object_unref (contact);
543 }
544
545 static void
546 contact_get_property (GObject *object,
547                       guint param_id,
548                       GValue *value,
549                       GParamSpec *pspec)
550 {
551   EmpathyContact *contact = EMPATHY_CONTACT (object);
552
553   switch (param_id)
554     {
555       case PROP_TP_CONTACT:
556         g_value_set_object (value, empathy_contact_get_tp_contact (contact));
557         break;
558       case PROP_ACCOUNT:
559         g_value_set_object (value, empathy_contact_get_account (contact));
560         break;
561       case PROP_PERSONA:
562         g_value_set_object (value, empathy_contact_get_persona (contact));
563         break;
564       case PROP_ID:
565         g_value_set_string (value, empathy_contact_get_id (contact));
566         break;
567       case PROP_ALIAS:
568         g_value_set_string (value, empathy_contact_get_alias (contact));
569         break;
570       case PROP_LOGGED_ALIAS:
571         g_value_set_string (value, empathy_contact_get_logged_alias (contact));
572         break;
573       case PROP_AVATAR:
574         g_value_set_boxed (value, empathy_contact_get_avatar (contact));
575         break;
576       case PROP_PRESENCE:
577         g_value_set_uint (value, empathy_contact_get_presence (contact));
578         break;
579       case PROP_PRESENCE_MESSAGE:
580         g_value_set_string (value, empathy_contact_get_presence_message (contact));
581         break;
582       case PROP_HANDLE:
583         g_value_set_uint (value, empathy_contact_get_handle (contact));
584         break;
585       case PROP_CAPABILITIES:
586         g_value_set_flags (value, empathy_contact_get_capabilities (contact));
587         break;
588       case PROP_IS_USER:
589         g_value_set_boolean (value, empathy_contact_is_user (contact));
590         break;
591       default:
592         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
593         break;
594     };
595 }
596
597 static void
598 contact_set_property (GObject *object,
599                       guint param_id,
600                       const GValue *value,
601                       GParamSpec *pspec)
602 {
603   EmpathyContact *contact = EMPATHY_CONTACT (object);
604   EmpathyContactPriv *priv = GET_PRIV (object);
605
606   switch (param_id)
607     {
608       case PROP_TP_CONTACT:
609         priv->tp_contact = g_value_dup_object (value);
610         break;
611       case PROP_ACCOUNT:
612         g_assert (priv->account == NULL);
613         priv->account = g_value_dup_object (value);
614         break;
615       case PROP_PERSONA:
616         empathy_contact_set_persona (contact, g_value_get_object (value));
617         break;
618       case PROP_ID:
619         empathy_contact_set_id (contact, g_value_get_string (value));
620         break;
621       case PROP_ALIAS:
622         empathy_contact_set_alias (contact, g_value_get_string (value));
623         break;
624       case PROP_LOGGED_ALIAS:
625         g_assert (priv->logged_alias == NULL);
626         priv->logged_alias = g_value_dup_string (value);
627         break;
628       case PROP_PRESENCE:
629         empathy_contact_set_presence (contact, g_value_get_uint (value));
630         break;
631       case PROP_PRESENCE_MESSAGE:
632         empathy_contact_set_presence_message (contact, g_value_get_string (value));
633         break;
634       case PROP_HANDLE:
635         empathy_contact_set_handle (contact, g_value_get_uint (value));
636         break;
637       case PROP_CAPABILITIES:
638         empathy_contact_set_capabilities (contact, g_value_get_flags (value));
639         break;
640       case PROP_IS_USER:
641         empathy_contact_set_is_user (contact, g_value_get_boolean (value));
642         break;
643       default:
644         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
645         break;
646     };
647 }
648
649 static void
650 remove_tp_contact (gpointer data,
651     GObject *object)
652 {
653   g_hash_table_remove (contacts_table, data);
654 }
655
656 static EmpathyContact *
657 empathy_contact_new (TpContact *tp_contact)
658 {
659   EmpathyContact *retval;
660
661   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
662
663   retval = g_object_new (EMPATHY_TYPE_CONTACT,
664       "tp-contact", tp_contact,
665       NULL);
666
667   g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact);
668
669   return retval;
670 }
671
672 typedef struct
673 {
674   TplEntity *entity;
675   TpAccount *account;
676 } FindContactData;
677
678 static gboolean
679 contact_is_tpl_entity (gpointer key,
680     gpointer value,
681     gpointer user_data)
682 {
683   EmpathyContact *contact = value;
684   FindContactData *data = user_data;
685   TpAccount *account = empathy_contact_get_account (contact);
686   const gchar *path = NULL;
687
688   if (account != NULL)
689     path = tp_proxy_get_object_path (account);
690
691   return !tp_strdiff (empathy_contact_get_id (contact),
692               tpl_entity_get_identifier (data->entity)) &&
693          !tp_strdiff (tp_proxy_get_object_path (data->account), path);
694 }
695
696 static void
697 get_contacts_cb (TpConnection *connection,
698     guint n_contacts,
699     TpContact * const *contacts,
700     const gchar * const *requested_ids,
701     GHashTable *failed_id_errors,
702     const GError *error,
703     gpointer user_data,
704     GObject *weak_object)
705 {
706   EmpathyContact *self = (EmpathyContact *) weak_object;
707   EmpathyContactPriv *priv = GET_PRIV (self);
708   TpContact *tp_contact;
709
710   if (n_contacts != 1)
711     return;
712
713   tp_contact = contacts[0];
714
715   g_return_if_fail (priv->tp_contact == NULL);
716   priv->tp_contact = g_object_ref (tp_contact);
717   g_object_notify (G_OBJECT (self), "tp-contact");
718
719   /* Update capabilities now that we have a TpContact */
720   set_capabilities_from_tp_caps (self,
721       tp_contact_get_capabilities (tp_contact));
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_get_contacts_by_id (conn, 1, &id,
778               G_N_ELEMENTS (features), features, get_contacts_cb,
779               NULL, NULL, G_OBJECT (retval));
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_connection_manager (account),
1355       tp_account_get_protocol (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   guint i;
1704   GPtrArray *classes;
1705
1706   classes = tp_capabilities_get_channel_classes (caps);
1707
1708   for (i = 0; i < classes->len; i++)
1709     {
1710       GValueArray *class_struct;
1711       GHashTable *fixed_prop;
1712       GStrv allowed_prop;
1713       TpHandleType handle_type;
1714       const gchar *chan_type;
1715
1716       class_struct = g_ptr_array_index (classes, i);
1717       tp_value_array_unpack (class_struct, 2,
1718           &fixed_prop,
1719           &allowed_prop);
1720
1721       handle_type = tp_asv_get_uint32 (fixed_prop,
1722           TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
1723       if (handle_type != TP_HANDLE_TYPE_CONTACT)
1724         continue;
1725
1726       chan_type = tp_asv_get_string (fixed_prop,
1727           TP_PROP_CHANNEL_CHANNEL_TYPE);
1728
1729       if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1730         {
1731           capabilities |= EMPATHY_CAPABILITIES_FT;
1732         }
1733       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
1734         {
1735           const gchar *service;
1736
1737           service = tp_asv_get_string (fixed_prop,
1738               TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE);
1739
1740           if (!tp_strdiff (service, "rfb"))
1741             capabilities |= EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
1742         }
1743       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
1744         {
1745           guint j;
1746
1747           for (j = 0; allowed_prop[j] != NULL; j++)
1748             {
1749               if (!tp_strdiff (allowed_prop[j],
1750                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO))
1751                 capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1752               else if (!tp_strdiff (allowed_prop[j],
1753                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO))
1754                 capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1755             }
1756
1757           if (tp_asv_get_boolean (fixed_prop,
1758                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, NULL))
1759             capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1760           if (tp_asv_get_boolean (fixed_prop,
1761                     TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, NULL))
1762             capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1763         }
1764       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT))
1765         {
1766           if (tp_asv_get_boolean (fixed_prop,
1767                 TP_PROP_CHANNEL_INTERFACE_SMS_SMS_CHANNEL, NULL))
1768             capabilities |= EMPATHY_CAPABILITIES_SMS;
1769         }
1770       else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_CALL))
1771         {
1772           guint j;
1773
1774           if (tp_asv_get_boolean (fixed_prop,
1775               TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL))
1776             capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1777
1778           if (tp_asv_get_boolean (fixed_prop,
1779               TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL))
1780             capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1781
1782           for (j = 0; allowed_prop[j] != NULL; j++)
1783             {
1784               if (!tp_strdiff (allowed_prop[j],
1785                     TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO))
1786                 capabilities |= EMPATHY_CAPABILITIES_AUDIO;
1787               else if (!tp_strdiff (allowed_prop[j],
1788                     TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO))
1789                 capabilities |= EMPATHY_CAPABILITIES_VIDEO;
1790             }
1791         }
1792     }
1793
1794   return capabilities;
1795 }
1796
1797 static void
1798 set_capabilities_from_tp_caps (EmpathyContact *self,
1799     TpCapabilities *caps)
1800 {
1801   EmpathyCapabilities capabilities;
1802
1803   if (caps == NULL)
1804     return;
1805
1806   capabilities = tp_caps_to_capabilities (caps);
1807   empathy_contact_set_capabilities (self, capabilities);
1808 }
1809
1810 static void
1811 contact_set_avatar_from_tp_contact (EmpathyContact *contact)
1812 {
1813   EmpathyContactPriv *priv = GET_PRIV (contact);
1814   const gchar *mime;
1815   GFile *file;
1816
1817   mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
1818   file = tp_contact_get_avatar_file (priv->tp_contact);
1819
1820   if (file != NULL)
1821     {
1822       EmpathyAvatar *avatar;
1823       gchar *data;
1824       gsize len;
1825       gchar *path;
1826       GError *error = NULL;
1827
1828       if (!g_file_load_contents (file, NULL, &data, &len, NULL, &error))
1829         {
1830           DEBUG ("Failed to load avatar: %s", error->message);
1831
1832           g_error_free (error);
1833           contact_set_avatar (contact, NULL);
1834           return;
1835         }
1836
1837       path = g_file_get_path (file);
1838
1839       avatar = empathy_avatar_new ((guchar *) data, len, mime, path);
1840
1841       contact_set_avatar (contact, avatar);
1842       empathy_avatar_unref (avatar);
1843       g_free (path);
1844       g_free (data);
1845     }
1846   else
1847     {
1848       contact_set_avatar (contact, NULL);
1849     }
1850 }
1851
1852 EmpathyContact *
1853 empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
1854 {
1855   EmpathyContact *contact = NULL;
1856
1857   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
1858
1859   if (contacts_table == NULL)
1860     contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
1861   else
1862     contact = g_hash_table_lookup (contacts_table, tp_contact);
1863
1864   if (contact == NULL)
1865     {
1866       contact = empathy_contact_new (tp_contact);
1867
1868       /* The hash table does not keep any ref.
1869        * contact keeps a ref to tp_contact, and is removed from the table in
1870        * contact_dispose() */
1871       g_hash_table_insert (contacts_table, tp_contact, contact);
1872     }
1873   else
1874     {
1875       g_object_ref (contact);
1876     }
1877
1878   return contact;
1879 }
1880
1881 static int
1882 presence_cmp_func (EmpathyContact *a,
1883     EmpathyContact *b)
1884 {
1885   FolksPresenceDetails *presence_a, *presence_b;
1886
1887   presence_a = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (a));
1888   presence_b = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (b));
1889
1890   /* We negate the result because we're sorting in reverse order (i.e. such that
1891    * the Personas with the highest presence are at the beginning of the list. */
1892   return -folks_presence_details_typecmp (
1893       folks_presence_details_get_presence_type (presence_a),
1894       folks_presence_details_get_presence_type (presence_b));
1895 }
1896
1897 static gint
1898 voip_cmp_func (EmpathyContact *a,
1899     EmpathyContact *b)
1900 {
1901   gboolean has_audio_a, has_audio_b;
1902   gboolean has_video_a, has_video_b;
1903
1904   has_audio_a = empathy_contact_can_voip_audio (a);
1905   has_audio_b = empathy_contact_can_voip_audio (b);
1906   has_video_a = empathy_contact_can_voip_video (a);
1907   has_video_b = empathy_contact_can_voip_video (b);
1908
1909   /* First check video */
1910   if (has_video_a == has_video_b)
1911     {
1912       /* Use audio to to break tie */
1913       if (has_audio_a == has_audio_b)
1914         return 0;
1915       else if (has_audio_a)
1916         return -1;
1917       else
1918         return 1;
1919     }
1920   else if (has_video_a)
1921     return -1;
1922   else
1923     return 1;
1924 }
1925
1926 static gint
1927 ft_cmp_func (EmpathyContact *a,
1928     EmpathyContact *b)
1929 {
1930   gboolean can_send_files_a, can_send_files_b;
1931
1932   can_send_files_a = empathy_contact_can_send_files (a);
1933   can_send_files_b = empathy_contact_can_send_files (b);
1934
1935   if (can_send_files_a == can_send_files_b)
1936     return 0;
1937   else if (can_send_files_a)
1938     return -1;
1939   else
1940     return 1;
1941 }
1942
1943 static gint
1944 rfb_stream_tube_cmp_func (EmpathyContact *a,
1945     EmpathyContact *b)
1946 {
1947   gboolean rfb_a, rfb_b;
1948
1949   rfb_a = empathy_contact_can_use_rfb_stream_tube (a);
1950   rfb_b = empathy_contact_can_use_rfb_stream_tube (b);
1951
1952   if (rfb_a == rfb_b)
1953     return 0;
1954   else if (rfb_a)
1955     return -1;
1956   else
1957     return 1;
1958 }
1959
1960 /* Sort by presence as with presence_cmp_func(), but if the two contacts have
1961  * the same presence, prefer the one which can do both audio *and* video calls,
1962  * over the one which can only do one of the two. */
1963 static int
1964 voip_sort_func (EmpathyContact *a, EmpathyContact *b)
1965 {
1966   gint presence_sort = presence_cmp_func (a, b);
1967
1968   if (presence_sort != 0)
1969     return presence_sort;
1970
1971   return voip_cmp_func (a, b);
1972 }
1973
1974 /* Sort by presence as with presence_cmp_func() and then break ties using the
1975  * most "capable" individual. So users will be able to pick more actions on
1976  * the contact in the "Contact" menu of the chat window. */
1977 static gint
1978 chat_sort_func (EmpathyContact *a,
1979     EmpathyContact *b)
1980 {
1981   gint result;
1982
1983   result = presence_cmp_func (a, b);
1984   if (result != 0)
1985     return result;
1986
1987   /* Prefer individual supporting file transfer */
1988   result = ft_cmp_func (a, b);
1989   if (result != 0)
1990     return result;
1991
1992   /* Check audio/video capabilities */
1993   result = voip_cmp_func (a, b);
1994   if (result != 0)
1995     return result;
1996
1997   /* Check 'Share my destop' feature */
1998   return rfb_stream_tube_cmp_func (a, b);
1999 }
2000
2001 static GCompareFunc
2002 get_sort_func_for_action (EmpathyActionType action_type)
2003 {
2004   switch (action_type)
2005     {
2006       case EMPATHY_ACTION_AUDIO_CALL:
2007       case EMPATHY_ACTION_VIDEO_CALL:
2008         return (GCompareFunc) voip_sort_func;
2009       case EMPATHY_ACTION_CHAT:
2010         return (GCompareFunc) chat_sort_func;
2011       case EMPATHY_ACTION_VIEW_LOGS:
2012       case EMPATHY_ACTION_SEND_FILE:
2013       case EMPATHY_ACTION_SHARE_MY_DESKTOP:
2014       default:
2015         return (GCompareFunc) presence_cmp_func;
2016     }
2017 }
2018
2019 /**
2020  * empathy_contact_dup_best_for_action:
2021  * @individual: a #FolksIndividual
2022  * @action_type: the type of action to be performed on the contact
2023  *
2024  * Chooses a #FolksPersona from the given @individual which is best-suited for
2025  * the given @action_type. "Best-suited" is determined by choosing the persona
2026  * with the highest presence out of all the personas which can perform the given
2027  * @action_type (e.g. are capable of video calling).
2028  *
2029  * Return value: an #EmpathyContact for the best persona, or %NULL;
2030  * unref with g_object_unref()
2031  */
2032 EmpathyContact *
2033 empathy_contact_dup_best_for_action (FolksIndividual *individual,
2034     EmpathyActionType action_type)
2035 {
2036   GeeSet *personas;
2037   GeeIterator *iter;
2038   GList *contacts;
2039   EmpathyContact *best_contact = NULL;
2040
2041   /* Build a list of EmpathyContacts that we can sort */
2042   personas = folks_individual_get_personas (individual);
2043   contacts = NULL;
2044
2045   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2046   while (gee_iterator_next (iter))
2047     {
2048       FolksPersona *persona = gee_iterator_get (iter);
2049       TpContact *tp_contact;
2050       EmpathyContact *contact = NULL;
2051
2052       if (!empathy_folks_persona_is_interesting (persona))
2053         goto while_finish;
2054
2055       tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
2056       if (tp_contact == NULL)
2057         goto while_finish;
2058
2059       contact = empathy_contact_dup_from_tp_contact (tp_contact);
2060       empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
2061
2062       /* Only choose the contact if they're actually capable of the specified
2063        * action. */
2064       if (empathy_contact_can_do_action (contact, action_type))
2065         contacts = g_list_prepend (contacts, g_object_ref (contact));
2066
2067 while_finish:
2068       g_clear_object (&contact);
2069       g_clear_object (&persona);
2070     }
2071   g_clear_object (&iter);
2072
2073   /* Sort the contacts by some heuristic based on the action type, then take
2074    * the top contact. */
2075   if (contacts != NULL)
2076     {
2077       contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
2078       best_contact = g_object_ref (contacts->data);
2079     }
2080
2081   g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2082   g_list_free (contacts);
2083
2084   return best_contact;
2085 }
2086
2087 #define declare_contact_cb(name) \
2088 static void \
2089 contact_##name##_cb (GObject *source, \
2090     GAsyncResult *result, \
2091     gpointer user_data) \
2092 { \
2093   TpContact *contact = (TpContact *) source; \
2094   GError *error = NULL; \
2095   \
2096   if (!tp_contact_##name##_finish (contact, result, &error)) \
2097     { \
2098       DEBUG ("Failed to ##name## on %s\n", \
2099           tp_contact_get_identifier (contact)); \
2100       g_error_free (error); \
2101     } \
2102 }
2103
2104 declare_contact_cb (request_subscription)
2105 declare_contact_cb (authorize_publication)
2106 declare_contact_cb (unblock)
2107
2108 void
2109 empathy_contact_add_to_contact_list (EmpathyContact *self,
2110     const gchar *message)
2111 {
2112   EmpathyContactPriv *priv = GET_PRIV (self);
2113
2114   g_return_if_fail (priv->tp_contact != NULL);
2115
2116   tp_contact_request_subscription_async (priv->tp_contact, message,
2117       contact_request_subscription_cb, NULL);
2118
2119   tp_contact_authorize_publication_async (priv->tp_contact,
2120       contact_authorize_publication_cb, NULL);
2121
2122   tp_contact_unblock_async (priv->tp_contact, contact_unblock_cb, NULL);
2123 }
2124
2125 declare_contact_cb (remove)
2126
2127 void
2128 empathy_contact_remove_from_contact_list (EmpathyContact *self)
2129 {
2130   EmpathyContactPriv *priv = GET_PRIV (self);
2131
2132   g_return_if_fail (priv->tp_contact != NULL);
2133
2134   tp_contact_remove_async (priv->tp_contact, contact_remove_cb, NULL);
2135 }