GSettings: support emitting signals in threads
[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                          const gchar      *key,
203                          gpointer          origin_tag,
204                          gpointer          user_data)
205 {
206   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
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                               const gchar         *path,
216                               const gchar * const *items,
217                               gpointer             origin_tag,
218                               gpointer             user_data)
219 {
220   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
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                               const gchar         *path,
230                               gpointer             origin_tag,
231                               gpointer             user_data)
232 {
233   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
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                                   const gchar      *key,
243                                   gpointer          user_data)
244 {
245   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
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                                        const gchar      *path,
289                                        gpointer          user_data)
290 {
291   GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (user_data);
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_settings_backend_unwatch (delayed->priv->backend, delayed);
327   g_object_unref (delayed->priv->backend);
328 }
329
330 static void
331 g_delayed_settings_backend_class_init (GDelayedSettingsBackendClass *class)
332 {
333   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
334   GObjectClass *object_class = G_OBJECT_CLASS (class);
335
336   g_type_class_add_private (class, sizeof (GDelayedSettingsBackendPrivate));
337
338   backend_class->read = g_delayed_settings_backend_read;
339   backend_class->write = g_delayed_settings_backend_write;
340   backend_class->write_keys = g_delayed_settings_backend_write_keys;
341   backend_class->reset = g_delayed_settings_backend_reset;
342   backend_class->reset_path = g_delayed_settings_backend_reset_path;
343   backend_class->get_writable = g_delayed_settings_backend_get_writable;
344   backend_class->subscribe = g_delayed_settings_backend_subscribe;
345   backend_class->unsubscribe = g_delayed_settings_backend_unsubscribe;
346
347   object_class->finalize = g_delayed_settings_backend_finalize;
348 }
349
350 static void
351 g_delayed_settings_backend_init (GDelayedSettingsBackend *delayed)
352 {
353   delayed->priv =
354     G_TYPE_INSTANCE_GET_PRIVATE (delayed, G_TYPE_DELAYED_SETTINGS_BACKEND,
355                                  GDelayedSettingsBackendPrivate);
356
357   delayed->priv->delayed = g_settings_backend_create_tree ();
358 }
359
360 GDelayedSettingsBackend *
361 g_delayed_settings_backend_new (GSettingsBackend *backend,
362                                 gpointer          owner)
363 {
364   GDelayedSettingsBackend *delayed;
365
366   delayed = g_object_new (G_TYPE_DELAYED_SETTINGS_BACKEND, NULL);
367   delayed->priv->backend = g_object_ref (backend);
368   delayed->priv->owner = owner;
369
370   g_settings_backend_watch (delayed->priv->backend, NULL,
371                             delayed_backend_changed,
372                             delayed_backend_path_changed,
373                             delayed_backend_keys_changed,
374                             delayed_backend_writable_changed,
375                             delayed_backend_path_writable_changed,
376                             delayed);
377
378   return delayed;
379 }