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