2 * Copyright © 2010 Codethink Limited
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.
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.
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/>.
17 * Author: Ryan Lortie <desrt@desrt.ca>
22 #include "dconf-changeset.h"
23 #include "dconf-paths.h"
30 * @title: DConfChangeset
31 * @Short_description: A set of changes to a dconf database
33 * #DConfChangeset represents a set of changes that can be made to a
34 * dconf database. Currently supported operations are writing new
35 * values to keys and resetting keys and dirs.
37 * Create the changeset with dconf_changeset_new() and populate it with
38 * dconf_changeset_set(). Submit it to dconf with
39 * dconf_client_change_fast() or dconf_client_change_sync().
40 * dconf_changeset_new_write() is a convenience constructor for the
41 * common case of writing or resetting a single value.
47 * This is a reference counted opaque structure type. It is not a
50 * Use dconf_changeset_ref() and dconf_changeset_unref() to manipulate
54 struct _DConfChangeset
57 GHashTable
*dir_resets
;
58 guint is_database
: 1;
68 unref_gvariant0 (gpointer data
)
71 g_variant_unref (data
);
75 * dconf_changeset_new:
77 * Creates a new, empty, #DConfChangeset.
79 * Returns: (transfer full): the new #DConfChangeset.
82 dconf_changeset_new (void)
84 DConfChangeset
*changeset
;
86 changeset
= g_slice_new0 (DConfChangeset
);
87 changeset
->table
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, unref_gvariant0
);
88 changeset
->ref_count
= 1;
94 * dconf_changeset_new_database:
95 * @copy_of: (nullable): a #DConfChangeset to copy
97 * Creates a new #DConfChangeset in "database" mode, possibly
98 * initialising it with the values of another changeset.
100 * In a certain sense it's possible to imagine that a #DConfChangeset
101 * could express the contents of an entire dconf database -- the
102 * contents are the database are what you would have if you applied the
103 * changeset to an empty database. One thing that fails to map in this
104 * analogy are reset operations -- if we start with an empty database
105 * then reset operations are meaningless.
107 * A "database" mode changeset is therefore a changeset which is
108 * incapable of containing reset operations.
110 * It is not permitted to use a database-mode changeset for most
111 * operations (such as the @change argument to dconf_changeset_change()
112 * or the @changeset argument to #DConfClient APIs).
114 * If @copy_of is non-%NULL then its contents will be copied into the
115 * created changeset. @copy_of must be a database-mode changeset.
117 * Returns: (transfer full): a new #DConfChangeset in "database" mode
122 dconf_changeset_new_database (DConfChangeset
*copy_of
)
124 DConfChangeset
*changeset
;
126 g_return_val_if_fail (copy_of
== NULL
|| copy_of
->is_database
, NULL
);
128 changeset
= dconf_changeset_new ();
129 changeset
->is_database
= TRUE
;
136 g_hash_table_iter_init (&iter
, copy_of
->table
);
137 while (g_hash_table_iter_next (&iter
, &key
, &value
))
138 g_hash_table_insert (changeset
->table
, g_strdup (key
), g_variant_ref (value
));
145 * dconf_changeset_unref:
146 * @changeset: a #DConfChangeset
148 * Releases a #DConfChangeset reference.
151 dconf_changeset_unref (DConfChangeset
*changeset
)
153 if (g_atomic_int_dec_and_test (&changeset
->ref_count
))
155 g_free (changeset
->prefix
);
156 g_free (changeset
->paths
);
157 g_free (changeset
->values
);
159 g_hash_table_unref (changeset
->table
);
161 if (changeset
->dir_resets
)
162 g_hash_table_unref (changeset
->dir_resets
);
164 g_slice_free (DConfChangeset
, changeset
);
169 * dconf_changeset_ref:
170 * @changeset: a #DConfChangeset
172 * Increases the reference count on @changeset
174 * Returns: (transfer full): @changeset
177 dconf_changeset_ref (DConfChangeset
*changeset
)
179 g_atomic_int_inc (&changeset
->ref_count
);
185 dconf_changeset_record_dir_reset (DConfChangeset
*changeset
,
188 g_return_if_fail (dconf_is_dir (dir
, NULL
));
189 g_return_if_fail (!changeset
->is_database
);
190 g_return_if_fail (!changeset
->is_sealed
);
192 if (!changeset
->dir_resets
)
193 changeset
->dir_resets
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
195 g_hash_table_insert (changeset
->table
, g_strdup (dir
), NULL
);
196 g_hash_table_add (changeset
->dir_resets
, g_strdup (dir
));
200 * dconf_changeset_set:
201 * @changeset: a #DConfChangeset
202 * @path: a path to modify
203 * @value: (nullable): the value for the key, or %NULL to reset. If it has a
204 * floating reference it's consumed.
206 * Adds an operation to modify @path to a #DConfChangeset.
208 * @path may either be a key or a dir. If it is a key then @value may
209 * be a #GVariant, or %NULL (to set or reset the key).
211 * If @path is a dir then this must be a reset operation: @value must be
212 * %NULL. It is not permitted to assign a #GVariant value to a dir.
215 dconf_changeset_set (DConfChangeset
*changeset
,
219 g_return_if_fail (!changeset
->is_sealed
);
220 g_return_if_fail (dconf_is_path (path
, NULL
));
222 /* Check if we are performing a path reset */
223 if (g_str_has_suffix (path
, "/"))
228 g_return_if_fail (value
== NULL
);
230 /* When we reset a path we must also reset all keys within that
233 g_hash_table_iter_init (&iter
, changeset
->table
);
234 while (g_hash_table_iter_next (&iter
, &key
, NULL
))
235 if (g_str_has_prefix (key
, path
))
236 g_hash_table_iter_remove (&iter
);
238 /* If this is a non-database then record the reset itself. */
239 if (!changeset
->is_database
)
240 dconf_changeset_record_dir_reset (changeset
, path
);
243 /* ...or a value reset */
244 else if (value
== NULL
)
246 /* If we're a non-database, record the reset explicitly.
247 * Otherwise, just reset whatever may be there already.
249 if (!changeset
->is_database
)
250 g_hash_table_insert (changeset
->table
, g_strdup (path
), NULL
);
252 g_hash_table_remove (changeset
->table
, path
);
255 /* ...or a normal write. */
257 g_hash_table_insert (changeset
->table
, g_strdup (path
), g_variant_ref_sink (value
));
261 * dconf_changeset_get:
262 * @changeset: a #DConfChangeset
263 * @key: the key to check
264 * @value: (transfer full) (optional) (nullable): a return location for the value, or %NULL
266 * Checks if a #DConfChangeset has an outstanding request to change
267 * the value of the given @key.
269 * If the change doesn't involve @key then %FALSE is returned and the
270 * @value is unmodified.
272 * If the change modifies @key then @value is set either to the value
273 * for that key, or %NULL in the case that the key is being reset by the
276 * Returns: %TRUE if the key is being modified by the change
279 dconf_changeset_get (DConfChangeset
*changeset
,
285 if (!g_hash_table_lookup_extended (changeset
->table
, key
, NULL
, &tmp
))
287 /* Did not find an exact match, so check for dir resets */
288 if (changeset
->dir_resets
)
293 g_hash_table_iter_init (&iter
, changeset
->dir_resets
);
294 while (g_hash_table_iter_next (&iter
, &dir
, NULL
))
295 if (g_str_has_prefix (key
, dir
))
308 *value
= tmp
? g_variant_ref (tmp
) : NULL
;
314 * dconf_changeset_is_similar_to:
315 * @changeset: a #DConfChangeset
316 * @other: another #DConfChangeset
318 * Checks if @changeset is similar to @other.
320 * Two changes are considered similar if they write to the exact same
321 * set of keys. The values written are not considered.
323 * This check is used to prevent building up a queue of repeated writes
324 * of the same keys. This is often seen when an application writes to a
325 * key on every move of a slider or an application window.
327 * Strictly speaking, a write resettings all of "/a/" after a write
328 * containing "/a/b" could cause the later to be removed from the queue,
329 * but this situation is difficult to detect and is expected to be
332 * Returns: %TRUE if the changes are similar
335 dconf_changeset_is_similar_to (DConfChangeset
*changeset
,
336 DConfChangeset
*other
)
341 if (g_hash_table_size (changeset
->table
) != g_hash_table_size (other
->table
))
344 g_hash_table_iter_init (&iter
, changeset
->table
);
345 while (g_hash_table_iter_next (&iter
, &key
, NULL
))
346 if (!g_hash_table_contains (other
->table
, key
))
353 * DConfChangesetPredicate:
354 * @path: a path, as per dconf_is_path()
355 * @value: (nullable): a #GVariant, or %NULL
356 * @user_data: user data pointer
358 * Callback function type for predicates over items in a
361 * Use with dconf_changeset_all().
363 * Returns: %TRUE if the predicate is met for the given @path and @value
367 * dconf_changeset_all:
368 * @changeset: a #DConfChangeset
369 * @predicate: a #DConfChangesetPredicate
370 * @user_data: user data to pass to @predicate
372 * Checks if all changes in the changeset satisfy @predicate.
374 * @predicate is called on each item in the changeset, in turn, until it
377 * If @predicate returns %FALSE for any item, this function returns
378 * %FALSE. If not (including the case of no items) then this function
381 * Returns: %TRUE if all items in @changeset satisfy @predicate
384 dconf_changeset_all (DConfChangeset
*changeset
,
385 DConfChangesetPredicate predicate
,
391 g_hash_table_iter_init (&iter
, changeset
->table
);
392 while (g_hash_table_iter_next (&iter
, &key
, &value
))
393 if (!(* predicate
) (key
, value
, user_data
))
400 dconf_changeset_string_ptr_compare (gconstpointer a_p
,
403 const gchar
* const *a
= a_p
;
404 const gchar
* const *b
= b_p
;
406 return strcmp (*a
, *b
);
410 * dconf_changeset_seal:
411 * @changeset: a #DConfChangeset
415 * When a #DConfChangeset is first created, it is mutable and
416 * non-threadsafe. Once the changeset is populated with the required
417 * changes, it can be shared between multiple threads, but only by
418 * making it immutable by "sealing" it.
420 * After the changeset is sealed, you cannot call dconf_changeset_set()
421 * or any other functions that would modify it. It is safe, however, to
422 * share it between multiple threads.
424 * All changesets are unsealed on creation, including those that are
425 * made by copying changesets that are sealed.
426 * dconf_changeset_describe() will implicitly seal a changeset.
428 * This function is idempotent.
433 dconf_changeset_seal (DConfChangeset
*changeset
)
438 if (changeset
->is_sealed
)
441 changeset
->is_sealed
= TRUE
;
443 /* This function used to be called dconf_changeset_build_description()
444 * because that's basically what sealing is...
447 n_items
= g_hash_table_size (changeset
->table
);
449 /* If there are no items then what is there to describe? */
453 /* We do three separate passes. This might take a bit longer than
454 * doing it all at once but it keeps the complexity down.
456 * First, we iterate the table in order to determine the common
459 * Next, we iterate the table again to pull the strings out excluding
460 * the leading prefix.
462 * We sort the list of paths at this point because the writer
463 * requires a sorted list in order to ensure that dir resets come
464 * before writes to keys in that dir.
466 * Finally, we iterate over the sorted list and use the normal
467 * hashtable lookup in order to populate the values array in the same
470 * Doing it this way avoids the complication of trying to sort two
471 * arrays (keys and values) at the same time.
474 /* Pass 1: determine the common prefix. */
481 g_hash_table_iter_init (&iter
, changeset
->table
);
483 /* We checked above that we have at least one item. */
484 have_one
= g_hash_table_iter_next (&iter
, &key
, NULL
);
487 prefix_length
= strlen (key
);
490 /* Consider the remaining items to find the common prefix */
491 while (g_hash_table_iter_next (&iter
, &key
, NULL
))
493 const gchar
*this = key
;
496 for (i
= 0; i
< prefix_length
; i
++)
497 if (first
[i
] != this[i
])
504 /* We must surely always have a common prefix of '/' */
505 g_assert (prefix_length
> 0);
506 g_assert (first
[0] == '/');
508 /* We may find that "/a/ab" and "/a/ac" have a common prefix of
509 * "/a/a" but really we want to trim that back to "/a/".
511 * If there is only one item, leave it alone.
515 while (first
[prefix_length
- 1] != '/')
519 changeset
->prefix
= g_strndup (first
, prefix_length
);
522 /* Pass 2: collect the list of keys, dropping the prefix */
528 changeset
->paths
= g_new (const gchar
*, n_items
+ 1);
530 g_hash_table_iter_init (&iter
, changeset
->table
);
531 while (g_hash_table_iter_next (&iter
, &key
, NULL
))
533 const gchar
*path
= key
;
535 changeset
->paths
[i
++] = path
+ prefix_length
;
537 changeset
->paths
[i
] = NULL
;
538 g_assert (i
== n_items
);
540 /* Sort the list of keys */
541 qsort (changeset
->paths
, n_items
, sizeof (const gchar
*), dconf_changeset_string_ptr_compare
);
544 /* Pass 3: collect the list of values */
548 changeset
->values
= g_new (GVariant
*, n_items
);
550 for (i
= 0; i
< n_items
; i
++)
551 /* We dropped the prefix when collecting the array.
552 * Bring it back temporarily, for the lookup.
554 changeset
->values
[i
] = g_hash_table_lookup (changeset
->table
, changeset
->paths
[i
] - prefix_length
);
559 * dconf_changeset_describe:
560 * @changeset: a #DConfChangeset
561 * @prefix: (transfer none) (optional) (out): the prefix under which changes have been requested
562 * @paths: (transfer none) (optional) (out): the list of paths changed, relative to @prefix
563 * @values: (transfer none) (optional) (out): the list of values changed
565 * Describes @changeset.
567 * @prefix and @paths are presented in the same way as they are for the
568 * DConfClient::changed signal. @values is an array of the same length
569 * as @paths. For each key described by an element in @paths, @values
570 * will contain either a #GVariant (the requested new value of that key)
571 * or %NULL (to reset a reset).
573 * The @paths array is returned in an order such that dir will always
574 * come before keys contained within those dirs.
576 * If @changeset is not already sealed then this call will implicitly
577 * seal it. See dconf_changeset_seal().
579 * Returns: the number of changes (the length of @changes and @values).
582 dconf_changeset_describe (DConfChangeset
*changeset
,
583 const gchar
**prefix
,
584 const gchar
* const **paths
,
585 GVariant
* const **values
)
589 n_items
= g_hash_table_size (changeset
->table
);
591 dconf_changeset_seal (changeset
);
594 *prefix
= changeset
->prefix
;
597 *paths
= changeset
->paths
;
600 *values
= changeset
->values
;
606 * dconf_changeset_serialise:
607 * @changeset: a #DConfChangeset
609 * Serialises a #DConfChangeset.
611 * The returned value has no particular format and should only be passed
612 * to dconf_changeset_deserialise().
614 * Returns: (transfer full): a floating #GVariant
617 dconf_changeset_serialise (DConfChangeset
*changeset
)
619 GVariantBuilder builder
;
623 g_variant_builder_init (&builder
, G_VARIANT_TYPE ("a{smv}"));
625 g_hash_table_iter_init (&iter
, changeset
->table
);
626 while (g_hash_table_iter_next (&iter
, &key
, &value
))
627 g_variant_builder_add (&builder
, "{smv}", key
, value
);
629 return g_variant_builder_end (&builder
);
633 * dconf_changeset_deserialise:
634 * @serialised: (transfer none): a #GVariant from dconf_changeset_serialise()
636 * Creates a #DConfChangeset according to a serialised description
637 * returned from an earlier call to dconf_changeset_serialise().
639 * @serialised has no particular format -- you should only pass a value
640 * that resulted from an earlier serialise operation.
642 * This call never fails, even if @serialised is not in the correct
643 * format. Improperly-formatted parts are simply ignored.
645 * Returns: (transfer full): a new #DConfChangeset
648 dconf_changeset_deserialise (GVariant
*serialised
)
650 DConfChangeset
*changeset
;
655 changeset
= dconf_changeset_new ();
656 g_variant_iter_init (&iter
, serialised
);
657 while (g_variant_iter_loop (&iter
, "{&smv}", &key
, &value
))
659 /* If value is NULL: we may be resetting a key or a dir (a path).
660 * If value is non-NULL: we must be setting a key.
662 * ie: it is not possible to set a value to a directory.
664 * If we get an invalid case, just fall through and ignore it.
666 if (dconf_is_key (key
, NULL
))
667 g_hash_table_insert (changeset
->table
, g_strdup (key
), value
? g_variant_ref (value
) : NULL
);
669 else if (dconf_is_dir (key
, NULL
) && value
== NULL
)
670 dconf_changeset_record_dir_reset (changeset
, key
);
677 * dconf_changeset_new_write:
678 * @path: a dconf path
679 * @value: (nullable): a #GVariant, or %NULL. If it has a floating reference it's
682 * Creates a new #DConfChangeset with one change. This is equivalent to
683 * calling dconf_changeset_new() and then dconf_changeset_set() with
686 * Returns: a new #DConfChangeset
689 dconf_changeset_new_write (const gchar
*path
,
692 DConfChangeset
*changeset
;
694 changeset
= dconf_changeset_new ();
695 dconf_changeset_set (changeset
, path
, value
);
701 * dconf_changeset_is_empty:
702 * @changeset: a #DConfChangeset
704 * Checks if @changeset is empty (ie: contains no changes).
706 * Returns: %TRUE if @changeset is empty
709 dconf_changeset_is_empty (DConfChangeset
*changeset
)
711 return !g_hash_table_size (changeset
->table
);
715 * dconf_changeset_change:
716 * @changeset: a #DConfChangeset (to be changed)
717 * @changes: the changes to make to @changeset
719 * Applies @changes to @changeset.
721 * If @changeset is a normal changeset then reset requests in @changes
722 * will be allied to @changeset and then copied down into it. In this
723 * case the two changesets are effectively being merged.
725 * If @changeset is in database mode then the reset operations in
726 * @changes will simply be applied to @changeset.
731 dconf_changeset_change (DConfChangeset
*changeset
,
732 DConfChangeset
*changes
)
737 g_return_if_fail (!changeset
->is_sealed
);
739 /* Handling resets is a little bit tricky...
741 * Consider the case that we have @changeset containing a key /a/b and
742 * @changes containing a reset request for /a/ and a set request for
745 * It's clear that at the end of this all, we should have only /a/c
746 * but in order for that to be the case, we need to make sure that we
747 * process the reset of /a/ before we process the set of /a/c.
749 * The easiest way to do this is to visit the strings in sorted order.
750 * That removes the possibility of iterating over the hash table, but
751 * dconf_changeset_build_description() makes the list in the order we
752 * need so just call it and then iterate over the result.
755 if (!dconf_changeset_describe (changes
, NULL
, NULL
, NULL
))
758 prefix_len
= strlen (changes
->prefix
);
759 for (i
= 0; changes
->paths
[i
]; i
++)
764 /* The changes->paths are just pointers into the keys of the
765 * hashtable, fast-forwarded past the prefix. Rewind a bit.
767 path
= changes
->paths
[i
] - prefix_len
;
768 value
= changes
->values
[i
];
770 dconf_changeset_set (changeset
, path
, value
);
775 * dconf_changeset_diff:
776 * @from: a database mode changeset
777 * @to: a database mode changeset
779 * Compares to database-mode changesets and produces a changeset that
780 * describes their differences.
782 * If there is no difference, %NULL is returned.
784 * Applying the returned changeset to @from using
785 * dconf_changeset_change() will result in the two changesets being
788 * Returns: (transfer full) (nullable): the changes, or %NULL
793 dconf_changeset_diff (DConfChangeset
*from
,
796 DConfChangeset
*changeset
= NULL
;
800 g_return_val_if_fail (from
->is_database
, NULL
);
801 g_return_val_if_fail (to
->is_database
, NULL
);
803 /* We make no attempt to do dir resets, but we could...
805 * For now, we just reset each key individually.
807 * We create our list of changes in two steps:
809 * - iterate the 'to' changeset and note any keys that do not have
810 * the same value in the 'from' changeset
812 * - iterate the 'from' changeset and note any keys not present in
813 * the 'to' changeset, recording resets for them
815 * This will cover all changes.
817 * Note: because 'from' and 'to' are database changesets we don't have
818 * to worry about seeing NULL values or dirs.
820 g_hash_table_iter_init (&iter
, to
->table
);
821 while (g_hash_table_iter_next (&iter
, &key
, &val
))
823 GVariant
*from_val
= g_hash_table_lookup (from
->table
, key
);
825 if (from_val
== NULL
|| !g_variant_equal (val
, from_val
))
828 changeset
= dconf_changeset_new ();
830 dconf_changeset_set (changeset
, key
, val
);
834 g_hash_table_iter_init (&iter
, from
->table
);
835 while (g_hash_table_iter_next (&iter
, &key
, &val
))
836 if (!g_hash_table_lookup (to
->table
, key
))
839 changeset
= dconf_changeset_new ();
841 dconf_changeset_set (changeset
, key
, NULL
);