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