]> git.0d.be Git - empathy.git/blob - libempathy/empathy-contact-factory.c
5bd51c9d825f54154022ade73520b3fed025273a
[empathy.git] / libempathy / empathy-contact-factory.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  * 
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25
26 #include <libtelepathy/tp-conn.h>
27 #include <libtelepathy/tp-conn-iface-aliasing-gen.h>
28 #include <libtelepathy/tp-conn-iface-presence-gen.h>
29 #include <libtelepathy/tp-conn-iface-avatars-gen.h>
30 #include <libtelepathy/tp-conn-iface-capabilities-gen.h>
31 #include <libmissioncontrol/mission-control.h>
32
33 #include "empathy-contact-factory.h"
34 #include "empathy-utils.h"
35 #include "empathy-debug.h"
36
37 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
38                        EMPATHY_TYPE_CONTACT_FACTORY, EmpathyContactFactoryPriv))
39
40 #define DEBUG_DOMAIN "ContactFactory"
41
42 struct _EmpathyContactFactoryPriv {
43         MissionControl *mc;
44         GHashTable     *accounts;
45 };
46
47 typedef struct {
48         EmpathyContactFactory *factory;
49         McAccount             *account;
50         guint                  refcount;
51
52         TpConn                *tp_conn;
53         DBusGProxy            *aliasing_iface;
54         DBusGProxy            *avatars_iface;
55         DBusGProxy            *presence_iface;
56         DBusGProxy            *capabilities_iface;
57
58         GList                 *contacts;
59         guint                  self_handle;
60 } ContactFactoryAccountData;
61
62 typedef struct {
63         ContactFactoryAccountData *account_data;
64         GList                     *contacts;
65 } RequestHandlesData;
66
67 typedef struct {
68         ContactFactoryAccountData *account_data;
69         guint                     *handles;
70 } RequestAliasesData;
71
72 static void empathy_contact_factory_class_init (EmpathyContactFactoryClass *klass);
73 static void empathy_contact_factory_init       (EmpathyContactFactory      *factory);
74
75 G_DEFINE_TYPE (EmpathyContactFactory, empathy_contact_factory, G_TYPE_OBJECT);
76
77 static gint
78 contact_factory_find_by_handle (gconstpointer a,
79                                 gconstpointer b)
80 {
81         EmpathyContact *contact;
82         guint           handle; 
83
84         contact = EMPATHY_CONTACT (a);
85         handle = GPOINTER_TO_UINT (b);
86
87         return handle - empathy_contact_get_handle (contact);
88 }
89
90 static EmpathyContact *
91 contact_factory_account_data_find_by_handle (ContactFactoryAccountData *account_data,
92                                              guint                      handle)
93 {
94         GList *l;
95
96         l = g_list_find_custom (account_data->contacts,
97                                 GUINT_TO_POINTER (handle),
98                                 contact_factory_find_by_handle);
99
100         return l ? l->data : NULL;
101 }
102
103 static gint
104 contact_factory_find_by_id (gconstpointer a,
105                             gconstpointer b)
106 {
107         EmpathyContact *contact;
108         const gchar    *id = b;
109
110         contact = EMPATHY_CONTACT (a);
111
112         return strcmp (id, empathy_contact_get_id (contact));
113 }
114
115 static EmpathyContact *
116 contact_factory_account_data_find_by_id (ContactFactoryAccountData *account_data,
117                                          const gchar               *id)
118 {
119         GList *l;
120
121         l = g_list_find_custom (account_data->contacts,
122                                 id,
123                                 contact_factory_find_by_id);
124
125         return l ? l->data : NULL;
126 }
127
128 static void contact_factory_account_data_disconnect (ContactFactoryAccountData *account_data);
129
130 static void
131 contact_factory_weak_notify (gpointer data,
132                              GObject *where_the_object_was)
133 {
134         ContactFactoryAccountData *account_data = data;
135
136         empathy_debug (DEBUG_DOMAIN, "Remove finalized contact %p",
137                        where_the_object_was);
138
139         account_data->contacts = g_list_remove (account_data->contacts,
140                                                 where_the_object_was);
141         if (!account_data->contacts) {
142                 EmpathyContactFactoryPriv *priv = GET_PRIV (account_data->factory);
143
144                 g_hash_table_remove (priv->accounts, account_data->account);
145         }
146 }
147
148 static void
149 contact_factory_remove_foreach (gpointer data,
150                                 gpointer user_data)
151 {
152         ContactFactoryAccountData *account_data = user_data;
153         EmpathyContact            *contact = data;
154
155         g_object_weak_unref (G_OBJECT (contact),
156                              contact_factory_weak_notify,
157                              account_data);
158 }
159
160 static ContactFactoryAccountData *
161 contact_factory_account_data_ref (ContactFactoryAccountData *account_data)
162 {
163         account_data->refcount++;
164
165         return account_data;
166 }
167
168 static void
169 contact_factory_account_data_unref (ContactFactoryAccountData *account_data)
170 {
171         account_data->refcount--;
172         if (account_data->refcount > 0) {
173                 return;
174         }
175
176         empathy_debug (DEBUG_DOMAIN, "Account data finalized: %p (%s)",
177                        account_data,
178                        mc_account_get_normalized_name (account_data->account));
179
180         contact_factory_account_data_disconnect (account_data);
181
182         if (account_data->contacts) {
183                 g_list_foreach (account_data->contacts,
184                                 contact_factory_remove_foreach,
185                                 account_data);
186                 g_list_free (account_data->contacts);
187         }
188
189         if (account_data->account) {
190                 g_object_unref (account_data->account);
191         }
192
193         if (account_data->tp_conn) {
194                 g_object_unref (account_data->tp_conn);
195         }
196
197         g_slice_free (ContactFactoryAccountData, account_data);
198 }
199
200 static void
201 contact_factory_presences_table_foreach (const gchar      *state_str,
202                                          GHashTable       *presences_table,
203                                          EmpathyPresence **presence)
204 {
205         McPresence    state;
206         const GValue *message;
207
208         state = empathy_presence_state_from_str (state_str);
209         if (state == MC_PRESENCE_UNSET) {
210                 return;
211         }
212
213         if (*presence) {
214                 g_object_unref (*presence);
215                 *presence = NULL;
216         }
217
218         *presence = empathy_presence_new ();
219         empathy_presence_set_state (*presence, state);
220
221         message = g_hash_table_lookup (presences_table, "message");
222         if (message != NULL) {
223                 empathy_presence_set_status (*presence,
224                                              g_value_get_string (message));
225         }
226 }
227
228 static void
229 contact_factory_parse_presence_foreach (guint                      handle,
230                                         GValueArray               *presence_struct,
231                                         ContactFactoryAccountData *account_data)
232 {
233         GHashTable      *presences_table;
234         EmpathyContact  *contact;
235         EmpathyPresence *presence = NULL;
236
237         contact = contact_factory_account_data_find_by_handle (account_data,
238                                                                handle);
239         if (!contact) {
240                 return;
241         }
242
243         presences_table = g_value_get_boxed (g_value_array_get_nth (presence_struct, 1));
244
245         g_hash_table_foreach (presences_table,
246                               (GHFunc) contact_factory_presences_table_foreach,
247                               &presence);
248
249         empathy_debug (DEBUG_DOMAIN, "Changing presence for contact %s (%d) to %s (%d)",
250                       empathy_contact_get_id (contact),
251                       handle,
252                       presence ? empathy_presence_get_status (presence) : "unset",
253                       presence ? empathy_presence_get_state (presence) : MC_PRESENCE_UNSET);
254
255         empathy_contact_set_presence (contact, presence);
256         g_object_unref (presence);
257 }
258
259 static void
260 contact_factory_get_presence_cb (DBusGProxy *proxy,
261                                  GHashTable *handle_table,
262                                  GError     *error,
263                                  gpointer    user_data)
264 {
265         ContactFactoryAccountData *account_data = user_data;
266
267         if (error) {
268                 empathy_debug (DEBUG_DOMAIN, "Error getting presence: %s",
269                               error->message);
270                 goto OUT;
271         }
272
273         g_hash_table_foreach (handle_table,
274                               (GHFunc) contact_factory_parse_presence_foreach,
275                               account_data);
276
277         g_hash_table_destroy (handle_table);
278 OUT:
279         contact_factory_account_data_unref (account_data);
280 }
281
282 static void
283 contact_factory_presence_update_cb (DBusGProxy                *proxy,
284                                     GHashTable                *handle_table,
285                                     ContactFactoryAccountData *account_data)
286 {
287         g_hash_table_foreach (handle_table,
288                               (GHFunc) contact_factory_parse_presence_foreach,
289                               account_data);
290 }
291
292 static void
293 contact_factory_set_aliases_cb (DBusGProxy *proxy,
294                                 GError     *error,
295                                 gpointer    user_data)
296 {
297         ContactFactoryAccountData *account_data = user_data;
298
299         if (error) {
300                 empathy_debug (DEBUG_DOMAIN, "Error setting alias: %s",
301                                error->message);
302         }
303
304         contact_factory_account_data_unref (account_data);
305 }
306
307 static void
308 contact_factory_request_aliases_cb (DBusGProxy  *proxy,
309                                     gchar      **contact_names,
310                                     GError      *error,
311                                     gpointer     user_data)
312 {
313         RequestAliasesData  *data = user_data;
314         guint                i = 0;
315         gchar              **name;
316
317         if (error) {
318                 empathy_debug (DEBUG_DOMAIN, "Error requesting aliases: %s",
319                               error->message);
320                 goto OUT;
321         }
322
323         for (name = contact_names; *name; name++) {
324                 EmpathyContact *contact;
325
326                 contact = contact_factory_account_data_find_by_handle (data->account_data,
327                                                                        data->handles[i]);
328                 if (!contact) {
329                         continue;
330                 }
331
332                 empathy_debug (DEBUG_DOMAIN, "Renaming contact %s (%d) to %s (request cb)",
333                                empathy_contact_get_id (contact),
334                                data->handles[i], *name);
335
336                 empathy_contact_set_name  (contact, *name);
337
338                 i++;
339         }
340
341         g_strfreev (contact_names);
342 OUT:
343         contact_factory_account_data_unref (data->account_data);
344         g_free (data->handles);
345         g_slice_free (RequestAliasesData, data);
346 }
347
348 static void
349 contact_factory_aliases_changed_cb (DBusGProxy *proxy,
350                                     GPtrArray  *renamed_handlers,
351                                     gpointer    user_data)
352 {
353         ContactFactoryAccountData *account_data = user_data;
354         guint                     i;
355
356         for (i = 0; renamed_handlers->len > i; i++) {
357                 guint           handle;
358                 const gchar    *alias;
359                 GValueArray    *renamed_struct;
360                 EmpathyContact *contact;
361
362                 renamed_struct = g_ptr_array_index (renamed_handlers, i);
363                 handle = g_value_get_uint(g_value_array_get_nth (renamed_struct, 0));
364                 alias = g_value_get_string(g_value_array_get_nth (renamed_struct, 1));
365                 contact = contact_factory_account_data_find_by_handle (account_data, handle);
366
367                 if (!contact) {
368                         /* We don't know this contact, skip */
369                         continue;
370                 }
371
372                 if (G_STR_EMPTY (alias)) {
373                         alias = NULL;
374                 }
375
376                 empathy_debug (DEBUG_DOMAIN, "Renaming contact %s (%d) to %s (changed cb)",
377                                empathy_contact_get_id (contact),
378                                handle, alias);
379
380                 empathy_contact_set_name (contact, alias);
381         }
382 }
383
384 static void
385 contact_factory_set_avatar_cb (DBusGProxy *proxy,
386                                gchar      *token,
387                                GError     *error,
388                                gpointer    user_data)
389 {
390         ContactFactoryAccountData *account_data = user_data;
391
392         if (error) {
393                 empathy_debug (DEBUG_DOMAIN, "Error setting avatar: %s",
394                                error->message);
395         }
396
397         contact_factory_account_data_unref (account_data);
398         g_free (token);
399 }
400
401 static void
402 contact_factory_clear_avatar_cb (DBusGProxy *proxy,
403                                  GError     *error,
404                                  gpointer    user_data)
405 {
406         ContactFactoryAccountData *account_data = user_data;
407
408         if (error) {
409                 empathy_debug (DEBUG_DOMAIN, "Error clearing avatar: %s",
410                                error->message);
411         }
412
413         contact_factory_account_data_unref (account_data);
414 }
415
416 static void
417 contact_factory_avatar_retrieved_cb (DBusGProxy *proxy,
418                                      guint       handle,
419                                      gchar      *token,
420                                      GArray     *avatar_data,
421                                      gchar      *mime_type,
422                                      gpointer    user_data)
423 {
424         ContactFactoryAccountData *account_data = user_data;
425         EmpathyContact            *contact;
426         EmpathyAvatar             *avatar;
427
428         contact = contact_factory_account_data_find_by_handle (account_data,
429                                                                handle);
430         if (!contact) {
431                 return;
432         }
433
434         empathy_debug (DEBUG_DOMAIN, "Avatar retrieved for contact %s (%d)",
435                        empathy_contact_get_id (contact),
436                        handle);
437
438         avatar = empathy_avatar_new (avatar_data->data,
439                                      avatar_data->len,
440                                      mime_type,
441                                      token);
442
443         empathy_contact_set_avatar (contact, avatar);
444         empathy_avatar_unref (avatar);
445 }
446
447 static void
448 contact_factory_request_avatars_cb (DBusGProxy *proxy,
449                                     GError     *error,
450                                     gpointer    user_data)
451 {
452         ContactFactoryAccountData *account_data = user_data;
453
454         if (error) {
455                 empathy_debug (DEBUG_DOMAIN, "Error requesting avatars: %s",
456                                error->message);
457         }
458
459         contact_factory_account_data_unref (account_data);
460 }
461
462 typedef struct {
463         ContactFactoryAccountData *account_data;
464         GArray                    *handles;
465 } TokensData;
466
467 static gboolean
468 contact_factory_avatar_maybe_update (ContactFactoryAccountData *account_data,
469                                      guint                      handle,
470                                      const gchar               *token)
471 {
472         EmpathyContact *contact;
473         EmpathyAvatar  *avatar;
474
475         contact = contact_factory_account_data_find_by_handle (account_data,
476                                                                handle);
477         if (!contact) {
478                 return TRUE;
479         }
480
481         /* Check if we have an avatar */
482         if (G_STR_EMPTY (token)) {
483                 empathy_contact_set_avatar (contact, NULL);
484                 return TRUE;
485         }
486
487         /* Check if the avatar changed */
488         avatar = empathy_contact_get_avatar (contact);
489         if (avatar && !empathy_strdiff (avatar->token, token)) {
490                 return TRUE;
491         }
492
493         /* The avatar changed, search the new one in the cache */
494         avatar = empathy_avatar_new_from_cache (token);
495         if (avatar) {
496                 /* Got from cache, use it */
497                 empathy_contact_set_avatar (contact, avatar);
498                 empathy_avatar_unref (avatar);
499                 return TRUE;
500         }
501
502         /* Avatar is not up-to-date, we have to request it. */
503         return FALSE;
504 }
505
506 static void
507 contact_factory_avatar_tokens_foreach (gpointer key,
508                                        gpointer value,
509                                        gpointer user_data)
510 {
511         TokensData  *data = user_data;
512         const gchar *token = value;
513         guint        handle = GPOINTER_TO_UINT (key);
514
515         if (!contact_factory_avatar_maybe_update (data->account_data,
516                                                   handle, token)) {
517                 g_array_append_val (data->handles, handle);
518         }
519 }
520
521 static void
522 contact_factory_get_known_avatar_tokens_cb (DBusGProxy *proxy,
523                                             GHashTable *tokens,
524                                             GError     *error,
525                                             gpointer    user_data)
526 {
527         ContactFactoryAccountData *account_data = user_data;
528         TokensData                 data;
529
530         if (error) {
531                 empathy_debug (DEBUG_DOMAIN,
532                                "Error getting known avatars tokens: %s",
533                                error->message);
534                 goto OUT;
535         }
536
537         data.account_data = account_data;
538         data.handles = g_array_new (FALSE, FALSE, sizeof (guint));
539         g_hash_table_foreach (tokens,
540                               contact_factory_avatar_tokens_foreach,
541                               &data);
542
543         empathy_debug (DEBUG_DOMAIN, "Got %d tokens, need to request %d avatars",
544                        g_hash_table_size (tokens),
545                        data.handles->len);
546
547         /* Request needed avatars */
548         if (data.handles->len > 0) {
549                 tp_conn_iface_avatars_request_avatars_async (account_data->avatars_iface,
550                                                              data.handles,
551                                                              contact_factory_request_avatars_cb,
552                                                              contact_factory_account_data_ref (account_data));
553         }
554
555         g_hash_table_destroy (tokens);
556         g_array_free (data.handles, TRUE);
557 OUT:
558         contact_factory_account_data_unref (account_data);
559 }
560
561 static void
562 contact_factory_avatar_updated_cb (DBusGProxy *proxy,
563                                    guint       handle,
564                                    gchar      *new_token,
565                                    gpointer    user_data)
566 {
567         ContactFactoryAccountData *account_data = user_data;
568         GArray                    *handles;
569
570         if (contact_factory_avatar_maybe_update (account_data, handle, new_token)) {
571                 /* Avatar was cached, nothing to do */
572                 return;
573         }
574
575         empathy_debug (DEBUG_DOMAIN, "Need to request avatar for token %s",
576                        new_token);
577
578         handles = g_array_new (FALSE, FALSE, sizeof (guint));
579         g_array_append_val (handles, handle);
580
581         tp_conn_iface_avatars_request_avatars_async (account_data->avatars_iface,
582                                                      handles,
583                                                      contact_factory_request_avatars_cb,
584                                                      contact_factory_account_data_ref (account_data));
585         g_array_free (handles, TRUE);
586 }
587
588 static void
589 contact_factory_update_capabilities (ContactFactoryAccountData *account_data,
590                                      guint                      handle,
591                                      const gchar               *channel_type,
592                                      guint                      generic,
593                                      guint                      specific)
594 {
595         EmpathyContact      *contact;
596         EmpathyCapabilities  capabilities;
597
598         contact = contact_factory_account_data_find_by_handle (account_data,
599                                                                handle);
600         if (!contact) {
601                 return;
602         }
603
604         capabilities = empathy_contact_get_capabilities (contact);
605
606         if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) == 0) {
607                 capabilities &= ~EMPATHY_CAPABILITIES_AUDIO;
608                 capabilities &= ~EMPATHY_CAPABILITIES_VIDEO;
609                 if (specific & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO) {
610                         capabilities |= EMPATHY_CAPABILITIES_AUDIO;
611                 }
612                 if (specific & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO) {
613                         capabilities |= EMPATHY_CAPABILITIES_VIDEO;
614                 }
615         }
616
617         empathy_debug (DEBUG_DOMAIN, "Changing capabilities for contact %s (%d) to %d",
618                        empathy_contact_get_id (contact),
619                        empathy_contact_get_handle (contact),
620                        capabilities);
621
622         empathy_contact_set_capabilities (contact, capabilities);
623 }
624
625 static void
626 contact_factory_get_capabilities_cb (DBusGProxy *proxy,
627                                      GPtrArray  *capabilities,
628                                      GError     *error,
629                                      gpointer    user_data)
630 {
631         ContactFactoryAccountData *account_data = user_data;
632         guint                      i;
633
634         if (error) {
635                 empathy_debug (DEBUG_DOMAIN, "Error getting capabilities: %s",
636                                error->message);
637                 goto OUT;
638         }
639
640         for (i = 0; i < capabilities->len; i++) {
641                 GValueArray *values;
642                 guint        handle;
643                 const gchar *channel_type;
644                 guint        generic;
645                 guint        specific;
646
647                 values = g_ptr_array_index (capabilities, i);
648                 handle = g_value_get_uint (g_value_array_get_nth (values, 0));
649                 channel_type = g_value_get_string (g_value_array_get_nth (values, 1));
650                 generic = g_value_get_uint (g_value_array_get_nth (values, 2));
651                 specific = g_value_get_uint (g_value_array_get_nth (values, 3));
652
653                 contact_factory_update_capabilities (account_data,
654                                                      handle,
655                                                      channel_type,
656                                                      generic,
657                                                      specific);
658
659                 g_value_array_free (values);
660         }
661
662         g_ptr_array_free (capabilities, TRUE);
663 OUT:
664         contact_factory_account_data_unref (account_data);
665 }
666
667 static void
668 contact_factory_capabilities_changed_cb (DBusGProxy *proxy,
669                                          GPtrArray  *capabilities,
670                                          gpointer    user_data)
671 {
672         ContactFactoryAccountData *account_data = user_data;
673         guint                      i;
674
675         for (i = 0; i < capabilities->len; i++) {
676                 GValueArray *values;
677                 guint        handle;
678                 const gchar *channel_type;
679                 guint        generic;
680                 guint        specific;
681
682                 values = g_ptr_array_index (capabilities, i);
683                 handle = g_value_get_uint (g_value_array_get_nth (values, 0));
684                 channel_type = g_value_get_string (g_value_array_get_nth (values, 1));
685                 generic = g_value_get_uint (g_value_array_get_nth (values, 3));
686                 specific = g_value_get_uint (g_value_array_get_nth (values, 5));
687
688                 contact_factory_update_capabilities (account_data,
689                                                      handle,
690                                                      channel_type,
691                                                      generic,
692                                                      specific);
693         }
694 }
695
696 static void
697 contact_factory_request_everything (ContactFactoryAccountData *account_data,
698                                     GArray                    *handles)
699 {
700         if (account_data->presence_iface) {
701                 tp_conn_iface_presence_get_presence_async (account_data->presence_iface,
702                                                            handles,
703                                                            contact_factory_get_presence_cb,
704                                                            contact_factory_account_data_ref (account_data));
705         }
706
707         if (account_data->aliasing_iface) {
708                 RequestAliasesData *data;
709
710                 data = g_slice_new (RequestAliasesData);
711                 data->account_data = contact_factory_account_data_ref (account_data);
712                 data->handles = g_memdup (handles->data, handles->len * sizeof (guint));
713
714                 tp_conn_iface_aliasing_request_aliases_async (account_data->aliasing_iface,
715                                                               handles,
716                                                               contact_factory_request_aliases_cb,
717                                                               data);
718         }
719
720         if (account_data->avatars_iface) {
721                 tp_conn_iface_avatars_get_known_avatar_tokens_async (account_data->avatars_iface,
722                                                                      handles,
723                                                                      contact_factory_get_known_avatar_tokens_cb,
724                                                                      contact_factory_account_data_ref (account_data));
725         }
726
727         if (account_data->capabilities_iface) {
728                 tp_conn_iface_capabilities_get_capabilities_async (account_data->capabilities_iface,
729                                                                    handles,
730                                                                    contact_factory_get_capabilities_cb,
731                                                                    contact_factory_account_data_ref (account_data));
732         }
733 }
734
735 static void
736 contact_factory_request_handles_cb (DBusGProxy *proxy,
737                                     GArray     *handles,
738                                     GError     *error,
739                                     gpointer    user_data)
740 {
741         RequestHandlesData *data = user_data;
742         GList              *l;
743         guint               i = 0;
744
745         if (error) {
746                 empathy_debug (DEBUG_DOMAIN, "Failed to request handles: %s",
747                                error->message);
748                 goto OUT;
749         }
750
751         for (l = data->contacts; l; l = l->next) {
752                 guint handle;
753
754                 handle = g_array_index (handles, guint, i);
755                 empathy_contact_set_handle (l->data, handle);
756                 if (handle == data->account_data->self_handle) {
757                         empathy_contact_set_is_user (l->data, TRUE);
758                 }
759
760                 i++;
761         }
762
763         contact_factory_request_everything (data->account_data, handles);
764         g_array_free (handles, TRUE);
765
766 OUT:
767         g_list_foreach (data->contacts, (GFunc) g_object_unref, NULL);
768         g_list_free (data->contacts);
769         contact_factory_account_data_unref (data->account_data);
770         g_slice_free (RequestHandlesData, data);
771 }
772
773 static void
774 contact_factory_disconnect_contact_foreach (gpointer data,
775                                             gpointer user_data)
776 {
777         EmpathyContact *contact = data;
778         
779         empathy_contact_set_presence (contact, NULL);
780         empathy_contact_set_handle (contact, 0);
781 }
782
783 static void
784 contact_factory_destroy_cb (TpConn                    *tp_conn,
785                             ContactFactoryAccountData *account_data)
786 {
787         empathy_debug (DEBUG_DOMAIN, "Account disconnected or CM crashed");
788
789         g_object_unref (account_data->tp_conn);
790         account_data->tp_conn = NULL;
791         account_data->aliasing_iface = NULL;
792         account_data->avatars_iface = NULL;
793         account_data->presence_iface = NULL;
794         account_data->capabilities_iface = NULL;
795
796         g_list_foreach (account_data->contacts,
797                         contact_factory_disconnect_contact_foreach,
798                         account_data);
799 }
800
801 static void
802 contact_factory_account_data_disconnect (ContactFactoryAccountData *account_data)
803 {
804         if (account_data->aliasing_iface) {
805                 dbus_g_proxy_disconnect_signal (account_data->aliasing_iface,
806                                                 "AliasesChanged",
807                                                 G_CALLBACK (contact_factory_aliases_changed_cb),
808                                                 account_data);
809         }
810         if (account_data->avatars_iface) {
811                 dbus_g_proxy_disconnect_signal (account_data->avatars_iface,
812                                                 "AvatarUpdated",
813                                                 G_CALLBACK (contact_factory_avatar_updated_cb),
814                                                 account_data);
815                 dbus_g_proxy_disconnect_signal (account_data->avatars_iface,
816                                                 "AvatarRetrieved",
817                                                 G_CALLBACK (contact_factory_avatar_retrieved_cb),
818                                                 account_data);
819         }
820         if (account_data->presence_iface) {
821                 dbus_g_proxy_disconnect_signal (account_data->presence_iface,
822                                                 "PresenceUpdate",
823                                                 G_CALLBACK (contact_factory_presence_update_cb),
824                                                 account_data);
825         }
826         if (account_data->capabilities_iface) {
827                 dbus_g_proxy_disconnect_signal (account_data->capabilities_iface,
828                                                 "CapabilitiesChanged",
829                                                 G_CALLBACK (contact_factory_capabilities_changed_cb),
830                                                 account_data);
831         }
832         if (account_data->tp_conn) {
833                 g_signal_handlers_disconnect_by_func (account_data->tp_conn,
834                                                       contact_factory_destroy_cb,
835                                                       account_data);
836         }
837 }
838
839 static void
840 contact_factory_account_data_update (ContactFactoryAccountData *account_data)
841 {
842         EmpathyContactFactory     *factory = account_data->factory;
843         EmpathyContactFactoryPriv *priv = GET_PRIV (factory);
844         McAccount                 *account = account_data->account;
845         TpConn                    *tp_conn = NULL;
846         RequestHandlesData        *data;
847         const gchar              **contact_ids;
848         guint                      i;
849         GList                     *l;
850         GError                    *error = NULL;
851
852         if (account_data->account) {
853                 guint status;
854
855                 /* status == 0 means the status is CONNECTED */
856                 status = mission_control_get_connection_status (priv->mc,
857                                                                 account, NULL);
858                 if (status == 0) {
859                         tp_conn = mission_control_get_connection (priv->mc,
860                                                                   account, NULL);
861                 }
862         }
863
864         if (!tp_conn) {
865                 /* We are not connected anymore, remove the old connection */
866                 contact_factory_account_data_disconnect (account_data);
867                 if (account_data->tp_conn) {
868                         contact_factory_destroy_cb (account_data->tp_conn,
869                                                     account_data);
870                 }
871                 return;
872         }
873         else if (account_data->tp_conn) {
874                 /* We were connected and we still are connected, nothing
875                  * changed so nothing to do. */
876                 g_object_unref (tp_conn);
877                 return;
878         }
879
880         /* We got a new connection */
881         account_data->tp_conn = tp_conn;
882         account_data->aliasing_iface = tp_conn_get_interface (tp_conn,
883                                                               TELEPATHY_CONN_IFACE_ALIASING_QUARK);
884         account_data->avatars_iface = tp_conn_get_interface (tp_conn,
885                                                              TELEPATHY_CONN_IFACE_AVATARS_QUARK);
886         account_data->presence_iface = tp_conn_get_interface (tp_conn,
887                                                               TELEPATHY_CONN_IFACE_PRESENCE_QUARK);
888         account_data->capabilities_iface = tp_conn_get_interface (tp_conn,
889                                                                   TELEPATHY_CONN_IFACE_CAPABILITIES_QUARK);
890
891         /* Connect signals */
892         if (account_data->aliasing_iface) {
893                 dbus_g_proxy_connect_signal (account_data->aliasing_iface,
894                                              "AliasesChanged",
895                                              G_CALLBACK (contact_factory_aliases_changed_cb),
896                                              account_data, NULL);
897         }
898         if (account_data->avatars_iface) {
899                 dbus_g_proxy_connect_signal (account_data->avatars_iface,
900                                              "AvatarUpdated",
901                                              G_CALLBACK (contact_factory_avatar_updated_cb),
902                                              account_data, NULL);
903                 dbus_g_proxy_connect_signal (account_data->avatars_iface,
904                                              "AvatarRetrieved",
905                                              G_CALLBACK (contact_factory_avatar_retrieved_cb),
906                                              account_data, NULL);
907         }
908         if (account_data->presence_iface) {
909                 dbus_g_proxy_connect_signal (account_data->presence_iface,
910                                              "PresenceUpdate",
911                                              G_CALLBACK (contact_factory_presence_update_cb),
912                                              account_data, NULL);
913         }
914         if (account_data->capabilities_iface) {
915                 dbus_g_proxy_connect_signal (account_data->capabilities_iface,
916                                              "CapabilitiesChanged",
917                                              G_CALLBACK (contact_factory_capabilities_changed_cb),
918                                              account_data, NULL);
919         }
920         g_signal_connect (tp_conn, "destroy",
921                           G_CALLBACK (contact_factory_destroy_cb),
922                           account_data);
923
924         /* Get our own handle */
925         if (!tp_conn_get_self_handle (DBUS_G_PROXY (account_data->tp_conn),
926                                       &account_data->self_handle,
927                                       &error)) {
928                 empathy_debug (DEBUG_DOMAIN, "GetSelfHandle Error: %s",
929                               error ? error->message : "No error given");
930                 g_clear_error (&error);
931         }
932
933         /* Request new handles for all contacts */
934         if (account_data->contacts) {
935                 data = g_slice_new (RequestHandlesData);
936                 data->account_data = contact_factory_account_data_ref (account_data);
937                 data->contacts = g_list_copy (account_data->contacts);
938                 g_list_foreach (data->contacts, (GFunc) g_object_ref, NULL);
939
940                 i = g_list_length (data->contacts);
941                 contact_ids = g_new0 (const gchar*, i + 1);
942                 i = 0;
943                 for (l = data->contacts; l; l = l->next) {
944                         contact_ids[i] = empathy_contact_get_id (l->data);
945                         i++;
946                 }
947
948                 tp_conn_request_handles_async (DBUS_G_PROXY (account_data->tp_conn),
949                                                TP_HANDLE_TYPE_CONTACT,
950                                                contact_ids,
951                                                contact_factory_request_handles_cb,
952                                                data);
953                 g_free (contact_ids);
954         }
955 }
956
957 static ContactFactoryAccountData *
958 contact_factory_account_data_new (EmpathyContactFactory *factory,
959                                   McAccount             *account)
960 {
961         ContactFactoryAccountData *account_data;
962
963         account_data = g_slice_new0 (ContactFactoryAccountData);
964         account_data->factory = factory;
965         account_data->account = g_object_ref (account);
966         account_data->refcount = 1;
967
968         contact_factory_account_data_update (account_data);
969
970         return account_data;
971 }
972
973 static void
974 contact_factory_status_changed_cb (MissionControl                  *mc,
975                                    TelepathyConnectionStatus        status,
976                                    McPresence                       presence,
977                                    TelepathyConnectionStatusReason  reason,
978                                    const gchar                     *unique_name,
979                                    EmpathyContactFactory           *factory)
980 {
981         EmpathyContactFactoryPriv *priv = GET_PRIV (factory);
982         ContactFactoryAccountData *account_data;
983         McAccount                 *account;
984
985         account = mc_account_lookup (unique_name);
986         account_data = g_hash_table_lookup (priv->accounts, account);
987         if (account_data) {
988                 contact_factory_account_data_update (account_data);
989         }
990         g_object_unref (account);
991 }
992
993 static ContactFactoryAccountData *
994 contact_factory_account_data_get (EmpathyContactFactory *factory,
995                                   McAccount             *account)
996 {
997         EmpathyContactFactoryPriv *priv = GET_PRIV (factory);
998         ContactFactoryAccountData *account_data;
999
1000         account_data = g_hash_table_lookup (priv->accounts, account);
1001         if (!account_data) {
1002                 account_data = contact_factory_account_data_new (factory, account);
1003                 g_hash_table_insert (priv->accounts,
1004                                      g_object_ref (account),
1005                                      account_data);
1006         }
1007
1008         return account_data;
1009 }
1010
1011 static void
1012 contact_factory_account_data_add_contact (ContactFactoryAccountData *account_data,
1013                                           EmpathyContact            *contact)
1014 {
1015         g_object_weak_ref (G_OBJECT (contact),
1016                            contact_factory_weak_notify,
1017                            account_data);
1018         account_data->contacts = g_list_prepend (account_data->contacts, contact);
1019
1020         if (!account_data->presence_iface) {
1021                 EmpathyPresence *presence;
1022
1023                 /* We have no presence iface, set default presence
1024                  * to available */
1025                 presence = empathy_presence_new_full (MC_PRESENCE_AVAILABLE,
1026                                                      NULL);
1027
1028                 empathy_contact_set_presence (contact, presence);
1029                 g_object_unref (presence);
1030         }
1031
1032         empathy_debug (DEBUG_DOMAIN, "Contact added: %s (%d)",
1033                        empathy_contact_get_id (contact),
1034                        empathy_contact_get_handle (contact));
1035 }
1036
1037 static void
1038 contact_factory_hold_handles_cb (DBusGProxy *proxy,
1039                                  GError     *error,
1040                                  gpointer    userdata)
1041 {
1042         if (error) {
1043                 empathy_debug (DEBUG_DOMAIN, "Failed to hold handles: %s",
1044                                error->message);
1045         }
1046 }
1047
1048 EmpathyContact *
1049 empathy_contact_factory_get_user (EmpathyContactFactory *factory,
1050                                   McAccount             *account)
1051 {
1052         ContactFactoryAccountData *account_data;
1053
1054         g_return_val_if_fail (EMPATHY_IS_CONTACT_FACTORY (factory), NULL);
1055         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
1056
1057         account_data = contact_factory_account_data_get (factory, account);
1058
1059         return empathy_contact_factory_get_from_handle (factory, account,
1060                                                         account_data->self_handle);
1061 }
1062
1063 EmpathyContact *
1064 empathy_contact_factory_get_from_id (EmpathyContactFactory *factory,
1065                                      McAccount             *account,
1066                                      const gchar           *id)
1067 {
1068         ContactFactoryAccountData *account_data;
1069         EmpathyContact            *contact;
1070
1071         g_return_val_if_fail (EMPATHY_IS_CONTACT_FACTORY (factory), NULL);
1072         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
1073         g_return_val_if_fail (id != NULL, NULL);
1074
1075         /* Check if the contact already exists */
1076         account_data = contact_factory_account_data_get (factory, account);
1077         contact = contact_factory_account_data_find_by_id (account_data, id);
1078         if (contact) {
1079                 return g_object_ref (contact);
1080         }
1081
1082         /* Create new contact */
1083         contact = g_object_new (EMPATHY_TYPE_CONTACT,
1084                                 "account", account,
1085                                 "id", id,
1086                                 NULL);
1087         contact_factory_account_data_add_contact (account_data, contact);
1088
1089         /* If the account is connected, request contact's handle */
1090         if (account_data->tp_conn) {
1091                 RequestHandlesData *data;
1092                 const gchar        *contact_ids[] = {id, NULL};
1093                 
1094                 data = g_slice_new (RequestHandlesData);
1095                 data->account_data = contact_factory_account_data_ref (account_data);
1096                 data->contacts = g_list_prepend (NULL, g_object_ref (contact));
1097                 tp_conn_request_handles_async (DBUS_G_PROXY (account_data->tp_conn),
1098                                                TP_HANDLE_TYPE_CONTACT,
1099                                                contact_ids,
1100                                                contact_factory_request_handles_cb,
1101                                                data);
1102         }
1103
1104         return contact;
1105 }
1106
1107 EmpathyContact *
1108 empathy_contact_factory_get_from_handle (EmpathyContactFactory *factory,
1109                                          McAccount             *account,
1110                                          guint                  handle)
1111 {
1112         EmpathyContact *contact;
1113         GArray         *handles;
1114         GList          *contacts;
1115
1116         g_return_val_if_fail (EMPATHY_IS_CONTACT_FACTORY (factory), NULL);
1117         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
1118
1119         handles = g_array_new (FALSE, FALSE, sizeof (guint));
1120         g_array_append_val (handles, handle);
1121
1122         contacts = empathy_contact_factory_get_from_handles (factory, account, handles);
1123         g_array_free (handles, TRUE);
1124
1125         contact = contacts ? contacts->data : NULL;
1126         g_list_free (contacts);
1127
1128         return contact;
1129 }
1130
1131 GList *
1132 empathy_contact_factory_get_from_handles (EmpathyContactFactory *factory,
1133                                           McAccount             *account,
1134                                           GArray                *handles)
1135 {
1136         ContactFactoryAccountData *account_data;
1137         GList                     *contacts = NULL;
1138         GArray                    *new_handles;
1139         gchar                    **handles_names;
1140         guint                      i;
1141         GError                    *error = NULL;
1142
1143         g_return_val_if_fail (EMPATHY_IS_CONTACT_FACTORY (factory), NULL);
1144         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
1145         g_return_val_if_fail (handles != NULL, NULL);
1146
1147         /* Search all contacts we already have */
1148         account_data = contact_factory_account_data_get (factory, account);
1149         new_handles = g_array_new (FALSE, FALSE, sizeof (guint));
1150         for (i = 0; i < handles->len; i++) {
1151                 EmpathyContact *contact;
1152                 guint           handle;
1153
1154                 handle = g_array_index (handles, guint, i);
1155                 if (handle == 0) {
1156                         continue;
1157                 }
1158
1159                 contact = contact_factory_account_data_find_by_handle (account_data, handle);
1160                 if (contact) {
1161                         contacts = g_list_prepend (contacts, g_object_ref (contact));
1162                 } else {
1163                         g_array_append_val (new_handles, handle);
1164                 }
1165         }
1166
1167         if (new_handles->len == 0) {
1168                 g_array_free (new_handles, TRUE);
1169                 return contacts;
1170         }
1171
1172         /* Get the IDs of all new handles */
1173         if (!tp_conn_inspect_handles (DBUS_G_PROXY (account_data->tp_conn),
1174                                       TP_HANDLE_TYPE_CONTACT,
1175                                       new_handles,
1176                                       &handles_names,
1177                                       &error)) {
1178                 empathy_debug (DEBUG_DOMAIN, 
1179                               "Couldn't inspect contact: %s",
1180                               error ? error->message : "No error given");
1181                 g_clear_error (&error);
1182                 g_array_free (new_handles, TRUE);
1183                 return contacts;
1184         }
1185
1186         /* Create new contacts */
1187         for (i = 0; i < new_handles->len; i++) {
1188                 EmpathyContact *contact;
1189                 gchar          *id;
1190                 guint           handle;
1191                 gboolean        is_user;
1192
1193                 id = handles_names[i];
1194                 handle = g_array_index (new_handles, guint, i);
1195
1196                 is_user = (handle == account_data->self_handle);
1197                 contact = g_object_new (EMPATHY_TYPE_CONTACT,
1198                                         "account", account,
1199                                         "handle", handle,
1200                                         "id", id,
1201                                         "is-user", is_user,
1202                                         NULL);
1203                 contact_factory_account_data_add_contact (account_data,
1204                                                           contact);
1205                 contacts = g_list_prepend (contacts, contact);
1206                 g_free (id);
1207         }
1208         g_free (handles_names);
1209
1210         /* Hold all new handles. */
1211         tp_conn_hold_handles_async (DBUS_G_PROXY (account_data->tp_conn),
1212                                     TP_HANDLE_TYPE_CONTACT,
1213                                     new_handles,
1214                                     contact_factory_hold_handles_cb,
1215                                     NULL);
1216
1217         contact_factory_request_everything (account_data, new_handles);
1218
1219         g_array_free (new_handles, TRUE);
1220
1221         return contacts;
1222 }
1223
1224 void
1225 empathy_contact_factory_set_alias (EmpathyContactFactory *factory,
1226                                    EmpathyContact        *contact,
1227                                    const gchar           *alias)
1228 {
1229         ContactFactoryAccountData *account_data;
1230         McAccount                 *account;
1231         GHashTable                *new_alias;
1232         guint                      handle;
1233
1234         g_return_if_fail (EMPATHY_IS_CONTACT_FACTORY (factory));
1235         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1236
1237         account = empathy_contact_get_account (contact);
1238         account_data = contact_factory_account_data_get (factory, account);
1239
1240         if (!account_data->aliasing_iface) {
1241                 return;
1242         }
1243
1244         handle = empathy_contact_get_handle (contact);
1245
1246         empathy_debug (DEBUG_DOMAIN, "Setting alias for contact %s (%d) to %s",
1247                        empathy_contact_get_id (contact),
1248                        handle, alias);
1249
1250         new_alias = g_hash_table_new_full (g_direct_hash,
1251                                            g_direct_equal,
1252                                            NULL,
1253                                            g_free);
1254
1255         g_hash_table_insert (new_alias,
1256                              GUINT_TO_POINTER (handle),
1257                              g_strdup (alias));
1258
1259         tp_conn_iface_aliasing_set_aliases_async (account_data->aliasing_iface,
1260                                                   new_alias,
1261                                                   contact_factory_set_aliases_cb,
1262                                                   contact_factory_account_data_ref (account_data));
1263
1264         g_hash_table_destroy (new_alias);
1265 }
1266
1267 void
1268 empathy_contact_factory_set_avatar (EmpathyContactFactory *factory,
1269                                     McAccount             *account,
1270                                     const gchar           *data,
1271                                     gsize                  size,
1272                                     const gchar           *mime_type)
1273 {
1274         ContactFactoryAccountData *account_data;
1275
1276         g_return_if_fail (EMPATHY_IS_CONTACT_FACTORY (factory));
1277         g_return_if_fail (MC_IS_ACCOUNT (account));
1278
1279         account_data = contact_factory_account_data_get (factory, account);
1280
1281         if (!account_data->avatars_iface) {
1282                 return;
1283         }
1284
1285         if (data && size > 0 && size < G_MAXUINT) {
1286                 GArray avatar;
1287
1288                 avatar.data = (gchar*) data;
1289                 avatar.len = size;
1290
1291                 empathy_debug (DEBUG_DOMAIN, "Setting avatar on account %s",
1292                                mc_account_get_unique_name (account));
1293
1294                 tp_conn_iface_avatars_set_avatar_async (account_data->avatars_iface,
1295                                                         &avatar,
1296                                                         mime_type,
1297                                                         contact_factory_set_avatar_cb,
1298                                                         contact_factory_account_data_ref (account_data));
1299         } else {
1300                 empathy_debug (DEBUG_DOMAIN, "Clearing avatar on account %s",
1301                                mc_account_get_unique_name (account));
1302                 tp_conn_iface_avatars_clear_avatar_async (account_data->avatars_iface,
1303                                                           contact_factory_clear_avatar_cb,
1304                                                           contact_factory_account_data_ref (account_data));
1305         }
1306 }
1307
1308 static void
1309 contact_factory_finalize (GObject *object)
1310 {
1311         EmpathyContactFactoryPriv *priv;
1312
1313         priv = GET_PRIV (object);
1314
1315         dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
1316                                         "AccountStatusChanged",
1317                                         G_CALLBACK (contact_factory_status_changed_cb),
1318                                         object);
1319
1320         g_hash_table_destroy (priv->accounts);
1321         g_object_unref (priv->mc);
1322
1323         G_OBJECT_CLASS (empathy_contact_factory_parent_class)->finalize (object);
1324 }
1325
1326 static void
1327 empathy_contact_factory_class_init (EmpathyContactFactoryClass *klass)
1328 {
1329         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1330
1331         object_class->finalize = contact_factory_finalize;
1332
1333         g_type_class_add_private (object_class, sizeof (EmpathyContactFactoryPriv));
1334 }
1335
1336 static void
1337 empathy_contact_factory_init (EmpathyContactFactory *factory)
1338 {
1339         EmpathyContactFactoryPriv *priv;
1340
1341         priv = GET_PRIV (factory);
1342
1343         priv->mc = empathy_mission_control_new ();
1344         priv->accounts = g_hash_table_new_full (empathy_account_hash,
1345                                                 empathy_account_equal,
1346                                                 g_object_unref,
1347                                                 (GDestroyNotify) contact_factory_account_data_unref);
1348
1349         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc),
1350                                      "AccountStatusChanged",
1351                                      G_CALLBACK (contact_factory_status_changed_cb),
1352                                      factory, NULL);
1353 }
1354
1355 EmpathyContactFactory *
1356 empathy_contact_factory_new (void)
1357 {
1358         static EmpathyContactFactory *factory = NULL;
1359
1360         if (!factory) {
1361                 factory = g_object_new (EMPATHY_TYPE_CONTACT_FACTORY, NULL);
1362                 g_object_add_weak_pointer (G_OBJECT (factory), (gpointer) &factory);
1363         } else {
1364                 g_object_ref (factory);
1365         }
1366
1367         return factory;
1368 }
1369