Coverage Report

Created: 2025-07-01 07:09

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