1 /*
2  * Copyright © 2010 Codethink Limited
3  * Copyright © 2012 Canonical Limited
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the licence, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Ryan Lortie <desrt@desrt.ca>
19  */
20 
21 #include "config.h"
22 
23 #include "dconf-client.h"
24 
25 #include "../engine/dconf-engine.h"
26 #include "../common/dconf-paths.h"
27 #include <glib-object.h>
28 
29 /**
30  * SECTION:client
31  * @title: DConfClient
32  * @short_description: Direct read and write access to dconf, based on GDBus
33  *
34  * This is the primary client interface to dconf.
35  *
36  * It allows applications to directly read from and write to the dconf
37  * database.  Applications can subscribe to change notifications.
38  *
39  * Most applications probably don't want to access dconf directly and
40  * would be better off using something like #GSettings.
41  *
42  * Please note that the API of libdconf is not stable in any way.  It
43  * has changed in incompatible ways in the past and there will be
44  * further changes in the future.
45  **/
46 
47 /**
48  * DConfClient:
49  *
50  * The main object for interacting with dconf.  This is a #GObject, so
51  * you should manage it with g_object_ref() and g_object_unref().
52  **/
53 struct _DConfClient
54 {
55   GObject parent_instance;
56 
57   DConfEngine  *engine;
58   GMainContext *context;
59 };
60 
61 G_DEFINE_TYPE (DConfClient, dconf_client, G_TYPE_OBJECT)
62 
63 enum
64 {
65   SIGNAL_CHANGED,
66   SIGNAL_WRITABILITY_CHANGED,
67   N_SIGNALS
68 };
69 static guint dconf_client_signals[N_SIGNALS];
70 
71 static void
dconf_client_finalize(GObject * object)72 dconf_client_finalize (GObject *object)
73 {
74   DConfClient *client = DCONF_CLIENT (object);
75 
76   dconf_engine_unref (client->engine);
77   g_main_context_unref (client->context);
78 
79   G_OBJECT_CLASS (dconf_client_parent_class)
80     ->finalize (object);
81 }
82 
83 static void
dconf_client_init(DConfClient * client)84 dconf_client_init (DConfClient *client)
85 {
86 }
87 
88 static void
dconf_client_class_init(DConfClientClass * class)89 dconf_client_class_init (DConfClientClass *class)
90 {
91   GObjectClass *object_class = G_OBJECT_CLASS (class);
92 
93   object_class->finalize = dconf_client_finalize;
94 
95   /**
96    * DConfClient::changed:
97    * @client: the #DConfClient reporting the change
98    * @prefix: the prefix under which the changes happened
99    * @changes: the list of paths that were changed, relative to @prefix
100    * @tag: the tag for the change, if it originated from the service
101    *
102    * This signal is emitted when the #DConfClient has a possible change
103    * to report.  The signal is an indication that a change may have
104    * occurred; it's possible that the keys will still have the same value
105    * as before.
106    *
107    * To ensure that you receive notification about changes to paths that
108    * you are interested in you must call dconf_client_watch_fast() or
109    * dconf_client_watch_sync().  You may still receive notifications for
110    * paths that you did not explicitly watch.
111    *
112    * @prefix will be an absolute dconf path; see dconf_is_path().
113    * @changes is a %NULL-terminated array of dconf rel paths; see
114    * dconf_is_rel_path().
115    *
116    * @tag is an opaque tag string, or %NULL.  The only thing you should
117    * do with @tag is to compare it to tag values returned by
118    * dconf_client_write_sync() or dconf_client_change_sync().
119    *
120    * The number of changes being reported is equal to the length of
121    * @changes.  Appending each item in @changes to @prefix will give the
122    * absolute path of each changed item.
123    *
124    * If a single key has changed then @prefix will be equal to the key
125    * and @changes will contain a single item: the empty string.
126    *
127    * If a single dir has changed (indicating that any key under the dir
128    * may have changed) then @prefix will be equal to the dir and
129    * @changes will contain a single empty string.
130    *
131    * If more than one change is being reported then @changes will have
132    * more than one item.
133    **/
134   dconf_client_signals[SIGNAL_CHANGED] = g_signal_new ("changed", DCONF_TYPE_CLIENT, G_SIGNAL_RUN_LAST,
135                                                        0, NULL, NULL, NULL, G_TYPE_NONE, 3,
136                                                        G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
137                                                        G_TYPE_STRV | G_SIGNAL_TYPE_STATIC_SCOPE,
138                                                        G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
139 
140   /**
141    * DConfClient::writability-changed:
142    * @client: the #DConfClient reporting the change
143    * @path: the dir or key that changed
144    *
145    * Signal emitted when writability for a key (or all keys in a dir) changes.
146    * It will be immediately followed by #DConfClient::changed signal for
147    * the path.
148    */
149   dconf_client_signals[SIGNAL_WRITABILITY_CHANGED] = g_signal_new ("writability-changed", DCONF_TYPE_CLIENT,
150                                                                    G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
151                                                                    G_TYPE_NONE, 1,
152                                                                    G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
153 }
154 
155 typedef struct
156 {
157   DConfClient  *client;
158   gchar        *prefix;
159   gchar       **changes;
160   gchar        *tag;
161   gboolean      is_writability;
162 } DConfClientChange;
163 
164 static gboolean
dconf_client_dispatch_change_signal(gpointer user_data)165 dconf_client_dispatch_change_signal (gpointer user_data)
166 {
167   DConfClientChange *change = user_data;
168 
169   if (change->is_writability)
170     {
171       /* We know that the engine does it this way... */
172       g_assert (change->changes[0][0] == '\0' && change->changes[1] == NULL);
173 
174       g_signal_emit (change->client,
175                      dconf_client_signals[SIGNAL_WRITABILITY_CHANGED], 0,
176                      change->prefix);
177     }
178 
179   g_signal_emit (change->client, dconf_client_signals[SIGNAL_CHANGED], 0,
180                  change->prefix, change->changes, change->tag);
181 
182   g_object_unref (change->client);
183   g_free (change->prefix);
184   g_strfreev (change->changes);
185   g_free (change->tag);
186   g_slice_free (DConfClientChange, change);
187 
188   return G_SOURCE_REMOVE;
189 }
190 
191 void
dconf_engine_change_notify(DConfEngine * engine,const gchar * prefix,const gchar * const * changes,const gchar * tag,gboolean is_writability,gpointer origin_tag,gpointer user_data)192 dconf_engine_change_notify (DConfEngine         *engine,
193                             const gchar         *prefix,
194                             const gchar * const *changes,
195                             const gchar *        tag,
196                             gboolean             is_writability,
197                             gpointer             origin_tag,
198                             gpointer             user_data)
199 {
200   GWeakRef *weak_ref = user_data;
201   DConfClientChange *change;
202   DConfClient *client;
203 
204   client = g_weak_ref_get (weak_ref);
205 
206   if (client == NULL)
207     return;
208 
209   g_return_if_fail (DCONF_IS_CLIENT (client));
210 
211   change = g_slice_new (DConfClientChange);
212   change->client = client;
213   change->prefix = g_strdup (prefix);
214   change->changes = g_strdupv ((gchar **) changes);
215   change->tag = g_strdup (tag);
216   change->is_writability = is_writability;
217 
218   g_main_context_invoke (client->context, dconf_client_dispatch_change_signal, change);
219 }
220 
221 static void
dconf_client_free_weak_ref(gpointer data)222 dconf_client_free_weak_ref (gpointer data)
223 {
224   GWeakRef *weak_ref = data;
225 
226   g_weak_ref_clear (weak_ref);
227   g_slice_free (GWeakRef, weak_ref);
228 }
229 
230 /**
231  * dconf_client_new:
232  *
233  * Creates a new #DConfClient.
234  *
235  * Returns: (transfer full): a new #DConfClient
236  **/
237 DConfClient *
dconf_client_new(void)238 dconf_client_new (void)
239 {
240   DConfClient *client;
241   GWeakRef *weak_ref;
242 
243   client = g_object_new (DCONF_TYPE_CLIENT, NULL);
244   weak_ref = g_slice_new (GWeakRef);
245   g_weak_ref_init (weak_ref, client);
246   client->engine = dconf_engine_new (NULL, weak_ref, dconf_client_free_weak_ref);
247   client->context = g_main_context_ref_thread_default ();
248 
249   return client;
250 }
251 
252 /**
253  * dconf_client_read:
254  * @client: a #DConfClient
255  * @key: the key to read the value of
256  *
257  * Reads the current value of @key.
258  *
259  * If @key exists, its value is returned.  Otherwise, %NULL is returned.
260  *
261  * If there are outstanding "fast" changes in progress they may affect
262  * the result of this call.
263  *
264  * Returns: (transfer full) (nullable): a #GVariant, or %NULL
265  **/
266 GVariant *
dconf_client_read(DConfClient * client,const gchar * key)267 dconf_client_read (DConfClient *client,
268                    const gchar *key)
269 {
270   g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
271 
272   return dconf_engine_read (client->engine, DCONF_READ_FLAGS_NONE, NULL, key);
273 }
274 
275 /**
276  * DConfReadFlags:
277  * @DCONF_READ_FLAGS_NONE: no flags
278  * @DCONF_READ_DEFAULT_VALUE: read the default value, ignoring any
279  *   values in writable databases or any queued changes.  This is
280  *   effectively equivalent to asking what value would be read after a
281  *   reset was written for the key in question.
282  * @DCONF_READ_USER_VALUE: read the user value, ignoring any system
283  *   databases, including ignoring locks.  It is even possible to read
284  *   "invisible" values in the user database in this way, which would
285  *   have normally been ignored because of locks.
286  *
287  * Since: 0.26
288  */
289 
290 /**
291  * dconf_client_read_full:
292  * @client: a #DConfClient
293  * @key: the key to read the default value of
294  * @flags: #DConfReadFlags
295  * @read_through: a #GQueue of #DConfChangeset
296  *
297  * Reads the current value of @key.
298  *
299  * If @flags contains %DCONF_READ_USER_VALUE then only the user value
300  * will be read.  Locks are ignored, which means that it is possible to
301  * use this API to read "invisible" user values which are hidden by
302  * system locks.
303  *
304  * If @flags contains %DCONF_READ_DEFAULT_VALUE then only non-user
305  * values will be read.  The result will be exactly equivalent to the
306  * value that would be read if the current value of the key were to be
307  * reset.
308  *
309  * Flags may not contain both %DCONF_READ_USER_VALUE and
310  * %DCONF_READ_DEFAULT_VALUE.
311  *
312  * If @read_through is non-%NULL, %DCONF_READ_DEFAULT_VALUE is not
313  * given then @read_through is checked for the key in question, subject
314  * to the restriction that the key in question is writable.  This
315  * effectively answers the question of "what would happen if these
316  * changes were committed".
317  *
318  * If there are outstanding "fast" changes in progress they may affect
319  * the result of this call.
320  *
321  * If @flags is %DCONF_READ_FLAGS_NONE and @read_through is %NULL then
322  * this call is exactly equivalent to dconf_client_read().
323  *
324  * Returns: (transfer full) (nullable): a #GVariant, or %NULL
325  *
326  * Since: 0.26
327  */
328 GVariant *
dconf_client_read_full(DConfClient * client,const gchar * key,DConfReadFlags flags,const GQueue * read_through)329 dconf_client_read_full (DConfClient    *client,
330                         const gchar    *key,
331                         DConfReadFlags  flags,
332                         const GQueue   *read_through)
333 {
334   g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
335 
336   return dconf_engine_read (client->engine, flags, read_through, key);
337 }
338 
339 /**
340  * dconf_client_list:
341  * @client: a #DConfClient
342  * @dir: the dir to list the contents of
343  * @length: the length of the returned list
344  *
345  * Gets the list of all dirs and keys immediately under @dir.
346  *
347  * If @length is non-%NULL then it will be set to the length of the
348  * returned array.  In any case, the array is %NULL-terminated.
349  *
350  * IF there are outstanding "fast" changes in progress then this call
351  * may return inaccurate results with respect to those outstanding
352  * changes.
353  *
354  * Returns: (transfer full) (not nullable): an array of strings, never %NULL.
355  **/
356 gchar **
dconf_client_list(DConfClient * client,const gchar * dir,gint * length)357 dconf_client_list (DConfClient *client,
358                    const gchar *dir,
359                    gint        *length)
360 {
361   g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
362 
363   return dconf_engine_list (client->engine, dir, length);
364 }
365 
366 /**
367  * dconf_client_list_locks:
368  * @client: a #DConfClient
369  * @dir: the dir to limit results to
370  * @length: the length of the returned list.
371  *
372  * Lists all locks under @dir in effect for @client.
373  *
374  * If no locks are in effect, an empty list is returned.  If no keys are
375  * writable at all then a list containing @dir is returned.
376  *
377  * The returned list will be %NULL-terminated.
378  *
379  * Returns: (transfer full) (not nullable): an array of strings, never %NULL.
380  *
381  * Since: 0.26
382  */
383 gchar **
dconf_client_list_locks(DConfClient * client,const gchar * dir,gint * length)384 dconf_client_list_locks (DConfClient *client,
385                          const gchar *dir,
386                          gint        *length)
387 {
388   g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
389   g_return_val_if_fail (dconf_is_dir (dir, NULL), NULL);
390 
391   return dconf_engine_list_locks (client->engine, dir, length);
392 }
393 
394 /**
395  * dconf_client_is_writable:
396  * @client: a #DConfClient
397  * @key: the key to check for writability
398  *
399  * Checks if @key is writable (ie: the key has no locks).
400  *
401  * This call does not verify that writing to the key will actually be
402  * successful.  It only checks that the database is writable and that
403  * there are no locks affecting @key.  Other issues (such as a full disk
404  * or an inability to connect to the bus and start the service) may
405  * cause the write to fail.
406  *
407  * Returns: %TRUE if @key is writable
408  **/
409 gboolean
dconf_client_is_writable(DConfClient * client,const gchar * key)410 dconf_client_is_writable (DConfClient *client,
411                           const gchar *key)
412 {
413   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
414 
415   return dconf_engine_is_writable (client->engine, key);
416 }
417 
418 /**
419  * dconf_client_write_fast:
420  * @client: a #DConfClient
421  * @key: the key to write to
422  * @value: a #GVariant, the value to write. If it has a floating reference it's
423  *  consumed.
424  * @error: a pointer to a %NULL #GError, or %NULL
425  *
426  * Writes @value to the given @key, or reset @key to its default value.
427  *
428  * If @value is %NULL then @key is reset to its default value (which may
429  * be completely unset), otherwise @value becomes the new value.
430  *
431  * This call merely queues up the write and returns immediately, without
432  * blocking.  The only errors that can be detected or reported at this
433  * point are attempts to write to read-only keys.  If the application
434  * exits immediately after this function returns then the queued call
435  * may never be sent; see dconf_client_sync().
436  *
437  * A local copy of the written value is kept so that calls to
438  * dconf_client_read() that occur before the service actually makes the
439  * change will return the new value.
440  *
441  * If the write is queued then a change signal will be directly emitted.
442  * If this function is being called from the main context of @client
443  * then the signal is emitted before this function returns; otherwise it
444  * is scheduled on the main context.
445  *
446  * Returns: %TRUE if the write was queued
447  **/
448 gboolean
dconf_client_write_fast(DConfClient * client,const gchar * key,GVariant * value,GError ** error)449 dconf_client_write_fast (DConfClient  *client,
450                          const gchar  *key,
451                          GVariant     *value,
452                          GError      **error)
453 {
454   DConfChangeset *changeset;
455   gboolean success;
456 
457   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
458 
459   changeset = dconf_changeset_new_write (key, value);
460   success = dconf_engine_change_fast (client->engine, changeset, NULL, error);
461   dconf_changeset_unref (changeset);
462 
463   return success;
464 }
465 
466 /**
467  * dconf_client_write_sync:
468  * @client: a #DConfClient
469  * @key: the key to write to
470  * @value: a #GVariant, the value to write. If it has a floating reference it's
471  *  consumed.
472  * @tag: (out) (optional) (not nullable) (transfer full): the tag from this write
473  * @cancellable: a #GCancellable, or %NULL
474  * @error: a pointer to a %NULL #GError, or %NULL
475  *
476  * Write @value to the given @key, or reset @key to its default value.
477  *
478  * If @value is %NULL then @key is reset to its default value (which may
479  * be completely unset), otherwise @value becomes the new value.
480  *
481  * This call blocks until the write is complete.  This call will
482  * therefore detect and report all cases of failure.  If the modified
483  * key is currently being watched then a signal will be emitted from the
484  * main context of @client (once the signal arrives from the service).
485  *
486  * If @tag is non-%NULL then it is set to the unique tag associated with
487  * this write.  This is the same tag that will appear in the following
488  * change signal.
489  *
490  * Returns: %TRUE on success, else %FALSE with @error set
491  **/
492 gboolean
dconf_client_write_sync(DConfClient * client,const gchar * key,GVariant * value,gchar ** tag,GCancellable * cancellable,GError ** error)493 dconf_client_write_sync (DConfClient   *client,
494                          const gchar   *key,
495                          GVariant      *value,
496                          gchar        **tag,
497                          GCancellable  *cancellable,
498                          GError       **error)
499 {
500   DConfChangeset *changeset;
501   gboolean success;
502 
503   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
504 
505   changeset = dconf_changeset_new_write (key, value);
506   success = dconf_engine_change_sync (client->engine, changeset, tag, error);
507   dconf_changeset_unref (changeset);
508 
509   return success;
510 }
511 
512 /**
513  * dconf_client_change_fast:
514  * @client: a #DConfClient
515  * @changeset: the changeset describing the requested change
516  * @error: a pointer to a %NULL #GError, or %NULL
517  *
518  * Performs the change operation described by @changeset.
519  *
520  * Once @changeset is passed to this call it can no longer be modified.
521  *
522  * This call merely queues up the write and returns immediately, without
523  * blocking.  The only errors that can be detected or reported at this
524  * point are attempts to write to read-only keys.  If the application
525  * exits immediately after this function returns then the queued call
526  * may never be sent; see dconf_client_sync().
527  *
528  * A local copy of the written value is kept so that calls to
529  * dconf_client_read() that occur before the service actually makes the
530  * change will return the new value.
531  *
532  * If the write is queued then a change signal will be directly emitted.
533  * If this function is being called from the main context of @client
534  * then the signal is emitted before this function returns; otherwise it
535  * is scheduled on the main context.
536  *
537  * Returns: %TRUE if the requested changed was queued
538  **/
539 gboolean
dconf_client_change_fast(DConfClient * client,DConfChangeset * changeset,GError ** error)540 dconf_client_change_fast (DConfClient     *client,
541                           DConfChangeset  *changeset,
542                           GError         **error)
543 {
544   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
545 
546   return dconf_engine_change_fast (client->engine, changeset, NULL, error);
547 }
548 
549 /**
550  * dconf_client_change_sync:
551  * @client: a #DConfClient
552  * @changeset: the changeset describing the requested change
553  * @tag: (out) (optional) (not nullable) (transfer full): the tag from this write
554  * @cancellable: a #GCancellable, or %NULL
555  * @error: a pointer to a %NULL #GError, or %NULL
556  *
557  * Performs the change operation described by @changeset.
558  *
559  * Once @changeset is passed to this call it can no longer be modified.
560  *
561  * This call blocks until the change is complete.  This call will
562  * therefore detect and report all cases of failure.  If any of the
563  * modified keys are currently being watched then a signal will be
564  * emitted from the main context of @client (once the signal arrives
565  * from the service).
566  *
567  * If @tag is non-%NULL then it is set to the unique tag associated with
568  * this change.  This is the same tag that will appear in the following
569  * change signal.  If @changeset makes no changes then @tag may be
570  * non-unique (eg: the empty string may be used for empty changesets).
571  *
572  * Returns: %TRUE on success, else %FALSE with @error set
573  **/
574 gboolean
dconf_client_change_sync(DConfClient * client,DConfChangeset * changeset,gchar ** tag,GCancellable * cancellable,GError ** error)575 dconf_client_change_sync (DConfClient     *client,
576                           DConfChangeset  *changeset,
577                           gchar          **tag,
578                           GCancellable    *cancellable,
579                           GError         **error)
580 {
581   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
582 
583   return dconf_engine_change_sync (client->engine, changeset, tag, error);
584 }
585 
586 /**
587  * dconf_client_watch_fast:
588  * @client: a #DConfClient
589  * @path: a path to watch
590  *
591  * Requests change notifications for @path.
592  *
593  * If @path is a key then the single key is monitored.  If @path is a
594  * dir then all keys under the dir are monitored.
595  *
596  * This function queues the watch request with D-Bus and returns
597  * immediately.  There is a very slim chance that the dconf database
598  * could change before the watch is actually established.  If that is
599  * the case then a synthetic change signal will be emitted.
600  *
601  * Errors are silently ignored.
602  **/
603 void
dconf_client_watch_fast(DConfClient * client,const gchar * path)604 dconf_client_watch_fast (DConfClient *client,
605                          const gchar *path)
606 {
607   g_return_if_fail (DCONF_IS_CLIENT (client));
608 
609   dconf_engine_watch_fast (client->engine, path);
610 }
611 
612 /**
613  * dconf_client_watch_sync:
614  * @client: a #DConfClient
615  * @path: a path to watch
616  *
617  * Requests change notifications for @path.
618  *
619  * If @path is a key then the single key is monitored.  If @path is a
620  * dir then all keys under the dir are monitored.
621  *
622  * This function submits each of the various watch requests that are
623  * required to monitor a key and waits until each of them returns.  By
624  * the time this function returns, the watch has been established.
625  *
626  * Errors are silently ignored.
627  **/
628 void
dconf_client_watch_sync(DConfClient * client,const gchar * path)629 dconf_client_watch_sync (DConfClient *client,
630                          const gchar *path)
631 {
632   g_return_if_fail (DCONF_IS_CLIENT (client));
633 
634   dconf_engine_watch_sync (client->engine, path);
635 }
636 
637 /**
638  * dconf_client_unwatch_fast:
639  * @client: a #DConfClient
640  * @path: a path previously watched
641  *
642  * Cancels the effect of a previous call to dconf_client_watch_fast().
643  *
644  * This call returns immediately.
645  *
646  * It is still possible that change signals are received after this call
647  * had returned (watching guarantees notification of changes, but
648  * unwatching does not guarantee no notifications).
649  **/
650 void
dconf_client_unwatch_fast(DConfClient * client,const gchar * path)651 dconf_client_unwatch_fast (DConfClient *client,
652                            const gchar *path)
653 {
654   g_return_if_fail (DCONF_IS_CLIENT (client));
655 
656   dconf_engine_unwatch_fast (client->engine, path);
657 }
658 
659 /**
660  * dconf_client_unwatch_sync:
661  * @client: a #DConfClient
662  * @path: a path previously watched
663  *
664  * Cancels the effect of a previous call to dconf_client_watch_sync().
665  *
666  * This function submits each of the various unwatch requests and waits
667  * until each of them returns.  It is still possible that change signals
668  * are received after this call has returned (watching guarantees
669  * notification of changes, but unwatching does not guarantee no
670  * notifications).
671  **/
672 void
dconf_client_unwatch_sync(DConfClient * client,const gchar * path)673 dconf_client_unwatch_sync (DConfClient *client,
674                            const gchar *path)
675 {
676   g_return_if_fail (DCONF_IS_CLIENT (client));
677 
678   dconf_engine_unwatch_sync (client->engine, path);
679 }
680 
681 /**
682  * dconf_client_sync:
683  * @client: a #DConfClient
684  *
685  * Blocks until all outstanding "fast" change or write operations have
686  * been submitted to the service.
687  *
688  * Applications should generally call this before exiting on any
689  * #DConfClient that they wrote to.
690  **/
691 void
dconf_client_sync(DConfClient * client)692 dconf_client_sync (DConfClient *client)
693 {
694   g_return_if_fail (DCONF_IS_CLIENT (client));
695 
696   dconf_engine_sync (client->engine);
697 }
698