]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact.c
Use the RequestableChannelClasses property instead of hardcoding salut to check if...
[empathy.git] / libempathy / empathy-contact.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2004 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29
30 #include <glib/gi18n.h>
31
32 #include <telepathy-glib/util.h>
33 #include <telepathy-glib/gtypes.h>
34
35 #include <extensions/extensions.h>
36
37 #include "empathy-contact.h"
38 #include "empathy-utils.h"
39 #include "empathy-enum-types.h"
40
41 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
42 #include "empathy-debug.h"
43
44 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContact)
45 typedef struct {
46   gchar *id;
47   gchar *name;
48   EmpathyAvatar *avatar;
49   McAccount *account;
50   McPresence presence;
51   gchar *presence_message;
52   guint handle;
53   EmpathyCapabilities capabilities;
54   gboolean is_user;
55   guint hash;
56   EmpathyContactReady ready;
57 } EmpathyContactPriv;
58
59 static void contact_finalize (GObject *object);
60 static void contact_get_property (GObject *object, guint param_id,
61     GValue *value, GParamSpec *pspec);
62 static void contact_set_property (GObject *object, guint param_id,
63     const GValue *value, GParamSpec *pspec);
64
65 G_DEFINE_TYPE (EmpathyContact, empathy_contact, G_TYPE_OBJECT);
66
67 enum
68 {
69   PROP_0,
70   PROP_ID,
71   PROP_NAME,
72   PROP_AVATAR,
73   PROP_ACCOUNT,
74   PROP_PRESENCE,
75   PROP_PRESENCE_MESSAGE,
76   PROP_HANDLE,
77   PROP_CAPABILITIES,
78   PROP_IS_USER,
79   PROP_READY
80 };
81
82 static void
83 empathy_contact_class_init (EmpathyContactClass *class)
84 {
85   GObjectClass *object_class;
86
87   object_class = G_OBJECT_CLASS (class);
88
89   object_class->finalize = contact_finalize;
90   object_class->get_property = contact_get_property;
91   object_class->set_property = contact_set_property;
92
93   g_object_class_install_property (object_class,
94       PROP_ID,
95       g_param_spec_string ("id",
96         "Contact id",
97         "String identifying contact",
98         NULL,
99         G_PARAM_READWRITE));
100
101   g_object_class_install_property (object_class,
102       PROP_NAME,
103       g_param_spec_string ("name",
104         "Contact Name",
105         "The name of the contact",
106         NULL,
107         G_PARAM_READWRITE));
108
109   g_object_class_install_property (object_class,
110       PROP_AVATAR,
111       g_param_spec_boxed ("avatar",
112         "Avatar image",
113         "The avatar image",
114         EMPATHY_TYPE_AVATAR,
115         G_PARAM_READWRITE));
116
117   g_object_class_install_property (object_class,
118       PROP_ACCOUNT,
119       g_param_spec_object ("account",
120         "Contact Account",
121         "The account associated with the contact",
122         MC_TYPE_ACCOUNT,
123         G_PARAM_READWRITE));
124
125   g_object_class_install_property (object_class,
126       PROP_PRESENCE,
127       g_param_spec_uint ("presence",
128         "Contact presence",
129         "Presence of contact",
130         MC_PRESENCE_UNSET,
131         LAST_MC_PRESENCE,
132         MC_PRESENCE_UNSET,
133         G_PARAM_READWRITE));
134
135   g_object_class_install_property (object_class,
136       PROP_PRESENCE_MESSAGE,
137       g_param_spec_string ("presence-message",
138         "Contact presence message",
139         "Presence message of contact",
140         NULL,
141         G_PARAM_READWRITE));
142
143   g_object_class_install_property (object_class,
144       PROP_HANDLE,
145       g_param_spec_uint ("handle",
146         "Contact Handle",
147         "The handle of the contact",
148         0,
149         G_MAXUINT,
150         0,
151         G_PARAM_READWRITE));
152
153   g_object_class_install_property (object_class,
154       PROP_CAPABILITIES,
155       g_param_spec_flags ("capabilities",
156         "Contact Capabilities",
157         "Capabilities of the contact",
158         EMPATHY_TYPE_CAPABILITIES,
159         EMPATHY_CAPABILITIES_UNKNOWN,
160         G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
161
162   g_object_class_install_property (object_class,
163       PROP_IS_USER,
164       g_param_spec_boolean ("is-user",
165         "Contact is-user",
166         "Is contact the user",
167         FALSE,
168         G_PARAM_READWRITE));
169
170   g_object_class_install_property (object_class,
171       PROP_READY,
172       g_param_spec_flags ("ready",
173         "Contact ready flags",
174         "Flags for ready properties",
175         EMPATHY_TYPE_CONTACT_READY,
176         EMPATHY_CONTACT_READY_NONE,
177         G_PARAM_READABLE));
178
179   g_type_class_add_private (object_class, sizeof (EmpathyContactPriv));
180 }
181
182 static void
183 empathy_contact_init (EmpathyContact *contact)
184 {
185   EmpathyContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
186     EMPATHY_TYPE_CONTACT, EmpathyContactPriv);
187
188   contact->priv = priv;
189 }
190
191 static void
192 contact_finalize (GObject *object)
193 {
194   EmpathyContactPriv *priv;
195
196   priv = GET_PRIV (object);
197
198   DEBUG ("finalize: %p", object);
199
200   g_free (priv->name);
201   g_free (priv->id);
202   g_free (priv->presence_message);
203
204   if (priv->avatar)
205       empathy_avatar_unref (priv->avatar);
206
207   if (priv->account)
208       g_object_unref (priv->account);
209
210   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
211 }
212
213 static void
214 contact_get_property (GObject *object,
215                       guint param_id,
216                       GValue *value,
217                       GParamSpec *pspec)
218 {
219   EmpathyContactPriv *priv;
220
221   priv = GET_PRIV (object);
222
223   switch (param_id)
224     {
225       case PROP_ID:
226         g_value_set_string (value, priv->id);
227         break;
228       case PROP_NAME:
229         g_value_set_string (value,
230             empathy_contact_get_name (EMPATHY_CONTACT (object)));
231         break;
232       case PROP_AVATAR:
233         g_value_set_boxed (value, priv->avatar);
234         break;
235       case PROP_ACCOUNT:
236         g_value_set_object (value, priv->account);
237         break;
238       case PROP_PRESENCE:
239         g_value_set_uint (value, priv->presence);
240         break;
241       case PROP_PRESENCE_MESSAGE:
242         g_value_set_string (value, priv->presence_message);
243         break;
244       case PROP_HANDLE:
245         g_value_set_uint (value, priv->handle);
246         break;
247       case PROP_CAPABILITIES:
248         g_value_set_flags (value, priv->capabilities);
249         break;
250       case PROP_IS_USER:
251         g_value_set_boolean (value, priv->is_user);
252         break;
253       case PROP_READY:
254         g_value_set_flags (value, priv->ready);
255         break;
256       default:
257         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
258         break;
259     };
260 }
261
262 static void
263 contact_set_property (GObject *object,
264                       guint param_id,
265                       const GValue *value,
266                       GParamSpec *pspec)
267 {
268   EmpathyContactPriv *priv;
269
270   priv = GET_PRIV (object);
271
272   switch (param_id)
273     {
274       case PROP_ID:
275         empathy_contact_set_id (EMPATHY_CONTACT (object),
276         g_value_get_string (value));
277         break;
278       case PROP_NAME:
279         empathy_contact_set_name (EMPATHY_CONTACT (object),
280         g_value_get_string (value));
281         break;
282       case PROP_AVATAR:
283         empathy_contact_set_avatar (EMPATHY_CONTACT (object),
284         g_value_get_boxed (value));
285         break;
286       case PROP_ACCOUNT:
287         empathy_contact_set_account (EMPATHY_CONTACT (object),
288         MC_ACCOUNT (g_value_get_object (value)));
289         break;
290       case PROP_PRESENCE:
291         empathy_contact_set_presence (EMPATHY_CONTACT (object),
292         g_value_get_uint (value));
293         break;
294       case PROP_PRESENCE_MESSAGE:
295         empathy_contact_set_presence_message (EMPATHY_CONTACT (object),
296         g_value_get_string (value));
297         break;
298       case PROP_HANDLE:
299         empathy_contact_set_handle (EMPATHY_CONTACT (object),
300         g_value_get_uint (value));
301         break;
302       case PROP_CAPABILITIES:
303         empathy_contact_set_capabilities (EMPATHY_CONTACT (object),
304         g_value_get_flags (value));
305         break;
306       case PROP_IS_USER:
307         empathy_contact_set_is_user (EMPATHY_CONTACT (object),
308         g_value_get_boolean (value));
309         break;
310       default:
311         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
312         break;
313     };
314 }
315
316 static void
317 contact_set_ready_flag (EmpathyContact *contact,
318                         EmpathyContactReady flag)
319 {
320   EmpathyContactPriv *priv = GET_PRIV (contact);
321
322   if (!(priv->ready & flag))
323     {
324       priv->ready |= flag;
325       g_object_notify (G_OBJECT (contact), "ready");
326     }
327 }
328
329 EmpathyContact *
330 empathy_contact_new (McAccount *account)
331 {
332   return g_object_new (EMPATHY_TYPE_CONTACT,
333       "account", account,
334       NULL);
335 }
336
337 EmpathyContact *
338 empathy_contact_new_full (McAccount  *account,
339                           const gchar *id,
340                           const gchar *name)
341 {
342   return g_object_new (EMPATHY_TYPE_CONTACT,
343       "account", account,
344        "name", name,
345        "id", id,
346        NULL);
347 }
348
349 const gchar *
350 empathy_contact_get_id (EmpathyContact *contact)
351 {
352   EmpathyContactPriv *priv;
353
354   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
355
356   priv = GET_PRIV (contact);
357
358   return priv->id;
359 }
360
361 void
362 empathy_contact_set_id (EmpathyContact *contact,
363                         const gchar *id)
364 {
365   EmpathyContactPriv *priv;
366
367   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
368   g_return_if_fail (id != NULL);
369
370   priv = GET_PRIV (contact);
371
372   /* We temporally ref the contact because it could be destroyed
373    * during the signal emition */
374   g_object_ref (contact);
375   if (tp_strdiff (id, priv->id))
376     {
377       g_free (priv->id);
378       priv->id = g_strdup (id);
379
380       g_object_notify (G_OBJECT (contact), "id");
381       if (G_STR_EMPTY (priv->name))
382           g_object_notify (G_OBJECT (contact), "name");
383     }
384   contact_set_ready_flag (contact, EMPATHY_CONTACT_READY_ID);
385
386   g_object_unref (contact);
387 }
388
389 const gchar *
390 empathy_contact_get_name (EmpathyContact *contact)
391 {
392   EmpathyContactPriv *priv;
393
394   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
395
396   priv = GET_PRIV (contact);
397
398   if (G_STR_EMPTY (priv->name))
399       return empathy_contact_get_id (contact);
400
401   return priv->name;
402 }
403
404 void
405 empathy_contact_set_name (EmpathyContact *contact,
406                           const gchar *name)
407 {
408   EmpathyContactPriv *priv;
409
410   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
411
412   priv = GET_PRIV (contact);
413
414   g_object_ref (contact);
415   if (tp_strdiff (name, priv->name))
416     {
417       g_free (priv->name);
418       priv->name = g_strdup (name);
419       g_object_notify (G_OBJECT (contact), "name");
420     }
421   contact_set_ready_flag (contact, EMPATHY_CONTACT_READY_NAME);
422   g_object_unref (contact);
423 }
424
425 EmpathyAvatar *
426 empathy_contact_get_avatar (EmpathyContact *contact)
427 {
428   EmpathyContactPriv *priv;
429
430   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
431
432   priv = GET_PRIV (contact);
433
434   return priv->avatar;
435 }
436
437 void
438 empathy_contact_set_avatar (EmpathyContact *contact,
439                             EmpathyAvatar *avatar)
440 {
441   EmpathyContactPriv *priv;
442
443   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
444
445   priv = GET_PRIV (contact);
446
447   if (priv->avatar == avatar)
448     return;
449
450   if (priv->avatar)
451     {
452       empathy_avatar_unref (priv->avatar);
453       priv->avatar = NULL;
454     }
455
456   if (avatar)
457       priv->avatar = empathy_avatar_ref (avatar);
458
459   g_object_notify (G_OBJECT (contact), "avatar");
460 }
461
462 McAccount *
463 empathy_contact_get_account (EmpathyContact *contact)
464 {
465   EmpathyContactPriv *priv;
466
467   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
468
469   priv = GET_PRIV (contact);
470
471   return priv->account;
472 }
473
474 static void
475 get_requestable_channel_classes_cb (TpProxy *connection,
476                                     const GValue *value,
477                                     const GError *error,
478                                     gpointer user_data,
479                                     GObject *weak_object)
480 {
481   GPtrArray *classes;
482   guint i;
483
484   if (error != NULL)
485     {
486       DEBUG ("Error: %s", error->message);
487       return;
488     }
489
490   classes = g_value_get_boxed (value);
491
492   for (i = 0; i < classes->len; i++)
493     {
494       GValue class = {0,};
495       GValue *chan_type, *handle_type;
496       GHashTable *fixed_prop;
497       EmpathyCapabilities caps;
498       EmpathyContact *contact;
499
500       g_value_init (&class, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
501       g_value_set_static_boxed (&class, g_ptr_array_index (classes, i));
502
503       dbus_g_type_struct_get (&class,
504           0, &fixed_prop,
505           G_MAXUINT);
506
507       chan_type = g_hash_table_lookup (fixed_prop,
508           TP_IFACE_CHANNEL ".ChannelType");
509       if (chan_type == NULL || tp_strdiff (g_value_get_string (chan_type),
510             EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
511         continue;
512
513       handle_type = g_hash_table_lookup (fixed_prop,
514         TP_IFACE_CHANNEL ".TargetHandleType");
515
516       if (handle_type == NULL || g_value_get_uint (handle_type)
517           != TP_HANDLE_TYPE_CONTACT)
518         continue;
519
520       /* We can request file transfer channel to contacts. Set the FT caps. */
521       contact = EMPATHY_CONTACT (user_data);
522       caps = empathy_contact_get_capabilities (contact);
523       caps |= EMPATHY_CAPABILITIES_FT;
524
525       empathy_contact_set_capabilities (contact, caps);
526     }
527 }
528
529 void
530 empathy_contact_set_account (EmpathyContact *contact,
531                              McAccount *account)
532 {
533   EmpathyContactPriv *priv;
534   MissionControl *mc;
535   TpConnection *connection;
536
537   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
538   g_return_if_fail (MC_IS_ACCOUNT (account));
539
540   priv = GET_PRIV (contact);
541
542   if (account == priv->account)
543     return;
544
545   if (priv->account)
546       g_object_unref (priv->account);
547   priv->account = g_object_ref (account);
548
549   /* FIXME: We should use the futur ContactCapabilities interface */
550   /* Can we request FT channel on this connection? */
551   mc = empathy_mission_control_new ();
552   connection = mission_control_get_tpconnection (mc, account, NULL);
553
554   tp_cli_dbus_properties_call_get (connection, -1,
555       TP_IFACE_CONNECTION_INTERFACE_REQUESTS, "RequestableChannelClasses",
556       get_requestable_channel_classes_cb, contact, NULL, G_OBJECT (contact));
557
558   g_object_notify (G_OBJECT (contact), "account");
559
560   g_object_unref (mc);
561   g_object_unref (connection);
562 }
563
564 McPresence
565 empathy_contact_get_presence (EmpathyContact *contact)
566 {
567   EmpathyContactPriv *priv;
568
569   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), MC_PRESENCE_UNSET);
570
571   priv = GET_PRIV (contact);
572
573   return priv->presence;
574 }
575
576 void
577 empathy_contact_set_presence (EmpathyContact *contact,
578                               McPresence presence)
579 {
580   EmpathyContactPriv *priv;
581
582   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
583
584   priv = GET_PRIV (contact);
585
586   if (presence == priv->presence)
587     return;
588
589   priv->presence = presence;
590
591   g_object_notify (G_OBJECT (contact), "presence");
592 }
593
594 const gchar *
595 empathy_contact_get_presence_message (EmpathyContact *contact)
596 {
597   EmpathyContactPriv *priv;
598
599   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
600
601   priv = GET_PRIV (contact);
602
603   return priv->presence_message;
604 }
605
606 void
607 empathy_contact_set_presence_message (EmpathyContact *contact,
608                                       const gchar *message)
609 {
610   EmpathyContactPriv *priv = GET_PRIV (contact);
611
612   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
613
614   if (!tp_strdiff (message, priv->presence_message))
615     return;
616
617   g_free (priv->presence_message);
618   priv->presence_message = g_strdup (message);
619
620   g_object_notify (G_OBJECT (contact), "presence-message");
621 }
622
623 guint
624 empathy_contact_get_handle (EmpathyContact *contact)
625 {
626   EmpathyContactPriv *priv;
627
628   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
629
630   priv = GET_PRIV (contact);
631
632   return priv->handle;
633 }
634
635 void
636 empathy_contact_set_handle (EmpathyContact *contact,
637                             guint handle)
638 {
639   EmpathyContactPriv *priv;
640
641   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
642
643   priv = GET_PRIV (contact);
644
645   g_object_ref (contact);
646   if (handle != priv->handle)
647     {
648       priv->handle = handle;
649       g_object_notify (G_OBJECT (contact), "handle");
650     }
651   contact_set_ready_flag (contact, EMPATHY_CONTACT_READY_HANDLE);
652   g_object_unref (contact);
653 }
654
655 EmpathyCapabilities
656 empathy_contact_get_capabilities (EmpathyContact *contact)
657 {
658   EmpathyContactPriv *priv;
659
660   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), 0);
661
662   priv = GET_PRIV (contact);
663
664   return priv->capabilities;
665 }
666
667 void
668 empathy_contact_set_capabilities (EmpathyContact *contact,
669                                   EmpathyCapabilities capabilities)
670 {
671   EmpathyContactPriv *priv;
672
673   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
674
675   priv = GET_PRIV (contact);
676
677   if (priv->capabilities == capabilities)
678     return;
679
680   priv->capabilities = capabilities;
681
682   g_object_notify (G_OBJECT (contact), "capabilities");
683 }
684
685 gboolean
686 empathy_contact_is_user (EmpathyContact *contact)
687 {
688   EmpathyContactPriv *priv;
689
690   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
691
692   priv = GET_PRIV (contact);
693
694   return priv->is_user;
695 }
696
697 void
698 empathy_contact_set_is_user (EmpathyContact *contact,
699                              gboolean is_user)
700 {
701   EmpathyContactPriv *priv;
702
703   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
704
705   priv = GET_PRIV (contact);
706
707   if (priv->is_user == is_user)
708     return;
709
710   priv->is_user = is_user;
711
712   g_object_notify (G_OBJECT (contact), "is-user");
713 }
714
715 gboolean
716 empathy_contact_is_online (EmpathyContact *contact)
717 {
718   EmpathyContactPriv *priv;
719
720   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
721
722   priv = GET_PRIV (contact);
723
724   return (priv->presence > MC_PRESENCE_OFFLINE);
725 }
726
727 const gchar *
728 empathy_contact_get_status (EmpathyContact *contact)
729 {
730   EmpathyContactPriv *priv;
731
732   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), "");
733
734   priv = GET_PRIV (contact);
735
736   if (priv->presence_message)
737     return priv->presence_message;
738
739   return empathy_presence_get_default_message (priv->presence);
740 }
741
742 gboolean
743 empathy_contact_can_voip (EmpathyContact *contact)
744 {
745   EmpathyContactPriv *priv;
746
747   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
748
749   priv = GET_PRIV (contact);
750
751   return priv->capabilities & (EMPATHY_CAPABILITIES_AUDIO |
752       EMPATHY_CAPABILITIES_VIDEO);
753 }
754
755 gboolean
756 empathy_contact_can_send_files (EmpathyContact *contact)
757 {
758   EmpathyContactPriv *priv;
759
760   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
761
762   priv = GET_PRIV (contact);
763
764   return priv->capabilities & EMPATHY_CAPABILITIES_FT;
765 }
766
767 EmpathyContactReady
768 empathy_contact_get_ready (EmpathyContact *contact)
769 {
770   EmpathyContactPriv *priv;
771
772   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
773
774   priv = GET_PRIV (contact);
775
776   return priv->ready;
777 }
778
779 gboolean
780 empathy_contact_equal (gconstpointer v1,
781                        gconstpointer v2)
782 {
783   McAccount *account_a;
784   McAccount *account_b;
785   const gchar *id_a;
786   const gchar *id_b;
787
788   g_return_val_if_fail (EMPATHY_IS_CONTACT (v1), FALSE);
789   g_return_val_if_fail (EMPATHY_IS_CONTACT (v2), FALSE);
790
791   account_a = empathy_contact_get_account (EMPATHY_CONTACT (v1));
792   account_b = empathy_contact_get_account (EMPATHY_CONTACT (v2));
793
794   id_a = empathy_contact_get_id (EMPATHY_CONTACT (v1));
795   id_b = empathy_contact_get_id (EMPATHY_CONTACT (v2));
796
797   return empathy_account_equal (account_a, account_b) &&
798       !tp_strdiff (id_a, id_b);
799 }
800
801 guint
802 empathy_contact_hash (gconstpointer key)
803 {
804   EmpathyContactPriv *priv;
805
806   g_return_val_if_fail (EMPATHY_IS_CONTACT (key), +1);
807
808   priv = GET_PRIV (EMPATHY_CONTACT (key));
809
810   if (priv->hash == 0)
811     {
812       priv->hash = empathy_account_hash (priv->account) ^
813           g_str_hash (priv->id);
814     }
815
816   return priv->hash;
817 }
818
819 static gboolean
820 contact_is_ready_func (GObject *contact,
821                        gpointer user_data)
822 {
823   EmpathyContactPriv *priv = GET_PRIV (contact);
824   EmpathyContactReady ready;
825
826   ready = GPOINTER_TO_UINT (user_data);
827
828   /* When the name is NULL, empathy_contact_get_name() fallback to the id.
829    * When the caller want to wait the name to be ready, it also want to wait
830    * the id to be ready in case of fallback. */
831   if ((ready & EMPATHY_CONTACT_READY_NAME) && G_STR_EMPTY (priv->name))
832       ready |= EMPATHY_CONTACT_READY_ID;
833
834   return (priv->ready & ready) == ready;
835 }
836
837 void
838 empathy_contact_run_until_ready (EmpathyContact *contact,
839                                  EmpathyContactReady ready,
840                                  GMainLoop **loop)
841 {
842   empathy_run_until_ready_full (contact, "notify::ready",
843       contact_is_ready_func, GUINT_TO_POINTER (ready),
844       loop);
845 }
846
847 static gchar *
848 contact_get_avatar_filename (EmpathyContact *contact,
849                              const gchar *token)
850 {
851   EmpathyContactPriv *priv = GET_PRIV (contact);
852   gchar *avatar_path;
853   gchar *avatar_file;
854   gchar *token_escaped;
855   gchar *contact_escaped;
856
857   if (G_STR_EMPTY (priv->id))
858     return NULL;
859
860   contact_escaped = tp_escape_as_identifier (priv->id);
861   token_escaped = tp_escape_as_identifier (token);
862
863   avatar_path = g_build_filename (g_get_user_cache_dir (),
864       PACKAGE_NAME,
865       "avatars",
866       mc_account_get_unique_name (priv->account),
867       contact_escaped,
868       NULL);
869   g_mkdir_with_parents (avatar_path, 0700);
870
871   avatar_file = g_build_filename (avatar_path, token_escaped, NULL);
872
873   g_free (contact_escaped);
874   g_free (token_escaped);
875   g_free (avatar_path);
876
877   return avatar_file;
878 }
879
880 void
881 empathy_contact_load_avatar_data (EmpathyContact *contact,
882                                   const guchar  *data,
883                                   const gsize len,
884                                   const gchar *format,
885                                   const gchar *token)
886 {
887   EmpathyAvatar *avatar;
888   gchar *filename;
889   GError *error = NULL;
890
891   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
892   g_return_if_fail (data != NULL);
893   g_return_if_fail (len > 0);
894   g_return_if_fail (format != NULL);
895   g_return_if_fail (!G_STR_EMPTY (token));
896
897   /* Load and set the avatar */
898   avatar = empathy_avatar_new (g_memdup (data, len), len, g_strdup (format),
899       g_strdup (token));
900   empathy_contact_set_avatar (contact, avatar);
901   empathy_avatar_unref (avatar);
902
903   /* Save to cache if not yet in it */
904   filename = contact_get_avatar_filename (contact, token);
905   if (filename && !g_file_test (filename, G_FILE_TEST_EXISTS))
906     {
907       if (!empathy_avatar_save_to_file (avatar, filename, &error))
908         {
909           DEBUG ("Failed to save avatar in cache: %s",
910             error ? error->message : "No error given");
911           g_clear_error (&error);
912         }
913       else
914           DEBUG ("Avatar saved to %s", filename);
915     }
916   g_free (filename);
917 }
918
919 gboolean
920 empathy_contact_load_avatar_cache (EmpathyContact *contact,
921                                    const gchar *token)
922 {
923   EmpathyAvatar *avatar = NULL;
924   gchar *filename;
925   gchar *data = NULL;
926   gsize len;
927   GError *error = NULL;
928
929   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
930   g_return_val_if_fail (!G_STR_EMPTY (token), FALSE);
931
932   /* Load the avatar from file if it exists */
933   filename = contact_get_avatar_filename (contact, token);
934   if (filename && g_file_test (filename, G_FILE_TEST_EXISTS))
935     {
936       if (!g_file_get_contents (filename, &data, &len, &error))
937         {
938           DEBUG ("Failed to load avatar from cache: %s",
939             error ? error->message : "No error given");
940           g_clear_error (&error);
941         }
942     }
943
944   if (data)
945     {
946       DEBUG ("Avatar loaded from %s", filename);
947       avatar = empathy_avatar_new (data, len, NULL, g_strdup (token));
948       empathy_contact_set_avatar (contact, avatar);
949       empathy_avatar_unref (avatar);
950     }
951
952   g_free (filename);
953
954   return data != NULL;
955 }
956
957 GType
958 empathy_avatar_get_type (void)
959 {
960   static GType type_id = 0;
961
962   if (!type_id)
963     {
964       type_id = g_boxed_type_register_static ("EmpathyAvatar",
965           (GBoxedCopyFunc) empathy_avatar_ref,
966           (GBoxedFreeFunc) empathy_avatar_unref);
967     }
968
969   return type_id;
970 }
971
972 EmpathyAvatar *
973 empathy_avatar_new (guchar *data,
974                     gsize len,
975                     gchar *format,
976                     gchar *token)
977 {
978   EmpathyAvatar *avatar;
979
980   avatar = g_slice_new0 (EmpathyAvatar);
981   avatar->data = data;
982   avatar->len = len;
983   avatar->format = format;
984   avatar->token = token;
985   avatar->refcount = 1;
986
987   return avatar;
988 }
989
990 void
991 empathy_avatar_unref (EmpathyAvatar *avatar)
992 {
993   g_return_if_fail (avatar != NULL);
994
995   avatar->refcount--;
996   if (avatar->refcount == 0)
997     {
998       g_free (avatar->data);
999       g_free (avatar->format);
1000       g_free (avatar->token);
1001       g_slice_free (EmpathyAvatar, avatar);
1002     }
1003 }
1004
1005 EmpathyAvatar *
1006 empathy_avatar_ref (EmpathyAvatar *avatar)
1007 {
1008   g_return_val_if_fail (avatar != NULL, NULL);
1009
1010   avatar->refcount++;
1011
1012   return avatar;
1013 }
1014
1015 /**
1016  * empathy_avatar_save_to_file:
1017  * @avatar: the avatar
1018  * @filename: name of a file to write avatar to
1019  * @error: return location for a GError, or NULL
1020  *
1021  * Save the avatar to a file named filename
1022  *
1023  * Returns: %TRUE on success, %FALSE if an error occurred 
1024  */
1025 gboolean
1026 empathy_avatar_save_to_file (EmpathyAvatar *self,
1027                              const gchar *filename,
1028                              GError **error)
1029 {
1030   return g_file_set_contents (filename, self->data, self->len, error);
1031 }
1032