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 "conn.h"
13
14 #include <dbus/dbus-glib.h>
15
16 #include <telepathy-glib/telepathy-glib.h>
17 #include <telepathy-glib/handle-repo-dynamic.h>
18 #include <telepathy-glib/handle-repo-static.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[NUM_TP_HANDLE_TYPES])151 create_handle_repos (TpBaseConnection *conn,
152 TpHandleRepoIface *repos[NUM_TP_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 = g_value_array_new (2);
167 g_value_array_append (pair, NULL);
168 g_value_array_append (pair, NULL);
169 g_value_init (pair->values + 0, G_TYPE_UINT);
170 g_value_init (pair->values + 1, G_TYPE_STRING);
171 g_value_set_uint (pair->values + 0, contact);
172 g_value_set_string (pair->values + 1,
173 example_contact_list_get_alias (contact_list, contact));
174
175 aliases = g_ptr_array_sized_new (1);
176 g_ptr_array_add (aliases, pair);
177
178 tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
179
180 g_ptr_array_free (aliases, TRUE);
181 g_value_array_free (pair);
182 }
183
184 static void
presence_updated_cb(ExampleContactList * contact_list,TpHandle contact,ExampleContactListConnection * self)185 presence_updated_cb (ExampleContactList *contact_list,
186 TpHandle contact,
187 ExampleContactListConnection *self)
188 {
189 TpBaseConnection *base = (TpBaseConnection *) self;
190 TpPresenceStatus *status;
191
192 /* we ignore the presence indicated by the contact list for our own handle */
193 if (contact == base->self_handle)
194 return;
195
196 status = tp_presence_status_new (
197 example_contact_list_get_presence (contact_list, contact),
198 NULL);
199 tp_presence_mixin_emit_one_presence_update ((GObject *) self,
200 contact, status);
201 tp_presence_status_free (status);
202 }
203
204 static GPtrArray *
create_channel_managers(TpBaseConnection * conn)205 create_channel_managers (TpBaseConnection *conn)
206 {
207 ExampleContactListConnection *self =
208 EXAMPLE_CONTACT_LIST_CONNECTION (conn);
209 GPtrArray *ret = g_ptr_array_sized_new (1);
210
211 self->priv->contact_list = EXAMPLE_CONTACT_LIST (g_object_new (
212 EXAMPLE_TYPE_CONTACT_LIST,
213 "connection", conn,
214 "simulation-delay", self->priv->simulation_delay,
215 NULL));
216
217 g_signal_connect (self->priv->contact_list, "alias-updated",
218 G_CALLBACK (alias_updated_cb), self);
219 g_signal_connect (self->priv->contact_list, "presence-updated",
220 G_CALLBACK (presence_updated_cb), self);
221
222 g_ptr_array_add (ret, self->priv->contact_list);
223
224 return ret;
225 }
226
227 static gboolean
start_connecting(TpBaseConnection * conn,GError ** error)228 start_connecting (TpBaseConnection *conn,
229 GError **error)
230 {
231 ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
232 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
233 TP_HANDLE_TYPE_CONTACT);
234
235 /* In a real connection manager we'd ask the underlying implementation to
236 * start connecting, then go to state CONNECTED when finished, but here
237 * we can do it immediately. */
238
239 conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
240 NULL, error);
241
242 if (conn->self_handle == 0)
243 return FALSE;
244
245 tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
246 TP_CONNECTION_STATUS_REASON_REQUESTED);
247
248 return TRUE;
249 }
250
251 static void
shut_down(TpBaseConnection * conn)252 shut_down (TpBaseConnection *conn)
253 {
254 /* In a real connection manager we'd ask the underlying implementation to
255 * start shutting down, then call this function when finished, but here
256 * we can do it immediately. */
257 tp_base_connection_finish_shutdown (conn);
258 }
259
260 static void
aliasing_fill_contact_attributes(GObject * object,const GArray * contacts,GHashTable * attributes)261 aliasing_fill_contact_attributes (GObject *object,
262 const GArray *contacts,
263 GHashTable *attributes)
264 {
265 ExampleContactListConnection *self =
266 EXAMPLE_CONTACT_LIST_CONNECTION (object);
267 guint i;
268
269 for (i = 0; i < contacts->len; i++)
270 {
271 TpHandle contact = g_array_index (contacts, guint, i);
272
273 tp_contacts_mixin_set_contact_attribute (attributes, contact,
274 TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
275 tp_g_value_slice_new_string (
276 example_contact_list_get_alias (self->priv->contact_list,
277 contact)));
278 }
279 }
280
281 static void
constructed(GObject * object)282 constructed (GObject *object)
283 {
284 TpBaseConnection *base = TP_BASE_CONNECTION (object);
285 void (*chain_up) (GObject *) =
286 G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed;
287
288 if (chain_up != NULL)
289 chain_up (object);
290
291 tp_contacts_mixin_init (object,
292 G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin));
293
294 tp_base_connection_register_with_contacts_mixin (base);
295 tp_base_contact_list_mixin_register_with_contacts_mixin (base);
296
297 tp_contacts_mixin_add_contact_attributes_iface (object,
298 TP_IFACE_CONNECTION_INTERFACE_ALIASING,
299 aliasing_fill_contact_attributes);
300
301 tp_presence_mixin_init (object,
302 G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin));
303 tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
304 }
305
306 static gboolean
status_available(GObject * object,guint index_)307 status_available (GObject *object,
308 guint index_)
309 {
310 TpBaseConnection *base = TP_BASE_CONNECTION (object);
311
312 if (base->status != TP_CONNECTION_STATUS_CONNECTED)
313 return FALSE;
314
315 return TRUE;
316 }
317
318 static GHashTable *
get_contact_statuses(GObject * object,const GArray * contacts,GError ** error)319 get_contact_statuses (GObject *object,
320 const GArray *contacts,
321 GError **error)
322 {
323 ExampleContactListConnection *self =
324 EXAMPLE_CONTACT_LIST_CONNECTION (object);
325 TpBaseConnection *base = TP_BASE_CONNECTION (object);
326 guint i;
327 GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
328 NULL, (GDestroyNotify) tp_presence_status_free);
329
330 for (i = 0; i < contacts->len; i++)
331 {
332 TpHandle contact = g_array_index (contacts, guint, i);
333 ExampleContactListPresence presence;
334 GHashTable *parameters;
335
336 /* we get our own status from the connection, and everyone else's status
337 * from the contact lists */
338 if (contact == base->self_handle)
339 {
340 presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY
341 : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE);
342 }
343 else
344 {
345 presence = example_contact_list_get_presence (
346 self->priv->contact_list, contact);
347 }
348
349 parameters = g_hash_table_new_full (g_str_hash,
350 g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
351 g_hash_table_insert (result, GUINT_TO_POINTER (contact),
352 tp_presence_status_new (presence, parameters));
353 g_hash_table_destroy (parameters);
354 }
355
356 return result;
357 }
358
359 static gboolean
set_own_status(GObject * object,const TpPresenceStatus * status,GError ** error)360 set_own_status (GObject *object,
361 const TpPresenceStatus *status,
362 GError **error)
363 {
364 ExampleContactListConnection *self =
365 EXAMPLE_CONTACT_LIST_CONNECTION (object);
366 TpBaseConnection *base = TP_BASE_CONNECTION (object);
367 GHashTable *presences;
368
369 if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY)
370 {
371 if (self->priv->away)
372 return TRUE;
373
374 self->priv->away = TRUE;
375 }
376 else
377 {
378 if (!self->priv->away)
379 return TRUE;
380
381 self->priv->away = FALSE;
382 }
383
384 presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
385 NULL, NULL);
386 g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle),
387 (gpointer) status);
388 tp_presence_mixin_emit_presence_update (object, presences);
389 g_hash_table_destroy (presences);
390 return TRUE;
391 }
392
393 static const gchar *interfaces_always_present[] = {
394 TP_IFACE_CONNECTION_INTERFACE_ALIASING,
395 TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
396 TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
397 TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
398 TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING,
399 TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
400 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
401 TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
402 NULL };
403
404 const gchar * const *
example_contact_list_connection_get_possible_interfaces(void)405 example_contact_list_connection_get_possible_interfaces (void)
406 {
407 /* in this example CM we don't have any extra interfaces that are sometimes,
408 * but not always, present */
409 return interfaces_always_present;
410 }
411
412 static void
example_contact_list_connection_class_init(ExampleContactListConnectionClass * klass)413 example_contact_list_connection_class_init (
414 ExampleContactListConnectionClass *klass)
415 {
416 TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
417 GObjectClass *object_class = (GObjectClass *) klass;
418 GParamSpec *param_spec;
419
420 object_class->get_property = get_property;
421 object_class->set_property = set_property;
422 object_class->constructed = constructed;
423 object_class->finalize = finalize;
424 g_type_class_add_private (klass,
425 sizeof (ExampleContactListConnectionPrivate));
426
427 base_class->create_handle_repos = create_handle_repos;
428 base_class->get_unique_connection_name = get_unique_connection_name;
429 base_class->create_channel_managers = create_channel_managers;
430 base_class->start_connecting = start_connecting;
431 base_class->shut_down = shut_down;
432 base_class->interfaces_always_present = interfaces_always_present;
433
434 param_spec = g_param_spec_string ("account", "Account name",
435 "The username of this user", NULL,
436 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
437 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
438 g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
439
440 param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
441 "Delay between simulated network events",
442 0, G_MAXUINT32, 1000,
443 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
444 g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
445 param_spec);
446
447 tp_contacts_mixin_class_init (object_class,
448 G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin));
449
450 tp_presence_mixin_class_init (object_class,
451 G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin),
452 status_available, get_contact_statuses, set_own_status,
453 example_contact_list_presence_statuses ());
454 tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
455
456 tp_base_contact_list_mixin_class_init (base_class);
457 }
458
459 static void
get_alias_flags(TpSvcConnectionInterfaceAliasing * aliasing,DBusGMethodInvocation * context)460 get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
461 DBusGMethodInvocation *context)
462 {
463 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
464
465 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
466 tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
467 TP_CONNECTION_ALIAS_FLAG_USER_SET);
468 }
469
470 static void
get_aliases(TpSvcConnectionInterfaceAliasing * aliasing,const GArray * contacts,DBusGMethodInvocation * context)471 get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
472 const GArray *contacts,
473 DBusGMethodInvocation *context)
474 {
475 ExampleContactListConnection *self =
476 EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
477 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
478 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
479 TP_HANDLE_TYPE_CONTACT);
480 GHashTable *result;
481 GError *error = NULL;
482 guint i;
483
484 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
485
486 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
487 {
488 dbus_g_method_return_error (context, error);
489 g_error_free (error);
490 return;
491 }
492
493 result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
494
495 for (i = 0; i < contacts->len; i++)
496 {
497 TpHandle contact = g_array_index (contacts, TpHandle, i);
498 const gchar *alias = example_contact_list_get_alias (
499 self->priv->contact_list, contact);
500
501 g_hash_table_insert (result, GUINT_TO_POINTER (contact),
502 (gchar *) alias);
503 }
504
505 tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
506 result);
507 g_hash_table_destroy (result);
508 }
509
510 static void
request_aliases(TpSvcConnectionInterfaceAliasing * aliasing,const GArray * contacts,DBusGMethodInvocation * context)511 request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
512 const GArray *contacts,
513 DBusGMethodInvocation *context)
514 {
515 ExampleContactListConnection *self =
516 EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
517 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
518 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
519 TP_HANDLE_TYPE_CONTACT);
520 GPtrArray *result;
521 gchar **strings;
522 GError *error = NULL;
523 guint i;
524
525 TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
526
527 if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
528 {
529 dbus_g_method_return_error (context, error);
530 g_error_free (error);
531 return;
532 }
533
534 result = g_ptr_array_sized_new (contacts->len + 1);
535
536 for (i = 0; i < contacts->len; i++)
537 {
538 TpHandle contact = g_array_index (contacts, TpHandle, i);
539 const gchar *alias = example_contact_list_get_alias (
540 self->priv->contact_list, contact);
541
542 g_ptr_array_add (result, (gchar *) alias);
543 }
544
545 g_ptr_array_add (result, NULL);
546 strings = (gchar **) g_ptr_array_free (result, FALSE);
547 tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
548 (const gchar **) strings);
549 g_free (strings);
550 }
551
552 static void
set_aliases(TpSvcConnectionInterfaceAliasing * aliasing,GHashTable * aliases,DBusGMethodInvocation * context)553 set_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
554 GHashTable *aliases,
555 DBusGMethodInvocation *context)
556 {
557 ExampleContactListConnection *self =
558 EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
559 TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
560 TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
561 TP_HANDLE_TYPE_CONTACT);
562 GHashTableIter iter;
563 gpointer key, value;
564
565 g_hash_table_iter_init (&iter, aliases);
566
567 while (g_hash_table_iter_next (&iter, &key, &value))
568 {
569 GError *error = NULL;
570
571 if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key),
572 &error))
573 {
574 dbus_g_method_return_error (context, error);
575 g_error_free (error);
576 return;
577 }
578 }
579
580 g_hash_table_iter_init (&iter, aliases);
581
582 while (g_hash_table_iter_next (&iter, &key, &value))
583 {
584 example_contact_list_set_alias (self->priv->contact_list,
585 GPOINTER_TO_UINT (key), value);
586 }
587
588 tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
589 }
590
591 static void
init_aliasing(gpointer iface,gpointer iface_data G_GNUC_UNUSED)592 init_aliasing (gpointer iface,
593 gpointer iface_data G_GNUC_UNUSED)
594 {
595 TpSvcConnectionInterfaceAliasingClass *klass = iface;
596
597 #define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
598 klass, x)
599 IMPLEMENT(get_alias_flags);
600 IMPLEMENT(request_aliases);
601 IMPLEMENT(get_aliases);
602 IMPLEMENT(set_aliases);
603 #undef IMPLEMENT
604 }
605