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