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