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