]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-factory.c
No need of a contact factory to set location
[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-2009 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/gtypes.h>
28 #include <telepathy-glib/dbus.h>
29 #include <telepathy-glib/interfaces.h>
30
31 #include <extensions/extensions.h>
32
33 #include "empathy-tp-contact-factory.h"
34 #include "empathy-utils.h"
35
36 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT
37 #include "empathy-debug.h"
38
39 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactFactory)
40 typedef struct {
41         TpConnection   *connection;
42         GList          *contacts;
43
44         gchar         **avatar_mime_types;
45         guint           avatar_min_width;
46         guint           avatar_min_height;
47         guint           avatar_max_width;
48         guint           avatar_max_height;
49         guint           avatar_max_size;
50 } EmpathyTpContactFactoryPriv;
51
52 G_DEFINE_TYPE (EmpathyTpContactFactory, empathy_tp_contact_factory, G_TYPE_OBJECT);
53
54 enum {
55         PROP_0,
56         PROP_CONNECTION,
57
58         PROP_MIME_TYPES,
59         PROP_MIN_WIDTH,
60         PROP_MIN_HEIGHT,
61         PROP_MAX_WIDTH,
62         PROP_MAX_HEIGHT,
63         PROP_MAX_SIZE
64 };
65
66 static TpContactFeature contact_features[] = {
67         TP_CONTACT_FEATURE_ALIAS,
68         TP_CONTACT_FEATURE_PRESENCE,
69         TP_CONTACT_FEATURE_LOCATION,
70         TP_CONTACT_FEATURE_CAPABILITIES,
71 };
72
73 static EmpathyContact *
74 tp_contact_factory_find_by_handle (EmpathyTpContactFactory *tp_factory,
75                                    guint                    handle)
76 {
77         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
78         GList                       *l;
79
80         for (l = priv->contacts; l; l = l->next) {
81                 if (empathy_contact_get_handle (l->data) == handle) {
82                         return l->data;
83                 }
84         }
85
86         return NULL;
87 }
88
89 static EmpathyContact *
90 tp_contact_factory_find_by_tp_contact (EmpathyTpContactFactory *tp_factory,
91                                        TpContact               *tp_contact)
92 {
93         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
94         GList                       *l;
95
96         for (l = priv->contacts; l; l = l->next) {
97                 if (empathy_contact_get_tp_contact (l->data) == tp_contact) {
98                         return l->data;
99                 }
100         }
101
102         return NULL;
103 }
104
105 static void
106 tp_contact_factory_weak_notify (gpointer data,
107                                 GObject *where_the_object_was)
108 {
109         EmpathyTpContactFactoryPriv *priv = GET_PRIV (data);
110
111         DEBUG ("Remove finalized contact %p", where_the_object_was);
112
113         priv->contacts = g_list_remove (priv->contacts, where_the_object_was);
114 }
115
116 static void
117 tp_contact_factory_set_aliases_cb (TpConnection *connection,
118                                    const GError *error,
119                                    gpointer      user_data,
120                                    GObject      *tp_factory)
121 {
122         if (error) {
123                 DEBUG ("Error: %s", error->message);
124         }
125 }
126
127 static void
128 tp_contact_factory_avatar_retrieved_cb (TpConnection *connection,
129                                         guint         handle,
130                                         const gchar  *token,
131                                         const GArray *avatar_data,
132                                         const gchar  *mime_type,
133                                         gpointer      user_data,
134                                         GObject      *tp_factory)
135 {
136         EmpathyContact *contact;
137
138         contact = tp_contact_factory_find_by_handle (EMPATHY_TP_CONTACT_FACTORY (tp_factory),
139                                                      handle);
140         if (!contact) {
141                 return;
142         }
143
144         DEBUG ("Avatar retrieved for contact %s (%d)",
145                 empathy_contact_get_id (contact),
146                 handle);
147
148         empathy_contact_load_avatar_data (contact,
149                                           (guchar *) avatar_data->data,
150                                           avatar_data->len,
151                                           mime_type,
152                                           token);
153 }
154
155 static void
156 tp_contact_factory_request_avatars_cb (TpConnection *connection,
157                                        const GError *error,
158                                        gpointer      user_data,
159                                        GObject      *tp_factory)
160 {
161         if (error) {
162                 DEBUG ("Error: %s", error->message);
163         }
164 }
165
166 static gboolean
167 tp_contact_factory_avatar_maybe_update (EmpathyTpContactFactory *tp_factory,
168                                         guint                    handle,
169                                         const gchar             *token)
170 {
171         EmpathyContact *contact;
172         EmpathyAvatar  *avatar;
173
174         contact = tp_contact_factory_find_by_handle (tp_factory, handle);
175         if (!contact) {
176                 return TRUE;
177         }
178
179         /* Check if we have an avatar */
180         if (EMP_STR_EMPTY (token)) {
181                 empathy_contact_set_avatar (contact, NULL);
182                 return TRUE;
183         }
184
185         /* Check if the avatar changed */
186         avatar = empathy_contact_get_avatar (contact);
187         if (avatar && !tp_strdiff (avatar->token, token)) {
188                 return TRUE;
189         }
190
191         /* The avatar changed, search the new one in the cache */
192         if (empathy_contact_load_avatar_cache (contact, token)) {
193                 /* Got from cache, use it */
194                 return TRUE;
195         }
196
197         /* Avatar is not up-to-date, we have to request it. */
198         return FALSE;
199 }
200
201 static void
202 tp_contact_factory_got_known_avatar_tokens (TpConnection *connection,
203                                             GHashTable   *tokens,
204                                             const GError *error,
205                                             gpointer      user_data,
206                                             GObject      *weak_object)
207 {
208         EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);
209         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
210         GArray *handles;
211         GHashTableIter iter;
212         gpointer key, value;
213
214         if (error) {
215                 DEBUG ("Error: %s", error->message);
216                 return;
217         }
218
219         handles = g_array_new (FALSE, FALSE, sizeof (guint));
220
221         g_hash_table_iter_init (&iter, tokens);
222         while (g_hash_table_iter_next (&iter, &key, &value)) {
223                 guint handle = GPOINTER_TO_UINT (key);
224                 const gchar *token = value;
225
226                 if (!tp_contact_factory_avatar_maybe_update (tp_factory,
227                                                              handle, token)) {
228                         g_array_append_val (handles, handle);
229                 }
230         }
231
232         DEBUG ("Got %d tokens, need to request %d avatars",
233                 g_hash_table_size (tokens), handles->len);
234
235         /* Request needed avatars */
236         if (handles->len > 0) {
237                 tp_cli_connection_interface_avatars_call_request_avatars (priv->connection,
238                                                                           -1,
239                                                                           handles,
240                                                                           tp_contact_factory_request_avatars_cb,
241                                                                           NULL, NULL,
242                                                                           G_OBJECT (tp_factory));
243         }
244
245         g_array_free (handles, TRUE);
246 }
247
248 static void
249 tp_contact_factory_avatar_updated_cb (TpConnection *connection,
250                                       guint         handle,
251                                       const gchar  *new_token,
252                                       gpointer      user_data,
253                                       GObject      *tp_factory)
254 {
255         GArray *handles;
256
257         if (tp_contact_factory_avatar_maybe_update (EMPATHY_TP_CONTACT_FACTORY (tp_factory),
258                                                     handle, new_token)) {
259                 /* Avatar was cached, nothing to do */
260                 return;
261         }
262
263         DEBUG ("Need to request avatar for token %s", new_token);
264
265         handles = g_array_new (FALSE, FALSE, sizeof (guint));
266         g_array_append_val (handles, handle);
267
268         tp_cli_connection_interface_avatars_call_request_avatars (connection,
269                                                                   -1,
270                                                                   handles,
271                                                                   tp_contact_factory_request_avatars_cb,
272                                                                   NULL, NULL,
273                                                                   tp_factory);
274         g_array_free (handles, TRUE);
275 }
276
277 static void
278 tp_contact_factory_got_avatar_requirements_cb (TpConnection *proxy,
279                                                const gchar **mime_types,
280                                                guint         min_width,
281                                                guint         min_height,
282                                                guint         max_width,
283                                                guint         max_height,
284                                                guint         max_size,
285                                                const GError *error,
286                                                gpointer      user_data,
287                                                GObject      *tp_factory)
288 {
289         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
290
291         if (error) {
292                 DEBUG ("Failed to get avatar requirements: %s", error->message);
293                 /* We'll just leave avatar_mime_types as NULL; the
294                  * avatar-setting code can use this as a signal that you can't
295                  * set avatars.
296                  */
297         } else {
298                 priv->avatar_mime_types = g_strdupv ((gchar **) mime_types);
299                 priv->avatar_min_width = min_width;
300                 priv->avatar_min_height = min_height;
301                 priv->avatar_max_width = max_width;
302                 priv->avatar_max_height = max_height;
303                 priv->avatar_max_size = max_size;
304         }
305 }
306
307 static void
308 tp_contact_factory_add_contact (EmpathyTpContactFactory *tp_factory,
309                                 EmpathyContact          *contact)
310 {
311         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
312         TpHandle self_handle;
313         TpHandle handle;
314         GArray handles = {(gchar *) &handle, 1};
315
316         /* Keep a weak ref to that contact */
317         g_object_weak_ref (G_OBJECT (contact),
318                            tp_contact_factory_weak_notify,
319                            tp_factory);
320         priv->contacts = g_list_prepend (priv->contacts, contact);
321
322         /* The contact keeps a ref to its factory */
323         g_object_set_data_full (G_OBJECT (contact), "empathy-factory",
324                                 g_object_ref (tp_factory),
325                                 g_object_unref);
326
327         /* Set is-user property. Note that it could still be the handle is
328          * different from the connection's self handle, in the case the handle
329          * comes from a group interface. */
330         self_handle = tp_connection_get_self_handle (priv->connection);
331         handle = empathy_contact_get_handle (contact);
332         empathy_contact_set_is_user (contact, self_handle == handle);
333
334         /* FIXME: This should be done by TpContact */
335         if (tp_proxy_has_interface_by_id (priv->connection,
336                         TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS)) {
337                 tp_cli_connection_interface_avatars_call_get_known_avatar_tokens (
338                         priv->connection, -1, &handles,
339                         tp_contact_factory_got_known_avatar_tokens, NULL, NULL,
340                         G_OBJECT (tp_factory));
341         }
342
343         DEBUG ("Contact added: %s (%d)",
344                 empathy_contact_get_id (contact),
345                 empathy_contact_get_handle (contact));
346 }
347
348 typedef union {
349         EmpathyTpContactFactoryContactsByIdCb ids_cb;
350         EmpathyTpContactFactoryContactsByHandleCb handles_cb;
351         EmpathyTpContactFactoryContactCb contact_cb;
352 } GetContactsCb;
353
354 typedef struct {
355         EmpathyTpContactFactory *tp_factory;
356         GetContactsCb callback;
357         gpointer user_data;
358         GDestroyNotify destroy;
359 } GetContactsData;
360
361 static void
362 get_contacts_data_free (gpointer user_data)
363 {
364         GetContactsData *data = user_data;
365
366         if (data->destroy) {
367                 data->destroy (data->user_data);
368         }
369         g_object_unref (data->tp_factory);
370
371         g_slice_free (GetContactsData, data);
372 }
373
374 static EmpathyContact *
375 dup_contact_for_tp_contact (EmpathyTpContactFactory *tp_factory,
376                             TpContact               *tp_contact)
377 {
378         EmpathyContact *contact;
379
380         contact = tp_contact_factory_find_by_tp_contact (tp_factory,
381                                                          tp_contact);
382
383         if (contact != NULL) {
384                 g_object_ref (contact);
385         } else {
386                 contact = empathy_contact_new (tp_contact);
387                 tp_contact_factory_add_contact (tp_factory, contact);
388         }
389
390         return contact;
391 }
392
393 static EmpathyContact **
394 contacts_array_new (EmpathyTpContactFactory *tp_factory,
395                     guint                    n_contacts,
396                     TpContact * const *      contacts)
397 {
398         EmpathyContact **ret;
399         guint            i;
400
401         ret = g_new0 (EmpathyContact *, n_contacts);
402         for (i = 0; i < n_contacts; i++) {
403                 ret[i] = dup_contact_for_tp_contact (tp_factory, contacts[i]);
404         }
405
406         return ret;
407 }
408
409 static void
410 contacts_array_free (guint            n_contacts,
411                      EmpathyContact **contacts)
412 {
413         guint i;
414
415         for (i = 0; i < n_contacts; i++) {
416                 g_object_unref (contacts[i]);
417         }
418         g_free (contacts);
419 }
420
421 static void
422 get_contacts_by_id_cb (TpConnection *connection,
423                        guint n_contacts,
424                        TpContact * const *contacts,
425                        const gchar * const *requested_ids,
426                        GHashTable *failed_id_errors,
427                        const GError *error,
428                        gpointer user_data,
429                        GObject *weak_object)
430 {
431         GetContactsData *data = user_data;
432         EmpathyContact **empathy_contacts;
433
434         empathy_contacts = contacts_array_new (data->tp_factory,
435                                                n_contacts, contacts);
436         if (data->callback.ids_cb) {
437                 data->callback.ids_cb (data->tp_factory,
438                                        n_contacts, empathy_contacts,
439                                        requested_ids,
440                                        failed_id_errors,
441                                        error,
442                                        data->user_data, weak_object);
443         }
444
445         contacts_array_free (n_contacts, empathy_contacts);
446 }
447
448 /* The callback is NOT given a reference to the EmpathyContact objects */
449 void
450 empathy_tp_contact_factory_get_from_ids (EmpathyTpContactFactory *tp_factory,
451                                          guint                    n_ids,
452                                          const gchar * const     *ids,
453                                          EmpathyTpContactFactoryContactsByIdCb callback,
454                                          gpointer                 user_data,
455                                          GDestroyNotify           destroy,
456                                          GObject                 *weak_object)
457 {
458         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
459         GetContactsData *data;
460
461         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
462         g_return_if_fail (ids != NULL);
463
464         data = g_slice_new (GetContactsData);
465         data->callback.ids_cb = callback;
466         data->user_data = user_data;
467         data->destroy = destroy;
468         data->tp_factory = g_object_ref (tp_factory);
469         tp_connection_get_contacts_by_id (priv->connection,
470                                           n_ids, ids,
471                                           G_N_ELEMENTS (contact_features),
472                                           contact_features,
473                                           get_contacts_by_id_cb,
474                                           data,
475                                           (GDestroyNotify) get_contacts_data_free,
476                                           weak_object);
477 }
478
479 static void
480 get_contact_by_id_cb (TpConnection *connection,
481                       guint n_contacts,
482                       TpContact * const *contacts,
483                       const gchar * const *requested_ids,
484                       GHashTable *failed_id_errors,
485                       const GError *error,
486                       gpointer user_data,
487                       GObject *weak_object)
488 {
489         GetContactsData *data = user_data;
490         EmpathyContact  *contact = NULL;
491
492         if (n_contacts == 1) {
493                 contact = dup_contact_for_tp_contact (data->tp_factory,
494                                                       contacts[0]);
495         }
496         else if (error == NULL) {
497                 GHashTableIter iter;
498                 gpointer       value;
499
500                 g_hash_table_iter_init (&iter, failed_id_errors);
501                 while (g_hash_table_iter_next (&iter, NULL, &value)) {
502                         if (value) {
503                                 error = value;
504                                 break;
505                         }
506                 }
507         }
508
509         if (data->callback.contact_cb) {
510                 data->callback.contact_cb (data->tp_factory,
511                                            contact,
512                                            error,
513                                            data->user_data, weak_object);
514         }
515
516         if (contact != NULL)
517                 g_object_unref (contact);
518 }
519
520 /* The callback is NOT given a reference to the EmpathyContact objects */
521 void
522 empathy_tp_contact_factory_get_from_id (EmpathyTpContactFactory *tp_factory,
523                                         const gchar             *id,
524                                         EmpathyTpContactFactoryContactCb callback,
525                                         gpointer                 user_data,
526                                         GDestroyNotify           destroy,
527                                         GObject                 *weak_object)
528 {
529         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
530         GetContactsData *data;
531
532         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
533         g_return_if_fail (id != NULL);
534
535         data = g_slice_new (GetContactsData);
536         data->callback.contact_cb = callback;
537         data->user_data = user_data;
538         data->destroy = destroy;
539         data->tp_factory = g_object_ref (tp_factory);
540         tp_connection_get_contacts_by_id (priv->connection,
541                                           1, &id,
542                                           G_N_ELEMENTS (contact_features),
543                                           contact_features,
544                                           get_contact_by_id_cb,
545                                           data,
546                                           (GDestroyNotify) get_contacts_data_free,
547                                           weak_object);
548 }
549
550 static void
551 get_contacts_by_handle_cb (TpConnection *connection,
552                            guint n_contacts,
553                            TpContact * const *contacts,
554                            guint n_failed,
555                            const TpHandle *failed,
556                            const GError *error,
557                            gpointer user_data,
558                            GObject *weak_object)
559 {
560         GetContactsData *data = user_data;
561         EmpathyContact **empathy_contacts;
562
563         empathy_contacts = contacts_array_new (data->tp_factory,
564                                                n_contacts, contacts);
565         if (data->callback.handles_cb) {
566                 data->callback.handles_cb (data->tp_factory,
567                                            n_contacts, empathy_contacts,
568                                            n_failed, failed,
569                                            error,
570                                            data->user_data, weak_object);
571         }
572
573         contacts_array_free (n_contacts, empathy_contacts);
574 }
575
576 /* The callback is NOT given a reference to the EmpathyContact objects */
577 void
578 empathy_tp_contact_factory_get_from_handles (EmpathyTpContactFactory *tp_factory,
579                                              guint n_handles,
580                                              const TpHandle *handles,
581                                              EmpathyTpContactFactoryContactsByHandleCb callback,
582                                              gpointer                 user_data,
583                                              GDestroyNotify           destroy,
584                                              GObject                 *weak_object)
585 {
586         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
587         GetContactsData *data;
588
589         if (n_handles == 0) {
590                 callback (tp_factory, 0, NULL, 0, NULL, NULL, user_data, weak_object);
591                 return;
592         }
593
594         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
595         g_return_if_fail (handles != NULL);
596
597         data = g_slice_new (GetContactsData);
598         data->callback.handles_cb = callback;
599         data->user_data = user_data;
600         data->destroy = destroy;
601         data->tp_factory = g_object_ref (tp_factory);
602         tp_connection_get_contacts_by_handle (priv->connection,
603                                               n_handles, handles,
604                                               G_N_ELEMENTS (contact_features),
605                                               contact_features,
606                                               get_contacts_by_handle_cb,
607                                               data,
608                                               (GDestroyNotify) get_contacts_data_free,
609                                               weak_object);
610 }
611
612 /* The callback is NOT given a reference to the EmpathyContact objects */
613 static void
614 get_contact_by_handle_cb (TpConnection *connection,
615                           guint n_contacts,
616                           TpContact * const *contacts,
617                           guint n_failed,
618                           const TpHandle *failed,
619                           const GError *error,
620                           gpointer user_data,
621                           GObject *weak_object)
622 {
623         GetContactsData *data = user_data;
624         EmpathyContact  *contact = NULL;
625         GError *err = NULL;
626
627         if (n_contacts == 1) {
628                 contact = dup_contact_for_tp_contact (data->tp_factory,
629                                                       contacts[0]);
630         }
631         else {
632                 if (error == NULL) {
633                         /* tp-glib will provide an error only if the whole operation failed,
634                          * but not if, for example, the handle was invalid. We create an error
635                          * so the caller of empathy_tp_contact_factory_get_from_handle can
636                          * rely on the error to check if the operation succeeded or not. */
637
638                         err = g_error_new_literal (TP_ERRORS, TP_ERROR_INVALID_HANDLE,
639                                                       "handle is invalid");
640                 }
641                 else {
642                         err = g_error_copy (error);
643                 }
644         }
645
646         if (data->callback.contact_cb) {
647                 data->callback.contact_cb (data->tp_factory,
648                                            contact,
649                                            err,
650                                            data->user_data, weak_object);
651         }
652
653         g_clear_error (&err);
654         if (contact != NULL)
655                 g_object_unref (contact);
656 }
657
658 void
659 empathy_tp_contact_factory_get_from_handle (EmpathyTpContactFactory *tp_factory,
660                                             TpHandle                 handle,
661                                             EmpathyTpContactFactoryContactCb callback,
662                                             gpointer                 user_data,
663                                             GDestroyNotify           destroy,
664                                             GObject                 *weak_object)
665 {
666         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
667         GetContactsData *data;
668
669         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
670
671         data = g_slice_new (GetContactsData);
672         data->callback.contact_cb = callback;
673         data->user_data = user_data;
674         data->destroy = destroy;
675         data->tp_factory = g_object_ref (tp_factory);
676         tp_connection_get_contacts_by_handle (priv->connection,
677                                               1, &handle,
678                                               G_N_ELEMENTS (contact_features),
679                                               contact_features,
680                                               get_contact_by_handle_cb,
681                                               data,
682                                               (GDestroyNotify) get_contacts_data_free,
683                                               weak_object);
684 }
685
686 void
687 empathy_tp_contact_factory_set_alias (EmpathyTpContactFactory *tp_factory,
688                                       EmpathyContact          *contact,
689                                       const gchar             *alias)
690 {
691         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
692         GHashTable                  *new_alias;
693         guint                        handle;
694
695         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
696         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
697
698         handle = empathy_contact_get_handle (contact);
699
700         DEBUG ("Setting alias for contact %s (%d) to %s",
701                 empathy_contact_get_id (contact),
702                 handle, alias);
703
704         new_alias = g_hash_table_new_full (g_direct_hash,
705                                            g_direct_equal,
706                                            NULL,
707                                            g_free);
708
709         g_hash_table_insert (new_alias,
710                              GUINT_TO_POINTER (handle),
711                              g_strdup (alias));
712
713         tp_cli_connection_interface_aliasing_call_set_aliases (priv->connection,
714                                                                -1,
715                                                                new_alias,
716                                                                tp_contact_factory_set_aliases_cb,
717                                                                NULL, NULL,
718                                                                G_OBJECT (tp_factory));
719
720         g_hash_table_destroy (new_alias);
721 }
722
723 static void
724 tp_contact_factory_get_property (GObject    *object,
725                                  guint       param_id,
726                                  GValue     *value,
727                                  GParamSpec *pspec)
728 {
729         EmpathyTpContactFactoryPriv *priv = GET_PRIV (object);
730
731         switch (param_id) {
732         case PROP_CONNECTION:
733                 g_value_set_object (value, priv->connection);
734                 break;
735         case PROP_MIME_TYPES:
736                 g_value_set_boxed (value, priv->avatar_mime_types);
737                 break;
738         case PROP_MIN_WIDTH:
739                 g_value_set_uint (value, priv->avatar_min_width);
740                 break;
741         case PROP_MIN_HEIGHT:
742                 g_value_set_uint (value, priv->avatar_min_height);
743                 break;
744         case PROP_MAX_WIDTH:
745                 g_value_set_uint (value, priv->avatar_max_width);
746                 break;
747         case PROP_MAX_HEIGHT:
748                 g_value_set_uint (value, priv->avatar_max_height);
749                 break;
750         case PROP_MAX_SIZE:
751                 g_value_set_uint (value, priv->avatar_max_size);
752                 break;
753         default:
754                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
755                 break;
756         };
757 }
758
759 static void
760 tp_contact_factory_set_property (GObject      *object,
761                                  guint         param_id,
762                                  const GValue *value,
763                                  GParamSpec   *pspec)
764 {
765         EmpathyTpContactFactoryPriv *priv = GET_PRIV (object);
766
767         switch (param_id) {
768         case PROP_CONNECTION:
769                 priv->connection = g_value_dup_object (value);
770                 break;
771         default:
772                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
773                 break;
774         };
775 }
776
777 static void
778 tp_contact_factory_finalize (GObject *object)
779 {
780         EmpathyTpContactFactoryPriv *priv = GET_PRIV (object);
781         GList                       *l;
782
783         DEBUG ("Finalized: %p", object);
784
785         for (l = priv->contacts; l; l = l->next) {
786                 g_object_weak_unref (G_OBJECT (l->data),
787                                      tp_contact_factory_weak_notify,
788                                      object);
789         }
790
791         g_list_free (priv->contacts);
792
793         g_object_unref (priv->connection);
794
795         g_strfreev (priv->avatar_mime_types);
796
797         G_OBJECT_CLASS (empathy_tp_contact_factory_parent_class)->finalize (object);
798 }
799
800 static void
801 connection_ready_cb (TpConnection *connection,
802                                 const GError *error,
803                                 gpointer user_data)
804 {
805         EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (user_data);
806         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
807
808         if (error != NULL)
809                 goto out;
810
811         /* FIXME: This should be moved to TpContact */
812         tp_cli_connection_interface_avatars_connect_to_avatar_updated (priv->connection,
813                                                                        tp_contact_factory_avatar_updated_cb,
814                                                                        NULL, NULL,
815                                                                        G_OBJECT (tp_factory),
816                                                                        NULL);
817         tp_cli_connection_interface_avatars_connect_to_avatar_retrieved (priv->connection,
818                                                                          tp_contact_factory_avatar_retrieved_cb,
819                                                                          NULL, NULL,
820                                                                          G_OBJECT (tp_factory),
821                                                                          NULL);
822
823         tp_cli_connection_interface_avatars_call_get_avatar_requirements (priv->connection,
824                                                                           -1,
825                                                                           tp_contact_factory_got_avatar_requirements_cb,
826                                                                           NULL, NULL,
827                                                                           G_OBJECT (tp_factory));
828
829 out:
830         g_object_unref (tp_factory);
831 }
832
833 static GObject *
834 tp_contact_factory_constructor (GType                  type,
835                                 guint                  n_props,
836                                 GObjectConstructParam *props)
837 {
838         GObject *tp_factory;
839         EmpathyTpContactFactoryPriv *priv;
840
841         tp_factory = G_OBJECT_CLASS (empathy_tp_contact_factory_parent_class)->constructor (type, n_props, props);
842         priv = GET_PRIV (tp_factory);
843
844         /* Ensure to keep the self object alive while the call_when_ready is
845          * running */
846         g_object_ref (tp_factory);
847         tp_connection_call_when_ready (priv->connection, connection_ready_cb,
848                 tp_factory);
849
850         return tp_factory;
851 }
852
853 static void
854 empathy_tp_contact_factory_class_init (EmpathyTpContactFactoryClass *klass)
855 {
856         GObjectClass *object_class = G_OBJECT_CLASS (klass);
857
858         object_class->finalize = tp_contact_factory_finalize;
859         object_class->constructor = tp_contact_factory_constructor;
860         object_class->get_property = tp_contact_factory_get_property;
861         object_class->set_property = tp_contact_factory_set_property;
862
863         g_object_class_install_property (object_class,
864                                          PROP_CONNECTION,
865                                          g_param_spec_object ("connection",
866                                                               "Factory's Connection",
867                                                               "The connection associated with the factory",
868                                                               TP_TYPE_CONNECTION,
869                                                               G_PARAM_READWRITE |
870                                                               G_PARAM_CONSTRUCT_ONLY |
871                                                               G_PARAM_STATIC_STRINGS));
872         g_object_class_install_property (object_class,
873                                          PROP_MIME_TYPES,
874                                          g_param_spec_boxed ("avatar-mime-types",
875                                                              "Supported MIME types for avatars",
876                                                              "Types of images that may be set as "
877                                                              "avatars on this connection.",
878                                                              G_TYPE_STRV,
879                                                              G_PARAM_READABLE |
880                                                              G_PARAM_STATIC_STRINGS));
881         g_object_class_install_property (object_class,
882                                          PROP_MIN_WIDTH,
883                                          g_param_spec_uint ("avatar-min-width",
884                                                             "Minimum width for avatars",
885                                                             "Minimum width of avatar that may be set.",
886                                                             0,
887                                                             G_MAXUINT,
888                                                             0,
889                                                             G_PARAM_READABLE |
890                                                             G_PARAM_STATIC_STRINGS));
891         g_object_class_install_property (object_class,
892                                          PROP_MIN_HEIGHT,
893                                          g_param_spec_uint ("avatar-min-height",
894                                                             "Minimum height for avatars",
895                                                             "Minimum height of avatar that may be set.",
896                                                             0,
897                                                             G_MAXUINT,
898                                                             0,
899                                                             G_PARAM_READABLE |
900                                                             G_PARAM_STATIC_STRINGS));
901         g_object_class_install_property (object_class,
902                                          PROP_MAX_WIDTH,
903                                          g_param_spec_uint ("avatar-max-width",
904                                                             "Maximum width for avatars",
905                                                             "Maximum width of avatar that may be set "
906                                                             "or 0 if there is no maximum.",
907                                                             0,
908                                                             G_MAXUINT,
909                                                             0,
910                                                             G_PARAM_READABLE |
911                                                             G_PARAM_STATIC_STRINGS));
912         g_object_class_install_property (object_class,
913                                          PROP_MAX_HEIGHT,
914                                          g_param_spec_uint ("avatar-max-height",
915                                                             "Maximum height for avatars",
916                                                             "Maximum height of avatar that may be set "
917                                                             "or 0 if there is no maximum.",
918                                                             0,
919                                                             G_MAXUINT,
920                                                             0,
921                                                             G_PARAM_READABLE |
922                                                             G_PARAM_STATIC_STRINGS));
923         g_object_class_install_property (object_class,
924                                          PROP_MAX_SIZE,
925                                          g_param_spec_uint ("avatar-max-size",
926                                                             "Maximum size for avatars in bytes",
927                                                             "Maximum file size of avatar that may be "
928                                                             "set or 0 if there is no maximum.",
929                                                             0,
930                                                             G_MAXUINT,
931                                                             0,
932                                                             G_PARAM_READABLE |
933                                                             G_PARAM_STATIC_STRINGS));
934
935
936         g_type_class_add_private (object_class, sizeof (EmpathyTpContactFactoryPriv));
937 }
938
939 static void
940 empathy_tp_contact_factory_init (EmpathyTpContactFactory *tp_factory)
941 {
942         EmpathyTpContactFactoryPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (tp_factory,
943                 EMPATHY_TYPE_TP_CONTACT_FACTORY, EmpathyTpContactFactoryPriv);
944
945         tp_factory->priv = priv;
946 }
947
948 static GHashTable *factories = NULL;
949
950 static void
951 tp_contact_factory_connection_invalidated_cb (TpProxy *connection,
952                                               guint    domain,
953                                               gint     code,
954                                               gchar   *message,
955                                               gpointer user_data)
956 {
957         DEBUG ("Message: %s", message);
958         g_hash_table_remove (factories, connection);
959 }
960
961 static void
962 tp_contact_factory_connection_weak_notify_cb (gpointer connection,
963                                               GObject *where_the_object_was)
964 {
965         g_hash_table_remove (factories, connection);
966 }
967
968 static void
969 tp_contact_factory_remove_connection (gpointer connection)
970 {
971         g_signal_handlers_disconnect_by_func (connection,
972                 tp_contact_factory_connection_invalidated_cb, NULL);
973         g_object_unref (connection);
974 }
975
976 EmpathyTpContactFactory *
977 empathy_tp_contact_factory_dup_singleton (TpConnection *connection)
978 {
979         EmpathyTpContactFactory *tp_factory;
980
981         g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
982
983         if (factories == NULL) {
984                 factories = g_hash_table_new_full (empathy_proxy_hash,
985                                                    empathy_proxy_equal,
986                                                    tp_contact_factory_remove_connection,
987                                                    NULL);
988         }
989
990         tp_factory = g_hash_table_lookup (factories, connection);
991         if (tp_factory == NULL) {
992                 tp_factory = g_object_new (EMPATHY_TYPE_TP_CONTACT_FACTORY,
993                                            "connection", connection,
994                                            NULL);
995                 g_hash_table_insert (factories, g_object_ref (connection),
996                                      tp_factory);
997                 g_object_weak_ref (G_OBJECT (tp_factory),
998                                    tp_contact_factory_connection_weak_notify_cb,
999                                    connection);
1000                 g_signal_connect (connection, "invalidated",
1001                                   G_CALLBACK (tp_contact_factory_connection_invalidated_cb),
1002                                   NULL);
1003         } else {
1004                 g_object_ref (tp_factory);
1005         }
1006
1007         return tp_factory;
1008 }
1009