]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-factory.c
27160a4454abf1665e2253a013d175996a7966f0
[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 #if HAVE_GEOCLUE
30 #include <geoclue/geoclue-geocode.h>
31 #endif
32
33 #include <extensions/extensions.h>
34
35 #include "empathy-tp-contact-factory.h"
36 #include "empathy-utils.h"
37 #include "empathy-location.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT
40 #include "empathy-debug.h"
41
42 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactFactory)
43 typedef struct {
44         TpConnection   *connection;
45         GList          *contacts;
46
47         gchar         **avatar_mime_types;
48         guint           avatar_min_width;
49         guint           avatar_min_height;
50         guint           avatar_max_width;
51         guint           avatar_max_height;
52         guint           avatar_max_size;
53         gboolean        can_request_ft;
54         gboolean        can_request_st;
55 } EmpathyTpContactFactoryPriv;
56
57 G_DEFINE_TYPE (EmpathyTpContactFactory, empathy_tp_contact_factory, G_TYPE_OBJECT);
58
59 enum {
60         PROP_0,
61         PROP_CONNECTION,
62
63         PROP_MIME_TYPES,
64         PROP_MIN_WIDTH,
65         PROP_MIN_HEIGHT,
66         PROP_MAX_WIDTH,
67         PROP_MAX_HEIGHT,
68         PROP_MAX_SIZE
69 };
70
71 static TpContactFeature contact_features[] = {
72         TP_CONTACT_FEATURE_ALIAS,
73         TP_CONTACT_FEATURE_PRESENCE,
74 };
75
76 static EmpathyContact *
77 tp_contact_factory_find_by_handle (EmpathyTpContactFactory *tp_factory,
78                                    guint                    handle)
79 {
80         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
81         GList                       *l;
82
83         for (l = priv->contacts; l; l = l->next) {
84                 if (empathy_contact_get_handle (l->data) == handle) {
85                         return l->data;
86                 }
87         }
88
89         return NULL;
90 }
91
92 static EmpathyContact *
93 tp_contact_factory_find_by_tp_contact (EmpathyTpContactFactory *tp_factory,
94                                        TpContact               *tp_contact)
95 {
96         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
97         GList                       *l;
98
99         for (l = priv->contacts; l; l = l->next) {
100                 if (empathy_contact_get_tp_contact (l->data) == tp_contact) {
101                         return l->data;
102                 }
103         }
104
105         return NULL;
106 }
107
108 static void
109 tp_contact_factory_weak_notify (gpointer data,
110                                 GObject *where_the_object_was)
111 {
112         EmpathyTpContactFactoryPriv *priv = GET_PRIV (data);
113
114         DEBUG ("Remove finalized contact %p", where_the_object_was);
115
116         priv->contacts = g_list_remove (priv->contacts, where_the_object_was);
117 }
118
119 static void
120 tp_contact_factory_set_aliases_cb (TpConnection *connection,
121                                    const GError *error,
122                                    gpointer      user_data,
123                                    GObject      *tp_factory)
124 {
125         if (error) {
126                 DEBUG ("Error: %s", error->message);
127         }
128 }
129
130 static void
131 tp_contact_factory_set_location_cb (TpProxy *proxy,
132                                     const GError *error,
133                                     gpointer user_data,
134                                     GObject *weak_object)
135 {
136         if (error != NULL) {
137                 DEBUG ("Error setting location: %s", error->message);
138         }
139 }
140
141 static void
142 tp_contact_factory_set_avatar_cb (TpConnection *connection,
143                                   const gchar  *token,
144                                   const GError *error,
145                                   gpointer      user_data,
146                                   GObject      *tp_factory)
147 {
148         if (error) {
149                 DEBUG ("Error: %s", error->message);
150         }
151 }
152
153 static void
154 tp_contact_factory_clear_avatar_cb (TpConnection *connection,
155                                     const GError *error,
156                                     gpointer      user_data,
157                                     GObject      *tp_factory)
158 {
159         if (error) {
160                 DEBUG ("Error: %s", error->message);
161         }
162 }
163
164 static void
165 tp_contact_factory_avatar_retrieved_cb (TpConnection *connection,
166                                         guint         handle,
167                                         const gchar  *token,
168                                         const GArray *avatar_data,
169                                         const gchar  *mime_type,
170                                         gpointer      user_data,
171                                         GObject      *tp_factory)
172 {
173         EmpathyContact *contact;
174
175         contact = tp_contact_factory_find_by_handle (EMPATHY_TP_CONTACT_FACTORY (tp_factory),
176                                                      handle);
177         if (!contact) {
178                 return;
179         }
180
181         DEBUG ("Avatar retrieved for contact %s (%d)",
182                 empathy_contact_get_id (contact),
183                 handle);
184
185         empathy_contact_load_avatar_data (contact,
186                                           avatar_data->data,
187                                           avatar_data->len,
188                                           mime_type,
189                                           token);
190 }
191
192 static void
193 tp_contact_factory_request_avatars_cb (TpConnection *connection,
194                                        const GError *error,
195                                        gpointer      user_data,
196                                        GObject      *tp_factory)
197 {
198         if (error) {
199                 DEBUG ("Error: %s", error->message);
200         }
201 }
202
203 static gboolean
204 tp_contact_factory_avatar_maybe_update (EmpathyTpContactFactory *tp_factory,
205                                         guint                    handle,
206                                         const gchar             *token)
207 {
208         EmpathyContact *contact;
209         EmpathyAvatar  *avatar;
210
211         contact = tp_contact_factory_find_by_handle (tp_factory, handle);
212         if (!contact) {
213                 return TRUE;
214         }
215
216         /* Check if we have an avatar */
217         if (EMP_STR_EMPTY (token)) {
218                 empathy_contact_set_avatar (contact, NULL);
219                 return TRUE;
220         }
221
222         /* Check if the avatar changed */
223         avatar = empathy_contact_get_avatar (contact);
224         if (avatar && !tp_strdiff (avatar->token, token)) {
225                 return TRUE;
226         }
227
228         /* The avatar changed, search the new one in the cache */
229         if (empathy_contact_load_avatar_cache (contact, token)) {
230                 /* Got from cache, use it */
231                 return TRUE;
232         }
233
234         /* Avatar is not up-to-date, we have to request it. */
235         return FALSE;
236 }
237
238 typedef struct {
239         EmpathyTpContactFactory *tp_factory;
240         GArray                  *handles;
241 } TokensData;
242
243 static void
244 tp_contact_factory_avatar_tokens_foreach (gpointer key,
245                                           gpointer value,
246                                           gpointer user_data)
247 {
248         TokensData  *data = user_data;
249         const gchar *token = value;
250         guint        handle = GPOINTER_TO_UINT (key);
251
252         if (!tp_contact_factory_avatar_maybe_update (data->tp_factory,
253                                                      handle, token)) {
254                 g_array_append_val (data->handles, handle);
255         }
256 }
257
258 static void
259 tp_contact_factory_got_known_avatar_tokens (EmpathyTpContactFactory *tp_factory,
260                                             GHashTable              *tokens,
261                                             const GError            *error)
262 {
263         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
264         TokensData data;
265
266         if (error) {
267                 DEBUG ("Error: %s", error->message);
268                 return;
269         }
270
271         data.tp_factory = tp_factory;
272         data.handles = g_array_new (FALSE, FALSE, sizeof (guint));
273         g_hash_table_foreach (tokens,
274                               tp_contact_factory_avatar_tokens_foreach,
275                               &data);
276
277         DEBUG ("Got %d tokens, need to request %d avatars",
278                 g_hash_table_size (tokens), data.handles->len);
279
280         /* Request needed avatars */
281         if (data.handles->len > 0) {
282                 tp_cli_connection_interface_avatars_call_request_avatars (priv->connection,
283                                                                           -1,
284                                                                           data.handles,
285                                                                           tp_contact_factory_request_avatars_cb,
286                                                                           NULL, NULL,
287                                                                           G_OBJECT (tp_factory));
288         }
289
290         g_array_free (data.handles, TRUE);
291         g_hash_table_destroy (tokens);
292 }
293
294 static void
295 tp_contact_factory_avatar_updated_cb (TpConnection *connection,
296                                       guint         handle,
297                                       const gchar  *new_token,
298                                       gpointer      user_data,
299                                       GObject      *tp_factory)
300 {
301         GArray *handles;
302
303         if (tp_contact_factory_avatar_maybe_update (EMPATHY_TP_CONTACT_FACTORY (tp_factory),
304                                                     handle, new_token)) {
305                 /* Avatar was cached, nothing to do */
306                 return;
307         }
308
309         DEBUG ("Need to request avatar for token %s", new_token);
310
311         handles = g_array_new (FALSE, FALSE, sizeof (guint));
312         g_array_append_val (handles, handle);
313
314         tp_cli_connection_interface_avatars_call_request_avatars (connection,
315                                                                   -1,
316                                                                   handles,
317                                                                   tp_contact_factory_request_avatars_cb,
318                                                                   NULL, NULL,
319                                                                   tp_factory);
320         g_array_free (handles, TRUE);
321 }
322
323 static void
324 tp_contact_factory_update_capabilities (EmpathyTpContactFactory *tp_factory,
325                                         guint                    handle,
326                                         const gchar             *channel_type,
327                                         guint                    generic,
328                                         guint                    specific)
329 {
330         EmpathyContact      *contact;
331         EmpathyCapabilities  capabilities;
332
333         contact = tp_contact_factory_find_by_handle (tp_factory, handle);
334         if (!contact) {
335                 return;
336         }
337
338         capabilities = empathy_contact_get_capabilities (contact);
339         capabilities &= ~EMPATHY_CAPABILITIES_UNKNOWN;
340
341         if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) == 0) {
342                 capabilities &= ~EMPATHY_CAPABILITIES_AUDIO;
343                 capabilities &= ~EMPATHY_CAPABILITIES_VIDEO;
344                 if (specific & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO) {
345                         capabilities |= EMPATHY_CAPABILITIES_AUDIO;
346                 }
347                 if (specific & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO) {
348                         capabilities |= EMPATHY_CAPABILITIES_VIDEO;
349                 }
350         }
351
352         DEBUG ("Changing capabilities for contact %s (%d) to %d",
353                 empathy_contact_get_id (contact),
354                 empathy_contact_get_handle (contact),
355                 capabilities);
356
357         empathy_contact_set_capabilities (contact, capabilities);
358 }
359
360 static void
361 tp_contact_factory_got_capabilities (EmpathyTpContactFactory *tp_factory,
362                                      GPtrArray *capabilities,
363                                      const GError    *error)
364 {
365         guint i;
366
367         if (error) {
368                 DEBUG ("Error: %s", error->message);
369                 /* FIXME Should set the capabilities of the contacts for which this request
370                  * originated to NONE */
371                 return;
372         }
373
374         for (i = 0; i < capabilities->len; i++) {
375                 GValueArray *values;
376                 guint        handle;
377                 const gchar *channel_type;
378                 guint        generic;
379                 guint        specific;
380
381                 values = g_ptr_array_index (capabilities, i);
382                 handle = g_value_get_uint (g_value_array_get_nth (values, 0));
383                 channel_type = g_value_get_string (g_value_array_get_nth (values, 1));
384                 generic = g_value_get_uint (g_value_array_get_nth (values, 2));
385                 specific = g_value_get_uint (g_value_array_get_nth (values, 3));
386
387                 tp_contact_factory_update_capabilities (tp_factory,
388                                                         handle,
389                                                         channel_type,
390                                                         generic,
391                                                         specific);
392
393                 g_value_array_free (values);
394         }
395
396         g_ptr_array_free (capabilities, TRUE);
397 }
398
399 #if HAVE_GEOCLUE
400 #define GEOCODE_SERVICE "org.freedesktop.Geoclue.Providers.Yahoo"
401 #define GEOCODE_PATH "/org/freedesktop/Geoclue/Providers/Yahoo"
402
403 /* This callback is called by geoclue when it found a position
404  * for the given address.  A position is necessary for a contact
405  * to show up on the map
406  */
407 static void
408 geocode_cb (GeoclueGeocode *geocode,
409             GeocluePositionFields fields,
410             double latitude,
411             double longitude,
412             double altitude,
413             GeoclueAccuracy *accuracy,
414             GError *error,
415             gpointer contact)
416 {
417         GValue *new_value;
418         GHashTable *location;
419
420         location = empathy_contact_get_location (EMPATHY_CONTACT (contact));
421
422         if (error != NULL) {
423                 DEBUG ("Error geocoding location : %s", error->message);
424                 g_object_unref (geocode);
425                 g_object_unref (contact);
426                 return;
427         }
428
429         if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE) {
430                 new_value = tp_g_value_slice_new_double (latitude);
431                 g_hash_table_replace (location, g_strdup (EMPATHY_LOCATION_LAT),
432                         new_value);
433                 DEBUG ("\t - Latitude: %f", latitude);
434         }
435         if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) {
436                 new_value = tp_g_value_slice_new_double (longitude);
437                 g_hash_table_replace (location, g_strdup (EMPATHY_LOCATION_LON),
438                         new_value);
439                 DEBUG ("\t - Longitude: %f", longitude);
440         }
441         if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE) {
442                 new_value = tp_g_value_slice_new_double (altitude);
443                 g_hash_table_replace (location, g_strdup (EMPATHY_LOCATION_ALT),
444                         new_value);
445                 DEBUG ("\t - Altitude: %f", altitude);
446         }
447
448         /* Don't change the accuracy as we used an address to get this position */
449         g_object_notify (contact, "location");
450         g_object_unref (geocode);
451         g_object_unref (contact);
452 }
453 #endif
454
455 #if HAVE_GEOCLUE
456 static gchar *
457 get_dup_string (GHashTable *location,
458     gchar *key)
459 {
460   GValue *value;
461
462   value = g_hash_table_lookup (location, key);
463   if (value != NULL)
464     return g_value_dup_string (value);
465
466   return NULL;
467 }
468 #endif
469
470 static void
471 tp_contact_factory_geocode (EmpathyContact *contact)
472 {
473 #if HAVE_GEOCLUE
474         static GeoclueGeocode *geocode;
475         gchar *str;
476         GHashTable *address;
477         GValue* value;
478         GHashTable *location;
479
480         location = empathy_contact_get_location (contact);
481         if (location == NULL)
482                 return;
483
484         value = g_hash_table_lookup (location, EMPATHY_LOCATION_LAT);
485         if (value != NULL)
486                 return;
487
488         if (geocode == NULL) {
489                 geocode = geoclue_geocode_new (GEOCODE_SERVICE, GEOCODE_PATH);
490                 g_object_add_weak_pointer (G_OBJECT (geocode), (gpointer *) &geocode);
491         }
492         else
493                 g_object_ref (geocode);
494
495         address = geoclue_address_details_new ();
496
497         str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY_CODE);
498         if (str != NULL) {
499                 g_hash_table_insert (address,
500                         g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRYCODE), str);
501                 DEBUG ("\t - countrycode: %s", str);
502         }
503
504         str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY);
505         if (str != NULL) {
506                 g_hash_table_insert (address,
507                         g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRY), str);
508                 DEBUG ("\t - country: %s", str);
509         }
510
511         str = get_dup_string (location, EMPATHY_LOCATION_POSTAL_CODE);
512         if (str != NULL) {
513                 g_hash_table_insert (address,
514                         g_strdup (GEOCLUE_ADDRESS_KEY_POSTALCODE), str);
515                 DEBUG ("\t - postalcode: %s", str);
516         }
517
518         str = get_dup_string (location, EMPATHY_LOCATION_REGION);
519         if (str != NULL) {
520                 g_hash_table_insert (address,
521                         g_strdup (GEOCLUE_ADDRESS_KEY_REGION), str);
522                 DEBUG ("\t - region: %s", str);
523         }
524
525         str = get_dup_string (location, EMPATHY_LOCATION_LOCALITY);
526         if (str != NULL) {
527                 g_hash_table_insert (address,
528                         g_strdup (GEOCLUE_ADDRESS_KEY_LOCALITY), str);
529                 DEBUG ("\t - locality: %s", str);
530         }
531
532         str = get_dup_string (location, EMPATHY_LOCATION_STREET);
533         if (str != NULL) {
534                 g_hash_table_insert (address,
535                         g_strdup (GEOCLUE_ADDRESS_KEY_STREET), str);
536                 DEBUG ("\t - street: %s", str);
537         }
538
539         g_object_ref (contact);
540         geoclue_geocode_address_to_position_async (geocode, address,
541                 geocode_cb, contact);
542
543         g_hash_table_unref (address);
544 #endif
545 }
546
547 static void
548 tp_contact_factory_update_location (EmpathyTpContactFactory *tp_factory,
549                                     guint handle,
550                                     GHashTable *location)
551 {
552         EmpathyContact *contact;
553         GHashTable     *new_location;
554
555         contact = tp_contact_factory_find_by_handle (tp_factory, handle);
556
557         if (contact == NULL)
558                 return;
559
560         new_location = g_hash_table_new_full (g_str_hash, g_str_equal,
561                 (GDestroyNotify) g_free, (GDestroyNotify) tp_g_value_slice_free);
562         tp_g_hash_table_update (new_location, location, (GBoxedCopyFunc) g_strdup,
563                 (GBoxedCopyFunc) tp_g_value_slice_dup);
564         empathy_contact_set_location (contact, new_location);
565         g_hash_table_unref (new_location);
566
567         tp_contact_factory_geocode (contact);
568 }
569
570 static void
571 tp_contact_factory_got_locations (TpProxy                 *tp_proxy,
572                                   GHashTable              *locations,
573                                   const GError            *error,
574                                   gpointer                 user_data,
575                                   GObject                 *weak_object)
576 {
577         GHashTableIter iter;
578         gpointer key, value;
579         EmpathyTpContactFactory *tp_factory;
580
581         tp_factory = EMPATHY_TP_CONTACT_FACTORY (user_data);
582         if (error != NULL) {
583                 DEBUG ("Error: %s", error->message);
584                 return;
585         }
586
587         g_hash_table_iter_init (&iter, locations);
588         while (g_hash_table_iter_next (&iter, &key, &value)) {
589                 guint           handle = GPOINTER_TO_INT (key);
590                 GHashTable     *location = value;
591
592                 tp_contact_factory_update_location (tp_factory, handle, location);
593         }
594 }
595
596 static void
597 tp_contact_factory_capabilities_changed_cb (TpConnection    *connection,
598                                             const GPtrArray *capabilities,
599                                             gpointer         user_data,
600                                             GObject         *weak_object)
601 {
602         EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);
603         guint                    i;
604
605         for (i = 0; i < capabilities->len; i++) {
606                 GValueArray *values;
607                 guint        handle;
608                 const gchar *channel_type;
609                 guint        generic;
610                 guint        specific;
611
612                 values = g_ptr_array_index (capabilities, i);
613                 handle = g_value_get_uint (g_value_array_get_nth (values, 0));
614                 channel_type = g_value_get_string (g_value_array_get_nth (values, 1));
615                 generic = g_value_get_uint (g_value_array_get_nth (values, 3));
616                 specific = g_value_get_uint (g_value_array_get_nth (values, 5));
617
618                 tp_contact_factory_update_capabilities (tp_factory,
619                                                         handle,
620                                                         channel_type,
621                                                         generic,
622                                                         specific);
623         }
624 }
625
626 static void
627 tp_contact_factory_location_updated_cb (TpProxy      *proxy,
628                                         guint         handle,
629                                         GHashTable   *location,
630                                         gpointer      user_data,
631                                         GObject      *weak_object)
632 {
633         EmpathyTpContactFactory *tp_factory = EMPATHY_TP_CONTACT_FACTORY (weak_object);
634         tp_contact_factory_update_location (tp_factory, handle, location);
635 }
636
637 static void
638 get_requestable_channel_classes_cb (TpProxy *connection,
639                                     const GValue *value,
640                                     const GError *error,
641                                     gpointer user_data,
642                                     GObject *weak_object)
643 {
644         EmpathyTpContactFactory     *self = EMPATHY_TP_CONTACT_FACTORY (weak_object);
645         EmpathyTpContactFactoryPriv *priv = GET_PRIV (self);
646         GPtrArray                   *classes;
647         guint                        i;
648         GList                       *l;
649
650         if (error != NULL) {
651                 DEBUG ("Error: %s", error->message);
652                 return;
653         }
654
655         classes = g_value_get_boxed (value);
656         for (i = 0; i < classes->len; i++) {
657                 GValueArray *class_struct;
658                 GHashTable *fixed_prop;
659                 GValue *chan_type, *handle_type;
660
661                 class_struct = g_ptr_array_index (classes, i);
662                 fixed_prop = g_value_get_boxed (g_value_array_get_nth (class_struct, 0));
663
664                 handle_type = g_hash_table_lookup (fixed_prop,
665                         TP_IFACE_CHANNEL ".TargetHandleType");
666                 if (handle_type == NULL ||
667                     g_value_get_uint (handle_type) != TP_HANDLE_TYPE_CONTACT)
668                         continue;
669
670                 chan_type = g_hash_table_lookup (fixed_prop,
671                         TP_IFACE_CHANNEL ".ChannelType");
672                 if (chan_type == NULL)
673                         continue;
674
675                 if (!tp_strdiff (g_value_get_string (chan_type),
676                     TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
677                         priv->can_request_ft = TRUE;
678                 else if (!tp_strdiff (g_value_get_string (chan_type),
679                          TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
680                         priv->can_request_st = TRUE;
681         }
682
683         if (!priv->can_request_ft && !priv->can_request_st)
684                 return ;
685
686         /* Update the capabilities of all contacts */
687         for (l = priv->contacts; l != NULL; l = g_list_next (l)) {
688                 EmpathyContact *contact = l->data;
689                 EmpathyCapabilities caps;
690
691                 caps = empathy_contact_get_capabilities (contact);
692
693                 if (priv->can_request_ft)
694                         caps |= EMPATHY_CAPABILITIES_FT;
695
696                 if (priv->can_request_st)
697                         caps |= EMPATHY_CAPABILITIES_STREAM_TUBE;
698
699                 empathy_contact_set_capabilities (contact, caps);
700         }
701 }
702
703 static void
704 tp_contact_factory_got_avatar_requirements_cb (TpConnection *proxy,
705                                                const gchar **mime_types,
706                                                guint         min_width,
707                                                guint         min_height,
708                                                guint         max_width,
709                                                guint         max_height,
710                                                guint         max_size,
711                                                const GError *error,
712                                                gpointer      user_data,
713                                                GObject      *tp_factory)
714 {
715         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
716
717         if (error) {
718                 DEBUG ("Failed to get avatar requirements: %s", error->message);
719                 /* We'll just leave avatar_mime_types as NULL; the
720                  * avatar-setting code can use this as a signal that you can't
721                  * set avatars.
722                  */
723         } else {
724                 priv->avatar_mime_types = g_strdupv ((gchar **) mime_types);
725                 priv->avatar_min_width = min_width;
726                 priv->avatar_min_height = min_height;
727                 priv->avatar_max_width = max_width;
728                 priv->avatar_max_height = max_height;
729                 priv->avatar_max_size = max_size;
730         }
731 }
732
733 static void
734 tp_contact_factory_add_contact (EmpathyTpContactFactory *tp_factory,
735                                 EmpathyContact          *contact)
736 {
737         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
738         TpHandle self_handle;
739         TpHandle handle;
740         GArray handles = {(gchar *) &handle, 1};
741         GHashTable *tokens;
742         GPtrArray *capabilities;
743         GError *error = NULL;
744         EmpathyCapabilities caps;
745
746         /* Keep a weak ref to that contact */
747         g_object_weak_ref (G_OBJECT (contact),
748                            tp_contact_factory_weak_notify,
749                            tp_factory);
750         priv->contacts = g_list_prepend (priv->contacts, contact);
751
752         /* The contact keeps a ref to its factory */
753         g_object_set_data_full (G_OBJECT (contact), "empathy-factory",
754                                 g_object_ref (tp_factory),
755                                 g_object_unref);
756
757         caps = empathy_contact_get_capabilities (contact);
758
759         /* Set the FT capability */
760         if (priv->can_request_ft) {
761                 caps |= EMPATHY_CAPABILITIES_FT;
762         }
763
764         /* Set the Stream Tube capability */
765         if (priv->can_request_st) {
766                 caps |= EMPATHY_CAPABILITIES_STREAM_TUBE;
767         }
768
769         empathy_contact_set_capabilities (contact, caps);
770
771         /* Set is-user property. Note that it could still be the handle is
772          * different from the connection's self handle, in the case the handle
773          * comes from a group interface. */
774         self_handle = tp_connection_get_self_handle (priv->connection);
775         handle = empathy_contact_get_handle (contact);
776         empathy_contact_set_is_user (contact, self_handle == handle);
777
778         /* FIXME: This should be done by TpContact */
779         tp_cli_connection_interface_avatars_run_get_known_avatar_tokens (priv->connection,
780                                                                          -1,
781                                                                          &handles,
782                                                                          &tokens,
783                                                                          &error,
784                                                                          NULL);
785         tp_contact_factory_got_known_avatar_tokens (tp_factory, tokens, error);
786         g_clear_error (&error);
787
788         tp_cli_connection_interface_capabilities_run_get_capabilities (priv->connection,
789                                                                         -1,
790                                                                         &handles,
791                                                                         &capabilities,
792                                                                         &error,
793                                                                         NULL);
794         tp_contact_factory_got_capabilities (tp_factory, capabilities, error);
795         g_clear_error (&error);
796
797         if (tp_proxy_has_interface_by_id (TP_PROXY (priv->connection),
798                 EMP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION)) {
799                 emp_cli_connection_interface_location_call_get_locations (TP_PROXY (priv->connection),
800                                                                          -1,
801                                                                          &handles,
802                                                                          tp_contact_factory_got_locations,
803                                                                          tp_factory,
804                                                                          NULL,
805                                                                          NULL);
806         }
807
808         DEBUG ("Contact added: %s (%d)",
809                 empathy_contact_get_id (contact),
810                 empathy_contact_get_handle (contact));
811 }
812
813 typedef union {
814         EmpathyTpContactFactoryContactsByIdCb ids_cb;
815         EmpathyTpContactFactoryContactsByHandleCb handles_cb;
816         EmpathyTpContactFactoryContactCb contact_cb;
817 } GetContactsCb;
818
819 typedef struct {
820         EmpathyTpContactFactory *tp_factory;
821         GetContactsCb callback;
822         gpointer user_data;
823         GDestroyNotify destroy;
824 } GetContactsData;
825
826 static void
827 get_contacts_data_free (gpointer user_data)
828 {
829         GetContactsData *data = user_data;
830
831         if (data->destroy) {
832                 data->destroy (data->user_data);
833         }
834         g_object_unref (data->tp_factory);
835
836         g_slice_free (GetContactsData, data);
837 }
838
839 static EmpathyContact *
840 dup_contact_for_tp_contact (EmpathyTpContactFactory *tp_factory,
841                             TpContact               *tp_contact)
842 {
843         EmpathyContact *contact;
844
845         contact = tp_contact_factory_find_by_tp_contact (tp_factory,
846                                                          tp_contact);
847
848         if (contact != NULL) {
849                 g_object_ref (contact);
850         } else {
851                 contact = empathy_contact_new (tp_contact);
852                 tp_contact_factory_add_contact (tp_factory, contact);
853         }
854
855         return contact;
856 }
857
858 static EmpathyContact **
859 contacts_array_new (EmpathyTpContactFactory *tp_factory,
860                     guint                    n_contacts,
861                     TpContact * const *      contacts)
862 {
863         EmpathyContact **ret;
864         guint            i;
865
866         ret = g_new0 (EmpathyContact *, n_contacts);
867         for (i = 0; i < n_contacts; i++) {
868                 ret[i] = dup_contact_for_tp_contact (tp_factory, contacts[i]);
869         }
870
871         return ret;
872 }
873
874 static void
875 contacts_array_free (guint            n_contacts,
876                      EmpathyContact **contacts)
877 {
878         guint i;
879
880         for (i = 0; i < n_contacts; i++) {
881                 g_object_unref (contacts[i]);
882         }
883         g_free (contacts);
884 }
885
886 static void
887 get_contacts_by_id_cb (TpConnection *connection,
888                        guint n_contacts,
889                        TpContact * const *contacts,
890                        const gchar * const *requested_ids,
891                        GHashTable *failed_id_errors,
892                        const GError *error,
893                        gpointer user_data,
894                        GObject *weak_object)
895 {
896         GetContactsData *data = user_data;
897         EmpathyContact **empathy_contacts;
898
899         empathy_contacts = contacts_array_new (data->tp_factory,
900                                                n_contacts, contacts);
901         if (data->callback.ids_cb) {
902                 data->callback.ids_cb (data->tp_factory,
903                                        n_contacts, empathy_contacts,
904                                        requested_ids,
905                                        failed_id_errors,
906                                        error,
907                                        data->user_data, weak_object);
908         }
909
910         contacts_array_free (n_contacts, empathy_contacts);
911 }
912
913 void
914 empathy_tp_contact_factory_get_from_ids (EmpathyTpContactFactory *tp_factory,
915                                          guint                    n_ids,
916                                          const gchar * const     *ids,
917                                          EmpathyTpContactFactoryContactsByIdCb callback,
918                                          gpointer                 user_data,
919                                          GDestroyNotify           destroy,
920                                          GObject                 *weak_object)
921 {
922         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
923         GetContactsData *data;
924
925         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
926         g_return_if_fail (ids != NULL);
927
928         data = g_slice_new (GetContactsData);
929         data->callback.ids_cb = callback;
930         data->user_data = user_data;
931         data->destroy = destroy;
932         data->tp_factory = g_object_ref (tp_factory);
933         tp_connection_get_contacts_by_id (priv->connection,
934                                           n_ids, ids,
935                                           G_N_ELEMENTS (contact_features),
936                                           contact_features,
937                                           get_contacts_by_id_cb,
938                                           data,
939                                           (GDestroyNotify) get_contacts_data_free,
940                                           weak_object);
941 }
942
943 static void
944 get_contact_by_id_cb (TpConnection *connection,
945                       guint n_contacts,
946                       TpContact * const *contacts,
947                       const gchar * const *requested_ids,
948                       GHashTable *failed_id_errors,
949                       const GError *error,
950                       gpointer user_data,
951                       GObject *weak_object)
952 {
953         GetContactsData *data = user_data;
954         EmpathyContact  *contact = NULL;
955
956         if (n_contacts == 1) {
957                 contact = dup_contact_for_tp_contact (data->tp_factory,
958                                                       contacts[0]);
959         }
960         else if (error == NULL) {
961                 GHashTableIter iter;
962                 gpointer       value;
963
964                 g_hash_table_iter_init (&iter, failed_id_errors);
965                 while (g_hash_table_iter_next (&iter, NULL, &value)) {
966                         if (value) {
967                                 error = value;
968                                 break;
969                         }
970                 }
971         }
972
973         if (data->callback.contact_cb) {
974                 data->callback.contact_cb (data->tp_factory,
975                                            contact,
976                                            error,
977                                            data->user_data, weak_object);
978         }
979 }
980
981 void
982 empathy_tp_contact_factory_get_from_id (EmpathyTpContactFactory *tp_factory,
983                                         const gchar             *id,
984                                         EmpathyTpContactFactoryContactCb callback,
985                                         gpointer                 user_data,
986                                         GDestroyNotify           destroy,
987                                         GObject                 *weak_object)
988 {
989         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
990         GetContactsData *data;
991
992         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
993         g_return_if_fail (id != NULL);
994
995         data = g_slice_new (GetContactsData);
996         data->callback.contact_cb = callback;
997         data->user_data = user_data;
998         data->destroy = destroy;
999         data->tp_factory = g_object_ref (tp_factory);
1000         tp_connection_get_contacts_by_id (priv->connection,
1001                                           1, &id,
1002                                           G_N_ELEMENTS (contact_features),
1003                                           contact_features,
1004                                           get_contact_by_id_cb,
1005                                           data,
1006                                           (GDestroyNotify) get_contacts_data_free,
1007                                           weak_object);
1008 }
1009
1010 static void
1011 get_contacts_by_handle_cb (TpConnection *connection,
1012                            guint n_contacts,
1013                            TpContact * const *contacts,
1014                            guint n_failed,
1015                            const TpHandle *failed,
1016                            const GError *error,
1017                            gpointer user_data,
1018                            GObject *weak_object)
1019 {
1020         GetContactsData *data = user_data;
1021         EmpathyContact **empathy_contacts;
1022
1023         empathy_contacts = contacts_array_new (data->tp_factory,
1024                                                n_contacts, contacts);
1025         if (data->callback.handles_cb) {
1026                 data->callback.handles_cb (data->tp_factory,
1027                                            n_contacts, empathy_contacts,
1028                                            n_failed, failed,
1029                                            error,
1030                                            data->user_data, weak_object);
1031         }
1032
1033         contacts_array_free (n_contacts, empathy_contacts);
1034 }
1035
1036 void
1037 empathy_tp_contact_factory_get_from_handles (EmpathyTpContactFactory *tp_factory,
1038                                              guint n_handles,
1039                                              const TpHandle *handles,
1040                                              EmpathyTpContactFactoryContactsByHandleCb callback,
1041                                              gpointer                 user_data,
1042                                              GDestroyNotify           destroy,
1043                                              GObject                 *weak_object)
1044 {
1045         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
1046         GetContactsData *data;
1047
1048         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
1049         g_return_if_fail (handles != NULL);
1050
1051         data = g_slice_new (GetContactsData);
1052         data->callback.handles_cb = callback;
1053         data->user_data = user_data;
1054         data->destroy = destroy;
1055         data->tp_factory = g_object_ref (tp_factory);
1056         tp_connection_get_contacts_by_handle (priv->connection,
1057                                               n_handles, handles,
1058                                               G_N_ELEMENTS (contact_features),
1059                                               contact_features,
1060                                               get_contacts_by_handle_cb,
1061                                               data,
1062                                               (GDestroyNotify) get_contacts_data_free,
1063                                               weak_object);
1064 }
1065
1066 static void
1067 get_contact_by_handle_cb (TpConnection *connection,
1068                           guint n_contacts,
1069                           TpContact * const *contacts,
1070                           guint n_failed,
1071                           const TpHandle *failed,
1072                           const GError *error,
1073                           gpointer user_data,
1074                           GObject *weak_object)
1075 {
1076         GetContactsData *data = user_data;
1077         EmpathyContact  *contact = NULL;
1078
1079         if (n_contacts == 1) {
1080                 contact = dup_contact_for_tp_contact (data->tp_factory,
1081                                                       contacts[0]);
1082         }
1083
1084         if (data->callback.contact_cb) {
1085                 data->callback.contact_cb (data->tp_factory,
1086                                            contact,
1087                                            error,
1088                                            data->user_data, weak_object);
1089         }
1090 }
1091
1092 void
1093 empathy_tp_contact_factory_get_from_handle (EmpathyTpContactFactory *tp_factory,
1094                                             TpHandle                 handle,
1095                                             EmpathyTpContactFactoryContactCb callback,
1096                                             gpointer                 user_data,
1097                                             GDestroyNotify           destroy,
1098                                             GObject                 *weak_object)
1099 {
1100         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
1101         GetContactsData *data;
1102
1103         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
1104
1105         data = g_slice_new (GetContactsData);
1106         data->callback.contact_cb = callback;
1107         data->user_data = user_data;
1108         data->destroy = destroy;
1109         data->tp_factory = g_object_ref (tp_factory);
1110         tp_connection_get_contacts_by_handle (priv->connection,
1111                                               1, &handle,
1112                                               G_N_ELEMENTS (contact_features),
1113                                               contact_features,
1114                                               get_contact_by_handle_cb,
1115                                               data,
1116                                               (GDestroyNotify) get_contacts_data_free,
1117                                               weak_object);
1118 }
1119
1120 void
1121 empathy_tp_contact_factory_set_alias (EmpathyTpContactFactory *tp_factory,
1122                                       EmpathyContact          *contact,
1123                                       const gchar             *alias)
1124 {
1125         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
1126         GHashTable                  *new_alias;
1127         guint                        handle;
1128
1129         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
1130         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1131
1132         handle = empathy_contact_get_handle (contact);
1133
1134         DEBUG ("Setting alias for contact %s (%d) to %s",
1135                 empathy_contact_get_id (contact),
1136                 handle, alias);
1137
1138         new_alias = g_hash_table_new_full (g_direct_hash,
1139                                            g_direct_equal,
1140                                            NULL,
1141                                            g_free);
1142
1143         g_hash_table_insert (new_alias,
1144                              GUINT_TO_POINTER (handle),
1145                              g_strdup (alias));
1146
1147         tp_cli_connection_interface_aliasing_call_set_aliases (priv->connection,
1148                                                                -1,
1149                                                                new_alias,
1150                                                                tp_contact_factory_set_aliases_cb,
1151                                                                NULL, NULL,
1152                                                                G_OBJECT (tp_factory));
1153
1154         g_hash_table_destroy (new_alias);
1155 }
1156
1157 void
1158 empathy_tp_contact_factory_set_avatar (EmpathyTpContactFactory *tp_factory,
1159                                        const gchar             *data,
1160                                        gsize                    size,
1161                                        const gchar             *mime_type)
1162 {
1163         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
1164
1165         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
1166
1167         if (data && size > 0 && size < G_MAXUINT) {
1168                 GArray avatar;
1169
1170                 avatar.data = (gchar *) data;
1171                 avatar.len = size;
1172
1173                 DEBUG ("Setting avatar on connection %s",
1174                         tp_proxy_get_object_path (TP_PROXY (priv->connection)));
1175
1176                 tp_cli_connection_interface_avatars_call_set_avatar (priv->connection,
1177                                                                      -1,
1178                                                                      &avatar,
1179                                                                      mime_type,
1180                                                                      tp_contact_factory_set_avatar_cb,
1181                                                                      NULL, NULL,
1182                                                                      G_OBJECT (tp_factory));
1183         } else {
1184                 DEBUG ("Clearing avatar on connection %s",
1185                         tp_proxy_get_object_path (TP_PROXY (priv->connection)));
1186
1187                 tp_cli_connection_interface_avatars_call_clear_avatar (priv->connection,
1188                                                                        -1,
1189                                                                        tp_contact_factory_clear_avatar_cb,
1190                                                                        NULL, NULL,
1191                                                                        G_OBJECT (tp_factory));
1192         }
1193 }
1194
1195 void
1196 empathy_tp_contact_factory_set_location (EmpathyTpContactFactory *tp_factory,
1197                                          GHashTable              *location)
1198 {
1199         EmpathyTpContactFactoryPriv *priv = GET_PRIV (tp_factory);
1200
1201         g_return_if_fail (EMPATHY_IS_TP_CONTACT_FACTORY (tp_factory));
1202
1203         DEBUG ("Setting location");
1204
1205         emp_cli_connection_interface_location_call_set_location (TP_PROXY (priv->connection),
1206                                                                  -1,
1207                                                                  location,
1208                                                                  tp_contact_factory_set_location_cb,
1209                                                                  NULL, NULL,
1210                                                                  G_OBJECT (tp_factory));
1211 }
1212
1213 static void
1214 tp_contact_factory_get_property (GObject    *object,
1215                                  guint       param_id,
1216                                  GValue     *value,
1217                                  GParamSpec *pspec)
1218 {
1219         EmpathyTpContactFactoryPriv *priv = GET_PRIV (object);
1220
1221         switch (param_id) {
1222         case PROP_CONNECTION:
1223                 g_value_set_object (value, priv->connection);
1224                 break;
1225         case PROP_MIME_TYPES:
1226                 g_value_set_boxed (value, priv->avatar_mime_types);
1227                 break;
1228         case PROP_MIN_WIDTH:
1229                 g_value_set_uint (value, priv->avatar_min_width);
1230                 break;
1231         case PROP_MIN_HEIGHT:
1232                 g_value_set_uint (value, priv->avatar_min_height);
1233                 break;
1234         case PROP_MAX_WIDTH:
1235                 g_value_set_uint (value, priv->avatar_max_width);
1236                 break;
1237         case PROP_MAX_HEIGHT:
1238                 g_value_set_uint (value, priv->avatar_max_height);
1239                 break;
1240         case PROP_MAX_SIZE:
1241                 g_value_set_uint (value, priv->avatar_max_size);
1242                 break;
1243         default:
1244                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1245                 break;
1246         };
1247 }
1248
1249 static void
1250 tp_contact_factory_set_property (GObject      *object,
1251                                  guint         param_id,
1252                                  const GValue *value,
1253                                  GParamSpec   *pspec)
1254 {
1255         EmpathyTpContactFactoryPriv *priv = GET_PRIV (object);
1256
1257         switch (param_id) {
1258         case PROP_CONNECTION:
1259                 priv->connection = g_value_dup_object (value);
1260                 break;
1261         default:
1262                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1263                 break;
1264         };
1265 }
1266
1267 static void
1268 tp_contact_factory_finalize (GObject *object)
1269 {
1270         EmpathyTpContactFactoryPriv *priv = GET_PRIV (object);
1271         GList                       *l;
1272
1273         DEBUG ("Finalized: %p", object);
1274
1275         for (l = priv->contacts; l; l = l->next) {
1276                 g_object_weak_unref (G_OBJECT (l->data),
1277                                      tp_contact_factory_weak_notify,
1278                                      object);
1279         }
1280
1281         g_list_free (priv->contacts);
1282
1283         g_object_unref (priv->connection);
1284
1285         g_strfreev (priv->avatar_mime_types);
1286
1287         G_OBJECT_CLASS (empathy_tp_contact_factory_parent_class)->finalize (object);
1288 }
1289
1290 static GObject *
1291 tp_contact_factory_constructor (GType                  type,
1292                                 guint                  n_props,
1293                                 GObjectConstructParam *props)
1294 {
1295         GObject *tp_factory;
1296         EmpathyTpContactFactoryPriv *priv;
1297
1298         tp_factory = G_OBJECT_CLASS (empathy_tp_contact_factory_parent_class)->constructor (type, n_props, props);
1299         priv = GET_PRIV (tp_factory);
1300
1301         /* FIXME: This should be moved to TpContact */
1302         tp_cli_connection_interface_avatars_connect_to_avatar_updated (priv->connection,
1303                                                                        tp_contact_factory_avatar_updated_cb,
1304                                                                        NULL, NULL,
1305                                                                        tp_factory,
1306                                                                        NULL);
1307         tp_cli_connection_interface_avatars_connect_to_avatar_retrieved (priv->connection,
1308                                                                          tp_contact_factory_avatar_retrieved_cb,
1309                                                                          NULL, NULL,
1310                                                                          tp_factory,
1311                                                                          NULL);
1312         tp_cli_connection_interface_capabilities_connect_to_capabilities_changed (priv->connection,
1313                                                                                   tp_contact_factory_capabilities_changed_cb,
1314                                                                                   NULL, NULL,
1315                                                                                   tp_factory,
1316                                                                                   NULL);
1317
1318
1319         emp_cli_connection_interface_location_connect_to_location_updated (TP_PROXY (priv->connection),
1320                                                                            tp_contact_factory_location_updated_cb,
1321                                                                            NULL, NULL,
1322                                                                            G_OBJECT (tp_factory),
1323                                                                            NULL);
1324
1325         /* FIXME: This should be moved to TpConnection */
1326         tp_cli_connection_interface_avatars_call_get_avatar_requirements (priv->connection,
1327                                                                           -1,
1328                                                                           tp_contact_factory_got_avatar_requirements_cb,
1329                                                                           NULL, NULL,
1330                                                                           tp_factory);
1331         tp_cli_dbus_properties_call_get (priv->connection, -1,
1332                 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
1333                 "RequestableChannelClasses",
1334                 get_requestable_channel_classes_cb, NULL, NULL,
1335                 G_OBJECT (tp_factory));
1336
1337         return tp_factory;
1338 }
1339
1340 static void
1341 empathy_tp_contact_factory_class_init (EmpathyTpContactFactoryClass *klass)
1342 {
1343         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1344
1345         object_class->finalize = tp_contact_factory_finalize;
1346         object_class->constructor = tp_contact_factory_constructor;
1347         object_class->get_property = tp_contact_factory_get_property;
1348         object_class->set_property = tp_contact_factory_set_property;
1349
1350         g_object_class_install_property (object_class,
1351                                          PROP_CONNECTION,
1352                                          g_param_spec_object ("connection",
1353                                                               "Factory's Connection",
1354                                                               "The connection associated with the factory",
1355                                                               TP_TYPE_CONNECTION,
1356                                                               G_PARAM_READWRITE |
1357                                                               G_PARAM_CONSTRUCT_ONLY |
1358                                                               G_PARAM_STATIC_STRINGS));
1359         g_object_class_install_property (object_class,
1360                                          PROP_MIME_TYPES,
1361                                          g_param_spec_boxed ("avatar-mime-types",
1362                                                              "Supported MIME types for avatars",
1363                                                              "Types of images that may be set as "
1364                                                              "avatars on this connection.",
1365                                                              G_TYPE_STRV,
1366                                                              G_PARAM_READABLE |
1367                                                              G_PARAM_STATIC_STRINGS));
1368         g_object_class_install_property (object_class,
1369                                          PROP_MIN_WIDTH,
1370                                          g_param_spec_uint ("avatar-min-width",
1371                                                             "Minimum width for avatars",
1372                                                             "Minimum width of avatar that may be set.",
1373                                                             0,
1374                                                             G_MAXUINT,
1375                                                             0,
1376                                                             G_PARAM_READABLE |
1377                                                             G_PARAM_STATIC_STRINGS));
1378         g_object_class_install_property (object_class,
1379                                          PROP_MIN_HEIGHT,
1380                                          g_param_spec_uint ("avatar-min-height",
1381                                                             "Minimum height for avatars",
1382                                                             "Minimum height of avatar that may be set.",
1383                                                             0,
1384                                                             G_MAXUINT,
1385                                                             0,
1386                                                             G_PARAM_READABLE |
1387                                                             G_PARAM_STATIC_STRINGS));
1388         g_object_class_install_property (object_class,
1389                                          PROP_MAX_WIDTH,
1390                                          g_param_spec_uint ("avatar-max-width",
1391                                                             "Maximum width for avatars",
1392                                                             "Maximum width of avatar that may be set "
1393                                                             "or 0 if there is no maximum.",
1394                                                             0,
1395                                                             G_MAXUINT,
1396                                                             0,
1397                                                             G_PARAM_READABLE |
1398                                                             G_PARAM_STATIC_STRINGS));
1399         g_object_class_install_property (object_class,
1400                                          PROP_MAX_HEIGHT,
1401                                          g_param_spec_uint ("avatar-max-height",
1402                                                             "Maximum height for avatars",
1403                                                             "Maximum height of avatar that may be set "
1404                                                             "or 0 if there is no maximum.",
1405                                                             0,
1406                                                             G_MAXUINT,
1407                                                             0,
1408                                                             G_PARAM_READABLE |
1409                                                             G_PARAM_STATIC_STRINGS));
1410         g_object_class_install_property (object_class,
1411                                          PROP_MAX_SIZE,
1412                                          g_param_spec_uint ("avatar-max-size",
1413                                                             "Maximum size for avatars in bytes",
1414                                                             "Maximum file size of avatar that may be "
1415                                                             "set or 0 if there is no maximum.",
1416                                                             0,
1417                                                             G_MAXUINT,
1418                                                             0,
1419                                                             G_PARAM_READABLE |
1420                                                             G_PARAM_STATIC_STRINGS));
1421
1422
1423         g_type_class_add_private (object_class, sizeof (EmpathyTpContactFactoryPriv));
1424 }
1425
1426 static void
1427 empathy_tp_contact_factory_init (EmpathyTpContactFactory *tp_factory)
1428 {
1429         EmpathyTpContactFactoryPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (tp_factory,
1430                 EMPATHY_TYPE_TP_CONTACT_FACTORY, EmpathyTpContactFactoryPriv);
1431
1432         tp_factory->priv = priv;
1433         priv->can_request_ft = FALSE;
1434         priv->can_request_st = FALSE;
1435 }
1436
1437 static GHashTable *factories = NULL;
1438
1439 static void
1440 tp_contact_factory_connection_invalidated_cb (TpProxy *connection,
1441                                               guint    domain,
1442                                               gint     code,
1443                                               gchar   *message,
1444                                               gpointer user_data)
1445 {
1446         DEBUG ("Message: %s", message);
1447         g_hash_table_remove (factories, connection);
1448 }
1449
1450 static void
1451 tp_contact_factory_connection_weak_notify_cb (gpointer connection,
1452                                               GObject *where_the_object_was)
1453 {
1454         g_hash_table_remove (factories, connection);
1455 }
1456
1457 static void
1458 tp_contact_factory_remove_connection (gpointer connection)
1459 {
1460         g_signal_handlers_disconnect_by_func (connection,
1461                 tp_contact_factory_connection_invalidated_cb, NULL);
1462         g_object_unref (connection);
1463 }
1464
1465 EmpathyTpContactFactory *
1466 empathy_tp_contact_factory_dup_singleton (TpConnection *connection)
1467 {
1468         EmpathyTpContactFactory *tp_factory;
1469
1470         g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
1471
1472         if (factories == NULL) {
1473                 factories = g_hash_table_new_full (empathy_proxy_hash,
1474                                                    empathy_proxy_equal,
1475                                                    tp_contact_factory_remove_connection,
1476                                                    NULL);
1477         }
1478
1479         tp_factory = g_hash_table_lookup (factories, connection);
1480         if (tp_factory == NULL) {
1481                 tp_factory = g_object_new (EMPATHY_TYPE_TP_CONTACT_FACTORY,
1482                                            "connection", connection,
1483                                            NULL);
1484                 g_hash_table_insert (factories, g_object_ref (connection),
1485                                      tp_factory);
1486                 g_object_weak_ref (G_OBJECT (tp_factory),
1487                                    tp_contact_factory_connection_weak_notify_cb,
1488                                    connection);
1489                 g_signal_connect (connection, "invalidated",
1490                                   G_CALLBACK (tp_contact_factory_connection_invalidated_cb),
1491                                   NULL);
1492         } else {
1493                 g_object_ref (tp_factory);
1494         }
1495
1496         return tp_factory;
1497 }
1498