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