Create GSettingsListenerVTable
[platform/upstream/glib.git] / gio / gdelayedsettingsbackend.c
1 /*
2  * Copyright © 2009, 2010 Codethink Limited
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include "gdelayedsettingsbackend.h"
25 #include "gsettingsbackendinternal.h"
26
27 #include <string.h>
28
29
30 struct _GDelayedSettingsBackendPrivate
31 {
32   GSettingsBackend *backend;
33   GStaticMutex lock;
34   GTree *delayed;
35
36   GMainContext *owner_context;
37   gpointer owner;
38 };
39
40 G_DEFINE_TYPE (GDelayedSettingsBackend,
41                g_delayed_settings_backend,
42                G_TYPE_SETTINGS_BACKEND)
43
44 static gboolean
45 invoke_notify_unapplied (gpointer data)
46 {
47   g_object_notify (data, "has-unapplied");
48   g_object_unref (data);
49
50   return FALSE;
51 }
52
53 static void
54 g_delayed_settings_backend_notify_unapplied (GDelayedSettingsBackend *delayed)
55 {
56   GMainContext *target_context;
57   GObject *target;
58
59   g_static_mutex_lock (&delayed->priv->lock);
60   if (delayed->priv->owner)
61     {
62       target_context = delayed->priv->owner_context;
63       target = g_object_ref (delayed->priv->owner);
64     }
65   else
66     {
67       target_context = NULL;
68       target = NULL;
69     }
70   g_static_mutex_unlock (&delayed->priv->lock);
71
72   if (target != NULL)
73     {
74       if (g_settings_backend_get_active_context () != target_context)
75         {
76           GSource *source;
77
78           source = g_idle_source_new ();
79           g_source_set_priority (source, G_PRIORITY_DEFAULT);
80           g_source_set_callback (source, invoke_notify_unapplied,
81                                  target, g_object_unref);
82           g_source_attach (source, target_context);
83           g_source_unref (source);
84         }
85       else
86         invoke_notify_unapplied (target);
87     }
88 }
89
90
91 static GVariant *
92 g_delayed_settings_backend_read (GSettingsBackend   *backend,
93                                  const gchar        *key,
94                                  const GVariantType *expected_type,
95                                  gboolean            default_value)
96 {
97   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
98   gpointer result = NULL;
99
100   if (!default_value)
101     {
102       g_static_mutex_lock (&delayed->priv->lock);
103       if (g_tree_lookup_extended (delayed->priv->delayed, key, NULL, &result))
104         {
105           /* NULL in the tree means we should consult the default value */
106           if (result != NULL)
107             g_variant_ref (result);
108           else
109             default_value = TRUE;
110         }
111       g_static_mutex_unlock (&delayed->priv->lock);
112     }
113
114   if (result == NULL)
115     result = g_settings_backend_read (delayed->priv->backend, key,
116                                       expected_type, default_value);
117
118   return result;
119 }
120
121 static gboolean
122 g_delayed_settings_backend_write (GSettingsBackend *backend,
123                                   const gchar      *key,
124                                   GVariant         *value,
125                                   gpointer          origin_tag)
126 {
127   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
128   gboolean was_empty;
129
130   g_static_mutex_lock (&delayed->priv->lock);
131   was_empty = g_tree_nnodes (delayed->priv->delayed) == 0;
132   g_tree_insert (delayed->priv->delayed, g_strdup (key),
133                  g_variant_ref_sink (value));
134   g_static_mutex_unlock (&delayed->priv->lock);
135
136   g_settings_backend_changed (backend, key, origin_tag);
137
138   if (was_empty)
139     g_delayed_settings_backend_notify_unapplied (delayed);
140
141   return TRUE;
142 }
143
144 static gboolean
145 add_to_tree (gpointer key,
146              gpointer value,
147              gpointer user_data)
148 {
149   g_tree_insert (user_data, g_strdup (key), g_variant_ref (value));
150   return FALSE;
151 }
152
153 static gboolean
154 g_delayed_settings_backend_write_tree (GSettingsBackend *backend,
155                                        GTree            *tree,
156                                        gpointer          origin_tag)
157 {
158   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
159   gboolean was_empty;
160
161   g_static_mutex_lock (&delayed->priv->lock);
162   was_empty = g_tree_nnodes (delayed->priv->delayed) == 0;
163
164   g_tree_foreach (tree, add_to_tree, delayed->priv->delayed);
165   g_static_mutex_unlock (&delayed->priv->lock);
166
167   g_settings_backend_changed_tree (backend, tree, origin_tag);
168
169   if (was_empty)
170     g_delayed_settings_backend_notify_unapplied (delayed);
171
172   return TRUE;
173 }
174
175 static gboolean
176 g_delayed_settings_backend_get_writable (GSettingsBackend *backend,
177                                          const gchar      *name)
178 {
179   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
180
181   return g_settings_backend_get_writable (delayed->priv->backend, name);
182 }
183
184 static void
185 g_delayed_settings_backend_reset (GSettingsBackend *backend,
186                                   const gchar      *key,
187                                   gpointer          origin_tag)
188 {
189   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
190   gboolean was_empty;
191
192   g_static_mutex_lock (&delayed->priv->lock);
193   was_empty = g_tree_nnodes (delayed->priv->delayed) == 0;
194   g_tree_insert (delayed->priv->delayed, g_strdup (key), NULL);
195   g_static_mutex_unlock (&delayed->priv->lock);
196
197   if (was_empty)
198     g_delayed_settings_backend_notify_unapplied (delayed);
199 }
200
201 static void
202 g_delayed_settings_backend_subscribe (GSettingsBackend *backend,
203                                       const char       *name)
204 {
205   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
206
207   g_settings_backend_subscribe (delayed->priv->backend, name);
208 }
209
210 static void
211 g_delayed_settings_backend_unsubscribe (GSettingsBackend *backend,
212                                         const char       *name)
213 {
214   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
215
216   g_settings_backend_unsubscribe (delayed->priv->backend, name);
217 }
218
219 static GPermission *
220 g_delayed_settings_backend_get_permission (GSettingsBackend *backend,
221                                            const gchar      *path)
222 {
223   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
224
225   return g_settings_backend_get_permission (delayed->priv->backend, path);
226 }
227
228
229 /* method calls */
230 gboolean
231 g_delayed_settings_backend_get_has_unapplied (GDelayedSettingsBackend *delayed)
232 {
233   /* we don't need to lock for this... */
234
235   return g_tree_nnodes (delayed->priv->delayed) > 0;
236 }
237
238 void
239 g_delayed_settings_backend_apply (GDelayedSettingsBackend *delayed)
240 {
241   if (g_tree_nnodes (delayed->priv->delayed) > 0)
242     {
243       gboolean success;
244       GTree *tmp;
245
246       g_static_mutex_lock (&delayed->priv->lock);
247       tmp = delayed->priv->delayed;
248       delayed->priv->delayed = g_settings_backend_create_tree ();
249       success = g_settings_backend_write_tree (delayed->priv->backend,
250                                                tmp, delayed->priv);
251       g_static_mutex_unlock (&delayed->priv->lock);
252
253       if (!success)
254         g_settings_backend_changed_tree (G_SETTINGS_BACKEND (delayed),
255                                          tmp, NULL);
256
257       g_tree_unref (tmp);
258
259       g_delayed_settings_backend_notify_unapplied (delayed);
260     }
261 }
262
263 void
264 g_delayed_settings_backend_revert (GDelayedSettingsBackend *delayed)
265 {
266   if (g_tree_nnodes (delayed->priv->delayed) > 0)
267     {
268       GTree *tmp;
269
270       g_static_mutex_lock (&delayed->priv->lock);
271       tmp = delayed->priv->delayed;
272       delayed->priv->delayed = g_settings_backend_create_tree ();
273       g_static_mutex_unlock (&delayed->priv->lock);
274       g_settings_backend_changed_tree (G_SETTINGS_BACKEND (delayed), tmp, NULL);
275       g_tree_unref (tmp);
276
277       g_delayed_settings_backend_notify_unapplied (delayed);
278     }
279 }
280
281 /* change notification */
282 static void
283 delayed_backend_changed (GObject          *target,
284                          GSettingsBackend *backend,
285                          const gchar      *key,
286                          gpointer          origin_tag)
287 {
288   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
289
290   if (origin_tag != delayed->priv)
291     g_settings_backend_changed (G_SETTINGS_BACKEND (delayed),
292                                 key, origin_tag);
293 }
294
295 static void
296 delayed_backend_keys_changed (GObject             *target,
297                               GSettingsBackend    *backend,
298                               const gchar         *path,
299                               const gchar * const *items,
300                               gpointer             origin_tag)
301 {
302   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
303
304   if (origin_tag != delayed->priv)
305     g_settings_backend_keys_changed (G_SETTINGS_BACKEND (delayed),
306                                      path, items, origin_tag);
307 }
308
309 static void
310 delayed_backend_path_changed (GObject          *target,
311                               GSettingsBackend *backend,
312                               const gchar      *path,
313                               gpointer          origin_tag)
314 {
315   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
316
317   if (origin_tag != delayed->priv)
318     g_settings_backend_path_changed (G_SETTINGS_BACKEND (delayed),
319                                      path, origin_tag);
320 }
321
322 static void
323 delayed_backend_writable_changed (GObject          *target,
324                                   GSettingsBackend *backend,
325                                   const gchar      *key)
326 {
327   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
328   gboolean last_one = FALSE;
329
330   g_static_mutex_lock (&delayed->priv->lock);
331
332   if (g_tree_lookup (delayed->priv->delayed, key) &&
333       !g_settings_backend_get_writable (delayed->priv->backend, key))
334     {
335       /* drop the key from our changeset if it just became read-only.
336        * no need to signal since the writable change below implies it.
337        */
338       g_tree_remove (delayed->priv->delayed, key);
339
340       /* if that was the only key... */
341       last_one = g_tree_nnodes (delayed->priv->delayed) == 0;
342     }
343
344   g_static_mutex_unlock (&delayed->priv->lock);
345
346   if (last_one)
347     g_delayed_settings_backend_notify_unapplied (delayed);
348
349   g_settings_backend_writable_changed (G_SETTINGS_BACKEND (delayed), key);
350 }
351
352 /* slow method until we get foreach-with-remove in GTree
353  */
354 typedef struct
355 {
356   const gchar *path;
357   const gchar **keys;
358   gsize index;
359 } CheckPrefixState;
360
361 static gboolean
362 check_prefix (gpointer key,
363               gpointer value,
364               gpointer data)
365 {
366   CheckPrefixState *state = data;
367
368   if (g_str_has_prefix (key, state->path))
369     state->keys[state->index++] = key;
370
371   return FALSE;
372 }
373
374 static void
375 delayed_backend_path_writable_changed (GObject          *target,
376                                        GSettingsBackend *backend,
377                                        const gchar      *path)
378 {
379   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
380   gboolean last_one = FALSE;
381   gsize n_keys;
382
383   g_static_mutex_lock (&delayed->priv->lock);
384
385   n_keys = g_tree_nnodes (delayed->priv->delayed);
386
387   if (n_keys > 0)
388     {
389       CheckPrefixState state = { path, g_new (const gchar *, n_keys) };
390       gsize i;
391
392       /* collect a list of possibly-affected keys (ie: matching the path) */
393       g_tree_foreach (delayed->priv->delayed, check_prefix, &state);
394
395       /* drop the keys that have been affected */
396       for (i = 0; i < state.index; i++)
397         if (!g_settings_backend_get_writable (delayed->priv->backend,
398                                               state.keys[i]))
399           g_tree_remove (delayed->priv->delayed, state.keys[i]);
400
401       g_free (state.keys);
402
403       last_one = g_tree_nnodes (delayed->priv->delayed) == 0;
404     }
405
406   g_static_mutex_unlock (&delayed->priv->lock);
407
408   if (last_one)
409     g_delayed_settings_backend_notify_unapplied (delayed);
410
411   g_settings_backend_path_writable_changed (G_SETTINGS_BACKEND (delayed),
412                                             path);
413 }
414
415 static void
416 g_delayed_settings_backend_finalize (GObject *object)
417 {
418   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (object);
419
420   g_static_mutex_free (&delayed->priv->lock);
421   g_object_unref (delayed->priv->backend);
422   g_tree_unref (delayed->priv->delayed);
423
424   /* if our owner is still alive, why are we finalizing? */
425   g_assert (delayed->priv->owner == NULL);
426
427   G_OBJECT_CLASS (g_delayed_settings_backend_parent_class)
428     ->finalize (object);
429 }
430
431 static void
432 g_delayed_settings_backend_class_init (GDelayedSettingsBackendClass *class)
433 {
434   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
435   GObjectClass *object_class = G_OBJECT_CLASS (class);
436
437   g_type_class_add_private (class, sizeof (GDelayedSettingsBackendPrivate));
438
439   backend_class->read = g_delayed_settings_backend_read;
440   backend_class->write = g_delayed_settings_backend_write;
441   backend_class->write_tree = g_delayed_settings_backend_write_tree;
442   backend_class->reset = g_delayed_settings_backend_reset;
443   backend_class->get_writable = g_delayed_settings_backend_get_writable;
444   backend_class->subscribe = g_delayed_settings_backend_subscribe;
445   backend_class->unsubscribe = g_delayed_settings_backend_unsubscribe;
446   backend_class->get_permission = g_delayed_settings_backend_get_permission;
447
448   object_class->finalize = g_delayed_settings_backend_finalize;
449 }
450
451 static void
452 g_delayed_settings_backend_init (GDelayedSettingsBackend *delayed)
453 {
454   delayed->priv =
455     G_TYPE_INSTANCE_GET_PRIVATE (delayed, G_TYPE_DELAYED_SETTINGS_BACKEND,
456                                  GDelayedSettingsBackendPrivate);
457
458   delayed->priv->delayed = g_settings_backend_create_tree ();
459   g_static_mutex_init (&delayed->priv->lock);
460 }
461
462 static void
463 g_delayed_settings_backend_disown (gpointer  data,
464                                    GObject  *where_the_object_was)
465 {
466   GDelayedSettingsBackend *delayed = data;
467
468   g_static_mutex_lock (&delayed->priv->lock);
469   delayed->priv->owner_context = NULL;
470   delayed->priv->owner = NULL;
471   g_static_mutex_unlock (&delayed->priv->lock);
472 }
473
474 GDelayedSettingsBackend *
475 g_delayed_settings_backend_new (GSettingsBackend *backend,
476                                 gpointer          owner,
477                                 GMainContext     *owner_context)
478 {
479   static GSettingsListenerVTable vtable = {
480     delayed_backend_changed,
481     delayed_backend_path_changed,
482     delayed_backend_keys_changed,
483     delayed_backend_writable_changed,
484     delayed_backend_path_writable_changed
485   };
486   GDelayedSettingsBackend *delayed;
487
488   delayed = g_object_new (G_TYPE_DELAYED_SETTINGS_BACKEND, NULL);
489   delayed->priv->backend = g_object_ref (backend);
490   delayed->priv->owner_context = owner_context;
491   delayed->priv->owner = owner;
492
493   g_object_weak_ref (owner, g_delayed_settings_backend_disown, delayed);
494
495   g_settings_backend_watch (delayed->priv->backend,
496                             &vtable, G_OBJECT (delayed), NULL);
497
498   return delayed;
499 }