Coverage Report

Created: 2025-06-13 06:55

/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
}