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