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