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