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