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