1 /*
2  * contacts-mixin.c - Source for TpContactsMixin
3  * Copyright © 2008-2010 Collabora Ltd.
4  * Copyright © 2008 Nokia Corporation
5  *   @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 /**
23  * SECTION:contacts-mixin
24  * @title: TpContactsMixin
25  * @short_description: a mixin implementation of the contacts connection
26  * interface
27  * @see_also: #TpSvcConnectionInterfaceContacts
28  *
29  * This mixin can be added to a #TpBaseConnection subclass to implement the
30  * Contacts interface in a generic way.
31  *
32  * To use the contacts mixin, include a #TpContactsMixinClass somewhere in
33  * your class structure and a #TpContactsMixin somewhere in your instance
34  * structure, and call tp_contacts_mixin_class_init() from your class_init
35  * function, tp_contacts_mixin_init() from your init function or constructor,
36  * and tp_contacts_mixin_finalize() from your dispose or finalize function.
37  *
38  * To use the contacts mixin as the implementation of
39  * #TpSvcConnectionInterfaceContacts, in the function you pass to
40  * G_IMPLEMENT_INTERFACE, you should call tp_contacts_mixin_iface_init.
41  * TpContactsMixin implements all of the D-Bus methods and properties in the
42  * Contacts interface.
43  *
44  * To add interfaces with contact attributes to this interface use
45  * tp_contacts_mixin_add_contact_attributes_iface:
46  *
47  * Since: 0.7.14
48  *
49  */
50 
51 #include "config.h"
52 
53 #include <telepathy-glib/contacts-mixin.h>
54 
55 #include <dbus/dbus-glib-lowlevel.h>
56 #include <dbus/dbus-glib.h>
57 
58 #include <telepathy-glib/base-connection.h>
59 #include <telepathy-glib/dbus.h>
60 #include <telepathy-glib/enums.h>
61 #include <telepathy-glib/errors.h>
62 #include <telepathy-glib/gtypes.h>
63 #include <telepathy-glib/interfaces.h>
64 
65 #define DEBUG_FLAG TP_DEBUG_CONNECTION
66 
67 #include "debug-internal.h"
68 
69 struct _TpContactsMixinPrivate
70 {
71   /* String interface name -> FillContactAttributes func */
72   GHashTable *interfaces;
73 };
74 
75 enum {
76   MIXIN_DP_CONTACT_ATTRIBUTE_INTERFACES,
77   NUM_MIXIN_CONTACTS_DBUS_PROPERTIES
78 };
79 
80 static TpDBusPropertiesMixinPropImpl known_contacts_props[] = {
81   { "ContactAttributeInterfaces", NULL, NULL },
82   { NULL }
83 };
84 
85 static const gchar *always_included_interfaces[] = {
86   TP_IFACE_CONNECTION,
87   NULL
88 };
89 
90 static void
tp_presence_mixin_get_contacts_dbus_property(GObject * object,GQuark interface,GQuark name,GValue * value,gpointer unused G_GNUC_UNUSED)91 tp_presence_mixin_get_contacts_dbus_property (GObject *object,
92                                               GQuark interface,
93                                               GQuark name,
94                                               GValue *value,
95                                               gpointer unused
96                                               G_GNUC_UNUSED)
97 {
98   static GQuark q[NUM_MIXIN_CONTACTS_DBUS_PROPERTIES] = { 0, };
99   TpContactsMixin *self = TP_CONTACTS_MIXIN (object);
100 
101   DEBUG ("called.");
102 
103   if (G_UNLIKELY (q[0] == 0))
104     {
105       q[MIXIN_DP_CONTACT_ATTRIBUTE_INTERFACES] =
106         g_quark_from_static_string ("ContactAttributeInterfaces");
107     }
108 
109   g_return_if_fail (object != NULL);
110 
111   if (name == q[MIXIN_DP_CONTACT_ATTRIBUTE_INTERFACES])
112     {
113       gchar **interfaces;
114       GHashTableIter iter;
115       gpointer key;
116       int i = 0;
117 
118       g_assert (G_VALUE_HOLDS(value, G_TYPE_STRV));
119 
120       /* FIXME, cache this when connected ? */
121       interfaces = g_malloc0(
122         (g_hash_table_size (self->priv->interfaces) + 1) * sizeof (gchar *));
123 
124       g_hash_table_iter_init (&iter, self->priv->interfaces);
125       while (g_hash_table_iter_next (&iter, &key, NULL))
126           {
127             interfaces[i] = g_strdup ((gchar *) key);
128             i++;
129           }
130       g_value_take_boxed (value, interfaces);
131     }
132   else
133     {
134       g_assert_not_reached ();
135     }
136 }
137 
138 
139 /**
140  * tp_contacts_mixin_class_get_offset_quark: (skip)
141  *
142  * <!--no documentation beyond Returns: needed-->
143  *
144  * Returns: the quark used for storing mixin offset on a GObjectClass
145  *
146  * Since: 0.7.14
147  *
148  */
149 GQuark
tp_contacts_mixin_class_get_offset_quark()150 tp_contacts_mixin_class_get_offset_quark ()
151 {
152   static GQuark offset_quark = 0;
153 
154   if (G_UNLIKELY (offset_quark == 0))
155     offset_quark = g_quark_from_static_string (
156         "TpContactsMixinClassOffsetQuark");
157 
158   return offset_quark;
159 }
160 
161 /**
162  * tp_contacts_mixin_get_offset_quark: (skip)
163  *
164  * <!--no documentation beyond Returns: needed-->
165  *
166  * Returns: the quark used for storing mixin offset on a GObject
167  *
168  * Since: 0.7.14
169  *
170  */
171 GQuark
tp_contacts_mixin_get_offset_quark()172 tp_contacts_mixin_get_offset_quark ()
173 {
174   static GQuark offset_quark = 0;
175 
176   if (G_UNLIKELY (offset_quark == 0))
177     offset_quark = g_quark_from_static_string ("TpContactsMixinOffsetQuark");
178 
179   return offset_quark;
180 }
181 
182 
183 /**
184  * tp_contacts_mixin_class_init: (skip)
185  * @obj_cls: The class of the implementation that uses this mixin
186  * @offset: The byte offset of the TpContactsMixinClass within the class
187  *          structure
188  *
189  * Initialize the contacts mixin. Should be called from the implementation's
190  * class_init function like so:
191  *
192  * <informalexample><programlisting>
193  * tp_contacts_mixin_class_init ((GObjectClass *) klass,
194  *                          G_STRUCT_OFFSET (SomeObjectClass, contacts_mixin));
195  * </programlisting></informalexample>
196  *
197  * Since: 0.7.14
198  *
199  */
200 
201 void
tp_contacts_mixin_class_init(GObjectClass * obj_cls,glong offset)202 tp_contacts_mixin_class_init (GObjectClass *obj_cls, glong offset)
203 {
204   g_assert (G_IS_OBJECT_CLASS (obj_cls));
205 
206   g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
207       TP_CONTACTS_MIXIN_CLASS_OFFSET_QUARK,
208       GINT_TO_POINTER (offset));
209 
210   tp_dbus_properties_mixin_implement_interface (obj_cls,
211       TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS,
212       tp_presence_mixin_get_contacts_dbus_property,
213       NULL, known_contacts_props);
214 }
215 
216 
217 /**
218  * tp_contacts_mixin_init: (skip)
219  * @obj: An instance of the implementation that uses this mixin
220  * @offset: The byte offset of the TpContactsMixin within the object structure
221  *
222  * Initialize the contacts mixin. Should be called from the implementation's
223  * instance init function like so:
224  *
225  * <informalexample><programlisting>
226  * tp_contacts_mixin_init ((GObject *) self,
227  *                     G_STRUCT_OFFSET (SomeObject, contacts_mixin));
228  * </programlisting></informalexample>
229  *
230  * Since: 0.7.14
231  *
232  */
233 void
tp_contacts_mixin_init(GObject * obj,gsize offset)234 tp_contacts_mixin_init (GObject *obj, gsize offset)
235 {
236   TpContactsMixin *mixin;
237 
238   g_assert (G_IS_OBJECT (obj));
239 
240   g_type_set_qdata (G_OBJECT_TYPE (obj),
241                     TP_CONTACTS_MIXIN_OFFSET_QUARK,
242                     GSIZE_TO_POINTER (offset));
243 
244   mixin = TP_CONTACTS_MIXIN (obj);
245 
246   mixin->priv = g_slice_new0 (TpContactsMixinPrivate);
247   mixin->priv->interfaces = g_hash_table_new_full (g_str_hash, g_str_equal,
248     g_free, NULL);
249 }
250 
251 /**
252  * tp_contacts_mixin_finalize: (skip)
253  * @obj: An object with this mixin.
254  *
255  * Free resources held by the contacts mixin.
256  *
257  * Since: 0.7.14
258  *
259  */
260 void
tp_contacts_mixin_finalize(GObject * obj)261 tp_contacts_mixin_finalize (GObject *obj)
262 {
263   TpContactsMixin *mixin = TP_CONTACTS_MIXIN (obj);
264 
265   DEBUG ("%p", obj);
266 
267   /* free any data held directly by the object here */
268   g_hash_table_unref (mixin->priv->interfaces);
269   g_slice_free (TpContactsMixinPrivate, mixin->priv);
270 }
271 
272 /**
273  * tp_contacts_mixin_get_contact_attributes: (skip)
274  * @obj: A connection instance that uses this mixin. The connection must be connected.
275  * @handles: List of handles to retrieve contacts for. Any invalid handles will be
276  * dropped from the returned mapping.
277  * @interfaces: A list of interfaces to retrieve attributes from.
278  * @assumed_interfaces: A list of additional interfaces to retrieve attributes
279  *  from. This can be used for interfaces documented as automatically included,
280  *  like %TP_IFACE_CONNECTION for GetContactAttributes,
281  *  or %TP_IFACE_CONNECTION and %TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST for
282  *  GetContactListAttributes.
283  * @sender: The DBus client's unique name. If this is not NULL, the requested handles
284  * will be held on behalf of this client.
285  *
286  * Get contact attributes for the given contacts. Provide attributes for all requested
287  * interfaces. If contact attributes are not immediately known, the behaviour is defined
288  * by the interface; the attribute should either be omitted from the result or replaced
289  * with a default value.
290  *
291  * Returns: A dictionary mapping the contact handles to contact attributes.
292  *
293  */
294 GHashTable *
tp_contacts_mixin_get_contact_attributes(GObject * obj,const GArray * handles,const gchar ** interfaces,const gchar ** assumed_interfaces,const gchar * sender)295 tp_contacts_mixin_get_contact_attributes (GObject *obj,
296     const GArray *handles,
297     const gchar **interfaces,
298     const gchar **assumed_interfaces,
299     const gchar *sender)
300 {
301   GHashTable *result;
302   guint i;
303   TpBaseConnection *conn = TP_BASE_CONNECTION (obj);
304   TpContactsMixin *self = TP_CONTACTS_MIXIN (obj);
305   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
306         TP_HANDLE_TYPE_CONTACT);
307   GArray *valid_handles;
308   TpContactsMixinFillContactAttributesFunc func;
309 
310   g_return_val_if_fail (TP_IS_BASE_CONNECTION (obj), NULL);
311   g_return_val_if_fail (TP_CONTACTS_MIXIN_OFFSET (obj) != 0, NULL);
312   g_return_val_if_fail (tp_base_connection_check_connected (conn, NULL), NULL);
313 
314   /* Setup handle array and hash with valid handles, optionally holding them */
315   valid_handles = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle),
316       handles->len);
317   result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
318       (GDestroyNotify) g_hash_table_unref);
319 
320   for (i = 0 ; i < handles->len ; i++)
321     {
322       TpHandle h;
323       h = g_array_index (handles, TpHandle, i);
324       if (tp_handle_is_valid (contact_repo, h, NULL))
325         {
326           GHashTable *attr_hash = g_hash_table_new_full (g_str_hash,
327               g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free);
328           g_array_append_val (valid_handles, h);
329           g_hash_table_insert (result, GUINT_TO_POINTER(h), attr_hash);
330         }
331     }
332 
333   for (i = 0; assumed_interfaces != NULL && assumed_interfaces[i] != NULL; i++)
334     {
335       func = g_hash_table_lookup (self->priv->interfaces, assumed_interfaces[i]);
336 
337       if (func == NULL)
338         DEBUG ("non-inspectable assumed interface %s given; ignoring",
339             assumed_interfaces[i]);
340       else
341         func (obj, valid_handles, result);
342     }
343 
344   for (i = 0; interfaces != NULL && interfaces[i] != NULL; i++)
345     {
346 
347       func = g_hash_table_lookup (self->priv->interfaces, interfaces[i]);
348 
349       if (func == NULL)
350         DEBUG ("non-inspectable interface %s given; ignoring", interfaces[i]);
351       else
352         func (obj, valid_handles, result);
353     }
354 
355   g_array_unref (valid_handles);
356 
357   return result;
358 }
359 
360 static void
tp_contacts_mixin_get_contact_attributes_impl(TpSvcConnectionInterfaceContacts * iface,const GArray * handles,const char ** interfaces,gboolean hold,DBusGMethodInvocation * context)361 tp_contacts_mixin_get_contact_attributes_impl (
362   TpSvcConnectionInterfaceContacts *iface,
363   const GArray *handles,
364   const char **interfaces,
365   gboolean hold,
366   DBusGMethodInvocation *context)
367 {
368   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
369   GHashTable *result;
370 
371   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
372 
373   result = tp_contacts_mixin_get_contact_attributes (G_OBJECT (conn),
374       handles, interfaces, always_included_interfaces, NULL);
375 
376   tp_svc_connection_interface_contacts_return_from_get_contact_attributes (
377       context, result);
378 
379   g_hash_table_unref (result);
380 }
381 
382 typedef struct
383 {
384   TpBaseConnection *conn;
385   GStrv interfaces;
386   DBusGMethodInvocation *context;
387 } GetContactByIdData;
388 
389 static void
ensure_handle_cb(GObject * source,GAsyncResult * result,gpointer user_data)390 ensure_handle_cb (GObject *source,
391     GAsyncResult *result,
392     gpointer user_data)
393 {
394   TpHandleRepoIface *contact_repo = (TpHandleRepoIface *) source;
395   GetContactByIdData *data = user_data;
396   TpHandle handle;
397   GArray *handles;
398   GHashTable *attributes;
399   GHashTable *ret;
400   GError *error = NULL;
401 
402   handle = tp_handle_ensure_finish (contact_repo, result, &error);
403   if (handle == 0)
404     {
405       dbus_g_method_return_error (data->context, error);
406       g_clear_error (&error);
407       goto out;
408     }
409 
410   handles = g_array_new (FALSE, FALSE, sizeof (TpHandle));
411   g_array_append_val (handles, handle);
412 
413   attributes = tp_contacts_mixin_get_contact_attributes (G_OBJECT (data->conn),
414       handles, (const gchar **) data->interfaces, always_included_interfaces,
415       NULL);
416 
417   ret = g_hash_table_lookup (attributes, GUINT_TO_POINTER (handle));
418   g_assert (ret != NULL);
419 
420   tp_svc_connection_interface_contacts_return_from_get_contact_by_id (
421       data->context, handle, ret);
422 
423   g_array_unref (handles);
424   g_hash_table_unref (attributes);
425 
426 out:
427   g_object_unref (data->conn);
428   g_strfreev (data->interfaces);
429   g_slice_free (GetContactByIdData, data);
430 }
431 
432 static void
tp_contacts_mixin_get_contact_by_id_impl(TpSvcConnectionInterfaceContacts * iface,const gchar * id,const gchar ** interfaces,DBusGMethodInvocation * context)433 tp_contacts_mixin_get_contact_by_id_impl (
434   TpSvcConnectionInterfaceContacts *iface,
435   const gchar *id,
436   const gchar **interfaces,
437   DBusGMethodInvocation *context)
438 {
439   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
440   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
441       TP_HANDLE_TYPE_CONTACT);
442   GetContactByIdData *data;
443 
444   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
445 
446   data = g_slice_new0 (GetContactByIdData);
447   data->conn = g_object_ref (conn);
448   data->interfaces = g_strdupv ((gchar **) interfaces);
449   data->context = context;
450 
451   tp_handle_ensure_async (contact_repo, conn, id, NULL,
452       ensure_handle_cb, data);
453 }
454 
455 /**
456  * tp_contacts_mixin_iface_init: (skip)
457  * @g_iface: A pointer to the #TpSvcConnectionInterfaceContacts in an object
458  * class
459  * @iface_data: Ignored
460  *
461  * Fill in the vtable entries needed to implement the contacts interface
462  * using this mixin. This function should usually be called via
463  * G_IMPLEMENT_INTERFACE.
464  *
465  * Since: 0.7.14
466  *
467  */
468 void
tp_contacts_mixin_iface_init(gpointer g_iface,gpointer iface_data)469 tp_contacts_mixin_iface_init (gpointer g_iface, gpointer iface_data)
470 {
471   TpSvcConnectionInterfaceContactsClass *klass =
472     (TpSvcConnectionInterfaceContactsClass *) g_iface;
473 
474 #define IMPLEMENT(x) tp_svc_connection_interface_contacts_implement_##x ( \
475     klass, tp_contacts_mixin_##x##_impl)
476   IMPLEMENT(get_contact_attributes);
477   IMPLEMENT(get_contact_by_id);
478 #undef IMPLEMENT
479 }
480 
481 /**
482  * tp_contacts_mixin_add_contact_attributes_iface: (skip)
483  * @obj: An instance of the implementation that uses this mixin
484  * @interface: Name of the interface that has ContactAttributes
485  * @fill_contact_attributes: Contact attribute filler function
486  *
487  * Declare that the given interface has contact attributes which can be added
488  * to the attributes hash using the filler function. All the handles in the
489  * handle array passed to the filler function are guaranteed to be valid and
490  * referenced.
491  *
492  * Since: 0.7.14
493  *
494  */
495 
496 void
tp_contacts_mixin_add_contact_attributes_iface(GObject * obj,const gchar * interface,TpContactsMixinFillContactAttributesFunc fill_contact_attributes)497 tp_contacts_mixin_add_contact_attributes_iface (GObject *obj,
498     const gchar *interface,
499     TpContactsMixinFillContactAttributesFunc fill_contact_attributes)
500 {
501   TpContactsMixin *self = TP_CONTACTS_MIXIN (obj);
502 
503   g_assert (g_hash_table_lookup (self->priv->interfaces, interface) == NULL);
504   g_assert (fill_contact_attributes != NULL);
505 
506   g_hash_table_insert (self->priv->interfaces, g_strdup (interface),
507     fill_contact_attributes);
508 }
509 
510 /**
511  * tp_contacts_mixin_set_contact_attribute: (skip)
512  * @contact_attributes: contacts attribute hash as passed to
513  *   TpContactsMixinFillContactAttributesFunc
514  * @handle: Handle to set the attribute on
515  * @attribute: attribute name
516  * @value: slice allocated GValue containing the value of the attribute, for
517  * instance with tp_g_value_slice_new. Ownership of the GValue is taken over by
518  * the mixin
519  *
520  * Utility function to set attribute for handle to value in the attributes hash
521  * as passed to a TpContactsMixinFillContactAttributesFunc.
522  *
523  * Since: 0.7.14
524  *
525  */
526 
527 void
tp_contacts_mixin_set_contact_attribute(GHashTable * contact_attributes,TpHandle handle,const gchar * attribute,GValue * value)528 tp_contacts_mixin_set_contact_attribute (GHashTable *contact_attributes,
529     TpHandle handle, const gchar *attribute, GValue *value)
530 {
531   GHashTable *attributes;
532 
533   attributes = g_hash_table_lookup (contact_attributes,
534     GUINT_TO_POINTER (handle));
535 
536   g_assert (attributes != NULL);
537   g_assert (G_IS_VALUE (value));
538 
539   g_hash_table_insert (attributes, g_strdup (attribute), value);
540 }
541 
542