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