644e92e8294d4ab797dc5b901ace741f40ed1c23
[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       /* if that was the only key... */
254       if (delayed->priv->owner &&
255           g_tree_nnodes (delayed->priv->delayed) == 0)
256         g_object_notify (delayed->priv->owner, "has-unapplied");
257     }
258
259   g_settings_backend_writable_changed (G_SETTINGS_BACKEND (delayed), key);
260 }
261
262 /* slow method until we get foreach-with-remove in GTree
263  */
264 typedef struct
265 {
266   const gchar *path;
267   const gchar **keys;
268   gsize index;
269 } CheckPrefixState;
270
271 static gboolean
272 check_prefix (gpointer key,
273               gpointer value,
274               gpointer data)
275 {
276   CheckPrefixState *state = data;
277
278   if (g_str_has_prefix (key, state->path))
279     state->keys[state->index++] = key;
280
281   return FALSE;
282 }
283
284 static void
285 delayed_backend_path_writable_changed (GSettingsBackend *backend,
286                                        const gchar      *path,
287                                        gpointer          user_data)
288 {
289   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
290   gsize n_keys;
291
292   n_keys = g_tree_nnodes (delayed->priv->delayed);
293
294   if (n_keys > 0)
295     {
296       CheckPrefixState state = { path, g_new (const gchar *, n_keys) };
297       gsize i;
298
299       /* collect a list of possibly-affected keys (ie: matching the path) */
300       g_tree_foreach (delayed->priv->delayed, check_prefix, &state);
301
302       /* drop the keys that have been affected */
303       for (i = 0; i < state.index; i++)
304         if (!g_settings_backend_get_writable (delayed->priv->backend,
305                                               state.keys[i]))
306           g_tree_remove (delayed->priv->delayed, state.keys[i]);
307
308       g_free (state.keys);
309
310       if (delayed->priv->owner &&
311           g_tree_nnodes (delayed->priv->delayed) == 0)
312         g_object_notify (delayed->priv->owner, "has-unapplied");
313     }
314
315   g_settings_backend_path_writable_changed (G_SETTINGS_BACKEND (delayed),
316                                             path);
317 }
318
319 static void
320 g_delayed_settings_backend_finalize (GObject *object)
321 {
322   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (object);
323
324   g_settings_backend_unwatch (delayed->priv->backend, delayed);
325   g_object_unref (delayed->priv->backend);
326 }
327
328 static void
329 g_delayed_settings_backend_class_init (GDelayedSettingsBackendClass *class)
330 {
331   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
332   GObjectClass *object_class = G_OBJECT_CLASS (class);
333
334   g_type_class_add_private (class, sizeof (GDelayedSettingsBackendPrivate));
335
336   backend_class->read = g_delayed_settings_backend_read;
337   backend_class->write = g_delayed_settings_backend_write;
338   backend_class->write_keys = g_delayed_settings_backend_write_keys;
339   backend_class->reset = g_delayed_settings_backend_reset;
340   backend_class->reset_path = g_delayed_settings_backend_reset_path;
341   backend_class->get_writable = g_delayed_settings_backend_get_writable;
342   backend_class->subscribe = g_delayed_settings_backend_subscribe;
343   backend_class->unsubscribe = g_delayed_settings_backend_unsubscribe;
344
345   object_class->finalize = g_delayed_settings_backend_finalize;
346 }
347
348 static void
349 g_delayed_settings_backend_init (GDelayedSettingsBackend *delayed)
350 {
351   delayed->priv =
352     G_TYPE_INSTANCE_GET_PRIVATE (delayed, G_TYPE_DELAYED_SETTINGS_BACKEND,
353                                  GDelayedSettingsBackendPrivate);
354
355   delayed->priv->delayed = g_settings_backend_create_tree ();
356 }
357
358 GDelayedSettingsBackend *
359 g_delayed_settings_backend_new (GSettingsBackend *backend,
360                                 gpointer          owner)
361 {
362   GDelayedSettingsBackend *delayed;
363
364   delayed = g_object_new (G_TYPE_DELAYED_SETTINGS_BACKEND, NULL);
365   delayed->priv->backend = g_object_ref (backend);
366   delayed->priv->owner = owner;
367
368   g_settings_backend_watch (delayed->priv->backend,
369                             delayed_backend_changed,
370                             delayed_backend_path_changed,
371                             delayed_backend_keys_changed,
372                             delayed_backend_writable_changed,
373                             delayed_backend_path_writable_changed,
374                             delayed);
375
376   return delayed;
377 }