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