Prepare 0.29.1
[dconf.git] / common / dconf-changeset.c
blobc80c88c8068ede3aeab32caf335f565d5f8f2e68
1 /*
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>
20 #include "config.h"
22 #include "dconf-changeset.h"
23 #include "dconf-paths.h"
25 #include <string.h>
26 #include <stdlib.h>
28 /**
29 * SECTION:changeset
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.
42 **/
44 /**
45 * DConfChangeset:
47 * This is a reference counted opaque structure type. It is not a
48 * #GObject.
50 * Use dconf_changeset_ref() and dconf_changeset_unref() to manipulate
51 * references.
52 **/
54 struct _DConfChangeset
56 GHashTable *table;
57 GHashTable *dir_resets;
58 guint is_database : 1;
59 guint is_sealed : 1;
60 gint ref_count;
62 gchar *prefix;
63 const gchar **paths;
64 GVariant **values;
67 static void
68 unref_gvariant0 (gpointer data)
70 if (data)
71 g_variant_unref (data);
74 /**
75 * dconf_changeset_new:
77 * Creates a new, empty, #DConfChangeset.
79 * Returns: (transfer full): the new #DConfChangeset.
80 **/
81 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;
90 return changeset;
93 /**
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
119 * Since: 0.16
121 DConfChangeset *
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;
131 if (copy_of)
133 GHashTableIter iter;
134 gpointer key, value;
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));
141 return changeset;
145 * dconf_changeset_unref:
146 * @changeset: a #DConfChangeset
148 * Releases a #DConfChangeset reference.
150 void
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
176 DConfChangeset *
177 dconf_changeset_ref (DConfChangeset *changeset)
179 g_atomic_int_inc (&changeset->ref_count);
181 return changeset;
184 static void
185 dconf_changeset_record_dir_reset (DConfChangeset *changeset,
186 const gchar *dir)
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.
214 void
215 dconf_changeset_set (DConfChangeset *changeset,
216 const gchar *path,
217 GVariant *value)
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, "/"))
225 GHashTableIter iter;
226 gpointer key;
228 g_return_if_fail (value == NULL);
230 /* When we reset a path we must also reset all keys within that
231 * path.
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);
251 else
252 g_hash_table_remove (changeset->table, path);
255 /* ...or a normal write. */
256 else
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
274 * request.
276 * Returns: %TRUE if the key is being modified by the change
278 gboolean
279 dconf_changeset_get (DConfChangeset *changeset,
280 const gchar *key,
281 GVariant **value)
283 gpointer tmp;
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)
290 GHashTableIter iter;
291 gpointer dir;
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))
297 if (value)
298 *value = NULL;
300 return TRUE;
304 return FALSE;
307 if (value)
308 *value = tmp ? g_variant_ref (tmp) : NULL;
310 return TRUE;
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
330 * extremely rare.
332 * Returns: %TRUE if the changes are similar
334 gboolean
335 dconf_changeset_is_similar_to (DConfChangeset *changeset,
336 DConfChangeset *other)
338 GHashTableIter iter;
339 gpointer key;
341 if (g_hash_table_size (changeset->table) != g_hash_table_size (other->table))
342 return FALSE;
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))
347 return FALSE;
349 return TRUE;
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
359 * #DConfChangeset.
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
375 * returns %FALSE.
377 * If @predicate returns %FALSE for any item, this function returns
378 * %FALSE. If not (including the case of no items) then this function
379 * returns %TRUE.
381 * Returns: %TRUE if all items in @changeset satisfy @predicate
383 gboolean
384 dconf_changeset_all (DConfChangeset *changeset,
385 DConfChangesetPredicate predicate,
386 gpointer user_data)
388 GHashTableIter iter;
389 gpointer key, value;
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))
394 return FALSE;
396 return TRUE;
399 static gint
400 dconf_changeset_string_ptr_compare (gconstpointer a_p,
401 gconstpointer b_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
413 * Seals @changeset.
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.
430 * Since: 0.18
432 void
433 dconf_changeset_seal (DConfChangeset *changeset)
435 gsize prefix_length;
436 gint n_items;
438 if (changeset->is_sealed)
439 return;
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? */
450 if (n_items == 0)
451 return;
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
457 * prefix.
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
468 * order.
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. */
476 GHashTableIter iter;
477 const gchar *first;
478 gboolean have_one;
479 gpointer key;
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);
485 g_assert (have_one);
487 prefix_length = strlen (key);
488 first = 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;
494 gint i;
496 for (i = 0; i < prefix_length; i++)
497 if (first[i] != this[i])
499 prefix_length = i;
500 break;
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.
513 if (n_items > 1)
515 while (first[prefix_length - 1] != '/')
516 prefix_length--;
519 changeset->prefix = g_strndup (first, prefix_length);
522 /* Pass 2: collect the list of keys, dropping the prefix */
524 GHashTableIter iter;
525 gpointer key;
526 gint i = 0;
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 */
546 gint i;
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).
581 guint
582 dconf_changeset_describe (DConfChangeset *changeset,
583 const gchar **prefix,
584 const gchar * const **paths,
585 GVariant * const **values)
587 gint n_items;
589 n_items = g_hash_table_size (changeset->table);
591 dconf_changeset_seal (changeset);
593 if (prefix)
594 *prefix = changeset->prefix;
596 if (paths)
597 *paths = changeset->paths;
599 if (values)
600 *values = changeset->values;
602 return n_items;
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
616 GVariant *
617 dconf_changeset_serialise (DConfChangeset *changeset)
619 GVariantBuilder builder;
620 GHashTableIter iter;
621 gpointer key, value;
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
647 DConfChangeset *
648 dconf_changeset_deserialise (GVariant *serialised)
650 DConfChangeset *changeset;
651 GVariantIter iter;
652 const gchar *key;
653 GVariant *value;
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);
673 return changeset;
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
680 * consumed.
682 * Creates a new #DConfChangeset with one change. This is equivalent to
683 * calling dconf_changeset_new() and then dconf_changeset_set() with
684 * @path and @value.
686 * Returns: a new #DConfChangeset
688 DConfChangeset *
689 dconf_changeset_new_write (const gchar *path,
690 GVariant *value)
692 DConfChangeset *changeset;
694 changeset = dconf_changeset_new ();
695 dconf_changeset_set (changeset, path, value);
697 return changeset;
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
708 gboolean
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.
728 * Since: 0.16
730 void
731 dconf_changeset_change (DConfChangeset *changeset,
732 DConfChangeset *changes)
734 gsize prefix_len;
735 gint i;
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
743 * /a/c.
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))
756 return;
758 prefix_len = strlen (changes->prefix);
759 for (i = 0; changes->paths[i]; i++)
761 const gchar *path;
762 GVariant *value;
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
786 * equal.
788 * Returns: (transfer full) (nullable): the changes, or %NULL
790 * Since: 0.16
792 DConfChangeset *
793 dconf_changeset_diff (DConfChangeset *from,
794 DConfChangeset *to)
796 DConfChangeset *changeset = NULL;
797 GHashTableIter iter;
798 gpointer key, val;
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))
827 if (!changeset)
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))
838 if (!changeset)
839 changeset = dconf_changeset_new ();
841 dconf_changeset_set (changeset, key, NULL);
844 return changeset;