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"
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
66 unref_gvariant0 (gpointer data
)
69 g_variant_unref (data
);
73 * dconf_changeset_new:
75 * Creates a new, empty, #DConfChangeset.
77 * Returns: the new #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;
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
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
;
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
));
143 * dconf_changeset_unref:
144 * @changeset: a #DConfChangeset
146 * Releases a #DConfChangeset reference.
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
172 dconf_changeset_ref (DConfChangeset
*changeset
)
174 g_atomic_int_inc (&changeset
->ref_count
);
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.
194 dconf_changeset_set (DConfChangeset
*changeset
,
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
, "/"))
207 g_return_if_fail (value
== NULL
);
209 /* When we reset a path we must also reset all keys within that
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
);
231 g_hash_table_remove (changeset
->table
, path
);
234 /* ...or a normal write. */
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
255 * Returns: %TRUE if the key is being modified by the change
258 dconf_changeset_get (DConfChangeset
*changeset
,
264 if (!g_hash_table_lookup_extended (changeset
->table
, key
, NULL
, &tmp
))
268 *value
= tmp
? g_variant_ref (tmp
) : NULL
;
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
292 * Returns: %TRUE if the changes are similar
295 dconf_changeset_is_similar_to (DConfChangeset
*changeset
,
296 DConfChangeset
*other
)
301 if (g_hash_table_size (changeset
->table
) != g_hash_table_size (other
->table
))
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
))
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
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
337 * If @predicate returns %FALSE for any item, this function returns
338 * %FALSE. If not (including the case of no items) then this function
341 * Returns: %TRUE if all items in @changeset satisfy @predicate
344 dconf_changeset_all (DConfChangeset
*changeset
,
345 DConfChangesetPredicate predicate
,
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
))
360 dconf_changeset_string_ptr_compare (gconstpointer a_p
,
363 const gchar
* const *a
= a_p
;
364 const gchar
* const *b
= b_p
;
366 return strcmp (*a
, *b
);
370 dconf_changeset_build_description (DConfChangeset
*changeset
)
375 n_items
= g_hash_table_size (changeset
->table
);
377 /* If there are no items then what is there to describe? */
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
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
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. */
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
);
415 prefix_length
= strlen (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
;
424 for (i
= 0; i
< prefix_length
; i
++)
425 if (first
[i
] != this[i
])
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.
443 while (first
[prefix_length
- 1] != '/')
447 changeset
->prefix
= g_strndup (first
, prefix_length
);
450 /* Pass 2: collect the list of keys, dropping the prefix */
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 */
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).
507 dconf_changeset_describe (DConfChangeset
*changeset
,
508 const gchar
**prefix
,
509 const gchar
* const **paths
,
510 GVariant
* const **values
)
514 n_items
= g_hash_table_size (changeset
->table
);
516 if (n_items
&& !changeset
->prefix
)
517 dconf_changeset_build_description (changeset
);
520 *prefix
= changeset
->prefix
;
523 *paths
= changeset
->paths
;
526 *values
= changeset
->values
;
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
543 dconf_changeset_serialise (DConfChangeset
*changeset
)
545 GVariantBuilder builder
;
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
574 dconf_changeset_deserialise (GVariant
*serialised
)
576 DConfChangeset
*changeset
;
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.
594 if (dconf_is_path (key
, NULL
))
595 g_hash_table_insert (changeset
->table
, g_strdup (key
), NULL
);
599 if (dconf_is_key (key
, NULL
))
600 g_hash_table_insert (changeset
->table
, g_strdup (key
), g_variant_ref (value
));
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
616 * Returns: a new #DConfChangeset
619 dconf_changeset_new_write (const gchar
*path
,
622 DConfChangeset
*changeset
;
624 changeset
= dconf_changeset_new ();
625 dconf_changeset_set (changeset
, path
, value
);
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
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.
661 dconf_changeset_change (DConfChangeset
*changeset
,
662 DConfChangeset
*changes
)
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
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
))
686 prefix_len
= strlen (changes
->prefix
);
687 for (i
= 0; changes
->paths
[i
]; i
++)
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
716 * Returns: (transfer full): the changes, or %NULL
721 dconf_changeset_diff (DConfChangeset
*from
,
724 DConfChangeset
*changeset
= NULL
;
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
))
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
))
767 changeset
= dconf_changeset_new ();
769 dconf_changeset_set (changeset
, key
, NULL
);