1 /*
2 * conn.c - an example connection
3 *
4 * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
5 * Copyright © 2007-2009 Nokia Corporation
6 *
7 * Copying and distribution of this file, with or without modification,
8 * are permitted in any medium without royalty provided the copyright
9 * notice and this notice are preserved.
10 */
11
12 #include "config.h"
13
14 #include "conn.h"
15
16 #include <dbus/dbus-glib.h>
17
18 #include <telepathy-glib/telepathy-glib.h>
19
20 #include "contact-list.h"
21 #include "protocol.h"
22
23 static void init_aliasing (gpointer, gpointer);
24
25 G_DEFINE_TYPE_WITH_CODE (ExampleContactListConnection,
26 example_contact_list_connection,
27 TP_TYPE_BASE_CONNECTION,
28 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
29 init_aliasing);
30 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
31 tp_contacts_mixin_iface_init);
32 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST,
33 tp_base_contact_list_mixin_list_iface_init);
34 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS,
35 tp_base_contact_list_mixin_groups_iface_init);
36 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING,
37 tp_base_contact_list_mixin_blocking_iface_init);
38 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
39 tp_presence_mixin_iface_init);
40 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
41 tp_presence_mixin_simple_presence_iface_init))
42
43 enum
44 {
45 PROP_ACCOUNT = 1,
46 PROP_SIMULATION_DELAY,
47 N_PROPS
48 };
49
50 struct _ExampleContactListConnectionPrivate
51 {
52 gchar *account;
53 guint simulation_delay;
54 ExampleContactList *contact_list;
55 gboolean away;
56 };
57
58 static void
example_contact_list_connection_init(ExampleContactListConnection * self)59 example_contact_list_connection_init (ExampleContactListConnection *self)
60 {
61 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
62 EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
63 ExampleContactListConnectionPrivate);
64 }
65
66 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * spec)67 get_property (GObject *object,
68 guint property_id,
69 GValue *value,
70 GParamSpec *spec)
71 {
72 ExampleContactListConnection *self =
73 EXAMPLE_CONTACT_LIST_CONNECTION (object);
74
75 switch (property_id)
76 {
77 case PROP_ACCOUNT:
78 g_value_set_string (value, self->priv->account);
79 break;
80
81 case PROP_SIMULATION_DELAY:
82 g_value_set_uint (value, self->priv->simulation_delay);
83 break;
84
85 default:
86 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
87 }
88 }
89
90 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * spec)91 set_property (GObject *object,
92 guint property_id,
93 const GValue *value,
94 GParamSpec *spec)
95 {
96 ExampleContactListConnection *self =
97 EXAMPLE_CONTACT_LIST_CONNECTION (object);
98
99 switch (property_id)
100 {
101 case PROP_ACCOUNT:
102 g_free (self->priv->account);
103 self->priv->account = g_value_dup_string (value);
104 break;
105
106 case PROP_SIMULATION_DELAY:
107 self->priv->simulation_delay = g_value_get_uint (value);
108 break;
109
110 default:
111 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
112 }
113 }
114
115 static void
finalize(GObject * object)116 finalize (GObject *object)
117 {
118 ExampleContactListConnection *self =
119 EXAMPLE_CONTACT_LIST_CONNECTION (object);
120
121 tp_contacts_mixin_finalize (object);
122 g_free (self->priv->account);
123
124 G_OBJECT_CLASS (example_contact_list_connection_parent_class)->finalize (
125 object);
126 }
127
128 static gchar *
get_unique_connection_name(TpBaseConnection * conn)129 get_unique_connection_name (TpBaseConnection *conn)
130 {
131 ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
132
133 return g_strdup_printf ("%s@%p", self->priv->account, self);
134 }
135
136 gchar *
example_contact_list_normalize_contact(TpHandleRepoIface * repo,const gchar * id,gpointer context,GError ** error)137 example_contact_list_normalize_contact (TpHandleRepoIface *repo,
138 const gchar *id,
139 gpointer context,
140 GError **error)
141 {
142 gchar *normal = NULL;
143
144 if (example_contact_list_protocol_check_contact_id (id, &normal, error))
145 return normal;
146 else
147 return NULL;
148 }
149
150 static void
create_handle_repos(TpBaseConnection * conn,TpHandleRepoIface * repos[TP_NUM_HANDLE_TYPES])151 create_handle_repos (TpBaseConnection *conn,
152 TpHandleRepoIface *repos[TP_NUM_HANDLE_TYPES])
153 {
154 repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
155 (TP_HANDLE_TYPE_CONTACT, example_contact_list_normalize_contact, NULL);
156 }
157
158 static void
alias_updated_cb(ExampleContactList * contact_list,TpHandle contact,ExampleContactListConnection * self)159 alias_updated_cb (ExampleContactList *contact_list,
160 TpHandle contact,
161 ExampleContactListConnection *self)
162 {
163 GPtrArray *aliases;
164 GValueArray *pair;
165
166 pair = tp_value_array_build (2,
167 G_TYPE_UINT, contact,
168 G_TYPE_STRING, example_contact_list_get_alias (contact_list, contact),
169 G_TYPE_INVALID);
170
171 aliases = g_ptr_array_sized_new (1);
172 g_ptr_array_add (aliases, pair);
173
174 tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
175
176 g_ptr_array_unref (aliases);
177 tp_value_array_free (pair);
178 }
179
180 static void
presence_updated_cb(ExampleContactList * contact_list,TpHandle contact,ExampleContactListConnection * self)181 presence_updated_cb (ExampleContactList *contact_list,
182 TpHandle contact,
183 ExampleContactListConnection *self)
184 {
185 TpBaseConnection *base = (TpBaseConnection *) self;
186 TpPresenceStatus *status;
187
188 /* we ignore the presence indicated by the contact list for our own handle */
189 if (contact == tp_base_connection_get_self_handle (base))
190 return;
191
192 status = tp_presence_status_new (
193 example_contact_list_get_presence (contact_list, contact),
194 NULL);
195 tp_presence_mixin_emit_one_presence_update ((GObject *) self,
196 contact, status);
197 tp_presence_status_free (status);
198 }
199
200 static GPtrArray *
create_channel_managers(TpBaseConnection * conn)201 create_channel_managers (TpBaseConnection *conn)
202 {
203 ExampleContactListConnection *self =
204 EXAMPLE_CONTACT_LIST_CONNECTION (conn);
205 GPtrArray *ret = g_ptr_array_sized_new (1);
206
207 self->priv->contact_list = EXAMPLE_CONTACT_LIST (g_object_new (
208 EXAMPLE_TYPE_CONTACT_LIST,
209 "connection", conn,
210 "simulation-delay", self->priv->simulation_delay,
211 NULL));
212
213 g_signal_connect (self->priv->contact_list, "alias-updated",
214 G_CALLBACK (alias_updated_cb), self);
215 g_signal_connect (self->priv->contact_list, "presence-updated",
216 G_CALLBACK (presence_updated_cb), self);
217
218 g_ptr_array_add (ret, self->priv->contact_list);
219
220 return ret;
221 }
222
223 static gboolean
start_connecting(TpBaseConnection * conn,GError ** error)224 start_connecting (TpBaseConnection *conn,
225 GError **error)
226 {
227 ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
228 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
229 TP_HANDLE_TYPE_CONTACT);
230 TpHandle self_handle;
231
232 /* In a real connection manager we'd ask the underlying implementation to
233 * start connecting, then go to state CONNECTED when finished, but here
234 * we can do it immediately. */
235
236 self_handle = tp_handle_ensure (contact_repo, self->priv->account,
237 NULL, error);
238
239 if (self_handle == 0)
240 return FALSE;
241
242 tp_base_connection_set_self_handle (conn, self_handle);
243
244 tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
245 TP_CONNECTION_STATUS_REASON_REQUESTED);
246
247 return TRUE;
248 }
249
250 static void
shut_down(TpBaseConnection * conn)251 shut_down (TpBaseConnection *conn)
252 {
253 /* In a real connection manager we'd ask the underlying implementation to
254 * start shutting down, then call this function when finished, but here
255 * we can do it immediately. */
256 tp_base_connection_finish_shutdown (conn);
257 }
258
259 static void
aliasing_fill_contact_attributes(GObject * object,const GArray * contacts,GHashTable * attributes)260 aliasing_fill_contact_attributes (GObject *object,
261 const GArray *contacts,
262 GHashTable *attributes)
263 {
264 ExampleContactListConnection *self =
265 EXAMPLE_CONTACT_LIST_CONNECTION (object);
266 guint i;
267
268 for (i = 0; i < contacts->len; i++)
269 {
270 TpHandle contact = g_array_index (contacts, guint, i);
271
272 tp_contacts_mixin_set_contact_attribute (attributes, contact,
273 TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
274 tp_g_value_slice_new_string (
275 example_contact_list_get_alias (self->priv->contact_list,
276 contact)));
277 }
278 }
279
280 static void
constructed(GObject * object)281 constructed (GObject *object)
282 {
283 TpBaseConnection *base = TP_BASE_CONNECTION (object);
284 void (*chain_up) (GObject *) =
285 G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed;
286
287 if (chain_up != NULL)
288 chain_up (object);
289
290 tp_contacts_mixin_init (object,
291 G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin));
292
293 tp_base_connection_register_with_contacts_mixin (base);
294 tp_base_contact_list_mixin_register_with_contacts_mixin (base);
295
296 tp_contacts_mixin_add_contact_attributes_iface (object,
297 TP_IFACE_CONNECTION_INTERFACE_ALIASING,
298 aliasing_fill_contact_attributes);
299
300 tp_presence_mixin_init (object,
301 G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin));
302 tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
303 }
304
305 static gboolean
status_available(GObject * object,guint index_)306 status_available (GObject *object,
307 guint index_)
308 {
309 TpBaseConnection *base = TP_BASE_CONNECTION (object);
310
311 return tp_base_connection_check_connected (base, NULL);
312 }
313
314 static GHashTable *
get_contact_statuses(GObject * object,const GArray * contacts,GError ** error)315 get_contact_statuses (GObject *object,
316 const GArray *contacts,
317 GError **error)
318 {
319 ExampleContactListConnection *self =
320 EXAMPLE_CONTACT_LIST_CONNECTION (object);
321 TpBaseConnection *base = TP_BASE_CONNECTION (object);
322 guint i;
323 GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
324 NULL, (GDestroyNotify) tp_presence_status_free);
325
326 for (i = 0; i < contacts->len; i++)
327 {
328 TpHandle contact = g_array_index (contacts, guint, i);
329 ExampleContactListPresence presence;
330 GHashTable *parameters;
331
332 /* we get our own status from the connection, and everyone else's status
333 * from the contact lists */
334 if (contact == tp_base_connection_get_self_handle (base))
335 {
336 presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY
337 : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE);
338 }
339 else
340 {
341 presence = example_contact_list_get_presence (
342 self->priv->contact_list, contact);
343 }
344
345 parameters = g_hash_table_new_full (g_str_hash,
346 g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
347 g_hash_table_insert (result, GUINT_TO_POINTER (contact),
348 tp_presence_status_new (presence, parameters));
349 g_hash_table_unref (parameters);
350 }
351
352 return result;
353 }
354
355 static gboolean
set_own_status(GObject * object,const TpPresenceStatus * status,GError ** error)356 set_own_status (GObject *object,
357 const TpPresenceStatus *status,
358 GError **error)
359 {
360 ExampleContactListConnection *self =
361 EXAMPLE_CONTACT_LIST_CONNECTION (object);
362 TpBaseConnection *base = TP_BASE_CONNECTION (object);
363 GHashTable *presences;
364
365 if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY)
366 {
367 if (self->priv->away)
368 return TRUE;
369
370 self->priv->away = TRUE;
371 }
372 else
373 {
374 if (!self->priv->away)
375 return TRUE;
376
377 self->priv->away = FALSE;
378 }
379
380 presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
381 NULL, NULL);
382 g_hash_table_insert (presences,
383 GUINT_TO_POINTER (tp_base_connection_get_self_handle (base)),
384 (gpointer) status);
385 tp_presence_mixin_emit_presence_update (object, presences);
386 g_hash_table_unref (presences);
387 return TRUE;
388 }
389
390 static const gchar *interfaces_always_present[] = {
391 TP_IFACE_CONNECTION_INTERFACE_ALIASING,
392 TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
393 TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
394 TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
395 TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING,
396 TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
397 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
398 TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
399 NULL };
400
401 const gchar * const *
example_contact_list_connection_get_possible_interfaces(void)402 example_contact_list_connection_get_possible_interfaces (void)
403 {
404 /* in this example CM we don't have any extra interfaces that are sometimes,
405 * but not always, present */
406 return interfaces_always_present;
407 }
408
409 static GPtrArray *
get_interfaces_always_present(TpBaseConnection * base)410 get_interfaces_always_present (TpBaseConnection *base)
411 {
412 GPtrArray *interfaces;
413 guint i;
414
415 interfaces = TP_BASE_CONNECTION_CLASS (
416 example_contact_list_connection_parent_class)->get_interfaces_always_present (
417 base);
418
419 for (i = 0; interfaces_always_present[i] != NULL; i++)
420 g_ptr_array_add (interfaces, (gchar *) interfaces_always_present[i]);
421
422 return interfaces;
423 }
424
425 static void
example_contact_list_connection_class_init(ExampleContactListConnectionClass * klass)426 example_contact_list_connection_class_init (
427 ExampleContactListConnectionClass *klass)
428 {
429 TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
430 GObjectClass *object_class = (GObjectClass *) klass;
431 GParamSpec *param_spec;
432
433 object_class->get_property = get_property;
434 object_class->set_property = set_property;
435 object_class->constructed = constructed;
436 object_class->finalize = finalize;
437 g_type_class_add_private (klass,
438 sizeof (ExampleContactListConnectionPrivate));
439
440 base_class->create_handle_repos = create_handle_repos;
441 base_class->get_unique_connection_name = get_unique_connection_name;
442 base_class->create_channel_managers = create_channel_managers;
443 base_class->start_connecting = start_connecting;
444 base_class->shut_down = shut_down;
445 base_class->get_interfaces_always_present = get_interfaces_always_present;
446
447 param_spec = g_param_spec_string ("account", "Account name",
448 "The username of this user", NULL,
449 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
450 g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
451
452 param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
453 "Delay between simulated network events",
454 0, G_MAXUINT32, 1000,
455 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
456 g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
457 param_spec);
458
459 tp_contacts_mixin_class_init (object_class,
460 G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin));
461
462 tp_presence_mixin_class_init (object_class,
463 G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin),
464 status_available, get_contact_statuses, set_own_status,
465 example_contact_list_presence_statuses ());
466 tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
467
468 tp_base_contact_list_mixin_class_init (base_class);
469 }
470
471 static void
get_alias_flags(TpSvcConnectionInterfaceAliasing * aliasing,DBusGMethodInvocation * context)472 get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
473 DBusGMethodInvocation *context)
474 {
475 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
476
477 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
478 tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
479 TP_CONNECTION_ALIAS_FLAG_USER_SET);
480 }
481
482 static void
get_aliases(TpSvcConnectionInterfaceAliasing * aliasing,const GArray * contacts,DBusGMethodInvocation * context)483 get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
484 const GArray *contacts,
485 DBusGMethodInvocation *context)
486 {
487 ExampleContactListConnection *self =
488 EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
489 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
490 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
491 TP_HANDLE_TYPE_CONTACT);
492 GHashTable *result;
493 GError *error = NULL;
494 guint i;
495
496 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
497
498 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
499 {
500 dbus_g_method_return_error (context, error);
501 g_error_free (error);
502 return;
503 }
504
505 result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
506
507 for (i = 0; i < contacts->len; i++)
508 {
509 TpHandle contact = g_array_index (contacts, TpHandle, i);
510 const gchar *alias = example_contact_list_get_alias (
511 self->priv->contact_list, contact);
512
513 g_hash_table_insert (result, GUINT_TO_POINTER (contact),
514 (gchar *) alias);
515 }
516
517 tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
518 result);
519 g_hash_table_unref (result);
520 }
521
522 static void
request_aliases(TpSvcConnectionInterfaceAliasing * aliasing,const GArray * contacts,DBusGMethodInvocation * context)523 request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
524 const GArray *contacts,
525 DBusGMethodInvocation *context)
526 {
527 ExampleContactListConnection *self =
528 EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
529 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
530 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
531 TP_HANDLE_TYPE_CONTACT);
532 GPtrArray *result;
533 gchar **strings;
534 GError *error = NULL;
535 guint i;
536
537 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
538
539 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
540 {
541 dbus_g_method_return_error (context, error);
542 g_error_free (error);
543 return;
544 }
545
546 result = g_ptr_array_sized_new (contacts->len + 1);
547
548 for (i = 0; i < contacts->len; i++)
549 {
550 TpHandle contact = g_array_index (contacts, TpHandle, i);
551 const gchar *alias = example_contact_list_get_alias (
552 self->priv->contact_list, contact);
553
554 g_ptr_array_add (result, (gchar *) alias);
555 }
556
557 g_ptr_array_add (result, NULL);
558 strings = (gchar **) g_ptr_array_free (result, FALSE);
559 tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
560 (const gchar **) strings);
561 g_free (strings);
562 }
563
564 static void
set_aliases(TpSvcConnectionInterfaceAliasing * aliasing,GHashTable * aliases,DBusGMethodInvocation * context)565 set_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
566 GHashTable *aliases,
567 DBusGMethodInvocation *context)
568 {
569 ExampleContactListConnection *self =
570 EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
571 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
572 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
573 TP_HANDLE_TYPE_CONTACT);
574 GHashTableIter iter;
575 gpointer key, value;
576
577 g_hash_table_iter_init (&iter, aliases);
578
579 while (g_hash_table_iter_next (&iter, &key, &value))
580 {
581 GError *error = NULL;
582
583 if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key),
584 &error))
585 {
586 dbus_g_method_return_error (context, error);
587 g_error_free (error);
588 return;
589 }
590 }
591
592 g_hash_table_iter_init (&iter, aliases);
593
594 while (g_hash_table_iter_next (&iter, &key, &value))
595 {
596 example_contact_list_set_alias (self->priv->contact_list,
597 GPOINTER_TO_UINT (key), value);
598 }
599
600 tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
601 }
602
603 static void
init_aliasing(gpointer iface,gpointer iface_data G_GNUC_UNUSED)604 init_aliasing (gpointer iface,
605 gpointer iface_data G_GNUC_UNUSED)
606 {
607 TpSvcConnectionInterfaceAliasingClass *klass = iface;
608
609 #define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
610 klass, x)
611 IMPLEMENT(get_alias_flags);
612 IMPLEMENT(request_aliases);
613 IMPLEMENT(get_aliases);
614 IMPLEMENT(set_aliases);
615 #undef IMPLEMENT
616 }
617