small docs fixes
[dconf.git] / common / dconf-changeset.c
blobd9b9f4124ca4d6476b96b8a5215118078c2a6be2
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, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 * Author: Ryan Lortie <desrt@desrt.ca>
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 gboolean is_database;
58 gint ref_count;
60 gchar *prefix;
61 const gchar **paths;
62 GVariant **values;
65 static void
66 unref_gvariant0 (gpointer data)
68 if (data)
69 g_variant_unref (data);
72 /**
73 * dconf_changeset_new:
75 * Creates a new, empty, #DConfChangeset.
77 * Returns: the new #DConfChangeset.
78 **/
79 DConfChangeset *
80 dconf_changeset_new (void)
82 DConfChangeset *changeset;
84 changeset = g_slice_new0 (DConfChangeset);
85 changeset->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, unref_gvariant0);
86 changeset->ref_count = 1;
88 return changeset;
91 /**
92 * dconf_changeset_new_database:
93 * @copy_of: (allow-none): a #DConfChangeset to copy
95 * Creates a new #DConfChangeset in "database" mode, possibly
96 * initialising it with the values of another changeset.
98 * In a certain sense it's possible to imagine that a #DConfChangeset
99 * could express the contents of an entire dconf database -- the
100 * contents are the database are what you would have if you applied the
101 * changeset to an empty database. One thing that fails to map in this
102 * analogy are reset operations -- if we start with an empty database
103 * then reset operations are meaningless.
105 * A "database" mode changeset is therefore a changeset which is
106 * incapable of containing reset operations.
108 * It is not permitted to use a database-mode changeset for most
109 * operations (such as the @change argument to dconf_changeset_change()
110 * or the @changeset argument to #DConfClient APIs).
112 * If @copy_of is non-%NULL then its contents will be copied into the
113 * created changeset. @copy_of must be a database-mode changeset.
115 * Returns: a new #DConfChangeset in "database" mode
117 * Since: 0.16
119 DConfChangeset *
120 dconf_changeset_new_database (DConfChangeset *copy_of)
122 DConfChangeset *changeset;
124 g_return_val_if_fail (copy_of == NULL || copy_of->is_database, NULL);
126 changeset = dconf_changeset_new ();
127 changeset->is_database = TRUE;
129 if (copy_of)
131 GHashTableIter iter;
132 gpointer key, value;
134 g_hash_table_iter_init (&iter, copy_of->table);
135 while (g_hash_table_iter_next (&iter, &key, &value))
136 g_hash_table_insert (changeset->table, g_strdup (key), g_variant_ref (value));
139 return changeset;
143 * dconf_changeset_unref:
144 * @changeset: a #DConfChangeset
146 * Releases a #DConfChangeset reference.
148 void
149 dconf_changeset_unref (DConfChangeset *changeset)
151 if (g_atomic_int_dec_and_test (&changeset->ref_count))
153 g_free (changeset->prefix);
154 g_free (changeset->paths);
155 g_free (changeset->values);
157 g_hash_table_unref (changeset->table);
159 g_slice_free (DConfChangeset, changeset);
164 * dconf_changeset_ref:
165 * @changeset: a #DConfChangeset
167 * Increases the reference count on @changeset
169 * Returns: @changeset
171 DConfChangeset *
172 dconf_changeset_ref (DConfChangeset *changeset)
174 g_atomic_int_inc (&changeset->ref_count);
176 return changeset;
180 * dconf_changeset_set:
181 * @changeset: a #DConfChangeset
182 * @path: a path to modify
183 * @value: the value for the key, or %NULL to reset
185 * Adds an operation to modify @path to a #DConfChangeset.
187 * @path may either be a key or a dir. If it is a key then @value may
188 * be a #GVariant, or %NULL (to set or reset the key).
190 * If @path is a dir then this must be a reset operation: @value must be
191 * %NULL. It is not permitted to assign a #GVariant value to a dir.
193 void
194 dconf_changeset_set (DConfChangeset *changeset,
195 const gchar *path,
196 GVariant *value)
198 g_return_if_fail (changeset->prefix == NULL);
199 g_return_if_fail (dconf_is_path (path, NULL));
201 /* Check if we are performing a path reset */
202 if (g_str_has_suffix (path, "/"))
204 GHashTableIter iter;
205 gpointer key;
207 g_return_if_fail (value == NULL);
209 /* When we reset a path we must also reset all keys within that
210 * path.
212 g_hash_table_iter_init (&iter, changeset->table);
213 while (g_hash_table_iter_next (&iter, &key, NULL))
214 if (g_str_has_prefix (key, path))
215 g_hash_table_iter_remove (&iter);
217 /* If this is a non-database then record the reset itself. */
218 if (!changeset->is_database)
219 g_hash_table_insert (changeset->table, g_strdup (path), NULL);
222 /* ...or a value reset */
223 else if (value == NULL)
225 /* If we're a non-database, record the reset explicitly.
226 * Otherwise, just reset whatever may be there already.
228 if (!changeset->is_database)
229 g_hash_table_insert (changeset->table, g_strdup (path), NULL);
230 else
231 g_hash_table_remove (changeset->table, path);
234 /* ...or a normal write. */
235 else
236 g_hash_table_insert (changeset->table, g_strdup (path), g_variant_ref_sink (value));
240 * dconf_changeset_get:
241 * @changeset: a #DConfChangeset
242 * @key: the key to check
243 * @value: a return location for the value, or %NULL
245 * Checks if a #DConfChangeset has an outstanding request to change
246 * the value of the given @key.
248 * If the change doesn't involve @key then %FALSE is returned and the
249 * @value is unmodified.
251 * If the change modifies @key then @value is set either to the value
252 * for that key, or %NULL in the case that the key is being reset by the
253 * request.
255 * Returns: %TRUE if the key is being modified by the change
257 gboolean
258 dconf_changeset_get (DConfChangeset *changeset,
259 const gchar *key,
260 GVariant **value)
262 gpointer tmp;
264 if (!g_hash_table_lookup_extended (changeset->table, key, NULL, &tmp))
265 return FALSE;
267 if (value)
268 *value = tmp ? g_variant_ref (tmp) : NULL;
270 return TRUE;
274 * dconf_changeset_is_similar_to:
275 * @changeset: a #DConfChangeset
276 * @other: another #DConfChangeset
278 * Checks if @changeset is similar to @other.
280 * Two changes are considered similar if they write to the exact same
281 * set of keys. The values written are not considered.
283 * This check is used to prevent building up a queue of repeated writes
284 * of the same keys. This is often seen when an application writes to a
285 * key on every move of a slider or an application window.
287 * Strictly speaking, a write resettings all of "/a/" after a write
288 * containing "/a/b" could cause the later to be removed from the queue,
289 * but this situation is difficult to detect and is expected to be
290 * extremely rare.
292 * Returns: %TRUE if the changes are similar
294 gboolean
295 dconf_changeset_is_similar_to (DConfChangeset *changeset,
296 DConfChangeset *other)
298 GHashTableIter iter;
299 gpointer key;
301 if (g_hash_table_size (changeset->table) != g_hash_table_size (other->table))
302 return FALSE;
304 g_hash_table_iter_init (&iter, changeset->table);
305 while (g_hash_table_iter_next (&iter, &key, NULL))
306 if (!g_hash_table_contains (other->table, key))
307 return FALSE;
309 return TRUE;
313 * DConfChangesetPredicate:
314 * @path: a path, as per dconf_is_path()
315 * @value: a #GVariant, or %NULL
316 * @user_data: user data pointer
318 * Callback function type for predicates over items in a
319 * #DConfChangeset.
321 * Use with dconf_changeset_all().
323 * Returns: %TRUE if the predicate is met for the given @path and @value
327 * dconf_changeset_all:
328 * @changeset: a #DConfChangeset
329 * @predicate: a #DConfChangesetPredicate
330 * @user_data: user data to pass to @predicate
332 * Checks if all changes in the changeset satisfy @predicate.
334 * @predicate is called on each item in the changeset, in turn, until it
335 * returns %FALSE.
337 * If @predicate returns %FALSE for any item, this function returns
338 * %FALSE. If not (including the case of no items) then this function
339 * returns %TRUE.
341 * Returns: %TRUE if all items in @changeset satisfy @predicate
343 gboolean
344 dconf_changeset_all (DConfChangeset *changeset,
345 DConfChangesetPredicate predicate,
346 gpointer user_data)
348 GHashTableIter iter;
349 gpointer key, value;
351 g_hash_table_iter_init (&iter, changeset->table);
352 while (g_hash_table_iter_next (&iter, &key, &value))
353 if (!(* predicate) (key, value, user_data))
354 return FALSE;
356 return TRUE;
359 static gint
360 dconf_changeset_string_ptr_compare (gconstpointer a_p,
361 gconstpointer b_p)
363 const gchar * const *a = a_p;
364 const gchar * const *b = b_p;
366 return strcmp (*a, *b);
369 static void
370 dconf_changeset_build_description (DConfChangeset *changeset)
372 gsize prefix_length;
373 gint n_items;
375 n_items = g_hash_table_size (changeset->table);
377 /* If there are no items then what is there to describe? */
378 if (n_items == 0)
379 return;
381 /* We do three separate passes. This might take a bit longer than
382 * doing it all at once but it keeps the complexity down.
384 * First, we iterate the table in order to determine the common
385 * prefix.
387 * Next, we iterate the table again to pull the strings out excluding
388 * the leading prefix.
390 * We sort the list of paths at this point because the writer
391 * requires a sorted list in order to ensure that dir resets come
392 * before writes to keys in that dir.
394 * Finally, we iterate over the sorted list and use the normal
395 * hashtable lookup in order to populate the values array in the same
396 * order.
398 * Doing it this way avoids the complication of trying to sort two
399 * arrays (keys and values) at the same time.
402 /* Pass 1: determine the common prefix. */
404 GHashTableIter iter;
405 const gchar *first;
406 gboolean have_one;
407 gpointer key;
409 g_hash_table_iter_init (&iter, changeset->table);
411 /* We checked above that we have at least one item. */
412 have_one = g_hash_table_iter_next (&iter, &key, NULL);
413 g_assert (have_one);
415 prefix_length = strlen (key);
416 first = key;
418 /* Consider the remaining items to find the common prefix */
419 while (g_hash_table_iter_next (&iter, &key, NULL))
421 const gchar *this = key;
422 gint i;
424 for (i = 0; i < prefix_length; i++)
425 if (first[i] != this[i])
427 prefix_length = i;
428 break;
432 /* We must surely always have a common prefix of '/' */
433 g_assert (prefix_length > 0);
434 g_assert (first[0] == '/');
436 /* We may find that "/a/ab" and "/a/ac" have a common prefix of
437 * "/a/a" but really we want to trim that back to "/a/".
439 * If there is only one item, leave it alone.
441 if (n_items > 1)
443 while (first[prefix_length - 1] != '/')
444 prefix_length--;
447 changeset->prefix = g_strndup (first, prefix_length);
450 /* Pass 2: collect the list of keys, dropping the prefix */
452 GHashTableIter iter;
453 gpointer key;
454 gint i = 0;
456 changeset->paths = g_new (const gchar *, n_items + 1);
458 g_hash_table_iter_init (&iter, changeset->table);
459 while (g_hash_table_iter_next (&iter, &key, NULL))
461 const gchar *path = key;
463 changeset->paths[i++] = path + prefix_length;
465 changeset->paths[i] = NULL;
466 g_assert (i == n_items);
468 /* Sort the list of keys */
469 qsort (changeset->paths, n_items, sizeof (const gchar *), dconf_changeset_string_ptr_compare);
472 /* Pass 3: collect the list of values */
474 gint i;
476 changeset->values = g_new (GVariant *, n_items);
478 for (i = 0; i < n_items; i++)
479 /* We dropped the prefix when collecting the array.
480 * Bring it back temporarily, for the lookup.
482 changeset->values[i] = g_hash_table_lookup (changeset->table, changeset->paths[i] - prefix_length);
487 * dconf_changeset_describe:
488 * @changeset: a #DConfChangeset
489 * @prefix: the prefix under which changes have been requested
490 * @paths: the list of paths changed, relative to @prefix
491 * @values: the list of values changed
493 * Describes @changeset.
495 * @prefix and @paths are presented in the same way as they are for the
496 * DConfClient::changed signal. @values is an array of the same length
497 * as @paths. For each key described by an element in @paths, @values
498 * will contain either a #GVariant (the requested new value of that key)
499 * or %NULL (to reset a reset).
501 * The @paths array is returned in an order such that dir will always
502 * come before keys contained within those dirs.
504 * Returns: the number of changes (the length of @changes and @values).
506 guint
507 dconf_changeset_describe (DConfChangeset *changeset,
508 const gchar **prefix,
509 const gchar * const **paths,
510 GVariant * const **values)
512 gint n_items;
514 n_items = g_hash_table_size (changeset->table);
516 if (n_items && !changeset->prefix)
517 dconf_changeset_build_description (changeset);
519 if (prefix)
520 *prefix = changeset->prefix;
522 if (paths)
523 *paths = changeset->paths;
525 if (values)
526 *values = changeset->values;
528 return n_items;
532 * dconf_changeset_serialise:
533 * @changeset: a #DConfChangeset
535 * Serialises a #DConfChangeset.
537 * The returned value has no particular format and should only be passed
538 * to dconf_changeset_deserialise().
540 * Returns: a floating #GVariant
542 GVariant *
543 dconf_changeset_serialise (DConfChangeset *changeset)
545 GVariantBuilder builder;
546 GHashTableIter iter;
547 gpointer key, value;
549 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{smv}"));
551 g_hash_table_iter_init (&iter, changeset->table);
552 while (g_hash_table_iter_next (&iter, &key, &value))
553 g_variant_builder_add (&builder, "{smv}", key, value);
555 return g_variant_builder_end (&builder);
559 * dconf_changeset_deserialise:
560 * @serialised: a #GVariant from dconf_changeset_serialise()
562 * Creates a #DConfChangeset according to a serialised description
563 * returned from an earlier call to dconf_changeset_serialise().
565 * @serialised has no particular format -- you should only pass a value
566 * that resulted from an earlier serialise operation.
568 * This call never fails, even if @serialised is not in the correct
569 * format. Improperly-formatted parts are simply ignored.
571 * Returns: a new #DConfChangeset
573 DConfChangeset *
574 dconf_changeset_deserialise (GVariant *serialised)
576 DConfChangeset *changeset;
577 GVariantIter iter;
578 const gchar *key;
579 GVariant *value;
581 changeset = dconf_changeset_new ();
582 g_variant_iter_init (&iter, serialised);
583 while (g_variant_iter_loop (&iter, "{&smv}", &key, &value))
585 /* If value is NULL: we may be resetting a key or a dir (a path).
586 * If value is non-NULL: we must be setting a key.
588 * ie: it is not possible to set a value to a directory.
590 * If we get an invalid case, just fall through and ignore it.
592 if (value == NULL)
594 if (dconf_is_path (key, NULL))
595 g_hash_table_insert (changeset->table, g_strdup (key), NULL);
597 else
599 if (dconf_is_key (key, NULL))
600 g_hash_table_insert (changeset->table, g_strdup (key), g_variant_ref (value));
604 return changeset;
608 * dconf_changeset_new_write:
609 * @path: a dconf path
610 * @value: a #GVariant, or %NULL
612 * Creates a new #DConfChangeset with one change. This is equivalent to
613 * calling dconf_changeset_new() and then dconf_changeset_set() with
614 * @path and @value.
616 * Returns: a new #DConfChangeset
618 DConfChangeset *
619 dconf_changeset_new_write (const gchar *path,
620 GVariant *value)
622 DConfChangeset *changeset;
624 changeset = dconf_changeset_new ();
625 dconf_changeset_set (changeset, path, value);
627 return changeset;
631 * dconf_changeset_is_empty:
632 * @changeset: a #DConfChangeset
634 * Checks if @changeset is empty (ie: contains no changes).
636 * Returns: %TRUE if @changeset is empty
638 gboolean
639 dconf_changeset_is_empty (DConfChangeset *changeset)
641 return !g_hash_table_size (changeset->table);
645 * dconf_changeset_change:
646 * @changeset: a #DConfChangeset (to be changed)
647 * @changes: the changes to make to @changeset
649 * Applies @changes to @changeset.
651 * If @changeset is a normal changeset then reset requests in @changes
652 * will be allied to @changeset and then copied down into it. In this
653 * case the two changesets are effectively being merged.
655 * If @changeset is in database mode then the reset operations in
656 * @changes will simply be applied to @changeset.
658 * Since: 0.16
660 void
661 dconf_changeset_change (DConfChangeset *changeset,
662 DConfChangeset *changes)
664 gsize prefix_len;
665 gint i;
667 /* Handling resets is a little bit tricky...
669 * Consider the case that we have @changeset containing a key /a/b and
670 * @changes containing a reset request for /a/ and a set request for
671 * /a/c.
673 * It's clear that at the end of this all, we should have only /a/c
674 * but in order for that to be the case, we need to make sure that we
675 * process the reset of /a/ before we process the set of /a/c.
677 * The easiest way to do this is to visit the strings in sorted order.
678 * That removes the possibility of iterating over the hash table, but
679 * dconf_changeset_build_description() makes the list in the order we
680 * need so just call it and then iterate over the result.
683 if (!dconf_changeset_describe (changes, NULL, NULL, NULL))
684 return;
686 prefix_len = strlen (changes->prefix);
687 for (i = 0; changes->paths[i]; i++)
689 const gchar *path;
690 GVariant *value;
692 /* The changes->paths are just pointers into the keys of the
693 * hashtable, fast-forwarded past the prefix. Rewind a bit.
695 path = changes->paths[i] - prefix_len;
696 value = changes->values[i];
698 dconf_changeset_set (changeset, path, value);
703 * dconf_changeset_diff:
704 * @from: a database mode changeset
705 * @to: a database mode changeset
707 * Compares to database-mode changesets and produces a changeset that
708 * describes their differences.
710 * If there is no difference, %NULL is returned.
712 * Applying the returned changeset to @from using
713 * dconf_changeset_change() will result in the two changesets being
714 * equal.
716 * Returns: (transfer full): the changes, or %NULL
718 * Since: 0.16
720 DConfChangeset *
721 dconf_changeset_diff (DConfChangeset *from,
722 DConfChangeset *to)
724 DConfChangeset *changeset = NULL;
725 GHashTableIter iter;
726 gpointer key, val;
728 g_return_val_if_fail (from->is_database, NULL);
729 g_return_val_if_fail (to->is_database, NULL);
731 /* We make no attempt to do dir resets, but we could...
733 * For now, we just reset each key individually.
735 * We create our list of changes in two steps:
737 * - iterate the 'to' changeset and note any keys that do not have
738 * the same value in the 'from' changeset
740 * - iterate the 'from' changeset and note any keys not present in
741 * the 'to' changeset, recording resets for them
743 * This will cover all changes.
745 * Note: because 'from' and 'to' are database changesets we don't have
746 * to worry about seeing NULL values or dirs.
748 g_hash_table_iter_init (&iter, to->table);
749 while (g_hash_table_iter_next (&iter, &key, &val))
751 GVariant *from_val = g_hash_table_lookup (from->table, key);
753 if (from_val == NULL || !g_variant_equal (val, from_val))
755 if (!changeset)
756 changeset = dconf_changeset_new ();
758 dconf_changeset_set (changeset, key, val);
762 g_hash_table_iter_init (&iter, from->table);
763 while (g_hash_table_iter_next (&iter, &key, &val))
764 if (!g_hash_table_lookup (to->table, key))
766 if (!changeset)
767 changeset = dconf_changeset_new ();
769 dconf_changeset_set (changeset, key, NULL);
772 return changeset;