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