Implement org.genivi.LUCHandler1.Deregister()
[profile/ivi/node-startup-controller.git] / luc-handler / luc-handler-service.c
1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /* -
3  * Copyright (c) 2012 GENIVI.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13
14 #include <glib-object.h>
15 #include <gio/gio.h>
16
17 #include <common/glib-extensions.h>
18
19 #include <luc-handler/luc-handler-dbus.h>
20 #include <luc-handler/luc-handler-service.h>
21
22
23
24 /* property identifiers */
25 enum
26 {
27   PROP_0,
28   PROP_CONNECTION,
29 };
30
31
32
33 static void      luc_handler_service_finalize              (GObject               *object);
34 static void      luc_handler_service_get_property          (GObject               *object,
35                                                             guint                  prop_id,
36                                                             GValue                *value,
37                                                             GParamSpec            *pspec);
38 static void      luc_handler_service_set_property          (GObject               *object,
39                                                             guint                  prop_id,
40                                                             const GValue          *value,
41                                                             GParamSpec            *pspec);
42 static gboolean  luc_handler_service_handle_register       (LUCHandler            *interface,
43                                                             GDBusMethodInvocation *invocation,
44                                                             GVariant              *apps,
45                                                             LUCHandlerService     *service);
46 static gboolean  luc_handler_service_handle_deregister     (LUCHandler            *interface,
47                                                             GDBusMethodInvocation *invocation,
48                                                             GVariant              *apps,
49                                                             LUCHandlerService     *service);
50 static gboolean  luc_handler_service_get_last_user_context (GValue                *value,
51                                                             GVariant              *variant,
52                                                             gpointer               user_data);
53 static GVariant *luc_handler_service_set_last_user_context (const GValue          *value,
54                                                             const GVariantType    *expected_type,
55                                                             gpointer               user_data);
56
57
58
59 struct _LUCHandlerServiceClass
60 {
61   GObjectClass __parent__;
62 };
63
64 struct _LUCHandlerService
65 {
66   GObject          __parent__;
67
68   GDBusConnection *connection;
69   LUCHandler      *interface;
70   GSettings       *settings;
71 };
72
73
74
75 G_DEFINE_TYPE (LUCHandlerService, luc_handler_service, G_TYPE_OBJECT);
76
77
78
79 static void
80 luc_handler_service_class_init (LUCHandlerServiceClass *klass)
81 {
82   GObjectClass *gobject_class;
83
84   gobject_class = G_OBJECT_CLASS (klass);
85   gobject_class->finalize = luc_handler_service_finalize;
86   gobject_class->get_property = luc_handler_service_get_property;
87   gobject_class->set_property = luc_handler_service_set_property;
88
89   g_object_class_install_property (gobject_class,
90                                    PROP_CONNECTION,
91                                    g_param_spec_object ("connection",
92                                                         "connection",
93                                                         "connection",
94                                                         G_TYPE_DBUS_CONNECTION,
95                                                         G_PARAM_READWRITE |
96                                                         G_PARAM_CONSTRUCT_ONLY |
97                                                         G_PARAM_STATIC_STRINGS));
98 }
99
100
101
102 static void
103 luc_handler_service_init (LUCHandlerService *service)
104 {
105   service->interface = luc_handler_skeleton_new ();
106
107   /* create GSettings object for the LUC Handler schema */
108   service->settings = g_settings_new ("org.genivi.LUCHandler1");
109
110   /* bind the settings key to the D-Bus property so changes are
111    * automatically synchronised; we need to use a custom binding
112    * here because g_settings_bind() alone does not work with dicts */
113   g_settings_bind_with_mapping (service->settings, "last-user-context",
114                                 service->interface, "last-user-context",
115                                 G_SETTINGS_BIND_DEFAULT,
116                                 luc_handler_service_get_last_user_context,
117                                 luc_handler_service_set_last_user_context,
118                                 g_object_ref (service),
119                                 (GDestroyNotify) g_object_unref);
120
121   /* implement the Register() handler */
122   g_signal_connect (service->interface, "handle-register",
123                     G_CALLBACK (luc_handler_service_handle_register),
124                     service);
125
126   /* implement the Deregister() handler */
127   g_signal_connect (service->interface, "handle-deregister",
128                     G_CALLBACK (luc_handler_service_handle_deregister),
129                     service);
130 }
131
132
133
134 static void
135 luc_handler_service_finalize (GObject *object)
136 {
137   LUCHandlerService *service = LUC_HANDLER_SERVICE (object);
138
139   /* release the D-Bus connection object */
140   if (service->connection != NULL)
141     g_object_unref (service->connection);
142
143   /* release the interface skeleton */
144   g_signal_handlers_disconnect_matched (service->interface,
145                                         G_SIGNAL_MATCH_DATA,
146                                         0, 0, NULL, NULL, service);
147   g_object_unref (service->interface);
148
149   /* release the settings object */
150   g_object_unref (service->settings);
151
152   (*G_OBJECT_CLASS (luc_handler_service_parent_class)->finalize) (object);
153 }
154
155
156
157 static void
158 luc_handler_service_get_property (GObject    *object,
159                                   guint       prop_id,
160                                   GValue     *value,
161                                   GParamSpec *pspec)
162 {
163   LUCHandlerService *service = LUC_HANDLER_SERVICE (object);
164
165   switch (prop_id)
166     {
167     case PROP_CONNECTION:
168       g_value_set_object (value, service->connection);
169       break;
170     default:
171       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
172       break;
173     }
174 }
175
176
177
178 static void
179 luc_handler_service_set_property (GObject      *object,
180                                   guint         prop_id,
181                                   const GValue *value,
182                                   GParamSpec   *pspec)
183 {
184   LUCHandlerService *service = LUC_HANDLER_SERVICE (object);
185
186   switch (prop_id)
187     {
188     case PROP_CONNECTION:
189       service->connection = g_value_dup_object (value);
190       break;
191     default:
192       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
193       break;
194     }
195 }
196
197
198
199 static gboolean
200 luc_handler_service_handle_register (LUCHandler            *object,
201                                      GDBusMethodInvocation *invocation,
202                                      GVariant              *apps,
203                                      LUCHandlerService     *service)
204 {
205   GVariantBuilder dict_builder;
206   GHashTableIter  hiter;
207   GVariantIter    viter;
208   GHashTable     *table;
209   GPtrArray      *apps_array;
210   GVariant       *current_context;
211   GVariant       *current_apps;
212   GVariant       *new_context;
213   GVariant       *new_apps;
214   GList          *lp;
215   GList          *luc_types;
216   gchar          *app;
217   gchar          *luc_type;
218   guint           n;
219
220   g_return_val_if_fail (IS_LUC_HANDLER (object), FALSE);
221   g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
222   g_return_val_if_fail (LUC_HANDLER_IS_SERVICE (service), FALSE);
223
224   /* create a hash table to merge the current context and the newly registered apps */
225   table = g_hash_table_new_full (g_str_hash, g_str_equal,
226                                  g_free, (GDestroyNotify) g_ptr_array_unref);
227
228   /* obtain the current content of the last user context */
229   current_context = luc_handler_get_last_user_context (service->interface);
230
231   /* prepare app lists for all LUC types present in the current context */
232   g_variant_iter_init (&viter, current_context);
233   while (g_variant_iter_loop (&viter, "{sas}", &luc_type, NULL))
234     {
235       g_hash_table_insert (table, g_strdup (luc_type),
236                            g_ptr_array_new_with_free_func (g_free));
237     }
238
239   /* add app lists for LUC types that are needed for the newly registered apps */
240   g_variant_iter_init (&viter, apps);
241   while (g_variant_iter_loop (&viter, "{sas}", &luc_type, NULL))
242     {
243       g_hash_table_insert (table, g_strdup (luc_type),
244                            g_ptr_array_new_with_free_func (g_free));
245     }
246
247   /* we now have a hash table that has all LUC types involved in the
248    * current context and in the newly registered apps */
249
250   /* fill the app lists for each LUC type involved, make sure that newly registered
251    * apps are added at the end so that they are "prioritized" */
252   g_hash_table_iter_init (&hiter, table);
253   while (g_hash_table_iter_next (&hiter, (gpointer) &luc_type, (gpointer) &apps_array))
254     {
255       /* get apps currently registered for the LUC type */
256       current_apps = g_variant_lookup_value (current_context, luc_type,
257                                              G_VARIANT_TYPE_STRING_ARRAY);
258
259       /* get apps to be registered for the LUC type now */
260       new_apps = g_variant_lookup_value (apps, luc_type, G_VARIANT_TYPE_STRING_ARRAY);
261
262       /* add all currently registered apps unless they are to be registered now.
263        * this is because we want apps to be registered now to be moved to the end
264        * of the lists */
265       for (n = 0; current_apps != NULL && n < g_variant_n_children (current_apps); n++)
266         {
267           g_variant_get_child (current_apps, n, "&s", &app);
268           if (!g_variant_string_array_has_string (new_apps, app))
269             g_ptr_array_add (apps_array, g_strdup (app));
270         }
271
272       /* add all newly registered apps at the end now */
273       for (n = 0; new_apps != NULL && n < g_variant_n_children (new_apps); n++)
274         {
275           g_variant_get_child (new_apps, n, "&s", &app);
276           g_ptr_array_add (apps_array, g_strdup (app));
277         }
278
279       /* release app lists for this LUC type */
280       if (current_apps != NULL)
281         g_variant_unref (current_apps);
282       if (new_apps != NULL)
283         g_variant_unref (new_apps);
284     }
285
286   /* construct a new dictionary variant for the new LUC */
287   g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sas}"));
288
289   /* copy LUC types and corresponding apps over to the new context. make
290    * sure the order (alphabetic) in which we add LUC types to the context
291    * dict is always the same. this is helpful for testing */
292   luc_types = g_hash_table_get_keys (table);
293   luc_types = g_list_sort (luc_types, (GCompareFunc) g_strcmp0);
294   for (lp = luc_types; lp != NULL; lp = lp->next)
295     {
296       /* get the apps list registered for this LUC type */
297       apps_array = g_hash_table_lookup (table, lp->data);
298
299       /* NULL-terminate the pointer so that we can treat it as a gchar ** */
300       g_ptr_array_add (apps_array, NULL);
301
302       /* add the LUC type and its apps to the new context */
303       g_variant_builder_add (&dict_builder, "{s^as}", lp->data, apps_array->pdata);
304     }
305
306   /* free the LUC types and our LUC type to apps mapping */
307   g_list_free (luc_types);
308   g_hash_table_unref (table);
309
310   /* apply the new last user context */
311   new_context = g_variant_builder_end (&dict_builder);
312   luc_handler_set_last_user_context (service->interface, new_context);
313
314   /* notify the caller that we have handled the register request */
315   g_dbus_method_invocation_return_value (invocation, NULL);
316   return TRUE;
317 }
318
319
320
321 static gboolean
322 luc_handler_service_handle_deregister (LUCHandler            *object,
323                                        GDBusMethodInvocation *invocation,
324                                        GVariant              *apps,
325                                        LUCHandlerService     *service)
326 {
327   GVariantBuilder apps_builder;
328   GVariantBuilder builder;
329   GVariantIter    viter;
330   GVariant       *current_context;
331   GVariant       *new_context;
332   GVariant       *current_apps;
333   GVariant       *apps_to_remove;
334   gchar          *app;
335   gchar          *luc_type;
336   guint           num_apps;
337   guint           n;
338
339   g_return_val_if_fail (IS_LUC_HANDLER (object), FALSE);
340   g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
341   g_return_val_if_fail (LUC_HANDLER_IS_SERVICE (service), FALSE);
342
343   /* obtain the current content of the last user context */
344   current_context = luc_handler_get_last_user_context (service->interface);
345
346   /* initialise the builder for the new context */
347   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sas}"));
348
349   /* copy the current context into the new context, drop all apps that
350    * are supposed to be registered */
351   g_variant_iter_init (&viter, current_context);
352   while (g_variant_iter_loop (&viter, "{s@as}", &luc_type, &current_apps))
353     {
354       /* get a list of apps to be removed from this LUC type */
355       apps_to_remove = g_variant_lookup_value (apps, luc_type,
356                                                G_VARIANT_TYPE_STRING_ARRAY);
357
358       if (apps_to_remove == NULL)
359         {
360           /* there are no apps to be removed from this LUC type, just copy
361            * all currently registered apps over */
362           g_variant_builder_add (&builder, "{s@as}", luc_type, current_apps);
363         }
364       else
365         {
366           /* we need to remove some apps from the current LUC type, so let's
367            * build a new string array */
368           g_variant_builder_init (&apps_builder, G_VARIANT_TYPE ("as"));
369
370           /* add all apps currently registered for this LUC type to the string
371            * array unless they are to be removed */
372           for (n = 0, num_apps = 0;
373                current_apps != NULL && n < g_variant_n_children (current_apps);
374                n++)
375             {
376               g_variant_get_child (current_apps, n, "&s", &app);
377               if (!g_variant_string_array_has_string (apps_to_remove, app))
378                 {
379                   g_variant_builder_add (&apps_builder, "s", app);
380                   num_apps++;
381                 }
382             }
383
384           /* add the LUC type and its apps to the new context unless there are none */
385           if (num_apps > 0)
386             g_variant_builder_add (&builder, "{sas}", luc_type, &apps_builder);
387
388           /* free resources used for building the string array */
389           g_variant_builder_clear (&apps_builder);
390         }
391
392       /* release the apps to be removed for this LUC type */
393       if (apps_to_remove != NULL)
394         g_variant_unref (apps_to_remove);
395     }
396
397   /* apply the new last user context */
398   new_context = g_variant_builder_end (&builder);
399   luc_handler_set_last_user_context (service->interface, new_context);
400
401   /* notify the caller that we have handled the deregistration request */
402   g_dbus_method_invocation_return_value (invocation, NULL);
403   return TRUE;
404 }
405
406
407
408 static gboolean
409 luc_handler_service_get_last_user_context (GValue   *value,
410                                            GVariant *variant,
411                                            gpointer  user_data)
412 {
413   g_return_val_if_fail (G_IS_VALUE (value), FALSE);
414   g_return_val_if_fail (variant != NULL, FALSE);
415
416   g_value_set_variant (value, variant);
417   return TRUE;
418 }
419
420
421
422 static GVariant *
423 luc_handler_service_set_last_user_context (const GValue       *value,
424                                            const GVariantType *expected_type,
425                                            gpointer           user_data)
426 {
427   g_return_val_if_fail (G_IS_VALUE (value), FALSE);
428   g_return_val_if_fail (expected_type != G_VARIANT_TYPE_DICTIONARY, FALSE);
429
430   return g_value_dup_variant (value);
431 }
432
433
434
435 LUCHandlerService *
436 luc_handler_service_new (GDBusConnection *connection)
437 {
438   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
439   return g_object_new (LUC_HANDLER_TYPE_SERVICE, "connection", connection, NULL);
440 }
441
442
443
444 gboolean
445 luc_handler_service_start (LUCHandlerService *service,
446                            GError           **error)
447 {
448   g_return_val_if_fail (LUC_HANDLER_IS_SERVICE (service), FALSE);
449   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
450
451   /* announce the org.genivi.LUCHandler1 service on the bus */
452   return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (service->interface),
453                                            service->connection,
454                                            "/org/genivi/LUCHandler1",
455                                            error);
456 }