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