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