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