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