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