GSettings delayed: fix handling of mandatory keys
[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 #include "gioalias.h"
30
31 struct _GDelayedSettingsBackendPrivate
32 {
33   GSettingsBackend *backend;
34   GTree *delayed;
35   gpointer owner;
36 };
37
38 G_DEFINE_TYPE (GDelayedSettingsBackend,
39                g_delayed_settings_backend,
40                G_TYPE_SETTINGS_BACKEND)
41
42 static GVariant *
43 g_delayed_settings_backend_read (GSettingsBackend   *backend,
44                                  const gchar        *key,
45                                  const GVariantType *expected_type)
46 {
47   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
48   GVariant *result;
49
50   if ((result = g_tree_lookup (delayed->priv->delayed, key)))
51     return g_variant_ref (result);
52
53   return g_settings_backend_read (delayed->priv->backend,
54                                   key, expected_type);
55 }
56 static gboolean
57 g_delayed_settings_backend_write (GSettingsBackend *backend,
58                                   const gchar      *key,
59                                   GVariant         *value,
60                                   gpointer          origin_tag)
61 {
62   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
63   gboolean was_empty;
64
65   was_empty = g_tree_nnodes (delayed->priv->delayed) == 0;
66   g_tree_insert (delayed->priv->delayed, g_strdup (key),
67                  g_variant_ref_sink (value));
68   g_settings_backend_changed (backend, key, origin_tag);
69
70   if (was_empty && delayed->priv->owner)
71     g_object_notify (delayed->priv->owner, "has-unapplied");
72
73   return TRUE;
74 }
75
76 static gboolean
77 add_to_tree (gpointer key,
78              gpointer value,
79              gpointer user_data)
80 {
81   g_tree_insert (user_data, g_strdup (key), g_variant_ref (value));
82   return FALSE;
83 }
84
85 static gboolean
86 g_delayed_settings_backend_write_keys (GSettingsBackend *backend,
87                                        GTree            *tree,
88                                        gpointer          origin_tag)
89 {
90   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
91   gboolean was_empty;
92
93   was_empty = g_tree_nnodes (delayed->priv->delayed) == 0;
94
95   g_tree_foreach (tree, add_to_tree, delayed->priv->delayed);
96
97   g_settings_backend_changed_tree (backend, tree, origin_tag);
98
99   if (was_empty && delayed->priv->owner)
100     g_object_notify (delayed->priv->owner, "has-unapplied");
101
102   return TRUE;
103 }
104
105 static gboolean
106 g_delayed_settings_backend_get_writable (GSettingsBackend *backend,
107                                          const gchar      *name)
108 {
109   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
110
111   return g_settings_backend_get_writable (delayed->priv->backend, name);
112 }
113
114 static void
115 g_delayed_settings_backend_reset (GSettingsBackend *backend,
116                                   const gchar      *key,
117                                   gpointer          origin_tag)
118 {
119   /* deal with this... */
120 }
121
122 static void
123 g_delayed_settings_backend_reset_path (GSettingsBackend *backend,
124                                        const gchar      *path,
125                                        gpointer          origin_tag)
126 {
127   /* deal with this... */
128 }
129
130 static void
131 g_delayed_settings_backend_subscribe (GSettingsBackend *backend,
132                                       const char       *name)
133 {
134   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
135
136   g_settings_backend_subscribe (delayed->priv->backend, name);
137 }
138
139 static void
140 g_delayed_settings_backend_unsubscribe (GSettingsBackend *backend,
141                                         const char       *name)
142 {
143   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
144
145   g_settings_backend_unsubscribe (delayed->priv->backend, name);
146 }
147
148
149 /* method calls */
150 gboolean
151 g_delayed_settings_backend_get_has_unapplied (GDelayedSettingsBackend *delayed)
152 {
153   return g_tree_nnodes (delayed->priv->delayed) > 0;
154 }
155
156 void
157 g_delayed_settings_backend_apply (GDelayedSettingsBackend *delayed)
158 {
159   if (g_tree_nnodes (delayed->priv->delayed) > 0)
160     {
161       gboolean success;
162       GTree *tmp;
163
164       tmp = delayed->priv->delayed;
165       delayed->priv->delayed = g_settings_backend_create_tree ();
166       success = g_settings_backend_write_keys (delayed->priv->backend,
167                                                tmp, delayed->priv);
168
169       if (!success)
170         g_settings_backend_changed_tree (G_SETTINGS_BACKEND (delayed),
171                                          tmp, NULL);
172
173       g_tree_unref (tmp);
174
175       if (delayed->priv->owner)
176         g_object_notify (delayed->priv->owner, "has-unapplied");
177     }
178 }
179
180 void
181 g_delayed_settings_backend_revert (GDelayedSettingsBackend *delayed)
182 {
183   if (g_tree_nnodes (delayed->priv->delayed) > 0)
184     {
185       GTree *tmp;
186
187       tmp = delayed->priv->delayed;
188       delayed->priv->delayed = g_settings_backend_create_tree ();
189       g_settings_backend_changed_tree (G_SETTINGS_BACKEND (delayed), tmp, NULL);
190       g_tree_unref (tmp);
191
192       if (delayed->priv->owner)
193         g_object_notify (delayed->priv->owner, "has-unapplied");
194     }
195 }
196
197 /* change notification */
198 static void
199 delayed_backend_changed (GSettingsBackend *backend,
200                          const gchar      *key,
201                          gpointer          origin_tag,
202                          gpointer          user_data)
203 {
204   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
205
206   if (origin_tag != delayed->priv)
207     g_settings_backend_changed (G_SETTINGS_BACKEND (delayed),
208                                 key, origin_tag);
209 }
210
211 static void
212 delayed_backend_keys_changed (GSettingsBackend    *backend,
213                               const gchar         *path,
214                               const gchar * const *items,
215                               gpointer             origin_tag,
216                               gpointer             user_data)
217 {
218   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
219
220   if (origin_tag != delayed->priv)
221     g_settings_backend_keys_changed (G_SETTINGS_BACKEND (delayed),
222                                      path, items, origin_tag);
223 }
224
225 static void
226 delayed_backend_path_changed (GSettingsBackend    *backend,
227                               const gchar         *path,
228                               gpointer             origin_tag,
229                               gpointer             user_data)
230 {
231   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
232
233   if (origin_tag != delayed->priv)
234     g_settings_backend_path_changed (G_SETTINGS_BACKEND (delayed),
235                                      path, origin_tag);
236 }
237
238 static void
239 delayed_backend_writable_changed (GSettingsBackend *backend,
240                                   const gchar      *key,
241                                   gpointer          user_data)
242 {
243   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
244
245   if (g_tree_lookup (delayed->priv->delayed, key) &&
246       !g_settings_backend_get_writable (delayed->priv->backend, key))
247     {
248       /* drop the key from our changeset if it just became read-only.
249        * no need to signal this since the writable change implies it.
250        */
251       g_tree_remove (delayed->priv->delayed, key);
252     }
253
254   g_settings_backend_writable_changed (G_SETTINGS_BACKEND (delayed), key);
255 }
256
257 /* slow method until we get foreach-with-remove in GTree
258  */
259 typedef struct
260 {
261   const gchar *path;
262   const gchar **keys;
263   gsize index;
264 } CheckPrefixState;
265
266 static gboolean
267 check_prefix (gpointer key,
268               gpointer value,
269               gpointer data)
270 {
271   CheckPrefixState *state = data;
272
273   if (g_str_has_prefix (key, state->path))
274     state->keys[state->index++] = key;
275
276   return FALSE;
277 }
278
279 static void
280 delayed_backend_path_writable_changed (GSettingsBackend *backend,
281                                        const gchar      *path,
282                                        gpointer          user_data)
283 {
284   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
285   CheckPrefixState state = { path, };
286   gsize i;
287
288   /* collect a list of possibly-affected keys (ie: matching the path) */
289   state.keys = g_new (const gchar *, g_tree_nnodes (delayed->priv->delayed));
290   g_tree_foreach (delayed->priv->delayed, check_prefix, &state);
291
292   /* drop the keys that have been affected */
293   for (i = 0; i < state.index; i++)
294     if (!g_settings_backend_get_writable (delayed->priv->backend,
295                                           state.keys[i]))
296       g_tree_remove (delayed->priv->delayed, state.keys[i]);
297
298   g_free (state.keys);
299
300   g_settings_backend_path_writable_changed (G_SETTINGS_BACKEND (delayed),
301                                             path);
302 }
303
304 static void
305 g_delayed_settings_backend_finalize (GObject *object)
306 {
307   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (object);
308
309   g_settings_backend_unwatch (delayed->priv->backend, delayed);
310   g_object_unref (delayed->priv->backend);
311 }
312
313 static void
314 g_delayed_settings_backend_class_init (GDelayedSettingsBackendClass *class)
315 {
316   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
317   GObjectClass *object_class = G_OBJECT_CLASS (class);
318
319   g_type_class_add_private (class, sizeof (GDelayedSettingsBackendPrivate));
320
321   backend_class->read = g_delayed_settings_backend_read;
322   backend_class->write = g_delayed_settings_backend_write;
323   backend_class->write_keys = g_delayed_settings_backend_write_keys;
324   backend_class->reset = g_delayed_settings_backend_reset;
325   backend_class->reset_path = g_delayed_settings_backend_reset_path;
326   backend_class->get_writable = g_delayed_settings_backend_get_writable;
327   backend_class->subscribe = g_delayed_settings_backend_subscribe;
328   backend_class->unsubscribe = g_delayed_settings_backend_unsubscribe;
329
330   object_class->finalize = g_delayed_settings_backend_finalize;
331 }
332
333 static void
334 g_delayed_settings_backend_init (GDelayedSettingsBackend *delayed)
335 {
336   delayed->priv =
337     G_TYPE_INSTANCE_GET_PRIVATE (delayed, G_TYPE_DELAYED_SETTINGS_BACKEND,
338                                  GDelayedSettingsBackendPrivate);
339
340   delayed->priv->delayed = g_settings_backend_create_tree ();
341 }
342
343 GDelayedSettingsBackend *
344 g_delayed_settings_backend_new (GSettingsBackend *backend,
345                                 gpointer          owner)
346 {
347   GDelayedSettingsBackend *delayed;
348
349   delayed = g_object_new (G_TYPE_DELAYED_SETTINGS_BACKEND, NULL);
350   delayed->priv->backend = g_object_ref (backend);
351   delayed->priv->owner = owner;
352
353   g_settings_backend_watch (delayed->priv->backend,
354                             delayed_backend_changed,
355                             delayed_backend_path_changed,
356                             delayed_backend_keys_changed,
357                             delayed_backend_writable_changed,
358                             delayed_backend_path_writable_changed,
359                             delayed);
360
361   return delayed;
362 }