summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2012-07-13 14:14:04 -0400
committerRyan Lortie <desrt@desrt.ca>2012-07-13 14:14:04 -0400
commit68a2895ca9896c795aa1644d30ac1ea8f129805b (patch)
tree9202d21fffcdcbb13304afff1959090f20355a94
parentbdce5c2b1780b9f6b4381fe2877f472335ecb050 (diff)
parent0bd2f8ee907b2c3e74b20c3cd58b0da1b6a586fb (diff)
downloaddconf-68a2895ca9896c795aa1644d30ac1ea8f129805b.tar.gz
Merge branch 'wip/reorg'
Conflicts: bin/dconf-dump.vala configure.ac editor/Makefile.am
-rw-r--r--.gitignore6
-rw-r--r--HACKING80
-rw-r--r--Makefile.am25
-rw-r--r--Makefile.gtester66
-rw-r--r--README81
-rw-r--r--bin/Makefile.am23
-rw-r--r--bin/dconf-dump.vala37
-rw-r--r--bin/dconf-update.vala2
-rw-r--r--bin/dconf.vala28
-rw-r--r--bin/gvdb.vapi2
-rw-r--r--client/.gitignore9
-rw-r--r--client/Makefile.am48
-rw-r--r--client/dconf-client.c570
-rw-r--r--client/dconf-client.h87
-rw-r--r--client/dconf-client.vala380
-rw-r--r--client/dconf.deps1
-rw-r--r--client/dconf.h1
-rw-r--r--client/dconf.vapi60
-rw-r--r--client/engine.vapi35
-rw-r--r--client/extra-docs.c89
-rw-r--r--common/.gitignore3
-rw-r--r--common/Makefile.am17
-rw-r--r--common/dconf-changeset.c562
-rw-r--r--common/dconf-changeset.h64
-rw-r--r--common/dconf-paths.c24
-rw-r--r--common/dconf-paths.h2
-rw-r--r--configure.ac19
-rw-r--r--dbus-1/.gitignore2
-rw-r--r--dbus-1/Makefile.am31
-rw-r--r--dbus-1/dconf-dbus-1.c633
-rw-r--r--dbus-1/dconf-libdbus-1.c352
-rw-r--r--dbus-1/dconf-libdbus-1.h11
-rw-r--r--docs/Makefile.am18
-rw-r--r--docs/dconf-docs.xml5
-rw-r--r--docs/dconf-sections.txt41
-rw-r--r--editor/Makefile.am37
-rw-r--r--editor/dconf-model.vala26
-rw-r--r--engine/.gitignore2
-rw-r--r--engine/Makefile.am20
-rw-r--r--engine/dconf-changeset-list.h38
-rw-r--r--engine/dconf-engine-profile.c214
-rw-r--r--engine/dconf-engine-profile.h (renamed from common/dconf-shmdir.h)12
-rw-r--r--engine/dconf-engine-source-private.h (renamed from common/dconf-shmdir.c)18
-rw-r--r--engine/dconf-engine-source-system.c82
-rw-r--r--engine/dconf-engine-source-user.c96
-rw-r--r--engine/dconf-engine-source.c116
-rw-r--r--engine/dconf-engine-source.h67
-rw-r--r--engine/dconf-engine.c1451
-rw-r--r--engine/dconf-engine.h194
-rw-r--r--gdbus/.gitignore4
-rw-r--r--gdbus/Makefile.am21
-rw-r--r--gdbus/dconf-gdbus-filter.c311
-rw-r--r--gdbus/dconf-gdbus-thread.c357
-rw-r--r--gsettings/Makefile.am21
-rwxr-xr-xgsettings/abicheck.sh2
-rw-r--r--gsettings/dconfcontext.c33
-rw-r--r--gsettings/dconfcontext.h9
-rw-r--r--gsettings/dconfsettingsbackend.c633
-rw-r--r--gvdb/.gitignore2
-rw-r--r--gvdb/Makefile.am16
-rw-r--r--gvdb/gvdb-builder.c7
-rw-r--r--gvdb/gvdb-reader.c425
-rw-r--r--gvdb/gvdb-reader.h33
-rw-r--r--service/Makefile.am13
-rw-r--r--service/dconf-interfaces.c21
-rw-r--r--service/dconf-rebuilder.c247
-rw-r--r--service/dconf-state.c15
-rw-r--r--service/dconf-state.h1
-rw-r--r--service/dconf-writer.c50
-rw-r--r--service/dconf-writer.h5
-rw-r--r--service/service.c92
-rw-r--r--shm/.gitignore2
-rw-r--r--shm/Makefile.am11
-rw-r--r--shm/dconf-shm.c151
-rw-r--r--shm/dconf-shm.h40
-rw-r--r--tests/.gitignore14
-rw-r--r--tests/Makefile.am109
-rw-r--r--tests/changeset.c398
-rw-r--r--tests/client.c179
-rw-r--r--tests/dbus.c423
-rw-r--r--tests/dbus1.c2
-rw-r--r--tests/dconf-mock-dbus.c52
-rw-r--r--tests/dconf-mock-gvdb.c195
-rw-r--r--tests/dconf-mock-shm.c124
-rw-r--r--tests/dconf-mock.h19
-rw-r--r--tests/engine.c427
-rw-r--r--tests/gsettings.c2
-rw-r--r--tests/gvdb.c432
-rw-r--r--tests/gvdbs/empty_gvdbbin0 -> 32 bytes
-rw-r--r--tests/gvdbs/example_gvdbbin0 -> 246 bytes
-rw-r--r--tests/gvdbs/example_gvdb.big-endianbin0 -> 246 bytes
-rw-r--r--tests/gvdbs/file_empty0
-rw-r--r--tests/gvdbs/file_too_small1
-rw-r--r--tests/gvdbs/invalid_header1
-rw-r--r--tests/gvdbs/nested_gvdbbin0 -> 371 bytes
-rw-r--r--tests/paths.c32
-rw-r--r--tests/profile/broken-profile2
-rw-r--r--tests/profile/colourful13
-rw-r--r--tests/profile/dos2
-rw-r--r--tests/profile/empty-profile0
-rw-r--r--tests/profile/many-sources10
-rw-r--r--tests/profile/no-newline-longline1
-rw-r--r--tests/profile/test-profile1
-rw-r--r--tests/profile/will-never-exist1
-rw-r--r--tests/shm.c170
-rw-r--r--tests/tmpdir.c53
-rw-r--r--tests/tmpdir.h9
-rwxr-xr-xtrim-lcov.py53
108 files changed, 8060 insertions, 2974 deletions
diff --git a/.gitignore b/.gitignore
index beccb04..8679fda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,10 @@ Makefile
*.gir
*.typelib
+# gcov
+*.gcda
+*.gcno
+
# autofoo stuff here
/m4/
/aux/
@@ -16,3 +20,5 @@ Makefile
/autom4te.cache
/aclocal.m4
/stamp-h1
+/dconf-lcov.info
+/lcov-html
diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000..64a6984
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,80 @@
+dconf is split into a large number of small modules. This is required
+because of the client/server architecture as well as the wide variety of
+situations that dconf is used in on the client side.
+
+gvdb/:
+
+ This code implements the GVDB file format used for the on-disk
+ database. GVDB is shared with a number of other users and lives in a
+ separate 'gvdb' module on git.gnome.org.
+
+ Changes should never be made to this directory. Instead, they should
+ be made against the 'gvdb' module and merged using git.
+
+ The code is split into a reader and a writer (builder).
+
+ This directory doesn't produce any libraries. The source files are
+ included into other libraries and executables by direct inclusion of
+ these source files into the Makefiles of other directories.
+
+common/:
+
+ Sources in this directory are used in both the dconf-service and
+ client-side library implementations.
+
+ This directory produces two libraries: libdconf-common.a and
+ libdconf-common-shared.a. They are exactly the same, except that
+ libdconf-common-shared.a was compiled with -fPIC.
+
+engine/:
+
+ This directory contains most of the core logic for implementing the
+ client-side of dconf.
+
+ The engine code requires (but does not contain) glue code for speaking
+ to D-Bus. All users of the engine must therefore include a module
+ that implements this glue.
+
+ This directory produces one library: libdconf-engine.a. This library
+ includes the gvdb-reader.
+
+gdbus/:
+
+ This directory contains the glue code for dconf over GDBus.
+
+ This directory produces one library: libdconf-gdbus.a.
+
+client/:
+
+ This is the standard GObject client-side library used for direct access to
+ dconf. It uses the GDBus glue from the gdbus/ directory above.
+
+ This directory produces the libdconf.so shared library.
+
+gsettings/:
+
+ This is the GSettings backend for dconf. It also uses GDBus.
+
+ This directory produces the libdconfsettings.so GIOModule.
+
+dbus-1/:
+
+ This directory contains a client-side library based on libdbus-1. It
+ also contains the D-Bus glue code for libdbus-1 (since it is the only
+ client-side library that is using it).
+
+ This directory produces the libdconf-dbus-1.so shared library.
+
+bin/:
+
+ This is the 'dconf' commandline tool. It uses the library from
+ client/ above.
+
+editor/:
+
+ This is the graphical dconf-editor. It also uses the client/ library.
+
+service/:
+
+ This is the dconf-service required for any client side library to do
+ writes.
diff --git a/Makefile.am b/Makefile.am
index 6f23b0a..b1f0ee1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,9 +1,32 @@
+include Makefile.gtester
+
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
-SUBDIRS = gvdb service gsettings dbus-1 tests client bin engine common docs
+SUBDIRS = shm gvdb common engine service gdbus gsettings dbus-1 client bin docs tests
if ENABLE_EDITOR
SUBDIRS += editor
endif
DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
+EXTRA_DIST = trim-lcov.py
+
+.PHONY: lcov lcov-clean
+# use recursive makes in order to ignore errors during check
+lcov: lcov-clean
+ -$(MAKE) $(AM_MAKEFLAGS) -k check
+ lcov --directory $(top_builddir) --capture --test-name dconf | $(top_srcdir)/trim-lcov.py > dconf-lcov.info
+ LANG=C genhtml --prefix $(top_builddir) --output-directory lcov-html --legend --show-details dconf-lcov.info
+ @echo
+ @echo " file://$(abs_top_builddir)/lcov-html/index.html"
+ @echo
+
+clean-am: lcov-clean gcno-clean
+
+gcno-clean:
+ -find -name '*.gcno' -delete
+
+lcov-clean:
+ -lcov --directory $(top_builddir) -z
+ find -name '*.gcda' -delete
+ rm -rf lcov-html dconf-lcov.info
diff --git a/Makefile.gtester b/Makefile.gtester
new file mode 100644
index 0000000..fdc0010
--- /dev/null
+++ b/Makefile.gtester
@@ -0,0 +1,66 @@
+# initialize variables for unconditional += appending
+TEST_PROGS =
+
+### testing rules
+
+# test: run all tests in cwd and subdirs
+test: test-nonrecursive
+ @ for subdir in $(SUBDIRS) . ; do \
+ test "$$subdir" = "." -o "$$subdir" = "po" || \
+ ( cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $? ; \
+ done
+
+# test-nonrecursive: run tests only in cwd
+test-nonrecursive: ${TEST_PROGS}
+ @test -z "${TEST_PROGS}" || G_DEBUG=gc-friendly MALLOC_CHECK_=2 MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256)) gtester --verbose ${TEST_PROGS}
+
+# test-report: run tests in subdirs and generate report
+# perf-report: run tests in subdirs with -m perf and generate report
+# full-report: like test-report: with -m perf and -m slow
+test-report perf-report full-report: ${TEST_PROGS}
+ @test -z "${TEST_PROGS}" || { \
+ case $@ in \
+ test-report) test_options="-k";; \
+ perf-report) test_options="-k -m=perf";; \
+ full-report) test_options="-k -m=perf -m=slow";; \
+ esac ; \
+ if test -z "$$GTESTER_LOGDIR" ; then \
+ gtester --verbose $$test_options -o test-report.xml ${TEST_PROGS} ; \
+ elif test -n "${TEST_PROGS}" ; then \
+ gtester --verbose $$test_options -o `mktemp "$$GTESTER_LOGDIR/log-XXXXXX"` ${TEST_PROGS} ; \
+ fi ; \
+ }
+ @ ignore_logdir=true ; \
+ if test -z "$$GTESTER_LOGDIR" ; then \
+ GTESTER_LOGDIR=`mktemp -d "\`pwd\`/.testlogs-XXXXXX"`; export GTESTER_LOGDIR ; \
+ ignore_logdir=false ; \
+ fi ; \
+ if test -d "$(top_srcdir)/.git" ; then \
+ REVISION=`git describe` ; \
+ else \
+ REVISION=$(VERSION) ; \
+ fi ; \
+ for subdir in $(SUBDIRS) . ; do \
+ test "$$subdir" = "." -o "$$subdir" = "po" || \
+ ( cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $? ; \
+ done ; \
+ $$ignore_logdir || { \
+ echo '<?xml version="1.0"?>' > $@.xml ; \
+ echo '<report-collection>' >> $@.xml ; \
+ echo '<info>' >> $@.xml ; \
+ echo ' <package>$(PACKAGE)</package>' >> $@.xml ; \
+ echo ' <version>$(VERSION)</version>' >> $@.xml ; \
+ echo " <revision>$$REVISION</revision>" >> $@.xml ; \
+ echo '</info>' >> $@.xml ; \
+ for lf in `ls -L "$$GTESTER_LOGDIR"/.` ; do \
+ sed '1,1s/^<?xml\b[^>?]*?>//' <"$$GTESTER_LOGDIR"/"$$lf" >> $@.xml ; \
+ done ; \
+ echo >> $@.xml ; \
+ echo '</report-collection>' >> $@.xml ; \
+ rm -rf "$$GTESTER_LOGDIR"/ ; \
+ gtester-report --version 2>/dev/null 1>&2 ; test "$$?" != 0 || gtester-report $@.xml >$@.html ; \
+ }
+.PHONY: test test-report perf-report full-report test-nonrecursive
+
+# run tests in cwd as part of make check
+check-local: test-nonrecursive
diff --git a/README b/README
new file mode 100644
index 0000000..c5c98b7
--- /dev/null
+++ b/README
@@ -0,0 +1,81 @@
+dconf is a simple key/value storage system that is heavily optimised for
+reading. This makes it an ideal system for storing user preferences
+(which are read 1000s of times for each time the user changes one). It
+was created with this usecase in mind.
+
+All preferences are stored in a single large binary file. Layering of
+preferences is possible using multiple files (ie: for site defaults).
+Lock-down is also supported. The binary file for the defaults can
+optionally be compiled from a set of plain text keyfiles.
+
+dconf has a partial client/server architecture. It uses D-Bus. The
+server is only involved in writes (and is not activated in the user
+session until the user modifies a preference). The service is
+stateless and can exit freely at any time (and is therefore robust
+against crashes). The list of paths that each process is watching is
+stored within the D-Bus daemon itself (as D-Bus signal match rules).
+
+Reads are performed by direct access (via mmap) to the on-disk database
+which is essentially a hashtable. For this reason, dconf reads
+typically involve zero system calls and are comparable to a hashtable
+lookup in terms of speed. Practically speaking, in simple non-layered
+setups, dconf is less than 10 times slower than GHashTable.
+
+Writes are assumed only to happen in response to explicit user
+interaction (like clicking on a checkbox in a preferences dialog) and
+are therefore not optimised at all. On some file systems, dconf-service
+will call fsync() for every write, which can introduce a latency of up
+to 100ms. This latency is hidden by the client libraries through a
+clever "fast" mechanism that records the outstanding changes locally (so
+they can be read back immediately) until the service signals that a
+write has completed.
+
+dconf mostly targets Free Software operating systems. It will
+theoretically run on Mac OS but there isn't much point to that (since
+Mac OS applications want to store preferences in plist files). It is
+not possible to use dconf on Windows because of the inability to rename
+over a file that's still in use (which is what the dconf-service does on
+every write).
+
+The dconf API is not particularly friendly. Because of this and the
+lack of portability, you almost certainly want to use some sort of
+wrapper API around it. The wrapper API used by Gtk+ and GNOME
+applications is GSettings, which is included as part of GLib. GSettings
+has backends for Windows (using the registry) and Mac OS (using property
+lists) as well as its dconf backend and is the proper API to use for
+graphical applications.
+
+dconf itself attempts to maintain a rather low profile with respect to
+dependencies. For the most part, there is only a dependency on GLib.
+
+With the exception of the bin/ and editor/ directories, dconf is written
+in C using libglib. This is a very strong dependency due to the fact
+that dconf's type system is GVariant.
+
+The dconf-service has a dependency on libgio, as do the client libraries
+that make use of GDBus (and the utilities that make use of those
+libraries).
+
+The standard client library is libdconf (in client/). If you can't use
+GSettings then you should probably want to use this next.
+
+There is also a libdbus-1 based library. It does not depend on libgio,
+but still depends on libglib. It is not recommended to use this library
+unless you have a legacy dependency on libdbus-1 (such as in Qt
+applications).
+
+bin/ and editor/ are written in Vala. The Vala compiler is not required
+to compile tarball releases but is required for building out of git.
+
+The editor also has a dependency on Gtk+ 3 and libxml2. The libxml2
+dependency should disappear at some point in the near future. In any
+case, building the editor is optional (and can be switched off using
+--disable-editor).
+
+Installing dconf follows the typical automake dance:
+
+ ./configure (or autogen.sh from git)
+ make
+ make install
+
+If you plan to contribute to dconf, please see the HACKING file.
diff --git a/bin/Makefile.am b/bin/Makefile.am
index 792b242..0501bfa 100644
--- a/bin/Makefile.am
+++ b/bin/Makefile.am
@@ -1,12 +1,25 @@
-AM_CFLAGS = -Wall -Wmissing-prototypes -Wwrite-strings
-CFLAGS += -Wno-error -Wno-unused-but-set-variable -Wno-unused-variable
-INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/engine -I$(top_srcdir)/client -I$(top_srcdir)/gvdb $(gio_CFLAGS)
+include $(top_srcdir)/Makefile.gtester
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/client
bin_PROGRAMS = dconf
dconf_VALAFLAGS = --vapidir ../client --pkg=gio-2.0 --pkg=posix --pkg=dconf
-dconf_LDADD = $(gio_LIBS) ../client/libdconf.so.0
-dconf_SOURCES = dconf.vala dconf-update.vala dconf-dump.vala ../gvdb/gvdb-builder.c gvdb.vapi
+
+dconf_CFLAGS = \
+ $(gio_CFLAGS) \
+ -w
+
+dconf_LDADD = \
+ ../client/libdconf.so.1 \
+ $(gio_LIBS)
+
+dconf_SOURCES = \
+ ../gvdb/gvdb-builder.c \
+ gvdb.vapi \
+ dconf-update.vala \
+ dconf-dump.vala \
+ dconf.vala
completiondir = $(sysconfdir)/bash_completion.d
completion_DATA = dconf-bash-completion.sh
diff --git a/bin/dconf-dump.vala b/bin/dconf-dump.vala
index e340a6c..135b230 100644
--- a/bin/dconf-dump.vala
+++ b/bin/dconf-dump.vala
@@ -47,48 +47,21 @@ KeyFile keyfile_from_stdin () throws Error {
return kf;
}
-class DConfLoadState {
- public string[] keys;
- public Variant?[] vals;
- int n_keys;
- int i;
-
- public DConfLoadState (int n) {
- keys = new string[n + 1];
- vals = new Variant?[n];
- n_keys = n;
- i = 0;
- }
-
- public bool add (void *key, void *value) {
- assert (i < n_keys);
-
- keys[i] = (string) key;
- vals[i] = (Variant) value;
- i++;
-
- return false;
- }
-}
-
void dconf_load (string[] args) throws Error {
var dir = args[2];
DConf.verify_dir (dir);
- var tree = new Tree<string, Variant> (strcmp);
+ var changeset = new DConf.Changeset ();
var kf = keyfile_from_stdin ();
foreach (var group in kf.get_groups ()) {
foreach (var key in kf.get_keys (group)) {
- var rel = (group == "/" ? "" : group + "/") + key;
- DConf.verify_rel_key (rel);
- tree.insert (rel, Variant.parse (null, kf.get_value (group, key)));
+ var path = dir + (group == "/" ? "" : group + "/") + key;
+ DConf.verify_key (path);
+ changeset.set (path, Variant.parse (null, kf.get_value (group, key)));
}
}
- DConfLoadState list = new DConfLoadState (tree.nnodes ());
- tree.foreach (list.add);
-
var client = new DConf.Client ();
- client.write_many (dir, list.keys, list.vals);
+ client.change_sync (changeset);
}
diff --git a/bin/dconf-update.vala b/bin/dconf-update.vala
index 434b022..9fcc4ec 100644
--- a/bin/dconf-update.vala
+++ b/bin/dconf-update.vala
@@ -240,7 +240,7 @@ void update_all (string dirname) {
}
}
-void dconf_update (string[] args) {
+void dconf_update (string[] args) throws GLib.Error {
update_all ("/etc/dconf/db");
}
diff --git a/bin/dconf.vala b/bin/dconf.vala
index 4117d23..646b275 100644
--- a/bin/dconf.vala
+++ b/bin/dconf.vala
@@ -173,7 +173,7 @@ void dconf_write (string?[] args) throws Error {
DConf.verify_key (key);
- client.write (key, Variant.parse (null, val));
+ client.write_sync (key, Variant.parse (null, val));
}
void dconf_reset (string?[] args) throws Error {
@@ -194,7 +194,7 @@ void dconf_reset (string?[] args) throws Error {
throw new OptionError.FAILED ("-f must be given to (recursively) reset entire dirs");
}
- client.write (path, null);
+ client.write_sync (path, null);
}
void show_path (DConf.Client client, string path) {
@@ -205,28 +205,24 @@ void show_path (DConf.Client client, string path) {
}
}
-void watch_function (DConf.Client client, string path, string[] items, string tag) {
- if (items.length == 0) {
- print ("%s\n", path);
- show_path (client, path);
- print ("\n");
- } else {
- foreach (var item in items) {
- var full = path + item;
- print ("%s\n", full);
- show_path (client, full);
- }
- print ("\n");
+void watch_function (DConf.Client client, string path, string[] items, string? tag) {
+ foreach (var item in items) {
+ var full = path + item;
+ print ("%s\n", full);
+ show_path (client, full);
}
+ print ("\n");
}
void dconf_watch (string?[] args) throws Error {
- var client = new DConf.Client (null, watch_function);
+ var client = new DConf.Client ();
var path = args[2];
DConf.verify_path (path);
- client.watch (path);
+ client.changed.connect (watch_function);
+ client.watch_sync (path);
+
new MainLoop (null, false).run ();
}
diff --git a/bin/gvdb.vapi b/bin/gvdb.vapi
index 1848c1d..1f3e92a 100644
--- a/bin/gvdb.vapi
+++ b/bin/gvdb.vapi
@@ -1,4 +1,4 @@
-[CCode (cheader_filename = "gvdb-builder.h")]
+[CCode (cheader_filename = "../gvdb/gvdb-builder.h")]
namespace Gvdb {
[Compact]
[CCode (cname = "GHashTable")]
diff --git a/client/.gitignore b/client/.gitignore
index 6b4b55c..d0f3ebb 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -1,7 +1,4 @@
-dconf.vapi
-dconf-client.c
-*.stamp
-*.deps
libdconf.so
-libdconf.so.0
-libdconf.so.0.0.0
+libdconf.so.1
+libdconf.so.1.0.0
+libdconf-client.a
diff --git a/client/Makefile.am b/client/Makefile.am
index b53163e..368e11f 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -1,41 +1,41 @@
-AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -D__dconf_h__ -fPIC -DPIC
-CFLAGS += -Wno-error -Wno-unused-but-set-variable -Wno-unused-variable
-INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(gio_CFLAGS)
+include $(top_srcdir)/Makefile.gtester
shlibdir=$(libdir)
-shlib_PROGRAMS = libdconf.so.0.0.0
-nodist_noinst_DATA = libdconf.so.0 libdconf.so
+shlib_PROGRAMS = libdconf.so.1.0.0
+nodist_noinst_DATA = libdconf.so.1 libdconf.so
+noinst_LIBRARIES = libdconf-client.a
-libdconf.so.0 libdconf.so: libdconf.so.0.0.0
- $(AM_V_GEN) ln -fs libdconf.so.0.0.0 $@
+libdconf.so.1 libdconf.so: libdconf.so.1.0.0
+ $(AM_V_GEN) ln -fs libdconf.so.1.0.0 $@
install-data-hook:
- ln -fs libdconf.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf.so.0
- ln -fs libdconf.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf.so
+ ln -fs libdconf.so.1.0.0 $(DESTDIR)$(shlibdir)/libdconf.so.1
+ ln -fs libdconf.so.1.0.0 $(DESTDIR)$(shlibdir)/libdconf.so
uninstall-hook:
- rm -f $(DESTDIR)$(shlibdir)/libdconf.so.0
+ rm -f $(DESTDIR)$(shlibdir)/libdconf.so.1
rm -f $(DESTDIR)$(shlibdir)/libdconf.so
dconfinclude_HEADERS = \
dconf-client.h \
dconf.h
-libdconf_so_0_0_0_LDADD = $(gio_LIBS)
-libdconf_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf.so.0
-libdconf_so_0_0_0_SOURCES = \
- ../common/dconf-shmdir.c \
- ../common/dconf-paths.c \
- ../engine/dconf-engine.c \
- ../gvdb/gvdb-reader.c \
- dconf-client.vala engine.vapi
-libdconf_so_0_0_0_VALAFLAGS = --library dconf --pkg=gio-2.0
+libdconf_client_a_CFLAGS = $(gio_CFLAGS)
+libdconf_client_a_SOURCES = \
+ dconf-client.c
-EXTRA_DIST = dconf.vapi extra-docs.c
-dconf.vapi: libdconf.so.0
+libdconf_so_1_0_0_CFLAGS = $(libdconf_client_a_CFLAGS) -fPIC -DPIC
+libdconf_so_1_0_0_LDADD = \
+ ../engine/libdconf-engine-shared.a \
+ ../common/libdconf-common-shared.a \
+ ../gdbus/libdconf-gdbus-thread-shared.a \
+ ../gvdb/libgvdb-shared.a \
+ ../shm/libdconf-shm-shared.a \
+ $(gio_LIBS)
+libdconf_so_1_0_0_LDFLAGS = -shared -Wl,-soname=libdconf.so.1
+libdconf_so_1_0_0_SOURCES = $(libdconf_client_a_SOURCES)
-dconf.deps:
- $(AM_V_GEN) echo gio-2.0 > dconf.deps
+EXTRA_DIST = dconf.vapi dconf.deps
vapi_DATA = dconf.vapi dconf.deps
vapidir = $(datadir)/vala/vapi
@@ -43,4 +43,4 @@ vapidir = $(datadir)/vala/vapi
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = dconf.pc
-CLEANFILES = dconf.deps libdconf.so.0 libdconf.so
+CLEANFILES = libdconf.so.1 libdconf.so
diff --git a/client/dconf-client.c b/client/dconf-client.c
new file mode 100644
index 0000000..7a2a96c
--- /dev/null
+++ b/client/dconf-client.c
@@ -0,0 +1,570 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-client.h"
+
+#include "../engine/dconf-engine.h"
+#include <glib-object.h>
+
+/**
+ * SECTION:client
+ * @title: DConfClient
+ * @short_description: Direct read and write access to DConf, based on GDBus
+ *
+ * This is the primary client interface to dconf.
+ *
+ * It allows applications to directly read from and write to the dconf
+ * database. Applications can subscribe to change notifications.
+ *
+ * Most applications probably don't want to access dconf directly and
+ * would be better off using something like #GSettings.
+ *
+ * Please note that the API of libdconf is not stable in any way. It
+ * has changed in incompatible ways in the past and there will be
+ * further changes in the future.
+ **/
+
+/**
+ * DConfClient:
+ *
+ * The main object for interacting with dconf. This is a #GObject, so
+ * you should manage it with g_object_ref() and g_object_unref().
+ **/
+struct _DConfClient
+{
+ GObject parent_instance;
+
+ DConfEngine *engine;
+ GMainContext *context;
+};
+
+G_DEFINE_TYPE (DConfClient, dconf_client, G_TYPE_OBJECT)
+
+enum
+{
+ SIGNAL_CHANGED,
+ N_SIGNALS
+};
+static guint dconf_client_signals[N_SIGNALS];
+
+static void
+dconf_client_finalize (GObject *object)
+{
+ DConfClient *client = DCONF_CLIENT (object);
+
+ dconf_engine_unref (client->engine);
+ g_main_context_unref (client->context);
+
+ G_OBJECT_CLASS (dconf_client_parent_class)
+ ->finalize (object);
+}
+
+static void
+dconf_client_init (DConfClient *client)
+{
+}
+
+static void
+dconf_client_class_init (DConfClientClass *class)
+{
+ class->finalize = dconf_client_finalize;
+
+ /**
+ * DConfClient::changed:
+ * @client: the #DConfClient reporting the change
+ * @prefix: the prefix under which the changes happened
+ * @changes: the list of paths that were changed, relative to @prefix
+ * @tag: the tag for the change, if it originated from the service
+ *
+ * This signal is emitted when the #DConfClient has a possible change
+ * to report. The signal is an indication that a change may have
+ * occured; it's possible that the keys will still have the same value
+ * as before.
+ *
+ * To ensure that you receive notification about changes to paths that
+ * you are interested in you must call dconf_client_watch_fast() or
+ * dconf_client_watch_sync(). You may still receive notifications for
+ * paths that you did not explicitly watch.
+ *
+ * @prefix will be an absolute dconf path; see dconf_is_path().
+ * @changes is a %NULL-terminated array of dconf rel paths; see
+ * dconf_is_rel_path().
+ *
+ * @tag is an opaque tag string, or %NULL. The only thing you should
+ * do with @tag is to compare it to tag values returned by
+ * dconf_client_write_sync() or dconf_client_change_sync().
+ *
+ * The number of changes being reported is equal to the length of
+ * @changes. Appending each item in @changes to @prefix will give the
+ * absolute path of each changed item.
+ *
+ * If a single key has changed then @prefix will be equal to the key
+ * and @changes will contain a single item: the empty string.
+ *
+ * If a single dir has changed (indicating that any key under the dir
+ * may have changed) then @prefix will be equal to the dir and
+ * @changes will contain a single empty string.
+ *
+ * If more than one change is being reported then @changes will have
+ * more than one item.
+ **/
+ dconf_client_signals[SIGNAL_CHANGED] = g_signal_new ("changed", DCONF_TYPE_CLIENT, G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE, 3,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_STRV | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+typedef struct
+{
+ DConfClient *client;
+ gchar *prefix;
+ gchar **changes;
+ gchar *tag;
+} DConfClientChange;
+
+static gboolean
+dconf_client_dispatch_change_signal (gpointer user_data)
+{
+ DConfClientChange *change = user_data;
+
+ g_signal_emit (change->client, dconf_client_signals[SIGNAL_CHANGED], 0,
+ change->prefix, change->changes, change->tag);
+
+ g_object_unref (change->client);
+ g_free (change->prefix);
+ g_strfreev (change->changes);
+ g_free (change->tag);
+ g_slice_free (DConfClientChange, change);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+dconf_engine_change_notify (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *changes,
+ const gchar * tag,
+ gpointer user_data)
+{
+ GWeakRef *weak_ref = user_data;
+ DConfClientChange *change;
+ DConfClient *client;
+
+ client = g_weak_ref_get (weak_ref);
+
+ if (client == NULL)
+ return;
+
+ g_return_if_fail (DCONF_IS_CLIENT (client));
+
+ change = g_slice_new (DConfClientChange);
+ change->client = client;
+ change->prefix = g_strdup (prefix);
+ change->changes = g_strdupv ((gchar **) changes);
+ change->tag = g_strdup (tag);
+
+ g_main_context_invoke (client->context, dconf_client_dispatch_change_signal, change);
+}
+
+static void
+dconf_client_free_weak_ref (gpointer data)
+{
+ GWeakRef *weak_ref = data;
+
+ g_weak_ref_clear (weak_ref);
+ g_slice_free (GWeakRef, weak_ref);
+}
+
+/**
+ * dconf_client_new:
+ *
+ * Creates a new #DConfClient.
+ *
+ * Returns: a new #DConfClient
+ **/
+DConfClient *
+dconf_client_new (void)
+{
+ DConfClient *client;
+ GWeakRef *weak_ref;
+
+ client = g_object_new (DCONF_TYPE_CLIENT, NULL);
+ weak_ref = g_slice_new (GWeakRef);
+ g_weak_ref_init (weak_ref, client);
+ client->engine = dconf_engine_new (weak_ref, dconf_client_free_weak_ref);
+ client->context = g_main_context_ref_thread_default ();
+
+ return client;
+}
+
+/**
+ * dconf_client_read:
+ * @client: a #DConfClient
+ * @key: the key to read the value of
+ *
+ * Reads the current value of @key.
+ *
+ * If @key exists, its value is returned. Otherwise, %NULL is returned.
+ *
+ * If there are outstanding "fast" changes in progress they may affect
+ * the result of this call.
+ *
+ * Returns: a #GVariant, or %NULL
+ **/
+GVariant *
+dconf_client_read (DConfClient *client,
+ const gchar *key)
+{
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
+
+ return dconf_engine_read (client->engine, NULL, key);
+}
+
+/**
+ * dconf_client_list:
+ * @client: a #DConfClient
+ * @dir: the dir to list the contents of
+ * @length: the length of the returned list
+ *
+ * Gets the list of all dirs and keys immediately under @dir.
+ *
+ * If @length is non-%NULL then it will be set to the length of the
+ * returned array. In any case, the array is %NULL-terminated.
+ *
+ * IF there are outstanding "fast" changes in progress then this call
+ * may return inaccurate results with respect to those outstanding
+ * changes.
+ *
+ * Returns: an array of strings, never %NULL.
+ **/
+gchar **
+dconf_client_list (DConfClient *client,
+ const gchar *dir,
+ gint *length)
+{
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
+
+ return dconf_engine_list (client->engine, dir, length);
+}
+
+/**
+ * dconf_client_is_writable:
+ * @client: a #DConfClient
+ * @key: the key to check for writability
+ *
+ * Checks if @key is writable (ie: the key has no locks).
+ *
+ * This call does not verify that writing to the key will actually be
+ * successful. It only checks that the database is writable and that
+ * there are no locks affecting @key. Other issues (such as a full disk
+ * or an inability to connect to the bus and start the service) may
+ * cause the write to fail.
+ *
+ * Returns: %TRUE is @key is writable
+ **/
+gboolean
+dconf_client_is_writable (DConfClient *client,
+ const gchar *key)
+{
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
+
+ return dconf_engine_is_writable (client->engine, key);
+}
+
+/**
+ * dconf_client_write_fast:
+ * @client: a #DConfClient
+ * @key: the key to write to
+ * @value: a #GVariant, the value to write
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Writes @value to the given @key, or reset @key to its default value.
+ *
+ * If @value is %NULL then @key is reset to its default value (which may
+ * be completely unset), otherwise @value becomes the new value.
+ *
+ * This call merely queues up the write and returns immediately, without
+ * blocking. The only errors that can be detected or reported at this
+ * point are attempts to write to read-only keys. If the application
+ * exits immediately after this function returns then the queued call
+ * may never be sent; see dconf_client_sync().
+ *
+ * A local copy of the written value is kept so that calls to
+ * dconf_client_read() that occur before the service actually makes the
+ * change will return the new value.
+ *
+ * If the write is queued then a change signal will be directly emitted.
+ * If this function is being called from the main context of @client
+ * then the signal is emitted before this function returns; otherwise it
+ * is scheduled on the main context.
+ *
+ * Returns: %TRUE if the write was queued
+ **/
+gboolean
+dconf_client_write_fast (DConfClient *client,
+ const gchar *key,
+ GVariant *value,
+ GError **error)
+{
+ DConfChangeset *changeset;
+ gboolean success;
+
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
+
+ changeset = dconf_changeset_new_write (key, value);
+ success = dconf_engine_change_fast (client->engine, changeset, error);
+ dconf_changeset_unref (changeset);
+
+ return success;
+}
+
+/**
+ * dconf_client_write_sync:
+ * @client: a #DConfClient
+ * @key: the key to write to
+ * @value: a #GVariant, the value to write
+ * @tag: (out) (allow-none): the tag from this write
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Write @value to the given @key, or reset @key to its default value.
+ *
+ * If @value is %NULL then @key is reset to its default value (which may
+ * be completely unset), otherwise @value becomes the new value.
+ *
+ * This call blocks until the write is complete. This call will
+ * therefore detect and report all cases of failure. If the modified
+ * key is currently being watched then a signal will be emitted from the
+ * main context of @client (once the signal arrives from the service).
+ *
+ * If @tag is non-%NULL then it is set to the unique tag associated with
+ * this write. This is the same tag that will appear in the following
+ * change signal.
+ *
+ * Returns: %TRUE on success, else %FALSE with @error set
+ **/
+gboolean
+dconf_client_write_sync (DConfClient *client,
+ const gchar *key,
+ GVariant *value,
+ gchar **tag,
+ GCancellable *cancellable,
+ GError **error)
+{
+ DConfChangeset *changeset;
+ gboolean success;
+
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
+
+ changeset = dconf_changeset_new_write (key, value);
+ success = dconf_engine_change_sync (client->engine, changeset, tag, error);
+ dconf_changeset_unref (changeset);
+
+ return success;
+}
+
+/**
+ * dconf_client_change_fast:
+ * @client: a #DConfClient
+ * @changeset: the changeset describing the requested change
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Performs the change operation described by @changeset.
+ *
+ * Once @changeset is passed to this call it can no longer be modified.
+ *
+ * This call merely queues up the write and returns immediately, without
+ * blocking. The only errors that can be detected or reported at this
+ * point are attempts to write to read-only keys. If the application
+ * exits immediately after this function returns then the queued call
+ * may never be sent; see dconf_client_sync().
+ *
+ * A local copy of the written value is kept so that calls to
+ * dconf_client_read() that occur before the service actually makes the
+ * change will return the new value.
+ *
+ * If the write is queued then a change signal will be directly emitted.
+ * If this function is being called from the main context of @client
+ * then the signal is emitted before this function returns; otherwise it
+ * is scheduled on the main context.
+ *
+ * Returns: %TRUE if the requested changed was queued
+ **/
+gboolean
+dconf_client_change_fast (DConfClient *client,
+ DConfChangeset *changeset,
+ GError **error)
+{
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
+
+ return dconf_engine_change_fast (client->engine, changeset, error);
+}
+
+/**
+ * dconf_client_change_sync:
+ * @client: a #DConfClient
+ * @changeset: the changeset describing the requested change
+ * @tag: (out) (allow-none): the tag from this write
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Performs the change operation described by @changeset.
+ *
+ * Once @changeset is passed to this call it can no longer be modified.
+ *
+ * This call blocks until the change is complete. This call will
+ * therefore detect and report all cases of failure. If any of the
+ * modified keys are currently being watched then a signal will be
+ * emitted from the main context of @client (once the signal arrives
+ * from the service).
+ *
+ * If @tag is non-%NULL then it is set to the unique tag associated with
+ * this change. This is the same tag that will appear in the following
+ * change signal.
+ *
+ * Returns: %TRUE on success, else %FALSE with @error set
+ **/
+gboolean
+dconf_client_change_sync (DConfClient *client,
+ DConfChangeset *changeset,
+ gchar **tag,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
+
+ return dconf_engine_change_sync (client->engine, changeset, tag, error);
+}
+
+/**
+ * dconf_client_watch_fast:
+ * @client: a #DConfClient
+ * @path: a path to watch
+ *
+ * Requests change notifications for @path.
+ *
+ * If @path is a key then the single key is monitored. If @path is a
+ * dir then all keys under the dir are monitored.
+ *
+ * This function queues the watch request with D-Bus and returns
+ * immediately. There is a very slim chance that the dconf database
+ * could change before the watch is actually established. If that is
+ * the case then a synthetic change signal will be emitted.
+ *
+ * Errors are silently ignored.
+ **/
+void
+dconf_client_watch_fast (DConfClient *client,
+ const gchar *path)
+{
+ g_return_if_fail (DCONF_IS_CLIENT (client));
+
+ dconf_engine_watch_fast (client->engine, path);
+}
+
+/**
+ * dconf_client_watch_sync:
+ * @client: a #DConfClient
+ * @path: a path to watch
+ *
+ * Requests change notifications for @path.
+ *
+ * If @path is a key then the single key is monitored. If @path is a
+ * dir then all keys under the dir are monitored.
+ *
+ * This function submits each of the the various watch requests that are
+ * required to monitor a key and waits until each of them returns. By
+ * the time this function returns, the watch has been established.
+ *
+ * Errors are silently ignored.
+ **/
+void
+dconf_client_watch_sync (DConfClient *client,
+ const gchar *path)
+{
+ g_return_if_fail (DCONF_IS_CLIENT (client));
+
+ dconf_engine_watch_sync (client->engine, path);
+}
+
+/**
+ * dconf_client_unwatch_fast:
+ * @client: a #DConfClient
+ * @path: a path previously watched
+ *
+ * Cancels the effect of a previous call to dconf_client_watch_fast().
+ *
+ * This call returns immediately.
+ *
+ * It is still possible that change signals are received after this call
+ * had returned (watching guarantees notification of changes, but
+ * unwatching does not guarantee no notifications).
+ **/
+void
+dconf_client_unwatch_fast (DConfClient *client,
+ const gchar *path)
+{
+ g_return_if_fail (DCONF_IS_CLIENT (client));
+
+ dconf_engine_unwatch_fast (client->engine, path);
+}
+
+/**
+ * dconf_client_unwatch_sync:
+ * @client: a #DConfClient
+ * @path: a path previously watched
+ *
+ * Cancels the effect of a previous call to dconf_client_watch_sync().
+ *
+ * This function submits each of the various unwatch requests and waits
+ * until each of them returns. It is still possible that change signals
+ * are received after this call has returned (watching guarantees
+ * notification of changes, but unwatching does not guarantee no
+ * notifications).
+ **/
+void
+dconf_client_unwatch_sync (DConfClient *client,
+ const gchar *path)
+{
+ g_return_if_fail (DCONF_IS_CLIENT (client));
+
+ dconf_engine_unwatch_sync (client->engine, path);
+}
+
+/**
+ * dconf_client_sync:
+ * @client: a #DConfClient
+ *
+ * Blocks until all outstanding "fast" change or write operations have
+ * been submitted to the service.
+ *
+ * Applications should generally call this before exiting on any
+ * #DConfClient that they wrote to.
+ **/
+void
+dconf_client_sync (DConfClient *client)
+{
+ g_return_if_fail (DCONF_IS_CLIENT (client));
+
+ dconf_engine_sync (client->engine);
+}
diff --git a/client/dconf-client.h b/client/dconf-client.h
index 802dce8..3a606c2 100644
--- a/client/dconf-client.h
+++ b/client/dconf-client.h
@@ -23,6 +23,7 @@
#define __dconf_client_h__
#include <gio/gio.h>
+#include "../common/dconf-changeset.h"
G_BEGIN_DECLS
@@ -33,26 +34,12 @@ G_BEGIN_DECLS
typedef GObjectClass DConfClientClass;
typedef struct _DConfClient DConfClient;
-typedef void (*DConfWatchFunc) (DConfClient *client,
- const gchar *path,
- const gchar * const *items,
- gint n_items,
- const gchar *tag,
- gpointer user_data);
-
GType dconf_client_get_type (void);
-DConfClient * dconf_client_new (const gchar *profile,
- DConfWatchFunc watch_func,
- gpointer user_data,
- GDestroyNotify notify);
+DConfClient * dconf_client_new (void);
GVariant * dconf_client_read (DConfClient *client,
const gchar *key);
-GVariant * dconf_client_read_default (DConfClient *client,
- const gchar *key);
-GVariant * dconf_client_read_no_default (DConfClient *client,
- const gchar *key);
gchar ** dconf_client_list (DConfClient *client,
const gchar *dir,
@@ -61,70 +48,38 @@ gchar ** dconf_client_list (DConfCl
gboolean dconf_client_is_writable (DConfClient *client,
const gchar *key);
-gboolean dconf_client_write (DConfClient *client,
+gboolean dconf_client_write_fast (DConfClient *client,
const gchar *key,
GVariant *value,
- gchar **tag,
- GCancellable *cancellable,
GError **error);
-void dconf_client_write_async (DConfClient *client,
+gboolean dconf_client_write_sync (DConfClient *client,
const gchar *key,
GVariant *value,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean dconf_client_write_finish (DConfClient *client,
- GAsyncResult *result,
gchar **tag,
+ GCancellable *cancellable,
GError **error);
-gboolean dconf_client_write_many (DConfClient *client,
- const gchar *dir,
- const gchar * const *rels,
- GVariant **values,
- gint n_values,
+gboolean dconf_client_change_fast (DConfClient *client,
+ DConfChangeset *changeset,
+ GError **error);
+gboolean dconf_client_change_sync (DConfClient *client,
+ DConfChangeset *changeset,
gchar **tag,
GCancellable *cancellable,
GError **error);
-/* write_many_async currently disabled due to missing Vala functionality
-void dconf_client_write_many_async (DConfClient *client,
- const gchar *dir,
- const gchar * const *rels,
- GVariant **values,
- gint n_values,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean dconf_client_write_many_finish (DConfClient *client,
- GAsyncResult *result,
- gchar **tag,
- GError **error);*/
+void dconf_client_watch_fast (DConfClient *client,
+ const gchar *path);
+void dconf_client_watch_sync (DConfClient *client,
+ const gchar *path);
+
+void dconf_client_unwatch_fast (DConfClient *client,
+ const gchar *path);
+void dconf_client_unwatch_sync (DConfClient *client,
+ const gchar *path);
+
+void dconf_client_sync (DConfClient *client);
-gboolean dconf_client_watch (DConfClient *client,
- const gchar *path,
- GCancellable *cancellable,
- GError **error);
-void dconf_client_watch_async (DConfClient *client,
- const gchar *path,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean dconf_client_watch_finish (DConfClient *client,
- GAsyncResult *result,
- GError **error);
-gboolean dconf_client_unwatch (DConfClient *client,
- const gchar *path,
- GCancellable *cancellable,
- GError **error);
-void dconf_client_unwatch_async (DConfClient *client,
- const gchar *path,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean dconf_client_unwatch_finish (DConfClient *client,
- GAsyncResult *result,
- GError **error);
G_END_DECLS
#endif /* __dconf_client_h__ */
diff --git a/client/dconf-client.vala b/client/dconf-client.vala
deleted file mode 100644
index fea10d7..0000000
--- a/client/dconf-client.vala
+++ /dev/null
@@ -1,380 +0,0 @@
-[CCode (cheader_filename = "dconf.h")]
-namespace DConf {
- public delegate void WatchFunc (DConf.Client client, string path, string[] items, string tag);
-
- public class Client : Object {
- DBusConnection? session;
- DBusConnection? system;
- WatchFunc watch_func;
- Engine engine;
-
- void got_signal (DBusConnection connection, string sender_name, string object_path, string interface_name, string signal_name, Variant parameters) {
- unowned string path;
- unowned string tag;
- string[] items;
-
- if (signal_name == "Notify" && parameters.is_of_type ((VariantType) "(sass)")) {
- VariantIter iter;
-
- parameters.get ("(&sas&s)", out path, out iter, out tag);
- items = new string[iter.n_children ()];
- for (var i = 0; i < items.length; i++) {
- iter.next ("s", out items[i]);
- }
- } else if (signal_name == "WritabilityNotify" && parameters.is_of_type ((VariantType) "(s)")) {
- parameters.get ("(&s)", out path);
- items = new string[0];
- tag = "";
- } else {
- assert_not_reached ();
- }
-
- watch_func (this, path, items, tag);
- }
-
- void call_sync (EngineMessage dcem, out string tag, Cancellable? cancellable) throws Error {
- DBusConnection connection;
-
- tag = null;
-
- if (dcem.bus_types[0] == 'e') {
- if (session == null) {
- session = Bus.get_sync (BusType.SESSION, cancellable);
- if (watch_func != null) {
- session.signal_subscribe (null, "ca.desrt.dconf.Writer", null, null, null, DBusSignalFlags.NO_MATCH_RULE, got_signal);
- }
- }
- connection = session;
- } else {
- assert (dcem.bus_types[0] == 'y');
- if (system == null) {
- system = Bus.get_sync (BusType.SYSTEM, cancellable);
- if (watch_func != null) {
- system.signal_subscribe (null, "ca.desrt.dconf.Writer", null, null, null, DBusSignalFlags.NO_MATCH_RULE, got_signal);
- }
- }
- connection = system;
- }
-
- foreach (var message in dcem.parameters) {
- var reply = connection.call_sync (dcem.bus_name, dcem.object_path, dcem.interface_name, dcem.method_name,
- message, dcem.reply_type, DBusCallFlags.NONE, -1, cancellable);
- if (!dcem.reply_type.equal (VariantType.UNIT)) {
- reply.get ("(s)", out tag);
- }
- }
- }
-
- async void call_async (EngineMessage dcem, out string tag, Cancellable? cancellable) throws Error {
- DBusConnection connection;
-
- tag = null;
-
- if (dcem.bus_types[0] == 'e') {
- if (session == null) {
- session = yield Bus.get (BusType.SESSION, cancellable);
- }
- connection = session;
- } else {
- assert (dcem.bus_types[0] == 'y');
- if (system == null) {
- system = yield Bus.get (BusType.SYSTEM, cancellable);
- }
- connection = system;
- }
-
- foreach (var message in dcem.parameters) {
- var reply = yield connection.call (dcem.bus_name, dcem.object_path, dcem.interface_name, dcem.method_name,
- message, dcem.reply_type, DBusCallFlags.NONE, -1, cancellable);
- if (dcem.reply_type != VariantType.UNIT) {
- reply.get ("(s)", out tag);
- }
- }
- }
-
- /**
- * dconf_client_is_writable:
- * @client: a #DConfClient
- * @key: a dconf key
- * Returns: %TRUE is @key is writable
- *
- * Checks if @key is writable (ie: the key has no mandatory setting).
- *
- * This call does not verify that writing to the key will actually be successful. It only checks for
- * the existence of mandatory keys/locks that might affect writing to @key. Other issues (such as a
- * full disk or an inability to connect to the bus and start the service) may cause the write to fail.
- **/
- public bool is_writable (string key) {
- return engine.is_writable (key);
- }
-
- /**
- * dconf_client_write:
- * @client: a #DConfClient
- * @key: a dconf key
- * @value: (allow-none): a #GVariant, or %NULL
- * @tag: (out) (allow-none): the tag from this write
- * @cancellable: a #GCancellable, or %NULL
- * @error: a pointer to a #GError, or %NULL
- * Returns: %TRUE if the write is successful
- *
- * Write a value to the given @key, or reset @key to its default value.
- *
- * If @value is %NULL then @key is reset to its default value (which may
- * be completely unset), otherwise @value becomes the new value.
- *
- * If @tag is non-%NULL then it is set to the unique tag associated with this write. This is the same
- * tag that appears in change notifications.
- **/
- public bool write (string key, Variant? value, out string tag = null, Cancellable? cancellable = null) throws Error {
- call_sync (engine.write (key, value), out tag, cancellable);
- return true;
- }
-
- /**
- * dconf_client_write_async:
- * @client: a #DConfClient
- * @key: a dconf key
- * @value: (allow-none): a #GVariant, or %NULL
- * @cancellable: a #GCancellable, or %NULL
- * @callback: the function to call when complete
- * @user_data: the user data for @callback
- *
- * Write a value to the given @key, or reset @key to its default value.
- *
- * This is the asynchronous version of dconf_client_write(). You should call
- * dconf_client_write_finish() from @callback to collect the result.
- **/
- public async bool write_async (string key, Variant? value, out string tag = null, Cancellable? cancellable = null) throws Error {
- yield call_async (engine.write (key, value), out tag, cancellable);
- return true;
- }
-
- /**
- * dconf_client_write_many:
- * @client: a #DConfClient
- * @dir: the dconf directory under which to make the writes
- * @rels: a %NULL-terminated array of relative keys
- * @values: an array of possibly-%NULL #GVariant pointers
- * @n_values: the length of @values, which must be equal to the length of @rels
- * @tag: (out) (allow-none): the tag from this write
- * @cancellable: a #GCancellable, or %NULL
- * @error: a pointer to a #GError, or %NULL
- * Returns: %TRUE if the write is successful
- *
- * Write multiple values at once.
- *
- * For each pair of items from @rels and @values, the value is written to the result of concatenating
- * @dir with the relative path. As with dconf_client_write(), if a given value is %NULL then the effect
- * is that the specified key is reset.
- *
- * If @tag is non-%NULL then it is set to the unique tag associated with this write. This is the same
- * tag that appears in change notifications.
- **/
- public bool write_many (string dir, [CCode (array_length = false, array_null_terminated = true)] string[] rels, Variant?[] values, out string? tag = null, Cancellable? cancellable = null) throws Error {
- call_sync (engine.write_many (dir, rels, values), out tag, cancellable);
- return true;
- }
-
- /*< disabled due to Vala compiler bugs >
- * dconf_client_write_many_async:
- * @client: a #DConfClient
- * @dir: the dconf directory under which to make the writes
- * @rels: a %NULL-terminated array of relative keys
- * @values: an array of possibly-%NULL #GVariant pointers
- * @n_values: the length of @values, which must be equal to the length of @rels
- * @cancellable: a #GCancellable, or %NULL
- * @callback: a #GAsyncReadyCallback to call when finished
- * @user_data: a pointer to pass as the last argument to @callback
- *
- * Write multiple values at once.
- *
- * This is the asynchronous version of dconf_client_write_many(). You should call
- * dconf_client_write_many_finish() from @callback to collect the result.
- *
- public async bool write_many_async (string dir, [CCode (array_length = false, array_null_terminated = true)] string[] rels, Variant?[] values, out string? tag = null, Cancellable? cancellable = null) throws Error {
- yield call_async (engine.write_many (dir, rels, values), out tag, cancellable);
- return true;
- }*/
-
- /**
- * dconf_client_read:
- * @client: a #DConfClient
- * @key: a valid dconf key
- * Returns: the value corresponding to @key, or %NULL if there is none
- *
- * Reads the value named by @key from dconf. If no such value exists, %NULL is returned.
- **/
- public Variant? read (string key) {
- return engine.read (key);
- }
-
- /**
- * dconf_client_read_default:
- * @client: a #DConfClient
- * @key: a valid dconf key
- * Returns: the default value corresponding to @key, or %NULL if there is none
- *
- * Reads the value named by @key from any existing default/mandatory databases but ignoring any value
- * set by the user. The result is as if the named key had just been reset.
- **/
- public Variant? read_default (string key) {
- return engine.read_default (key);
- }
-
- /**
- * dconf_client_read_no_default:
- * @client: a #DConfClient
- * @key: a valid dconf key
- * Returns: the user value corresponding to @key, or %NULL if there is none
- *
- * Reads the value named by @key as set by the user, ignoring any default/mandatory databases. Normal
- * applications will never want to do this, but it may be useful for administrative or configuration
- * tweaking utilities to have access to this information.
- *
- * Note that in the case of mandatory keys, the result of dconf_client_read_no_default() with a fallback
- * to dconf_client_read_default() is not necessarily the same as the result of a dconf_client_read().
- * This is because the user may have set a value before the key became marked as mandatory, in which
- * case this call will see the user's (otherwise inaccessible) key.
- **/
- public Variant? read_no_default (string key) {
- return engine.read_no_default (key);
- }
-
- /**
- * dconf_client_list:
- * @client: a #DConfClient
- * @dir: a dconf dir
- * @length: the number of items that were returned
- * Returns: (array length=length): the paths located directly below @dir
- *
- * Lists the keys and dirs located directly below @dir.
- *
- * You should free the return result with g_strfreev() when it is no longer needed.
- **/
- public string[] list (string dir) {
- return engine.list (dir);
- }
-
- /**
- * dconf_client_watch:
- * @client: a #DConfClient
- * @path: a dconf path
- * @cancellable: a #GCancellable, or %NULL
- * @error: a pointer to a %NULL #GError, or %NULL
- * Returns: %TRUE on success, else %FALSE with @error set
- *
- * Requests monitoring of a portion of the dconf database.
- *
- * If @path is a key (ie: doesn't end with a slash) then a single key is monitored for changes. If
- * @path is a dir (ie: sending with a slash) then all keys that have @path as a prefix are monitored.
- *
- * This function blocks until the watch has definitely been established with the bus daemon. If you
- * would like a non-blocking version of this call, see dconf_client_watch_async().
- **/
- public bool watch (string path, Cancellable? cancellable = null) throws GLib.Error {
- call_sync (engine.watch (path), null, cancellable);
- return true;
- }
-
- /**
- * dconf_client_watch_async:
- * @client: a #DConfClient
- * @path: a dconf path
- * @cancellable: a #GCancellable, or %NULL
- * @callback: a #GAsyncReadyCallback to call when finished
- * @user_data: a pointer to pass as the last argument to @callback
- *
- * Requests monitoring of a portion of the dconf database.
- *
- * This is the asynchronous version of dconf_client_watch(). You should call
- * dconf_client_watch_finish() from @callback to collect the result.
- **/
- public async bool watch_async (string name, Cancellable? cancellable = null) throws GLib.Error {
- yield call_async (engine.watch (name), null, cancellable);
- return true;
- }
-
- /**
- * dconf_client_unwatch:
- * @client: a #DConfClient
- * @path: a dconf path
- * @cancellable: a #GCancellable, or %NULL
- * @error: a pointer to a %NULL #GError, or %NULL
- * Returns: %TRUE on success, else %FALSE with @error set
- *
- * Cancels the effect of a previous call to dconf_client_watch().
- *
- * If the same path has been watched multiple times then only one of the watches is cancelled and the
- * net effect is that the path is still watched.
- *
- * This function blocks until the watch has definitely been removed from the bus daemon. It is possible
- * that notifications in transit will arrive after this call returns. For an asynchronous version of
- * this call, see dconf_client_unwatch_async().
- **/
- public bool unwatch (string name, Cancellable? cancellable = null) throws GLib.Error {
- call_sync (engine.unwatch (name), null, cancellable);
- return true;
- }
-
- /**
- * dconf_client_unwatch_async:
- * @client: a #DConfClient
- * @path: a dconf path
- * @cancellable: a #GCancellable, or %NULL
- * @callback: a #GAsyncReadyCallback to call when finished
- * @user_data: a pointer to pass as the last argument to @callback
- *
- * Cancels the effect of a previous call to dconf_client_watch().
- *
- * This is the asynchronous version of dconf_client_unwatch(). You should call
- * dconf_client_unwatch_finish() from @callback to collect the result. No additional notifications will
- * be delivered for this watch after @callback is called.
- **/
- public async bool unwatch_async (string name, Cancellable? cancellable = null) throws GLib.Error {
- yield call_async (engine.unwatch (name), null, cancellable);
- return true;
- }
-
- /**
- * dconf_client_new:
- * @profile: the dconf profile to use, or %NULL
- * @watch_func: the function to call when changes occur
- * @user_data: the user_data to pass to @watch_func
- * @notify: the function to free @user_data when no longer needed
- * Returns: a new #DConfClient
- *
- * Creates a new #DConfClient for the given context.
- *
- * If @profile is non-%NULL then it specifies the name of the profile to use. If @profile is %NULL then
- * the DCONF_PROFILE environment variable is consulted. If that is unset then the default profile of
- * "user" is used. If a profile named "user" is not installed then the dconf client is setup to access
- * ~/.config/dconf/user.
- **/
- public Client (string? profile = null, owned WatchFunc? watch_func = null) {
- engine = new Engine (profile);
- this.watch_func = (owned) watch_func;
- }
- }
-
- public extern bool is_path (string str, Error *error = null);
- public extern bool is_key (string str, Error *error = null);
- public extern bool is_dir (string str, Error *error = null);
- public extern bool is_rel_path (string str, Error *error = null);
- public extern bool is_rel_key (string str, Error *error = null);
- public extern bool is_rel_dir (string str, Error *error = null);
- [CCode (cname = "dconf_is_path")]
- public extern bool verify_path (string str) throws Error;
- [CCode (cname = "dconf_is_key")]
- public extern bool verify_key (string str) throws Error;
- [CCode (cname = "dconf_is_dir")]
- public extern bool verify_dir (string str) throws Error;
- [CCode (cname = "dconf_is_rel_path")]
- public extern bool verify_rel_path (string str) throws Error;
- [CCode (cname = "dconf_is_rel_key")]
- public extern bool verify_rel_key (string str) throws Error;
- [CCode (cname = "dconf_is_rel_dir")]
- public extern bool verify_rel_dir (string str) throws Error;
-}
-
-// vim:noet sw=4 ts=4
diff --git a/client/dconf.deps b/client/dconf.deps
new file mode 100644
index 0000000..cd10dfd
--- /dev/null
+++ b/client/dconf.deps
@@ -0,0 +1 @@
+gio-2.0
diff --git a/client/dconf.h b/client/dconf.h
index 77f1154..21b8f70 100644
--- a/client/dconf.h
+++ b/client/dconf.h
@@ -23,6 +23,7 @@
#define __dconf_h__
#include <dconf-paths.h>
+#include <dconf-changeset.h>
#include <dconf-client.h>
#endif /* __dconf_h__ */
diff --git a/client/dconf.vapi b/client/dconf.vapi
new file mode 100644
index 0000000..7e5cdae
--- /dev/null
+++ b/client/dconf.vapi
@@ -0,0 +1,60 @@
+/* dconf.vapi generated by valac 0.17.1.35-814b, do not modify. */
+
+namespace DConf {
+ [CCode (cheader_filename = "dconf.h")]
+ public class Client : GLib.Object {
+ public signal void changed (string prefix, [CCode (array_length = false, array_null_terminated = true)] string[] changes, string? tag);
+
+ public Client ();
+ public GLib.Variant? read (string key);
+ public string[] list (string dir);
+ public bool is_writable (string key);
+ public void write_fast (string path, GLib.Variant? value) throws GLib.Error;
+ public void write_sync (string path, GLib.Variant? value, out string tag = null, GLib.Cancellable? cancellable = null) throws GLib.Error;
+ public void change_fast (Changeset changeset) throws GLib.Error;
+ public void change_sync (Changeset changeset, out string tag = null, GLib.Cancellable? cancellable = null) throws GLib.Error;
+ public void watch_fast (string path);
+ public void unwatch_fast (string path);
+ public void watch_sync (string path);
+ public void unwatch_sync (string path);
+ }
+
+ [Compact]
+ [CCode (ref_function = "dconf_changeset_ref", unref_function = "dconf_changeset_unref")]
+ public class Changeset {
+ public delegate bool Predicate (string path, GLib.Variant? value);
+ public Changeset ();
+ public Changeset.write (string path, GLib.Variant? value);
+ public void set (string path, GLib.Variant? value);
+ public bool get (string path, out GLib.Variant? value);
+ public bool is_similar_to (Changeset other);
+ public bool all (Predicate predicate);
+ public GLib.Variant serialise ();
+ public static Changeset deserialise (GLib.Variant serialised);
+ }
+
+ [CCode (cheader_filename = "dconf.h")]
+ public static bool is_dir (string str, GLib.Error* error = null);
+ [CCode (cheader_filename = "dconf.h")]
+ public static bool is_key (string str, GLib.Error* error = null);
+ [CCode (cheader_filename = "dconf.h")]
+ public static bool is_path (string str, GLib.Error* error = null);
+ [CCode (cheader_filename = "dconf.h")]
+ public static bool is_rel_dir (string str, GLib.Error* error = null);
+ [CCode (cheader_filename = "dconf.h")]
+ public static bool is_rel_key (string str, GLib.Error* error = null);
+ [CCode (cheader_filename = "dconf.h")]
+ public static bool is_rel_path (string str, GLib.Error* error = null);
+ [CCode (cheader_filename = "dconf.h", cname = "dconf_is_dir")]
+ public static bool verify_dir (string str) throws GLib.Error;
+ [CCode (cheader_filename = "dconf.h", cname = "dconf_is_key")]
+ public static bool verify_key (string str) throws GLib.Error;
+ [CCode (cheader_filename = "dconf.h", cname = "dconf_is_path")]
+ public static bool verify_path (string str) throws GLib.Error;
+ [CCode (cheader_filename = "dconf.h", cname = "dconf_is_rel_dir")]
+ public static bool verify_rel_dir (string str) throws GLib.Error;
+ [CCode (cheader_filename = "dconf.h", cname = "dconf_is_rel_key")]
+ public static bool verify_rel_key (string str) throws GLib.Error;
+ [CCode (cheader_filename = "dconf.h", cname = "dconf_is_rel_path")]
+ public static bool verify_rel_path (string str) throws GLib.Error;
+}
diff --git a/client/engine.vapi b/client/engine.vapi
deleted file mode 100644
index 48dce55..0000000
--- a/client/engine.vapi
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace DConf {
- [Compact]
- [CCode (cheader_filename = "dconf-engine.h")]
- class Engine {
- internal Engine (string? profile);
- internal bool is_writable (string key);
- internal EngineMessage write (string key, GLib.Variant? value) throws GLib.Error;
- internal EngineMessage write_many (string dir, [CCode (array_length = false, array_null_terminated = true, type = "const gchar * const *")] string[] keys, [CCode (array_length = false)] GLib.Variant?[] values) throws GLib.Error;
- internal GLib.Variant? read (string key);
- internal GLib.Variant? read_default (string key);
- internal GLib.Variant? read_no_default (string key);
- internal EngineMessage set_locked (string key, bool locked);
- internal string[] list (string dir);
- internal EngineMessage watch (string name);
- internal EngineMessage unwatch (string name);
- }
-
- struct EngineMessage {
- string bus_name;
- string object_path;
- string interface_name;
- string method_name;
- int n_messages;
- [CCode (array_length_cname = "n_messages")]
- GLib.Variant[] parameters;
- [CCode (array_length_cname = "n_messages")]
- char[] bus_types;
- GLib.VariantType reply_type;
- }
-
- [CCode (has_target = false)]
- delegate GLib.Variant? ServiceFunc (EngineMessage dcem);
-}
-
-// vim:noet sw=4 ts=4
diff --git a/client/extra-docs.c b/client/extra-docs.c
deleted file mode 100644
index 2f12951..0000000
--- a/client/extra-docs.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/* extra docs here until we can emit them with Vala */
-
-/**
- * SECTION:client
- * @title: DConfClient
- * @short_description: Direct read and write access to DConf, based on GDBus
- *
- * This is a simple class that allows an application to directly read
- * from and write to the dconf database. There is also a very simple
- * mechanism for requesting and receiving notification of changes but
- * not robust mechanism for dispatching change notifications to multiple
- * listeners.
- *
- * Most applications probably don't want to access dconf directly and
- * would be better off using something like #GSettings.
- **/
-
-/**
- * DConfWatchFunc:
- * @client: the #DConfClient emitting the notification
- * @path: the path at which the change occured
- * @items: the items that were changed, given as relative paths
- * @n_items: the length of @items
- * @tag: the tag associated with the change
- * @user_data: the user data given to dconf_client_new()
- *
- * This is the type of the callback given to dconf_client_new().
- *
- * This function is called in response to changes occuring to the dconf
- * database that @client is associated with.
- *
- * @path can either be a key or a dir. If @path is a key then @items
- * will be empty and the notification should be taken to mean that one
- * key -- the key named by @path -- may have changed.
- *
- * If @path is a dir and @items is empty then it is an indication that
- * any key under @path may have changed.
- *
- * Otherwise (if @items is non-empty) then the set of affected keys is
- * the same as if the watch function had been called multiple times for
- * each item in the array appended to @path. This includes the
- * possibility of the resulting path being a dir.
- **/
-
-/**
- * DConfClient:
- *
- * An opaque structure type. May only be used with the following
- * functions.
- **/
-
-/**
- * dconf_client_write_finish:
- * @client: a #DConfClient
- * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
- * @tag: (out) (allow-none): the tag from this write
- * @error: a pointer to a #GError, or %NULL
- *
- * Collects the result from a prior call to dconf_client_write_async().
- **/
-
-/**
- * dconf_client_set_locked_finish:
- * @client: a #DConfClient
- * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
- * @error: a pointer to a #GError, or %NULL
- *
- * Collects the result from a prior call to
- * dconf_client_set_locked_async().
- **/
-
-/**
- * dconf_client_watch_finish:
- * @client: a #DConfClient
- * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
- * @error: a pointer to a #GError, or %NULL
- *
- * Collects the result from a prior call to dconf_client_watch_async().
- **/
-
-/**
- * dconf_client_unwatch_finish:
- * @client: a #DConfClient
- * @result: the #GAsyncResult passed to the #GAsyncReadyCallback
- * @error: a pointer to a #GError, or %NULL
- *
- * Collects the result from a prior call to
- * dconf_client_unwatch_async().
- **/
diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 0000000..8209281
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1,3 @@
+libdconf-common.a
+libdconf-common-hidden.a
+libdconf-common-shared.a
diff --git a/common/Makefile.am b/common/Makefile.am
index 0238cd1..77903f1 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -1,7 +1,18 @@
+include $(top_srcdir)/Makefile.gtester
+
dconfinclude_HEADERS = \
dconf-paths.h
-EXTRA_DIST = \
- dconf-shmdir.h \
- dconf-paths.h \
+noinst_LIBRARIES = libdconf-common.a libdconf-common-shared.a libdconf-common-hidden.a
+
+libdconf_common_a_CFLAGS = $(glib_CFLAGS)
+libdconf_common_a_SOURCES = \
+ dconf-changeset.h \
+ dconf-changeset.c \
dconf-paths.c
+
+libdconf_common_shared_a_CFLAGS = $(libdconf_common_a_CFLAGS) -fPIC -DPIC
+libdconf_common_shared_a_SOURCES = $(libdconf_common_a_SOURCES)
+
+libdconf_common_hidden_a_CFLAGS = $(libdconf_common_a_CFLAGS) -fPIC -DPIC -fvisibility=hidden
+libdconf_common_hidden_a_SOURCES = $(libdconf_common_a_SOURCES)
diff --git a/common/dconf-changeset.c b/common/dconf-changeset.c
new file mode 100644
index 0000000..177f682
--- /dev/null
+++ b/common/dconf-changeset.c
@@ -0,0 +1,562 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-changeset.h"
+#include "dconf-paths.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+/**
+ * SECTION:changeset
+ * @title: DConfChangeset
+ * @Short_description: A set of changes to a dconf database
+ *
+ * #DConfChangeset represents a set of changes that can be made to a
+ * dconf database. Currently supported operations are writing new
+ * values to keys and resetting keys and dirs.
+ *
+ * Create the changeset with dconf_changeset_new() and populate it with
+ * dconf_changeset_set(). Submit it to dconf with
+ * dconf_client_change_fast() or dconf_client_change_sync().
+ * dconf_changeset_new_write() is a convenience constructor for the
+ * common case of writing or resetting a single value.
+ **/
+
+/**
+ * DConfChangeset:
+ *
+ * This is a reference counted opaque structure type. It is not a
+ * #GObject.
+ *
+ * Use dconf_changeset_ref() and dconf_changeset_unref() to manipuate
+ * references.
+ **/
+
+struct _DConfChangeset
+{
+ GHashTable *table;
+ gint ref_count;
+
+ gchar *prefix;
+ const gchar **paths;
+ GVariant **values;
+};
+
+static void
+unref_gvariant0 (gpointer data)
+{
+ if (data)
+ g_variant_unref (data);
+}
+
+/**
+ * dconf_changeset_new:
+ *
+ * Creates a new, empty, #DConfChangeset.
+ *
+ * Returns: the new #DConfChangeset.
+ **/
+DConfChangeset *
+dconf_changeset_new (void)
+{
+ DConfChangeset *changeset;
+
+ changeset = g_slice_new0 (DConfChangeset);
+ changeset->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, unref_gvariant0);
+ changeset->ref_count = 1;
+
+ return changeset;
+}
+
+/**
+ * dconf_changeset_unref:
+ * @changeset: a #DConfChangeset
+ *
+ * Releases a #DConfChangeset reference.
+ **/
+void
+dconf_changeset_unref (DConfChangeset *changeset)
+{
+ if (g_atomic_int_dec_and_test (&changeset->ref_count))
+ {
+ g_free (changeset->prefix);
+ g_free (changeset->paths);
+ g_free (changeset->values);
+
+ g_hash_table_unref (changeset->table);
+
+ g_slice_free (DConfChangeset, changeset);
+ }
+}
+
+/**
+ * dconf_changeset_ref:
+ * @changeset: a #DConfChangeset
+ *
+ * Increases the reference count on @changeset
+ *
+ * Returns: @changeset
+ **/
+DConfChangeset *
+dconf_changeset_ref (DConfChangeset *changeset)
+{
+ g_atomic_int_inc (&changeset->ref_count);
+
+ return changeset;
+}
+
+/**
+ * dconf_changeset_set:
+ * @changeset: a #DConfChangeset
+ * @path: a path to modify
+ * @value: the value for the key, or %NULL to reset
+ *
+ * Adds an operation to modify @path to a #DConfChangeset.
+ *
+ * @path may either be a key or a dir. If it is a key then @value may
+ * be a #GVariant, or %NULL (to set or reset the key).
+ *
+ * If @path is a dir then this must be a reset operation: @value must be
+ * %NULL. It is not permitted to assign a #GVariant value to a dir.
+ **/
+void
+dconf_changeset_set (DConfChangeset *changeset,
+ const gchar *path,
+ GVariant *value)
+{
+ g_return_if_fail (changeset->prefix == NULL);
+ g_return_if_fail (dconf_is_path (path, NULL));
+
+ /* Check if we are performing a path reset */
+ if (g_str_has_suffix (path, "/"))
+ {
+ GHashTableIter iter;
+ gpointer key;
+
+ g_return_if_fail (value == NULL);
+
+ /* When we reset a path we must also reset all keys within that
+ * path.
+ */
+ g_hash_table_iter_init (&iter, changeset->table);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ if (g_str_has_prefix (key, path))
+ g_hash_table_iter_remove (&iter);
+
+ /* Record the reset itself. */
+ g_hash_table_insert (changeset->table, g_strdup (path), NULL);
+ }
+
+ /* else, just a normal value write or reset */
+ else
+ g_hash_table_insert (changeset->table, g_strdup (path), value ? g_variant_ref_sink (value) : NULL);
+}
+
+/**
+ * dconf_changeset_get:
+ * @changeset: a #DConfChangeset
+ * @key: the key to check
+ * @value: a return location for the value, or %NULL
+ *
+ * Checks if a #DConfChangeset has an outstanding request to change
+ * the value of the given @key.
+ *
+ * If the change doesn't involve @key then %FALSE is returned and the
+ * @value is unmodified.
+ *
+ * If the change modifies @key then @value is set either to the value
+ * for that key, or %NULL in the case that the key is being reset by the
+ * request.
+ *
+ * Returns: %TRUE if the key is being modified by the change
+ */
+gboolean
+dconf_changeset_get (DConfChangeset *changeset,
+ const gchar *key,
+ GVariant **value)
+{
+ gpointer tmp;
+
+ if (!g_hash_table_lookup_extended (changeset->table, key, NULL, &tmp))
+ return FALSE;
+
+ if (value)
+ *value = tmp ? g_variant_ref (tmp) : NULL;
+
+ return TRUE;
+}
+
+/**
+ * dconf_changeset_is_similar_to:
+ * @changeset: a #DConfChangeset
+ * @other: another #DConfChangeset
+ *
+ * Checks if @changeset is similar to @other.
+ *
+ * Two changes are considered similar if they write to the exact same
+ * set of keys. The values written are not considered.
+ *
+ * This check is used to prevent building up a queue of repeated writes
+ * of the same keys. This is often seen when an application writes to a
+ * key on every move of a slider or an application window.
+ *
+ * Strictly speaking, a write resettings all of "/a/" after a write
+ * containing "/a/b" could cause the later to be removed from the queue,
+ * but this situation is difficult to detect and is expected to be
+ * extremely rare.
+ *
+ * Returns: %TRUE if the changes are similar
+ **/
+gboolean
+dconf_changeset_is_similar_to (DConfChangeset *changeset,
+ DConfChangeset *other)
+{
+ GHashTableIter iter;
+ gpointer key;
+
+ if (g_hash_table_size (changeset->table) != g_hash_table_size (other->table))
+ return FALSE;
+
+ g_hash_table_iter_init (&iter, changeset->table);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ if (!g_hash_table_contains (other->table, key))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * DConfChangesetPredicate:
+ * @path: a path, as per dconf_is_path()
+ * @value: a #GVariant, or %NULL
+ * @user_data: user data pointer
+ *
+ * Callback function type for predicates over items in a
+ * #DConfChangeset.
+ *
+ * Use with dconf_changeset_all().
+ *
+ * Returns: %TRUE if the predicate is met for the given @path and @value
+ **/
+
+/**
+ * dconf_changeset_all:
+ * @changeset: a #DConfChangeset
+ * @predicate: a #DConfChangesetPredicate
+ * @user_data: user data to pass to @predicate
+ *
+ * Checks if all changes in the changeset satisfy @predicate.
+ *
+ * @predicate is called on each item in the changeset, in turn, until it
+ * returns %FALSE.
+ *
+ * If @preciate returns %FALSE for any item, this function returns
+ * %FALSE. If not (including the case of no items) then this function
+ * returns %TRUE.
+ *
+ * Returns: %TRUE if all items in @changeset satisfy @predicate
+ */
+gboolean
+dconf_changeset_all (DConfChangeset *changeset,
+ DConfChangesetPredicate predicate,
+ gpointer user_data)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, changeset->table);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ if (!(* predicate) (key, value, user_data))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gint
+dconf_changeset_string_ptr_compare (gconstpointer a_p,
+ gconstpointer b_p)
+{
+ const gchar * const *a = a_p;
+ const gchar * const *b = b_p;
+
+ return strcmp (*a, *b);
+}
+
+static void
+dconf_changeset_build_description (DConfChangeset *changeset)
+{
+ gsize prefix_length;
+ gint n_items;
+
+ n_items = g_hash_table_size (changeset->table);
+
+ /* If there are no items then what is there to describe? */
+ if (n_items == 0)
+ return;
+
+ /* We do three separate passes. This might take a bit longer than
+ * doing it all at once but it keeps the complexity down.
+ *
+ * First, we iterate the table in order to determine the common
+ * prefix.
+ *
+ * Next, we iterate the table again to pull the strings out excluding
+ * the leading prefix.
+ *
+ * We sort the list of paths at this point because the rebuilder
+ * requires a sorted list.
+ *
+ * Finally, we iterate over the sorted list and use the normal
+ * hashtable lookup in order to populate the values array in the same
+ * order.
+ *
+ * Doing it this way avoids the complication of trying to sort two
+ * arrays (keys and values) at the same time.
+ */
+
+ /* Pass 1: determine the common prefix. */
+ {
+ GHashTableIter iter;
+ const gchar *first;
+ gboolean have_one;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, changeset->table);
+
+ /* We checked above that we have at least one item. */
+ have_one = g_hash_table_iter_next (&iter, &key, NULL);
+ g_assert (have_one);
+
+ prefix_length = strlen (key);
+ first = key;
+
+ /* Consider the remaining items to find the common prefix */
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ const gchar *this = key;
+ gint i;
+
+ for (i = 0; i < prefix_length; i++)
+ if (first[i] != this[i])
+ {
+ prefix_length = i;
+ break;
+ }
+ }
+
+ /* We must surely always have a common prefix of '/' */
+ g_assert (prefix_length > 0);
+ g_assert (first[0] == '/');
+
+ /* We may find that "/a/ab" and "/a/ac" have a common prefix of
+ * "/a/a" but really we want to trim that back to "/a/".
+ *
+ * If there is only one item, leave it alone.
+ */
+ if (n_items > 1)
+ {
+ while (first[prefix_length - 1] != '/')
+ prefix_length--;
+ }
+
+ changeset->prefix = g_strndup (first, prefix_length);
+ }
+
+ /* Pass 2: collect the list of keys, dropping the prefix */
+ {
+ GHashTableIter iter;
+ gpointer key;
+ gint i = 0;
+
+ changeset->paths = g_new (const gchar *, n_items + 1);
+
+ g_hash_table_iter_init (&iter, changeset->table);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ const gchar *path = key;
+
+ changeset->paths[i++] = path + prefix_length;
+ }
+ changeset->paths[i] = NULL;
+ g_assert (i == n_items);
+
+ /* Sort the list of keys */
+ qsort (changeset->paths, n_items, sizeof (const gchar *), dconf_changeset_string_ptr_compare);
+ }
+
+ /* Pass 3: collect the list of values */
+ {
+ gint i;
+
+ changeset->values = g_new (GVariant *, n_items);
+
+ for (i = 0; i < n_items; i++)
+ /* We dropped the prefix when collecting the array.
+ * Bring it back temporarily, for the lookup.
+ */
+ changeset->values[i] = g_hash_table_lookup (changeset->table, changeset->paths[i] - prefix_length);
+ }
+}
+
+/**
+ * dconf_changeset_describe:
+ * @changeset: a #DConfChangeset
+ * @prefix: the prefix under which changes have been requested
+ * @paths: the list of paths changed, relative to @prefix
+ * @values: the list of values changed
+ *
+ * Describes @changeset.
+ *
+ * @prefix and @paths are presented in the same way as they are for the
+ * DConfClient::changed signal. @values is an array of the same length
+ * as @paths. For each key described by an element in @paths, @values
+ * will contain either a #GVariant (the requested new value of that key)
+ * or %NULL (to reset a reset).
+ *
+ * The @paths array is returned in an order such that dir will always
+ * come before keys contained within those dirs.
+ *
+ * Returns: the number of changes (the length of @changes and @values).
+ **/
+guint
+dconf_changeset_describe (DConfChangeset *changeset,
+ const gchar **prefix,
+ const gchar * const **paths,
+ GVariant * const **values)
+{
+ gint n_items;
+
+ n_items = g_hash_table_size (changeset->table);
+
+ if (n_items && !changeset->prefix)
+ dconf_changeset_build_description (changeset);
+
+ if (prefix)
+ *prefix = changeset->prefix;
+
+ if (paths)
+ *paths = changeset->paths;
+
+ if (values)
+ *values = changeset->values;
+
+ return n_items;
+}
+
+/**
+ * dconf_changeset_serialise:
+ * @changeset: a #DConfChangeset
+ *
+ * Serialises a #DConfChangeset.
+ *
+ * The returned value has no particular format and should only be passed
+ * to dconf_changeset_deserialise().
+ *
+ * Returns: a floating #GVariant
+ **/
+GVariant *
+dconf_changeset_serialise (DConfChangeset *changeset)
+{
+ GVariantBuilder builder;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{smv}"));
+
+ g_hash_table_iter_init (&iter, changeset->table);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ g_variant_builder_add (&builder, "{smv}", key, value);
+
+ return g_variant_builder_end (&builder);
+}
+
+/**
+ * dconf_changeset_deserialise:
+ * @serialised: a #GVariant from dconf_changeset_serialise()
+ *
+ * Creates a #DConfChangeset according to a serialised description
+ * returned from an earlier call to dconf_changeset_serialise().
+ *
+ * @serialised has no particular format -- you should only pass a value
+ * that reasulted from an earlier serialise operation.
+ *
+ * This call never fails, even if @serialised is not in the correct
+ * format. Improperly-formatted parts are simply ignored.
+ *
+ * Returns: a new #DConfChangeset
+ **/
+DConfChangeset *
+dconf_changeset_deserialise (GVariant *serialised)
+{
+ DConfChangeset *changeset;
+ GVariantIter iter;
+ const gchar *key;
+ GVariant *value;
+
+ changeset = dconf_changeset_new ();
+ g_variant_iter_init (&iter, serialised);
+ while (g_variant_iter_loop (&iter, "{&smv}", &key, &value))
+ {
+ /* If value is NULL: we may be resetting a key or a dir (a path).
+ * If value is non-NULL: we must be setting a key.
+ *
+ * ie: it is not possible to set a value to a directory.
+ *
+ * If we get an invalid case, just fall through and ignore it.
+ */
+ if (value == NULL)
+ {
+ if (dconf_is_path (key, NULL))
+ g_hash_table_insert (changeset->table, g_strdup (key), NULL);
+ }
+ else
+ {
+ if (dconf_is_key (key, NULL))
+ g_hash_table_insert (changeset->table, g_strdup (key), g_variant_ref (value));
+ }
+ }
+
+ return changeset;
+}
+
+/**
+ * dconf_changeset_new_write:
+ * @path: a dconf path
+ * @value: a #GVariant, or %NULL
+ *
+ * Creates a new #DConfChangeset with one change. This is equivalent to
+ * calling dconf_changeset_new() and then dconf_changeset_set() with
+ * @path and @value.
+ *
+ * Returns: a new #DConfChangeset
+ **/
+DConfChangeset *
+dconf_changeset_new_write (const gchar *path,
+ GVariant *value)
+{
+ DConfChangeset *changeset;
+
+ changeset = dconf_changeset_new ();
+ dconf_changeset_set (changeset, path, value);
+
+ return changeset;
+}
diff --git a/common/dconf-changeset.h b/common/dconf-changeset.h
new file mode 100644
index 0000000..c2a5435
--- /dev/null
+++ b/common/dconf-changeset.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_changeset_h__
+#define __dconf_changeset_h__
+
+#include <glib.h>
+
+typedef struct _DConfChangeset DConfChangeset;
+
+typedef gboolean (* DConfChangesetPredicate) (const gchar *path,
+ GVariant *value,
+ gpointer user_data);
+
+DConfChangeset * dconf_changeset_new (void);
+
+DConfChangeset * dconf_changeset_new_write (const gchar *path,
+ GVariant *value);
+
+DConfChangeset * dconf_changeset_ref (DConfChangeset *changeset);
+void dconf_changeset_unref (DConfChangeset *changeset);
+
+void dconf_changeset_set (DConfChangeset *changeset,
+ const gchar *path,
+ GVariant *value);
+
+gboolean dconf_changeset_get (DConfChangeset *changeset,
+ const gchar *key,
+ GVariant **value);
+
+gboolean dconf_changeset_is_similar_to (DConfChangeset *changeset,
+ DConfChangeset *other);
+
+gboolean dconf_changeset_all (DConfChangeset *changeset,
+ DConfChangesetPredicate predicate,
+ gpointer user_data);
+
+guint dconf_changeset_describe (DConfChangeset *changeset,
+ const gchar **prefix,
+ const gchar * const **paths,
+ GVariant * const **values);
+
+GVariant * dconf_changeset_serialise (DConfChangeset *changeset);
+DConfChangeset * dconf_changeset_deserialise (GVariant *serialised);
+
+#endif /* __dconf_changeset_h__ */
diff --git a/common/dconf-paths.c b/common/dconf-paths.c
index 6f82ee9..33a6f74 100644
--- a/common/dconf-paths.c
+++ b/common/dconf-paths.c
@@ -106,13 +106,14 @@
* dconf_is_path:
* @string: a string
* @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
- * Returns: %TRUE if @string is a path
*
* Checks if @string is a valid dconf path. dconf keys must start with
* '/' and not contain '//'.
*
* A dconf path may be either a key or a dir. See dconf_is_key() and
* dconf_is_dir() for examples of each.
+ *
+ * Returns: %TRUE if @string is a path
**/
gboolean
dconf_is_path (const gchar *string,
@@ -127,7 +128,6 @@ dconf_is_path (const gchar *string,
* dconf_is_key:
* @string: a string
* @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
- * Returns: %TRUE if @string is a key
*
* Checks if @string is a valid dconf key. dconf keys must start with
* '/', not contain '//' and not end with '/'.
@@ -138,6 +138,8 @@ dconf_is_path (const gchar *string,
* "/a", "/a/b" and "/a/b/c" are examples of keys. "", "/", "a", "a/b",
* "//a/b", "/a//b", and "/a/" are examples of strings that are not
* keys.
+ *
+ * Returns: %TRUE if @string is a key
**/
gboolean
dconf_is_key (const gchar *string,
@@ -152,7 +154,6 @@ dconf_is_key (const gchar *string,
* dconf_is_dir:
* @string: a string
* @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
- * Returns: %TRUE if @string is a dir
*
* Checks if @string is a valid dconf dir. dconf dirs must start and
* end with '/' and not contain '//'.
@@ -164,6 +165,8 @@ dconf_is_key (const gchar *string,
* "/", "/a/" and "/a/b/" are examples of dirs. "", "a/", "a/b/",
* "//a/b/", "/a//b/" and "/a" are examples of strings that are not
* dirs.
+ *
+ * Returns: %TRUE if @string is a dir
**/
gboolean
dconf_is_dir (const gchar *string,
@@ -175,10 +178,9 @@ dconf_is_dir (const gchar *string,
}
/**
- * dconf_is_rel:
+ * dconf_is_rel_path:
* @string: a string
* @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
- * Returns: %TRUE if @string is a relative path
*
* Checks if @string is a valid dconf relative path. A relative path is
* a string that, when concatenated to a dir, forms a valid dconf path.
@@ -186,10 +188,12 @@ dconf_is_dir (const gchar *string,
*
* A dconf rel may be either a relative key or a relative dir. See
* dconf_is_rel_key() and dconf_is_rel_dir() for examples of each.
+ *
+ * Returns: %TRUE if @string is a relative path
**/
gboolean
-dconf_is_rel (const gchar *string,
- GError **error)
+dconf_is_rel_path (const gchar *string,
+ GError **error)
{
#define type "relative path"
vars; nonnull; relative; no_double_slash; path;
@@ -201,7 +205,6 @@ dconf_is_rel (const gchar *string,
* dconf_is_rel_key:
* @string: a string
* @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
- * Returns: %TRUE if @string is a relative key
*
* Checks if @string is a valid dconf relative key. A relative key is a
* string that, when concatenated to a dir, forms a valid dconf key.
@@ -211,6 +214,8 @@ dconf_is_rel (const gchar *string,
* "a", "a/b" and "a/b/c" are examples of relative keys. "", "/", "/a",
* "/a/b", "//a/b", "/a//b", and "a/" are examples of strings that are
* not relative keys.
+ *
+ * Returns: %TRUE if @string is a relative key
**/
gboolean
dconf_is_rel_key (const gchar *string,
@@ -225,7 +230,6 @@ dconf_is_rel_key (const gchar *string,
* dconf_is_rel_dir:
* @string: a string
* @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
- * Returns: %TRUE if @string is a relative dir
*
* Checks if @string is a valid dconf relative dir. A relative dir is a
* string that, when appended to a dir, forms a valid dconf dir. This
@@ -237,6 +241,8 @@ dconf_is_rel_key (const gchar *string,
* "", "a/" and "a/b/" are examples of relative dirs. "/", "/a/",
* "/a/b/", "//a/b/", "a//b/" and "a" are examples of strings that are
* not relative dirs.
+ *
+ * Returns: %TRUE if @string is a relative dir
**/
gboolean
dconf_is_rel_dir (const gchar *string,
diff --git a/common/dconf-paths.h b/common/dconf-paths.h
index fa4d1e9..48e2182 100644
--- a/common/dconf-paths.h
+++ b/common/dconf-paths.h
@@ -32,7 +32,7 @@ gboolean dconf_is_key (const g
gboolean dconf_is_dir (const gchar *string,
GError **error);
-gboolean dconf_is_rel (const gchar *string,
+gboolean dconf_is_rel_path (const gchar *string,
GError **error);
gboolean dconf_is_rel_key (const gchar *string,
GError **error);
diff --git a/configure.ac b/configure.ac
index 7006b37..d580d75 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,9 +11,15 @@ AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([1.11 -Wno-portability no-dist-gzip dist-xz])
AM_SILENT_RULES([yes])
+# Set default CFLAGS before AC_PROG_CC does
+if test "${CFLAGS+yes}" != "yes"; then
+ CFLAGS='-O2 -g -Wall -Wwrite-strings -Wmissing-prototypes -fno-common'
+fi
+
# Check for programs
AC_PROG_CC
AM_PROG_VALAC([0.17.0])
+AC_PROG_RANLIB
# Use GSettings
GLIB_GSETTINGS
@@ -22,7 +28,7 @@ GLIB_GSETTINGS
GTK_DOC_CHECK([1.15])
# Dependencies
-PKG_CHECK_MODULES(glib, glib-2.0 >= 2.31.18)
+PKG_CHECK_MODULES(glib, glib-2.0 >= 2.33.3)
PKG_CHECK_MODULES(gio, gio-2.0)
PKG_CHECK_MODULES(dbus, dbus-1)
@@ -50,10 +56,21 @@ AC_SUBST(dconfincludedir, ${includedir}/dconf)
AC_PATH_PROG(gio_QUERYMODULES, gio-querymodules, no)
+AC_ARG_ENABLE(gcov,
+ AC_HELP_STRING([--enable-gcov],
+ [enable generation of code coverage information]))
+
+if test "x$enable_gcov" == "xyes"; then
+ CFLAGS="$CFLAGS -O0 -fprofile-arcs -ftest-coverage"
+ LDFLAGS="$LDFLAGS -lgcov"
+fi
+
AC_CONFIG_FILES([
common/Makefile
+ shm/Makefile
gvdb/Makefile
engine/Makefile
+ gdbus/Makefile
gsettings/Makefile
dbus-1/dconf-dbus-1.pc
client/dconf.pc
diff --git a/dbus-1/.gitignore b/dbus-1/.gitignore
index 32072d0..36fad9e 100644
--- a/dbus-1/.gitignore
+++ b/dbus-1/.gitignore
@@ -1,3 +1,5 @@
+libdconf-libdbus-1.a
+libdconf-libdbus-1-shared.a
libdconf-dbus-1.so
libdconf-dbus-1.so.0
libdconf-dbus-1.so.0.0.0
diff --git a/dbus-1/Makefile.am b/dbus-1/Makefile.am
index 6e740f1..7444c17 100644
--- a/dbus-1/Makefile.am
+++ b/dbus-1/Makefile.am
@@ -1,5 +1,14 @@
-AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -fPIC -DPIC
-INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(dbus_CFLAGS) $(glib_CFLAGS)
+include $(top_srcdir)/Makefile.gtester
+
+noinst_LIBRARIES = libdconf-libdbus-1.a libdconf-libdbus-1-shared.a
+
+libdconf_libdbus_1_a_CFLAGS = $(dbus_CFLAGS) $(glib_CFLAGS)
+libdconf_libdbus_1_a_SOURCES = \
+ dconf-libdbus-1.h \
+ dconf-libdbus-1.c
+
+libdconf_libdbus_1_shared_a_CFLAGS = $(libdconf_libdbus_1_a_CFLAGS) -fPIC -DPIC
+libdconf_libdbus_1_shared_a_SOURCES = $(libdconf_libdbus_1_a_SOURCES)
dconf_dbus_1includedir = $(includedir)/dconf-dbus-1
dconf_dbus_1include_HEADERS = dconf-dbus-1.h
@@ -10,15 +19,19 @@ pkgconfig_DATA = dconf-dbus-1.pc
shlibdir = $(libdir)
shlib_PROGRAMS = libdconf-dbus-1.so.0.0.0
-libdconf_dbus_1_so_0_0_0_LDADD = $(glib_LIBS) $(dbus_LIBS)
+libdconf_dbus_1_so_0_0_0_CFLAGS = $(dbus_CFLAGS) $(gio_CFLAGS) -fPIC -DPIC
+libdconf_dbus_1_so_0_0_0_LDADD = \
+ ../engine/libdconf-engine-shared.a \
+ ../common/libdconf-common-hidden.a \
+ ./libdconf-libdbus-1-shared.a \
+ ../gvdb/libgvdb-shared.a \
+ ../shm/libdconf-shm-shared.a \
+ $(dbus_LIBS) \
+ $(glib_LIBS)
libdconf_dbus_1_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf-dbus-1.so.0
-libdconf_dbus_1_so_0_0_0_SOURCES = \
- ../engine/dconf-engine.c \
- ../common/dconf-shmdir.c \
- ../gvdb/gvdb-reader.c \
- dconf-dbus-1.c
+libdconf_dbus_1_so_0_0_0_SOURCES = dconf-dbus-1.c
-noinst_DATA = libdconf-dbus-1.so libdconf-dbus-1.so.0
+nodist_noinst_DATA = libdconf-dbus-1.so libdconf-dbus-1.so.0
libdconf-dbus-1.so.0 libdconf-dbus-1.so: libdconf-dbus-1.so.0.0.0
$(AM_V_GEN) ln -fs libdconf-dbus-1.so.0.0.0 $@
diff --git a/dbus-1/dconf-dbus-1.c b/dbus-1/dconf-dbus-1.c
index 50dc0f8..bbc8b77 100644
--- a/dbus-1/dconf-dbus-1.c
+++ b/dbus-1/dconf-dbus-1.c
@@ -19,465 +19,62 @@
#include "dconf-dbus-1.h"
-#include <dconf-engine.h>
+#include "../engine/dconf-engine.h"
+#include "dconf-libdbus-1.h"
#include <string.h>
-typedef struct _Outstanding Outstanding;
-
struct _DConfDBusClient
{
- DBusConnection *session_bus;
- DBusConnection *system_bus;
+ DConfEngine *engine;
GSList *watches;
gint ref_count;
-
- Outstanding *outstanding;
- gchar *anti_expose_tag;
-
- DConfEngine *engine;
-};
-
-static void
-dconf_dbus_client_add_value_to_iter (DBusMessageIter *iter,
- GVariant *value)
-{
- GVariantClass class;
-
- class = g_variant_classify (value);
-
- switch (class)
- {
- case G_VARIANT_CLASS_BOOLEAN:
- {
- dbus_bool_t boolean;
-
- boolean = g_variant_get_boolean (value);
- dbus_message_iter_append_basic (iter, 'b', &boolean);
- }
- break;
-
- case G_VARIANT_CLASS_BYTE:
- case G_VARIANT_CLASS_INT16:
- case G_VARIANT_CLASS_UINT16:
- case G_VARIANT_CLASS_INT32:
- case G_VARIANT_CLASS_UINT32:
- case G_VARIANT_CLASS_INT64:
- case G_VARIANT_CLASS_UINT64:
- case G_VARIANT_CLASS_DOUBLE:
- dbus_message_iter_append_basic (iter, class,
- g_variant_get_data (value));
- break;
-
- case G_VARIANT_CLASS_STRING:
- case G_VARIANT_CLASS_OBJECT_PATH:
- case G_VARIANT_CLASS_SIGNATURE:
- {
- const gchar *str;
-
- str = g_variant_get_string (value, NULL);
- dbus_message_iter_append_basic (iter, class, &str);
- }
- break;
-
- case G_VARIANT_CLASS_ARRAY:
- {
- const gchar *contained;
- DBusMessageIter sub;
- gint i, n;
-
- contained = g_variant_get_type_string (value) + 1;
- n = g_variant_n_children (value);
- dbus_message_iter_open_container (iter, 'a', contained, &sub);
- for (i = 0; i < n; i++)
- {
- GVariant *child;
-
- child = g_variant_get_child_value (value, i);
- dconf_dbus_client_add_value_to_iter (&sub, child);
- g_variant_unref (child);
- }
-
- dbus_message_iter_close_container (iter, &sub);
- }
- break;
-
- case G_VARIANT_CLASS_TUPLE:
- {
- DBusMessageIter sub;
- gint i, n;
-
- n = g_variant_n_children (value);
- dbus_message_iter_open_container (iter, 'r', NULL, &sub);
- for (i = 0; i < n; i++)
- {
- GVariant *child;
-
- child = g_variant_get_child_value (value, i);
- dconf_dbus_client_add_value_to_iter (&sub, child);
- g_variant_unref (child);
- }
-
- dbus_message_iter_close_container (iter, &sub);
- }
- break;
-
- case G_VARIANT_CLASS_DICT_ENTRY:
- {
- DBusMessageIter sub;
- gint i;
-
- dbus_message_iter_open_container (iter, 'e', NULL, &sub);
- for (i = 0; i < 2; i++)
- {
- GVariant *child;
-
- child = g_variant_get_child_value (value, i);
- dconf_dbus_client_add_value_to_iter (&sub, child);
- g_variant_unref (child);
- }
-
- dbus_message_iter_close_container (iter, &sub);
- }
- break;
-
- case G_VARIANT_CLASS_VARIANT:
- {
- DBusMessageIter sub;
- GVariant *child;
-
- child = g_variant_get_variant (value);
- dbus_message_iter_open_container (iter, 'v',
- g_variant_get_type_string (child),
- &sub);
- dconf_dbus_client_add_value_to_iter (&sub, child);
- dbus_message_iter_close_container (iter, &sub);
- g_variant_unref (child);
- }
- break;
-
- default:
- g_assert_not_reached ();
- }
-}
-
-static void
-dconf_dbus_client_send (DConfDBusClient *dcdbc,
- DConfEngineMessage *dcem,
- DBusPendingCallNotifyFunction callback,
- gpointer user_data)
-{
- DBusConnection *connection;
- gint i;
-
- for (i = 0; i < dcem->n_messages; i++)
- {
- switch (dcem->bus_types[i])
- {
- case 'e':
- connection = dcdbc->session_bus;
- break;
-
- case 'y':
- connection = dcdbc->system_bus;
- break;
-
- default:
- g_assert_not_reached ();
- }
-
- if (connection == NULL && callback != NULL)
- callback (NULL, user_data);
-
- if (connection != NULL)
- {
- DBusPendingCall *pending;
- DBusMessageIter diter;
- DBusMessage *message;
- GVariantIter giter;
- GVariant *child;
-
- message = dbus_message_new_method_call (dcem->bus_name,
- dcem->object_path,
- dcem->interface_name,
- dcem->method_name);
-
- dbus_message_iter_init_append (message, &diter);
- g_variant_iter_init (&giter, dcem->parameters[i]);
-
- while ((child = g_variant_iter_next_value (&giter)))
- {
- dconf_dbus_client_add_value_to_iter (&diter, child);
- g_variant_unref (child);
- }
-
- dbus_connection_send_with_reply (connection, message,
- &pending, 120000);
- dbus_pending_call_set_notify (pending, callback, user_data, NULL);
- dbus_message_unref (message);
- }
- }
-}
-
-static GVariant *
-dconf_dbus_client_send_finish (DBusPendingCall *pending)
-{
- DBusMessage *message;
- GVariant *result;
-
- if (pending == NULL)
- return NULL;
-
- message = dbus_pending_call_steal_reply (pending);
- dbus_pending_call_unref (pending);
-
- /* We only have to deal with two types of replies: () and (s) */
- if (dbus_message_has_signature (message, "s"))
- {
- dbus_message_get_args (message, NULL,
- DBUS_TYPE_STRING, &result,
- DBUS_TYPE_INVALID);
- result = g_variant_new ("(s)", result);
- }
- else
- result = g_variant_new ("()");
-
- dbus_message_unref (message);
-
- return result;
-}
-
-struct _Outstanding
-{
- Outstanding *next;
-
- DConfDBusClient *dcdbc;
- DConfEngineMessage dcem;
-
- gchar *set_key;
- GVariant *set_value;
- GTree *tree;
};
-static void
-dconf_dbus_client_outstanding_returned (DBusPendingCall *pending,
- gpointer user_data)
-{
- Outstanding *outstanding = user_data;
- DConfDBusClient *dcdbc;
- GVariant *reply;
-
- dcdbc = outstanding->dcdbc;
-
- /* One way or another we no longer need this hooked into the list.
- */
- {
- Outstanding **tmp;
-
- for (tmp = &dcdbc->outstanding; tmp; tmp = &(*tmp)->next)
- if (*tmp == outstanding)
- {
- *tmp = outstanding->next;
- break;
- }
- }
-
- reply = dconf_dbus_client_send_finish (pending);
-
- if (reply)
- {
- /* Success.
- *
- * We want to ensure that we don't emit an extra change
- * notification signal when we see the signal that the service is
- * about to send, so store the tag so we know to ignore it when
- * the signal comes.
- *
- * No thread safety issue here since this variable is only
- * accessed from the worker thread.
- */
- g_free (dcdbc->anti_expose_tag);
-
- if (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(s)")))
- g_variant_get_child (reply, 0, "s", dcdbc->anti_expose_tag);
-
- g_variant_unref (reply);
- }
- else
- {
- /* An error of some kind.
- *
- * We already removed the outstanding entry from the list, so the
- * unmodified database is now visible to the client. Change
- * notify so that they see it.
- */
- if (outstanding->set_key)
- /* XXX emit */;
- else
- /* XXX emit */;
- }
-
- dconf_engine_message_destroy (&outstanding->dcem);
- dconf_dbus_client_unref (outstanding->dcdbc);
- g_free (outstanding->set_key);
-
- if (outstanding->set_value)
- g_variant_unref (outstanding->set_value);
-
- if (outstanding->tree)
- g_tree_unref (outstanding->tree);
-
- g_slice_free (Outstanding, outstanding);
-}
-
-static void
-dconf_dbus_client_queue (DConfDBusClient *dcdbc,
- DConfEngineMessage *dcem,
- const gchar *set_key,
- GVariant *set_value,
- GTree *tree)
-{
- Outstanding *outstanding;
-
- outstanding = g_slice_new (Outstanding);
- outstanding->dcdbc = dconf_dbus_client_ref (dcdbc);
- outstanding->dcem = *dcem;
-
- outstanding->set_key = g_strdup (set_key);
- outstanding->set_value = set_value ? g_variant_ref_sink (set_value) : NULL;
- outstanding->tree = tree ? g_tree_ref (tree) : NULL;
-
- outstanding->next = dcdbc->outstanding;
- dcdbc->outstanding = outstanding;
-
- dconf_dbus_client_send (outstanding->dcdbc,
- &outstanding->dcem,
- dconf_dbus_client_outstanding_returned,
- outstanding);
-}
-
-static gboolean
-dconf_dbus_client_scan_outstanding_tree (GTree *tree,
- const gchar *key,
- gsize key_length,
- gpointer *value)
-{
- gchar *mykey;
-
- mykey = g_alloca (key_length + 1);
- memcpy (mykey, key, key_length + 1);
-
- while (!g_tree_lookup_extended (tree, mykey, NULL, value) &&
- --key_length)
- {
- while (mykey[key_length - 1] != '/')
- key_length--;
-
- mykey[key_length] = '\0';
- }
-
- return key_length != 0;
-}
-
-static gboolean
-dconf_dbus_client_scan_outstanding (DConfDBusClient *dcdbc,
- const gchar *key,
- GVariant **value)
-{
- gboolean found = FALSE;
- Outstanding *node;
- gsize length;
-
- length = strlen (key);
-
- if G_LIKELY (dcdbc->outstanding == NULL)
- return FALSE;
-
- for (node = dcdbc->outstanding; node; node = node->next)
- {
- if (node->set_key)
- {
- if (strcmp (key, node->set_key) == 0)
- {
- if (node->set_value != NULL)
- *value = g_variant_ref (node->set_value);
- else
- *value = NULL;
-
- found = TRUE;
- break;
- }
- }
-
- else
- {
- gpointer result;
-
- if (dconf_dbus_client_scan_outstanding_tree (node->tree, key,
- length, &result))
- {
- if (result)
- *value = g_variant_ref (result);
- else
- *value = NULL;
-
- found = TRUE;
- break;
- }
- }
- }
-
- return found;
-}
-
-/* Watches are reference counted because they can be held both by the
- * list of watches and by the pending watch registration. In the normal
- * case, the registration completes before the watch is unsubscribed
- * from but it might be the case that the watch is unsubscribed from
- * before the AddMatch completes. For that reason, either thing could
- * be responsible for freeing the watch structure; we solve that
- * ambiguity using a reference count.
- *
- * We just initially set it to 2, since these are the only two users.
- * That way we can skip having the ref() function.
- */
typedef struct
{
- DConfDBusClient *dcdbc;
- gchar *name;
+ gchar *path;
DConfDBusNotify notify;
gpointer user_data;
- guint64 initial_state;
- gint ref_count;
} Watch;
-
-
-static void
-dconf_dbus_emit_change (DConfDBusClient *dcdbc,
- const gchar *key)
+void
+dconf_engine_change_notify (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *changes,
+ const gchar *tag,
+ gpointer user_data)
{
+ DConfDBusClient *dcdbc = user_data;
+ gchar **my_changes;
+ gint n_changes;
GSList *iter;
+ gint i;
+
+ n_changes = g_strv_length ((gchar **) changes);
+ my_changes = g_new (gchar *, n_changes);
+
+ for (i = 0; i < n_changes; i++)
+ my_changes[i] = g_strconcat (prefix, changes[i], NULL);
+ my_changes[i] = NULL;
for (iter = dcdbc->watches; iter; iter = iter->next)
{
Watch *watch = iter->data;
- if (g_str_has_prefix (key, watch->name))
- watch->notify (dcdbc, key, watch->user_data);
+ for (i = 0; i < n_changes; i++)
+ if (g_str_has_prefix (my_changes[i], watch->path))
+ watch->notify (dcdbc, my_changes[i], watch->user_data);
}
+
+ g_strfreev (my_changes);
}
GVariant *
dconf_dbus_client_read (DConfDBusClient *dcdbc,
const gchar *key)
{
- GVariant *value;
-
- if (dconf_dbus_client_scan_outstanding (dcdbc, key, &value))
- return value;
-
- return dconf_engine_read (dcdbc->engine, key);
+ return dconf_engine_read (dcdbc->engine, NULL, key);
}
gboolean
@@ -485,102 +82,32 @@ dconf_dbus_client_write (DConfDBusClient *dcdbc,
const gchar *key,
GVariant *value)
{
- DConfEngineMessage dcem;
+ DConfChangeset *changeset;
+ gboolean success;
- if (!dconf_engine_write (dcdbc->engine, key, value, &dcem, NULL))
- return FALSE;
+ changeset = dconf_changeset_new_write (key, value);
+ success = dconf_engine_change_fast (dcdbc->engine, changeset, NULL);
+ dconf_changeset_unref (changeset);
- dconf_dbus_client_queue (dcdbc, &dcem, key, value, NULL);
- dconf_dbus_emit_change (dcdbc, key);
-
- return TRUE;
+ return success;
}
-static Watch *
-watch_new (DConfDBusClient *dcdbc,
- const gchar *name,
- DConfDBusNotify notify,
- gpointer user_data)
+void
+dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
+ const gchar *path,
+ DConfDBusNotify notify,
+ gpointer user_data)
{
Watch *watch;
watch = g_slice_new (Watch);
- watch->dcdbc = dconf_dbus_client_ref (dcdbc);
- watch->user_data = user_data;
- watch->name = g_strdup (name);
+ watch->path = g_strdup (path);
watch->notify = notify;
-
- watch->initial_state = dconf_engine_get_state (dcdbc->engine);
- watch->ref_count = 2;
+ watch->user_data = user_data;
dcdbc->watches = g_slist_prepend (dcdbc->watches, watch);
- return watch;
-}
-
-static void
-watch_unref (Watch *watch)
-{
- if (--watch->ref_count == 0)
- {
- dconf_dbus_client_unref (watch->dcdbc);
- g_free (watch->name);
- g_slice_free (Watch, watch);
- }
-}
-
-static void
-add_match_done (DBusPendingCall *pending,
- gpointer user_data)
-{
- Watch *watch = user_data;
- GVariant *reply;
-
- reply = dconf_dbus_client_send_finish (pending);
-
- if (reply == NULL)
- {
- /* This is extremely unlikely to happen and it happens
- * asynchronous to the user's call. Since the user doesn't know
- * that it happened, we pretend that it didn't (ie: we leave the
- * watch structure in the list).
- */
-
- g_critical ("DBus AddMatch for dconf path '%s'", watch->name);
- watch_unref (watch);
-
- return;
- }
-
- else
- g_variant_unref (reply); /* it is just an empty tuple */
-
- /* In the normal case we're done.
- *
- * There is a fleeting chance, however, that the database has changed
- * in the meantime. In that case we can only assume that the subject
- * of this watch changed in that time period and emit a signal to that
- * effect.
- */
- if (dconf_engine_get_state (watch->dcdbc->engine) != watch->initial_state)
- watch->notify (watch->dcdbc, watch->name, watch->user_data);
-
- watch_unref (watch);
-}
-
-void
-dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
- const gchar *name,
- DConfDBusNotify notify,
- gpointer user_data)
-{
- DConfEngineMessage dcem;
- Watch *watch;
-
- watch = watch_new (dcdbc, name, notify, user_data);
- dconf_engine_watch (dcdbc->engine, name, &dcem);
- dconf_dbus_client_send (dcdbc, &dcem, add_match_done, watch);
- dconf_engine_message_destroy (&dcem);
+ dconf_engine_watch_fast (dcdbc->engine, path);
}
void
@@ -588,7 +115,6 @@ dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
DConfDBusNotify notify,
gpointer user_data)
{
- DConfEngineMessage dcem;
GSList **ptr;
for (ptr = &dcdbc->watches; *ptr; ptr = &(*ptr)->next)
@@ -598,12 +124,9 @@ dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
if (watch->notify == notify && watch->user_data == user_data)
{
*ptr = g_slist_remove_link (*ptr, *ptr);
-
- dconf_engine_unwatch (dcdbc->engine, watch->name, &dcem);
- dconf_dbus_client_send (dcdbc, &dcem, NULL, NULL);
- dconf_engine_message_destroy (&dcem);
- watch_unref (watch);
-
+ dconf_engine_unwatch_fast (dcdbc->engine, watch->path);
+ g_free (watch->path);
+ g_slice_free (Watch, watch);
return;
}
}
@@ -614,54 +137,7 @@ dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
gboolean
dconf_dbus_client_has_pending (DConfDBusClient *dcdbc)
{
- return dcdbc->outstanding != NULL;
-}
-
-static DBusHandlerResult
-dconf_dbus_client_filter (DBusConnection *connection,
- DBusMessage *message,
- void *user_data)
-{
- DConfDBusClient *dcdbc = user_data;
-
- if (dbus_message_is_signal (message, "ca.desrt.dconf.Writer", "Notify") &&
- dbus_message_has_signature (message, "sass"))
- {
- DBusMessageIter iter, sub;
- const gchar *path, *tag;
-
- dbus_message_iter_init (message, &iter);
- dbus_message_iter_get_basic (&iter, &path);
- dbus_message_iter_next (&iter);
- dbus_message_iter_recurse (&iter, &sub);
- dbus_message_iter_next (&iter);
- dbus_message_iter_get_basic (&iter, &tag);
- dbus_message_iter_next (&iter);
-
- /* Only emit the event if it hasn't been anti-exposed */
- if (dcdbc->anti_expose_tag == NULL ||
- strcmp (tag, dcdbc->anti_expose_tag) != 0)
- {
- /* Empty list means that only one key changed */
- if (!dbus_message_iter_get_arg_type (&sub))
- dconf_dbus_emit_change (dcdbc, path);
-
- while (dbus_message_iter_get_arg_type (&sub) == 's')
- {
- const gchar *item;
- gchar *full;
-
- dbus_message_iter_get_basic (&sub, &item);
- full = g_strconcat (path, item, NULL);
- dconf_dbus_emit_change (dcdbc, full);
- g_free (full);
-
- dbus_message_iter_next (&sub);
- }
- }
- }
-
- return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ return dconf_engine_has_outstanding (dcdbc->engine);
}
DConfDBusClient *
@@ -677,18 +153,14 @@ dconf_dbus_client_new (const gchar *profile,
if (system == NULL)
system = dbus_bus_get (DBUS_BUS_SYSTEM, NULL);
+ dconf_libdbus_1_provide_bus (G_BUS_TYPE_SESSION, session);
+ dconf_libdbus_1_provide_bus (G_BUS_TYPE_SYSTEM, system);
+
dcdbc = g_slice_new (DConfDBusClient);
- dcdbc->engine = dconf_engine_new (profile);
- dcdbc->system_bus = dbus_connection_ref (system);
- dcdbc->session_bus = dbus_connection_ref (session);
- dcdbc->anti_expose_tag = NULL;
- dcdbc->outstanding = NULL;
+ dcdbc->engine = dconf_engine_new (dcdbc, NULL);
dcdbc->watches = NULL;
dcdbc->ref_count = 1;
- dbus_connection_add_filter (system, dconf_dbus_client_filter, dcdbc, NULL);
- dbus_connection_add_filter (session, dconf_dbus_client_filter, dcdbc, NULL);
-
return dcdbc;
}
@@ -697,12 +169,7 @@ dconf_dbus_client_unref (DConfDBusClient *dcdbc)
{
if (--dcdbc->ref_count == 0)
{
- dbus_connection_remove_filter (dcdbc->session_bus,
- dconf_dbus_client_filter, dcdbc);
- dbus_connection_remove_filter (dcdbc->system_bus,
- dconf_dbus_client_filter, dcdbc);
- dbus_connection_unref (dcdbc->session_bus);
- dbus_connection_unref (dcdbc->system_bus);
+ g_return_if_fail (dcdbc->watches == NULL);
g_slice_free (DConfDBusClient, dcdbc);
}
diff --git a/dbus-1/dconf-libdbus-1.c b/dbus-1/dconf-libdbus-1.c
new file mode 100644
index 0000000..aedc2cf
--- /dev/null
+++ b/dbus-1/dconf-libdbus-1.c
@@ -0,0 +1,352 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ **/
+
+#include "dconf-libdbus-1.h"
+
+#include "../engine/dconf-engine.h"
+
+#include <string.h>
+
+static DBusConnection *dconf_libdbus_1_buses[5];
+
+struct _DConfDBusClient
+{
+ DConfEngine *engine;
+ GSList *watches;
+ gint ref_count;
+};
+
+#define DCONF_LIBDBUS_1_ERROR (g_quark_from_static_string("DCONF_LIBDBUS_1_ERROR"))
+#define DCONF_LIBDBUS_1_ERROR_FAILED 0
+
+static DBusMessage *
+dconf_libdbus_1_new_method_call (const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters)
+{
+ DBusMessageIter dbus_iter;
+ DBusMessage *message;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_ref_sink (parameters);
+
+ message = dbus_message_new_method_call (bus_name, object_path, interface_name, method_name);
+ dbus_message_iter_init_append (message, &dbus_iter);
+ g_variant_iter_init (&iter, parameters);
+
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ if (g_variant_is_of_type (child, G_VARIANT_TYPE_STRING))
+ {
+ const gchar *str;
+
+ str = g_variant_get_string (child, NULL);
+ dbus_message_iter_append_basic (&dbus_iter, DBUS_TYPE_STRING, &str);
+ }
+
+ else
+ {
+ const guint8 *bytes;
+ gsize n_elements;
+
+ g_assert (g_variant_is_of_type (child, G_VARIANT_TYPE_BYTESTRING));
+
+ bytes = g_variant_get_fixed_array (child, &n_elements, sizeof (guint8));
+ dbus_message_iter_append_fixed_array (&dbus_iter, DBUS_TYPE_BYTE, &bytes, n_elements);
+ }
+
+ g_variant_unref (child);
+ }
+
+ g_variant_unref (parameters);
+
+ return message;
+}
+
+static GVariant *
+dconf_libdbus_1_get_message_body (DBusMessage *message,
+ GError **error)
+{
+ GVariantBuilder builder;
+ const gchar *signature;
+ DBusMessageIter iter;
+
+ /* We support two types: strings and arrays of strings.
+ *
+ * It's very simple to detect if the message contains only these
+ * types: check that the signature contains only the letters "a" and
+ * "s" and that it does not contain "aa".
+ */
+ signature = dbus_message_get_signature (message);
+ if (signature[strspn(signature, "as")] != '\0' || strstr (signature, "aa"))
+ {
+ g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED,
+ "unable to handle message type '(%s)'", signature);
+ return NULL;
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+ dbus_message_iter_init (message, &iter);
+ while (dbus_message_iter_get_arg_type (&iter))
+ {
+ const gchar *string;
+
+ if (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING)
+ {
+ dbus_message_iter_get_basic (&iter, &string);
+ g_variant_builder_add (&builder, "s", string);
+ }
+ else
+ {
+ DBusMessageIter sub;
+
+ g_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY &&
+ dbus_message_iter_get_element_type (&iter) == DBUS_TYPE_STRING);
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+ dbus_message_iter_recurse (&iter, &sub);
+
+ while (dbus_message_iter_get_arg_type (&sub))
+ {
+ const gchar *string;
+ dbus_message_iter_get_basic (&sub, &string);
+ g_variant_builder_add (&builder, "s", string);
+ dbus_message_iter_next (&sub);
+ }
+
+ g_variant_builder_close (&builder);
+ }
+ dbus_message_iter_next (&iter);
+ }
+
+ return g_variant_ref_sink (g_variant_builder_end (&builder));
+}
+
+static GVariant *
+dconf_libdbus_1_interpret_result (DBusMessage *result,
+ const GVariantType *expected_type,
+ GError **error)
+{
+ GVariant *reply;
+
+ if (dbus_message_get_type (result) == DBUS_MESSAGE_TYPE_ERROR)
+ {
+ const gchar *errstr = "(no message)";
+
+ dbus_message_get_args (result, NULL, DBUS_TYPE_STRING, &errstr, DBUS_TYPE_INVALID);
+ g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED,
+ "%s: %s", dbus_message_get_error_name (result), errstr);
+ return NULL;
+ }
+
+ reply = dconf_libdbus_1_get_message_body (result, error);
+
+ if (reply && expected_type && !g_variant_is_of_type (reply, expected_type))
+ {
+ gchar *expected_string;
+
+ expected_string = g_variant_type_dup_string (expected_type);
+ g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED,
+ "received reply '%s' is not of the expected type %s",
+ g_variant_get_type_string (reply), expected_string);
+ g_free (expected_string);
+
+ g_variant_unref (reply);
+ reply = NULL;
+ }
+
+ return reply;
+}
+
+static void
+dconf_libdbus_1_method_call_done (DBusPendingCall *pending,
+ gpointer user_data)
+{
+ DConfEngineCallHandle *handle = user_data;
+ const GVariantType *expected_type;
+ DBusMessage *message;
+ GError *error = NULL;
+ GVariant *reply;
+
+ if (pending == NULL)
+ return;
+
+ message = dbus_pending_call_steal_reply (pending);
+ dbus_pending_call_unref (pending);
+
+ expected_type = dconf_engine_call_handle_get_expected_type (handle);
+ reply = dconf_libdbus_1_interpret_result (message, expected_type, &error);
+ dbus_message_unref (message);
+
+ dconf_engine_call_handle_reply (handle, reply, error);
+
+ if (reply)
+ g_variant_unref (reply);
+ if (error)
+ g_error_free (error);
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error)
+{
+ DBusConnection *connection;
+ DBusPendingCall *pending;
+ DBusMessage *message;
+
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_libdbus_1_buses));
+ connection = dconf_libdbus_1_buses[bus_type];
+ g_assert (connection != NULL);
+
+ message = dconf_libdbus_1_new_method_call (bus_name, object_path, interface_name, method_name, parameters);
+ dbus_connection_send_with_reply (connection, message, &pending, -1);
+ dbus_pending_call_set_notify (pending, dconf_libdbus_1_method_call_done, handle, NULL);
+ dbus_message_unref (message);
+
+ return TRUE;
+}
+
+static void
+dconf_libdbus_1_convert_error (DBusError *dbus_error,
+ GError **error)
+{
+ g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED,
+ "%s: %s", dbus_error->name, dbus_error->message);
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *expected_type,
+ GError **error)
+{
+ DBusConnection *connection;
+ DBusMessage *message;
+ DBusError dbus_error;
+ DBusMessage *result;
+ GVariant *reply;
+
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_libdbus_1_buses));
+ connection = dconf_libdbus_1_buses[bus_type];
+ g_assert (connection != NULL);
+
+ dbus_error_init (&dbus_error);
+ message = dconf_libdbus_1_new_method_call (bus_name, object_path, interface_name, method_name, parameters);
+ result = dbus_connection_send_with_reply_and_block (connection, message, -1, &dbus_error);
+ dbus_message_unref (message);
+
+ if (result == NULL)
+ {
+ dconf_libdbus_1_convert_error (&dbus_error, error);
+ dbus_error_free (&dbus_error);
+ return NULL;
+ }
+
+ reply = dconf_libdbus_1_interpret_result (result, expected_type, error);
+ dbus_message_unref (result);
+
+ return reply;
+}
+
+static DBusHandlerResult
+dconf_libdbus_1_filter (DBusConnection *connection,
+ DBusMessage *message,
+ gpointer user_data)
+{
+ GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+ if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_SIGNAL)
+ {
+ const gchar *interface;
+
+ interface = dbus_message_get_interface (message);
+
+ if (interface && g_str_equal (interface, "ca.desrt.dconf.Writer"))
+ {
+ GVariant *parameters;
+
+ parameters = dconf_libdbus_1_get_message_body (message, NULL);
+
+ if (parameters != NULL)
+ {
+ dconf_engine_handle_dbus_signal (bus_type,
+ dbus_message_get_sender (message),
+ dbus_message_get_path (message),
+ dbus_message_get_member (message),
+ parameters);
+ g_variant_unref (parameters);
+ }
+ }
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+void
+dconf_libdbus_1_provide_bus (GBusType bus_type,
+ DBusConnection *connection)
+{
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_libdbus_1_buses));
+
+ if (!dconf_libdbus_1_buses[bus_type])
+ {
+ dconf_libdbus_1_buses[bus_type] = dbus_connection_ref (connection);
+ dbus_connection_add_filter (connection, dconf_libdbus_1_filter, GINT_TO_POINTER (bus_type), NULL);
+ }
+}
+
+#ifndef PIC
+static gboolean
+dconf_libdbus_1_check_connection (gpointer user_data)
+{
+ DBusConnection *connection = user_data;
+
+ dbus_connection_read_write (connection, 0);
+ dbus_connection_dispatch (connection);
+
+ return G_SOURCE_CONTINUE;
+}
+
+void
+dconf_engine_dbus_init_for_testing (void)
+{
+ DBusConnection *session;
+ DBusConnection *system;
+
+ dconf_libdbus_1_provide_bus (G_BUS_TYPE_SESSION, session = dbus_bus_get (DBUS_BUS_SESSION, NULL));
+ dconf_libdbus_1_provide_bus (G_BUS_TYPE_SYSTEM, system = dbus_bus_get (DBUS_BUS_SYSTEM, NULL));
+
+ /* "mainloop integration" */
+ g_timeout_add (1, dconf_libdbus_1_check_connection, session);
+ g_timeout_add (1, dconf_libdbus_1_check_connection, system);
+}
+#endif
diff --git a/dbus-1/dconf-libdbus-1.h b/dbus-1/dconf-libdbus-1.h
new file mode 100644
index 0000000..201096a
--- /dev/null
+++ b/dbus-1/dconf-libdbus-1.h
@@ -0,0 +1,11 @@
+#ifndef __dconf_libdbus_1_h__
+#define __dconf_libdbus_1_h__
+
+#include <dbus/dbus.h>
+#include <gio/gio.h>
+
+G_GNUC_INTERNAL
+void dconf_libdbus_1_provide_bus (GBusType bus_type,
+ DBusConnection *connection);
+
+#endif /* __dconf_libdbus_1_h__ */
diff --git a/docs/Makefile.am b/docs/Makefile.am
index 4e11f65..da66f27 100644
--- a/docs/Makefile.am
+++ b/docs/Makefile.am
@@ -1,24 +1,16 @@
+include $(top_srcdir)/Makefile.gtester
+
include gtk-doc.make
DOC_MODULE = dconf
DOC_MAIN_SGML_FILE = dconf-docs.xml
-DOC_SOURCE_DIR = ../client ../common
+DOC_SOURCE_DIR = $(top_srcdir)/client $(top_srcdir)/common
+HFILE_GLOB = $(top_srcdir)/client/*.h $(top_srcdir)/common/*.h
+CFILE_GLOB = $(top_srcdir)/client/*.c $(top_srcdir)/common/*.c
MKDB_OPTIONS = --output-format=xml
INCLUDES = $(gio_CFLAGS)
GTKDOC_LIBS = $(gio_LIBS) -L../client -ldconf -Wl,-rpath=../client
-
-IGNORE_HFILES = \
- dconf-engine.h \
- dconf-shmdir.h \
- dconf-resetlist.h \
- dconf-readtype.h \
- dconf-rebuilder.h \
- gvdb-builder.h \
- gvdb-reader.h \
- gvdb-format.h
-
-
diff --git a/docs/dconf-docs.xml b/docs/dconf-docs.xml
index 31df0b3..0def819 100644
--- a/docs/dconf-docs.xml
+++ b/docs/dconf-docs.xml
@@ -12,18 +12,21 @@
<chapter>
<title>DConf Client API</title>
<xi:include href='xml/paths.xml'/>
+ <xi:include href='xml/changeset.xml'/>
<xi:include href='xml/client.xml'/>
</chapter>
<chapter id='object-tree'>
<title>Object Hierarchy</title>
<xi:include href='xml/tree_index.sgml'/>
+ <xi:include href='xml/object_index.sgml'/>
</chapter>
<index id='api-index-full'>
<title>API Index</title>
<xi:include href='xml/api-index-full.xml'><xi:fallback /></xi:include>
+ <xi:include href='xml/api-index-deprecated.xml'><xi:fallback /></xi:include>
</index>
- <xi:include href='xml/annotation-glossary.xml'><xi:fallback /></xi:include>
+ <xi:include href='xml/annotation-glossary.xml'/>
</book>
diff --git a/docs/dconf-sections.txt b/docs/dconf-sections.txt
index 7867ddf..4f924b7 100644
--- a/docs/dconf-sections.txt
+++ b/docs/dconf-sections.txt
@@ -1,23 +1,19 @@
<SECTION>
<FILE>client</FILE>
DConfClient
-DConfWatchFunc
dconf_client_new
dconf_client_read
-dconf_client_read_default
-dconf_client_read_no_default
dconf_client_list
dconf_client_is_writable
-dconf_client_write
-dconf_client_write_async
-dconf_client_write_finish
-dconf_client_write_many
-dconf_client_watch
-dconf_client_watch_async
-dconf_client_watch_finish
-dconf_client_unwatch
-dconf_client_unwatch_async
-dconf_client_unwatch_finish
+dconf_client_write_fast
+dconf_client_write_sync
+dconf_client_change_fast
+dconf_client_change_sync
+dconf_client_watch_fast
+dconf_client_watch_sync
+dconf_client_unwatch_fast
+dconf_client_unwatch_sync
+dconf_client_sync
<SUBSECTION Standard>
DConfClientClass
DCONF_CLIENT
@@ -31,7 +27,24 @@ dconf_client_get_type
dconf_is_dir
dconf_is_key
dconf_is_path
-dconf_is_rel
+dconf_is_rel_path
dconf_is_rel_dir
dconf_is_rel_key
</SECTION>
+
+<SECTION>
+<FILE>changeset</FILE>
+DConfChangeset
+DConfChangesetPredicate
+dconf_changeset_all
+dconf_changeset_describe
+dconf_changeset_deserialise
+dconf_changeset_get
+dconf_changeset_is_similar_to
+dconf_changeset_new
+dconf_changeset_new_write
+dconf_changeset_ref
+dconf_changeset_serialise
+dconf_changeset_set
+dconf_changeset_unref
+</SECTION>
diff --git a/editor/Makefile.am b/editor/Makefile.am
index a030c7d..3fa76f6 100644
--- a/editor/Makefile.am
+++ b/editor/Makefile.am
@@ -1,10 +1,33 @@
+include $(top_srcdir)/Makefile.gtester
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/client
+
bin_PROGRAMS = dconf-editor
-AM_CFLAGS = $(gtk_CFLAGS) $(libxml_CFLAGS) -I$(top_srcdir)/common -I$(top_srcdir)/client -DPKGDATADIR=\"@datadir@/dconf-editor\" -DVERSION=\"$(VERSION)\" -DGETTEXT_PACKAGE=\"dconf-editor\"
-AM_VALAFLAGS = --vapidir ../client --pkg gtk+-3.0 --pkg libxml-2.0 --pkg dconf
-CFLAGS += -Wno-error -Wno-unused-but-set-variable -Wno-unused-variable
-dconf_editor_LDADD = ../client/libdconf.so.0 $(gtk_LIBS) $(gee_LIBS) $(libxml_LIBS)
-dconf_editor_SOURCES = config.vapi dconf-editor.vala dconf-model.vala dconf-schema.vala dconf-view.vala
+dconf_editor_VALAFLAGS = --vapidir ../client --pkg gtk+-3.0 --pkg gmodule-2.0 --pkg libxml-2.0 --pkg dconf
+
+dconf_editor_LDADD = \
+ ../client/libdconf.so.1 \
+ $(gtk_LIBS) \
+ $(gee_LIBS) \
+ $(gmodule_LIBS) \
+ $(libxml_LIBS)
+
+dconf_editor_CFLAGS = \
+ $(gtk_CFLAGS) \
+ $(gee_CFLAGS) \
+ $(libxml_CFLAGS) \
+ -DPKGDATADIR=\"$(pkgdatadir)\" \
+ -DVERSION=\"$(VERSION)\" \
+ -DGETTEXT_PACKAGE=\"dconf-editor\" \
+ -w
+
+dconf_editor_SOURCES = \
+ config.vapi \
+ dconf-editor.vala \
+ dconf-model.vala \
+ dconf-schema.vala \
+ dconf-view.vala
desktopdir = $(datadir)/applications
desktop_in_files = dconf-editor.desktop.in.in
@@ -28,7 +51,7 @@ update-icon-cache:
gtk-update-icon-cache -f -t $(datadir)/icons/hicolor; \
fi
-uidir = $(datadir)/dconf-editor
-dist_ui_DATA = dconf-editor.ui dconf-editor-menu.ui
+pkgdatadir = $(datadir)/dconf-editor
+dist_pkgdata_DATA = dconf-editor.ui dconf-editor-menu.ui
EXTRA_DIST = $(gsettings_SCHEMAS) $(icons)
diff --git a/editor/dconf-model.vala b/editor/dconf-model.vala
index 46afa3e..ff45a29 100644
--- a/editor/dconf-model.vala
+++ b/editor/dconf-model.vala
@@ -52,7 +52,7 @@ public class Key : GLib.Object
_value = value;
try
{
- model.client.write(full_name, value);
+ model.client.write_sync(full_name, value);
}
catch (GLib.Error e)
{
@@ -143,7 +143,7 @@ public class Key : GLib.Object
_value = null;
try
{
- model.client.write(full_name, null);
+ model.client.write_sync(full_name, null);
}
catch (GLib.Error e)
{
@@ -570,28 +570,18 @@ public class SettingsModel: GLib.Object, Gtk.TreeModel
public signal void item_changed (string key);
- void watch_func (DConf.Client client, string path, string[] items, string tag) {
- if (items.length == 0) {
- item_changed (path);
- } else {
- foreach (var item in items) {
- item_changed (path + item);
- }
+ void watch_func (DConf.Client client, string path, string[] items, string? tag) {
+ foreach (var item in items) {
+ item_changed (path + item);
}
}
public SettingsModel()
{
- client = new DConf.Client (null, watch_func);
+ client = new DConf.Client ();
+ client.changed.connect (watch_func);
root = new Directory(this, null, "/", "/");
- try
- {
- client.watch ("/");
- }
- catch (Error e)
- {
- warning ("Failed to watch all keys: %s", e.message);
- }
+ client.watch_sync ("/");
schemas = new SchemaList();
try
diff --git a/engine/.gitignore b/engine/.gitignore
new file mode 100644
index 0000000..013c7e7
--- /dev/null
+++ b/engine/.gitignore
@@ -0,0 +1,2 @@
+libdconf-engine.a
+libdconf-engine-shared.a
diff --git a/engine/Makefile.am b/engine/Makefile.am
index 5228482..8f0f8f2 100644
--- a/engine/Makefile.am
+++ b/engine/Makefile.am
@@ -1,5 +1,19 @@
-dconfinclude_HEADERS = \
- dconf-engine.h
+include $(top_srcdir)/Makefile.gtester
-EXTRA_DIST = \
+noinst_LIBRARIES = libdconf-engine.a libdconf-engine-shared.a
+
+libdconf_engine_a_CFLAGS = $(glib_CFLAGS)
+libdconf_engine_a_SOURCES = \
+ dconf-changeset-list.h \
+ dconf-engine-profile.h \
+ dconf-engine-profile.c \
+ dconf-engine-source-private.h \
+ dconf-engine-source.h \
+ dconf-engine-source-user.c \
+ dconf-engine-source-system.c \
+ dconf-engine-source.c \
+ dconf-engine.h \
dconf-engine.c
+
+libdconf_engine_shared_a_CFLAGS = $(libdconf_engine_a_CFLAGS) -fPIC -DPIC
+libdconf_engine_shared_a_SOURCES = $(libdconf_engine_a_SOURCES)
diff --git a/engine/dconf-changeset-list.h b/engine/dconf-changeset-list.h
new file mode 100644
index 0000000..68963b0
--- /dev/null
+++ b/engine/dconf-changeset-list.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_changeset_list_h__
+#define __dconf_changeset_list_h__
+
+#include "../common/dconf-changeset.h"
+
+typedef struct _DConfChangesetList DConfChangesetList;
+
+struct _DConfChangesetList
+{
+ GQueue queue;
+};
+
+DConfChangeset * dconf_changeset_list_new (void);
+
+void dconf_changeset_list_free (DConfChangesetList *changeset_list);
+
+#endif /* __dconf_changeset_list_h__ */
diff --git a/engine/dconf-engine-profile.c b/engine/dconf-engine-profile.c
new file mode 100644
index 0000000..0413f32
--- /dev/null
+++ b/engine/dconf-engine-profile.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-engine-profile.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include "dconf-engine-source.h"
+
+/* This comment attempts to document the exact semantics of
+ * profile-loading.
+ *
+ * In no situation should the result of profile loading be an abort.
+ * There must be a defined outcome for all possible situations.
+ * Warnings may be issued to stderr, however.
+ *
+ * The first step is to determine what profile is to be used. If a
+ * profile is explicitly specified by the API then it has the top
+ * priority. Otherwise, if the DCONF_PROFILE environment variable is
+ * set, it takes next priority.
+ *
+ * In both of those cases, if the named profile starts with a slash
+ * character then it is taken to be an absolute pathname. If it does
+ * not start with a slash then it is assumed to specify a profile file
+ * relative to /etc/dconf/profile/ (ie: DCONF_PROFILE=test for profile
+ * file /etc/dconf/profile/test).
+ *
+ * If opening the profile file fails then the null profile is used.
+ * This is a profile that contains zero sources. All keys will be
+ * unwritable and all reads will return NULL.
+ *
+ * In the case that no explicit profile was given and DCONF_PROFILE is
+ * unset, dconf attempts to open and use a profile called "user" (ie:
+ * profile file /etc/dconf/profile/user). If that fails then the
+ * fallback is to act as if the profile file existed and contained a
+ * single line: "user-db:user".
+ *
+ * Note that the fallback case for a missing profile file is different
+ * in the case where a profile was explicitly specified (either by the
+ * API or the environment) and the case where one was not.
+ *
+ * Once a profile file is opened, each line is treated as a possible
+ * source. Comments and empty lines are ignored.
+ *
+ * All valid source specification lines need to start with 'user-db:' or
+ * 'system-db:'. If a line doesn't start with one of these then it gets
+ * ignored. If all the lines in the file get ignored then the result is
+ * effectively the null profile.
+ *
+ * If the first source is a "user-db:" then the resulting profile will
+ * be writable. No profile starting with a "system-db:" source can ever
+ * be writable.
+ *
+ * Note: even if the source fails to initialise (due to a missing file,
+ * for example) it will remain in the source list. This could have a
+ * performance cost: in the case of a system-db, for example, dconf will
+ * check if the file has come into existence on every read.
+ */
+
+static DConfEngineSource **
+dconf_engine_null_profile (gint *n_sources)
+{
+ *n_sources = 0;
+
+ return NULL;
+}
+
+static DConfEngineSource **
+dconf_engine_default_profile (gint *n_sources)
+{
+ DConfEngineSource **sources;
+
+ sources = g_new (DConfEngineSource *, 1);
+ sources[0] = dconf_engine_source_new_default ();
+ *n_sources = 1;
+
+ return sources;
+}
+
+static DConfEngineSource *
+dconf_engine_profile_handle_line (gchar *line)
+{
+ gchar *end;
+
+ /* remove whitespace at the front */
+ while (g_ascii_isspace (*line))
+ line++;
+
+ /* find the end of the line (or start of comments) */
+ end = line + strcspn (line, "#\n");
+
+ /* remove whitespace at the end */
+ while (end > line && g_ascii_isspace (end[-1]))
+ end--;
+
+ /* if we're left with nothing, return NULL */
+ if (line == end)
+ return NULL;
+
+ *end = '\0';
+
+ return dconf_engine_source_new (line);
+}
+
+static DConfEngineSource **
+dconf_engine_read_profile_file (FILE *file,
+ gint *n_sources)
+{
+ DConfEngineSource **sources;
+ gchar line[80];
+ gint n = 0, a;
+
+ sources = g_new (DConfEngineSource *, (a = 4));
+
+ while (fgets (line, sizeof line, file))
+ {
+ DConfEngineSource *source;
+
+ /* The input file has long lines. */
+ if G_UNLIKELY (!strchr (line, '\n'))
+ {
+ GString *long_line;
+
+ long_line = g_string_new (line);
+ while (fgets (line, sizeof line, file))
+ {
+ g_string_append (long_line, line);
+ if (strchr (line, '\n'))
+ break;
+ }
+
+ source = dconf_engine_profile_handle_line (long_line->str);
+ g_string_free (long_line, TRUE);
+ }
+
+ else
+ source = dconf_engine_profile_handle_line (line);
+
+ if (source != NULL)
+ {
+ if (n == a)
+ sources = g_renew (DConfEngineSource *, sources, a *= 2);
+
+ sources[n++] = source;
+ }
+ }
+
+ *n_sources = n;
+
+ return g_realloc_n (sources, n, sizeof (DConfEngineSource *));
+}
+
+DConfEngineSource **
+dconf_engine_profile_open (const gchar *profile,
+ gint *n_sources)
+{
+ DConfEngineSource **sources;
+ FILE *file;
+
+ if (profile == NULL)
+ profile = g_getenv ("DCONF_PROFILE");
+
+ if (profile == NULL)
+ {
+ file = fopen ("/etc/dconf/profile/user", "r");
+
+ /* Only in the case that no profile was specified do we use this
+ * fallback.
+ */
+ if (file == NULL)
+ return dconf_engine_default_profile (n_sources);
+ }
+ else if (profile[0] != '/')
+ {
+ gchar *filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
+ file = fopen (filename, "r");
+ g_free (filename);
+ }
+ else
+ file = fopen (profile, "r");
+
+ if (file != NULL)
+ {
+ sources = dconf_engine_read_profile_file (file, n_sources);
+ fclose (file);
+ }
+ else
+ {
+ g_warning ("unable to open named profile (%s): using the null configuration.", profile);
+ sources = dconf_engine_null_profile (n_sources);
+ }
+
+ return sources;
+}
diff --git a/common/dconf-shmdir.h b/engine/dconf-engine-profile.h
index 3b08de6..fb04e15 100644
--- a/common/dconf-shmdir.h
+++ b/engine/dconf-engine-profile.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,12 +20,13 @@
* Author: Ryan Lortie <desrt@desrt.ca>
*/
-#ifndef __dconf_shmdir_h__
-#define __dconf_shmdir_h__
+#ifndef __dconf_engine_profile_h__
+#define __dconf_engine_profile_h__
-#include <glib.h>
+#include "dconf-engine-source.h"
G_GNUC_INTERNAL
-gchar *dconf_shmdir_from_environment (void);
+DConfEngineSource ** dconf_engine_profile_open (const gchar *profile,
+ gint *n_sources);
-#endif /* __dconf_shmdir_h__ */
+#endif
diff --git a/common/dconf-shmdir.c b/engine/dconf-engine-source-private.h
index 839a487..822354a 100644
--- a/common/dconf-shmdir.c
+++ b/engine/dconf-engine-source-private.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,17 +20,12 @@
* Author: Ryan Lortie <desrt@desrt.ca>
*/
-#include "dconf-shmdir.h"
+#ifndef __dconf_engine_source_private_h__
+#define __dconf_engine_source_private_h__
-gchar *
-dconf_shmdir_from_environment (void)
-{
- gchar *result;
+#include "dconf-engine-source.h"
- result = g_build_filename (g_get_user_runtime_dir (), "dconf", NULL);
+G_GNUC_INTERNAL extern const DConfEngineSourceVTable dconf_engine_source_user_vtable;
+G_GNUC_INTERNAL extern const DConfEngineSourceVTable dconf_engine_source_system_vtable;
- if (g_mkdir_with_parents (result, 0700) != 0)
- g_critical ("unable to create '%s'; dconf will not work properly.", result);
-
- return result;
-}
+#endif /* __dconf_engine_source_private_h__ */
diff --git a/engine/dconf-engine-source-system.c b/engine/dconf-engine-source-system.c
new file mode 100644
index 0000000..727bad9
--- /dev/null
+++ b/engine/dconf-engine-source-system.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-engine-source-private.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static void
+dconf_engine_source_system_init (DConfEngineSource *source)
+{
+ source->bus_type = G_BUS_TYPE_SYSTEM;
+ source->bus_name = g_strdup ("ca.desrt.dconf");
+ source->object_path = g_strdup_printf ("/ca/desrt/dconf/Writer/%s", source->name);
+}
+
+static gboolean
+dconf_engine_source_system_needs_reopen (DConfEngineSource *source)
+{
+ return !source->values || !gvdb_table_is_valid (source->values);
+}
+
+static GvdbTable *
+dconf_engine_source_system_reopen (DConfEngineSource *source)
+{
+ static gboolean did_warn;
+ GError *error = NULL;
+ GvdbTable *table;
+ gchar *filename;
+
+ filename = g_build_filename ("/etc/dconf/db", source->name, NULL);
+ table = gvdb_table_new (filename, FALSE, &error);
+
+ if (table == NULL)
+ {
+ if (!did_warn)
+ {
+ g_critical ("unable to open file '%s': %s; expect degraded performance", filename, error->message);
+ did_warn = TRUE;
+ }
+
+ g_error_free (error);
+ }
+
+ g_free (filename);
+
+ return table;
+}
+
+static void
+dconf_engine_source_system_finalize (DConfEngineSource *source)
+{
+}
+
+G_GNUC_INTERNAL
+const DConfEngineSourceVTable dconf_engine_source_system_vtable = {
+ .instance_size = sizeof (DConfEngineSource),
+ .init = dconf_engine_source_system_init,
+ .finalize = dconf_engine_source_system_finalize,
+ .needs_reopen = dconf_engine_source_system_needs_reopen,
+ .reopen = dconf_engine_source_system_reopen
+};
diff --git a/engine/dconf-engine-source-user.c b/engine/dconf-engine-source-user.c
new file mode 100644
index 0000000..982bbdf
--- /dev/null
+++ b/engine/dconf-engine-source-user.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-engine-source-private.h"
+
+#include "../shm/dconf-shm.h"
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+typedef struct
+{
+ DConfEngineSource source;
+
+ guint8 *shm;
+} DConfEngineSourceUser;
+
+static GvdbTable *
+dconf_engine_source_user_open_gvdb (const gchar *name)
+{
+ GvdbTable *table;
+ gchar *filename;
+
+ /* This can fail in the normal case of the user not having any
+ * settings. That's OK and it shouldn't be considered as an error.
+ */
+ filename = g_build_filename (g_get_user_config_dir (), "dconf", name, NULL);
+ table = gvdb_table_new (filename, FALSE, NULL);
+ g_free (filename);
+
+ return table;
+}
+
+static void
+dconf_engine_source_user_init (DConfEngineSource *source)
+{
+ source->bus_type = G_BUS_TYPE_SESSION;
+ source->bus_name = g_strdup ("ca.desrt.dconf");
+ source->object_path = g_strdup_printf ("/ca/desrt/dconf/Writer/%s", source->name);
+ source->writable = TRUE;
+}
+
+static gboolean
+dconf_engine_source_user_needs_reopen (DConfEngineSource *source)
+{
+ DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+
+ return dconf_shm_is_flagged (user_source->shm);
+}
+
+static GvdbTable *
+dconf_engine_source_user_reopen (DConfEngineSource *source)
+{
+ DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+
+ dconf_shm_close (user_source->shm);
+ user_source->shm = dconf_shm_open (source->name);
+
+ return dconf_engine_source_user_open_gvdb (source->name);
+}
+
+static void
+dconf_engine_source_user_finalize (DConfEngineSource *source)
+{
+ DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+
+ dconf_shm_close (user_source->shm);
+}
+
+G_GNUC_INTERNAL
+const DConfEngineSourceVTable dconf_engine_source_user_vtable = {
+ .instance_size = sizeof (DConfEngineSourceUser),
+ .init = dconf_engine_source_user_init,
+ .finalize = dconf_engine_source_user_finalize,
+ .needs_reopen = dconf_engine_source_user_needs_reopen,
+ .reopen = dconf_engine_source_user_reopen
+};
diff --git a/engine/dconf-engine-source.c b/engine/dconf-engine-source.c
new file mode 100644
index 0000000..834644c
--- /dev/null
+++ b/engine/dconf-engine-source.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-engine-source-private.h"
+
+#include <string.h>
+
+void
+dconf_engine_source_free (DConfEngineSource *source)
+{
+ if (source->values)
+ gvdb_table_unref (source->values);
+
+ if (source->locks)
+ gvdb_table_unref (source->locks);
+
+ source->vtable->finalize (source);
+ g_free (source->bus_name);
+ g_free (source->object_path);
+ g_free (source->name);
+ g_free (source);
+}
+
+gboolean
+dconf_engine_source_refresh (DConfEngineSource *source)
+{
+ if (source->vtable->needs_reopen (source))
+ {
+ gboolean was_open;
+ gboolean is_open;
+
+ /* Record if we had a gvdb before or not. */
+ was_open = source->values != NULL;
+
+ g_clear_pointer (&source->values, gvdb_table_unref);
+ g_clear_pointer (&source->locks, gvdb_table_unref);
+
+ source->values = source->vtable->reopen (source);
+ if (source->values)
+ source->locks = gvdb_table_get_table (source->values, ".locks");
+
+ /* Check if we ended up with a gvdb. */
+ is_open = source->values != NULL;
+
+ /* Only return TRUE in the case that we either had a database
+ * before or ended up with one after. In the case that we just go
+ * from NULL to NULL, return FALSE.
+ */
+ return was_open || is_open;
+ }
+
+ return FALSE;
+}
+
+DConfEngineSource *
+dconf_engine_source_new (const gchar *description)
+{
+ const DConfEngineSourceVTable *vtable;
+ DConfEngineSource *source;
+
+ switch (description[0])
+ {
+ case 's':
+ vtable = &dconf_engine_source_system_vtable;
+ break;
+
+ case 'u':
+ vtable = &dconf_engine_source_user_vtable;
+ break;
+
+ default:
+ g_warning ("unknown dconf database description: %s", description);
+ return NULL;
+ }
+
+ source = g_malloc0 (vtable->instance_size);
+ source->vtable = vtable;
+ source->name = strchr (description, ':');
+ if (source->name)
+ source->name = g_strdup (source->name + 1);
+ source->vtable->init (source);
+
+ return source;
+}
+
+DConfEngineSource *
+dconf_engine_source_new_default (void)
+{
+ DConfEngineSource *source;
+
+ source = g_malloc0 (dconf_engine_source_user_vtable.instance_size);
+ source->vtable = &dconf_engine_source_user_vtable;
+ source->name = g_strdup ("user");
+ source->vtable->init (source);
+
+ return source;
+}
diff --git a/engine/dconf-engine-source.h b/engine/dconf-engine-source.h
new file mode 100644
index 0000000..3828dba
--- /dev/null
+++ b/engine/dconf-engine-source.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_engine_source_h__
+#define __dconf_engine_source_h__
+
+#include "../gvdb/gvdb-reader.h"
+#include <gio/gio.h>
+
+typedef struct _DConfEngineSourceVTable DConfEngineSourceVTable;
+typedef struct _DConfEngineSource DConfEngineSource;
+
+struct _DConfEngineSourceVTable
+{
+ gsize instance_size;
+
+ void (* init) (DConfEngineSource *source);
+ void (* finalize) (DConfEngineSource *source);
+ gboolean (* needs_reopen) (DConfEngineSource *source);
+ GvdbTable * (* reopen) (DConfEngineSource *source);
+};
+
+struct _DConfEngineSource
+{
+ const DConfEngineSourceVTable *vtable;
+
+ GvdbTable *values;
+ GvdbTable *locks;
+ GBusType bus_type;
+ gboolean writable;
+ gchar *bus_name;
+ gchar *object_path;
+ gchar *name;
+};
+
+G_GNUC_INTERNAL
+void dconf_engine_source_free (DConfEngineSource *source);
+
+G_GNUC_INTERNAL
+gboolean dconf_engine_source_refresh (DConfEngineSource *source);
+
+G_GNUC_INTERNAL
+DConfEngineSource * dconf_engine_source_new (const gchar *name);
+
+G_GNUC_INTERNAL
+DConfEngineSource * dconf_engine_source_new_default (void);
+
+#endif /* __dconf_engine_source_h__ */
diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c
index aedabcf..9e44f46 100644
--- a/engine/dconf-engine.c
+++ b/engine/dconf-engine.c
@@ -20,9 +20,9 @@
*/
#define _XOPEN_SOURCE 600
-#include "dconf-shmdir.h"
#include "dconf-engine.h"
-#include <gvdb-reader.h>
+
+#include "../gvdb/gvdb-reader.h"
#include <string.h>
#include <stdlib.h>
#include <errno.h>
@@ -31,191 +31,276 @@
#include <fcntl.h>
#include <sys/mman.h>
-void
-dconf_engine_message_destroy (DConfEngineMessage *dcem)
-{
- gint i;
+#include "dconf-engine-profile.h"
- for (i = 0; dcem->parameters[i]; i++)
- g_variant_unref (dcem->parameters[i]);
- g_free (dcem->parameters);
-}
+/* The engine has zero or more sources.
+ *
+ * If it has zero sources then things are very uninteresting. Nothing
+ * is writable, nothing will ever be written and reads will always
+ * return NULL.
+ *
+ * There are two interesting cases when there is a non-zero number of
+ * sources. Writing only ever occurs to the first source, if at all.
+ * Non-first sources are never writable.
+ *
+ * The first source may or may not be writable. In the usual case the
+ * first source is the one in the user's home directory and is writable,
+ * but it may be that the profile was setup for read-only access to
+ * system sources only.
+ *
+ * In the case that the first source is not writable (and therefore
+ * there are no writable sources), is_writable() will always return
+ * FALSE and no writes will ever be performed.
+ *
+ * It's possible to request changes in three ways:
+ *
+ * - synchronous: the D-Bus message is immediately sent to the
+ * dconf service and we block until we receive the reply. The change
+ * signal will follow soon thereafter (when we receive the signal on
+ * D-Bus).
+ *
+ * - asynchronous: typical asynchronous operation: we send the request
+ * and return immediately, notifying using a callback when the
+ * request is completed (and the new value is in the database). The
+ * change signal follows in the same way as with synchronous.
+ *
+ * - fast: we record the value locally and signal the change, returning
+ * immediately, as if the value is already in the database (from the
+ * viewpoint of the local process). We keep note of the new value
+ * locally until the service has confirmed that the write was
+ * successful. If the write fails, we emit a change signal. From
+ * the view of the program it looks like the value was successfully
+ * changed but then quickly changed back again by some external
+ * agent.
+ *
+ * In fast mode we have to do some management of the queue. If we
+ * immediately put all requests "in flight" then we can end up in a
+ * situation where the application writes many values for the same key
+ * and the service is kept (needlessly) busy writing over and over to
+ * the same key for some time after the requests stop coming in.
+ *
+ * If we limit the number of in-flight requests and put the other ones
+ * into a pending queue then we can perform merging of similar changes.
+ * If we notice that an item in the pending queue writes to the same
+ * keys as the newly-added request then we can simply drop the existing
+ * request (since its effect will be nullified by the new request).
+ *
+ * We want to keep the number of in-flight requests low in order to
+ * maximise our chance of dropping pending items, but we probably want
+ * it higher than 1 so that we can pipeline to hide latency.
+ *
+ * In order to minimise complexity, all changes go first to the pending
+ * queue. Changes are dispatched from the pending queue (and moved to
+ * the in-flight queue) when the number of requests in-flight is lower
+ * than the maximum.
+ *
+ * For both 'in_flight' and 'pending' queues we push to the tail and pop
+ * from the head. This puts the first operation on the head and the
+ * most recent operation on the tail.
+ *
+ * Since new operation go first to the pending queue, we find the most
+ * recent operations at the tail of that queue. Since we want to return
+ * the most-recently written value, we therefore scan for values
+ * starting at the tail of the pending queue and ending at the head of
+ * the in-flight queue.
+ *
+ * NB: I tell a lie. Async is not supported yet.
+ *
+ * Notes about threading:
+ *
+ * The engine is oblivious to threads and main contexts.
+ *
+ * What this means is that the engine has no interaction with GMainLoop
+ * and will not schedule idles or anything of the sort. All calls made
+ * by the engine to the client library will be made in response to
+ * incoming method calls, from the same thread as the incoming call.
+ *
+ * If dconf_engine_call_handle_reply() or
+ * dconf_engine_handle_dbus_signal() are called from 'exotic' threads
+ * (as will often be the case) then the resulting calls to
+ * dconf_engine_change_notify() will come from the same thread. That's
+ * left for the client library to deal with.
+ *
+ * All that said, the engine is completely threadsafe. The client
+ * library can call any method from any thread at any time -- as long as
+ * it is willing to deal with receiving the change notifies in those
+ * threads.
+ *
+ * Thread-safety is implemented using two locks.
+ *
+ * The first lock (sources_lock) protects the sources. Although the
+ * sources are only ever read from, it is necessary to lock them because
+ * it is not safe to read during a refresh (when the source is being
+ * closed and reopened). Accordingly, sources_lock need only be
+ * acquired when accessing the parts of the sources that are subject to
+ * change as a result of refreshes; the static parts (like bus type,
+ * object path, etc) can be accessed without holding the lock. The
+ * 'sources' array itself (and 'n_sources') are set at construction and
+ * never change after that.
+ *
+ * The second lock (queue_lock) protects the various queues that are
+ * used to implement the "fast" writes described above.
+ *
+ * If both locks are held at the same time thne the sources lock must
+ * have been acquired first.
+ */
-void
-dconf_engine_message_copy (DConfEngineMessage *orig,
- DConfEngineMessage *copy)
+#define MAX_IN_FLIGHT 2
+
+static GSList *dconf_engine_global_list;
+static GMutex dconf_engine_global_lock;
+
+struct _DConfEngine
{
- gint i, n;
+ gpointer user_data; /* Set at construct time */
+ GDestroyNotify free_func;
+ gint ref_count;
- *copy = *orig;
+ GMutex sources_lock; /* This lock is for the sources (ie: refreshing) and state. */
+ guint64 state; /* Counter that changes every time a source is refreshed. */
+ DConfEngineSource **sources; /* Array never changes, but each source changes internally. */
+ gint n_sources;
- for (n = 0; orig->parameters[n]; n++);
- copy->parameters = g_new (GVariant *, n + 1);
- for (i = 0; i < n; i++)
- copy->parameters[i] = g_variant_ref (orig->parameters[i]);
- copy->parameters[i] = NULL;
-}
+ GMutex queue_lock; /* This lock is for pending, in_flight, queue_cond */
+ GCond queue_cond; /* Signalled when the queues empty */
+ GQueue pending; /* DConfChangeset */
+ GQueue in_flight; /* DConfChangeset */
-static const gchar *
-dconf_engine_get_session_dir (void)
+ gchar *last_handled; /* reply tag from last item in in_flight */
+};
+
+/* When taking the sources lock we check if any of the databases have
+ * had updates.
+ *
+ * Anything that is accessing the database (even only reading) needs to
+ * be holding the lock (since refreshes could be happening in another
+ * thread), so this makes sense.
+ *
+ * We could probably optimise this to avoid checking some databases in
+ * certain cases (ie: we do not need to check the user's database when
+ * we are only interested in checking writability) but this works well
+ * enough for now and is less prone to errors.
+ *
+ * We could probably change to a reader/writer situation that is only
+ * holding the write lock when actually making changes during a refresh
+ * but the engine is probably only ever really in use by two threads at
+ * a given time (main thread doing reads, DBus worker thread clearing
+ * the queue) so it seems unlikely that lock contention will become an
+ * issue.
+ *
+ * If it does, we can revisit this...
+ */
+static void
+dconf_engine_acquire_sources (DConfEngine *engine)
{
- static const gchar *session_dir;
- static gsize initialised;
+ gint i;
- if (g_once_init_enter (&initialised))
- {
- session_dir = dconf_shmdir_from_environment ();
- g_once_init_leave (&initialised, 1);
- }
+ g_mutex_lock (&engine->sources_lock);
- return session_dir;
+ for (i = 0; i < engine->n_sources; i++)
+ if (dconf_engine_source_refresh (engine->sources[i]))
+ engine->state++;
}
-struct _DConfEngine
+static void
+dconf_engine_release_sources (DConfEngine *engine)
{
- GMutex lock;
- guint64 state;
-
+ g_mutex_unlock (&engine->sources_lock);
+}
- GvdbTable **gvdbs;
- GvdbTable **lock_tables;
- guint8 **shm;
- gchar **object_paths;
- gchar *bus_types;
- gchar **names;
- gint n_dbs;
-};
+static void
+dconf_engine_lock_queues (DConfEngine *engine)
+{
+ g_mutex_lock (&engine->queue_lock);
+}
static void
-dconf_engine_setup_user (DConfEngine *engine,
- gint i)
+dconf_engine_unlock_queues (DConfEngine *engine)
{
- /* invariant: we never have user gvdb without shm */
- g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+ g_mutex_unlock (&engine->queue_lock);
+}
- if (engine->names[i])
- {
- const gchar *session_dir = dconf_engine_get_session_dir ();
+DConfEngine *
+dconf_engine_new (gpointer user_data,
+ GDestroyNotify free_func)
+{
+ DConfEngine *engine;
- if (session_dir)
- {
- gchar *filename;
- gint fd;
-
- filename = g_build_filename (session_dir,
- engine->names[i],
- NULL);
- fd = open (filename, O_RDWR | O_CREAT, 0600);
- g_free (filename);
-
- if (fd >= 0)
- {
- if (ftruncate (fd, 1) == 0)
- {
- engine->shm[i] = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
-
- if (engine->shm[i] == MAP_FAILED)
- engine->shm[i] = NULL;
- }
-
- close (fd);
- }
- }
+ engine = g_slice_new0 (DConfEngine);
+ engine->user_data = user_data;
+ engine->free_func = free_func;
+ engine->ref_count = 1;
- if (engine->shm[i])
- {
- gchar *filename;
-
- filename = g_build_filename (g_get_user_config_dir (),
- "dconf",
- engine->names[i],
- NULL);
- engine->gvdbs[i] = gvdb_table_new (filename, FALSE, NULL);
- g_free (filename);
- }
- }
+ g_mutex_init (&engine->sources_lock);
+ g_mutex_init (&engine->queue_lock);
+ g_cond_init (&engine->queue_cond);
+
+ engine->sources = dconf_engine_profile_open (NULL, &engine->n_sources);
+
+ g_mutex_lock (&dconf_engine_global_lock);
+ dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine);
+ g_mutex_unlock (&dconf_engine_global_lock);
- g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+ return engine;
}
-static void
-dconf_engine_refresh_user (DConfEngine *engine,
- gint i)
+void
+dconf_engine_unref (DConfEngine *engine)
{
- g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+ gint ref_count;
- /* if we failed the first time, fail forever */
- if (engine->shm[i] && *engine->shm[i] == 1)
+ again:
+ ref_count = engine->ref_count;
+
+ if (ref_count == 1)
{
- if (engine->gvdbs[i])
+ gint i;
+
+ /* We are about to drop the last reference, but there is a chance
+ * that a signal may be happening at this very moment, causing the
+ * engine to gain another reference (due to its position in the
+ * global engine list).
+ *
+ * Acquiring the lock here means that either we will remove this
+ * engine from the list first or we will notice the reference
+ * count has increased (and skip the free).
+ */
+ g_mutex_lock (&dconf_engine_global_lock);
+ if (engine->ref_count != 1)
{
- gvdb_table_unref (engine->gvdbs[i]);
- engine->gvdbs[i] = NULL;
+ g_mutex_unlock (&dconf_engine_global_lock);
+ goto again;
}
+ dconf_engine_global_list = g_slist_remove (dconf_engine_global_list, engine);
+ g_mutex_unlock (&dconf_engine_global_lock);
- munmap (engine->shm[i], 1);
- engine->shm[i] = NULL;
+ g_mutex_clear (&engine->sources_lock);
+ g_mutex_clear (&engine->queue_lock);
+ g_cond_clear (&engine->queue_cond);
- dconf_engine_setup_user (engine, i);
- engine->state++;
- }
+ g_free (engine->last_handled);
- g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
-}
+ for (i = 0; i < engine->n_sources; i++)
+ dconf_engine_source_free (engine->sources[i]);
-static void
-dconf_engine_refresh_system (DConfEngine *engine,
- gint i)
-{
- if (engine->gvdbs[i] && !gvdb_table_is_valid (engine->gvdbs[i]))
- {
- if (engine->lock_tables[i])
- {
- gvdb_table_unref (engine->lock_tables[i]);
- engine->lock_tables[i] = NULL;
- }
+ g_free (engine->sources);
- gvdb_table_unref (engine->gvdbs[i]);
- engine->gvdbs[i] = NULL;
- }
+ if (engine->free_func)
+ engine->free_func (engine->user_data);
- if (engine->gvdbs[i] == NULL)
- {
- gchar *filename = g_build_filename ("/etc/dconf/db",
- engine->names[i], NULL);
- engine->gvdbs[i] = gvdb_table_new (filename, TRUE, NULL);
- if (engine->gvdbs[i] == NULL)
- g_error ("Unable to open '%s', specified in dconf profile\n",
- filename);
- engine->lock_tables[i] = gvdb_table_get_table (engine->gvdbs[i],
- ".locks");
- g_free (filename);
- engine->state++;
+ g_slice_free (DConfEngine, engine);
}
-}
-static void
-dconf_engine_refresh (DConfEngine *engine)
-{
- gint i;
-
- for (i = 0; i < engine->n_dbs; i++)
- if (engine->bus_types[i] == 'e')
- dconf_engine_refresh_user (engine, i);
- else
- dconf_engine_refresh_system (engine, i);
+ else if (!g_atomic_int_compare_and_exchange (&engine->ref_count, ref_count, ref_count - 1))
+ goto again;
}
-static void
-dconf_engine_setup (DConfEngine *engine)
+static DConfEngine *
+dconf_engine_ref (DConfEngine *engine)
{
- gint i;
+ g_atomic_int_inc (&engine->ref_count);
- for (i = 0; i < engine->n_dbs; i++)
- if (engine->bus_types[i] == 'e')
- dconf_engine_setup_user (engine, i);
- else
- dconf_engine_refresh_system (engine, i);
+ return engine;
}
guint64
@@ -223,535 +308,859 @@ dconf_engine_get_state (DConfEngine *engine)
{
guint64 state;
- g_mutex_lock (&engine->lock);
-
- dconf_engine_refresh (engine);
+ dconf_engine_acquire_sources (engine);
state = engine->state;
-
- g_mutex_unlock (&engine->lock);
+ dconf_engine_release_sources (engine);
return state;
}
static gboolean
-dconf_engine_load_profile (const gchar *profile,
- gchar **bus_types,
- gchar ***names,
- gint *n_dbs,
- GError **error)
-{
- gchar *filename;
- gint allocated;
- char line[80];
- FILE *f;
-
- /* DCONF_PROFILE starting with '/' gives an absolute path to a profile */
- if (profile[0] != '/')
- filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
- else
- filename = g_strdup (profile);
-
- f = fopen (filename, "r");
-
- if (f == NULL)
- {
- gint saved_errno = errno;
+dconf_engine_is_writable_internal (DConfEngine *engine,
+ const gchar *key)
+{
+ gint i;
+
+ /* We must check several things:
+ *
+ * - we have at least one source
+ *
+ * - the first source is writable
+ *
+ * - the key is not locked in a non-writable (ie: non-first) source
+ */
+ if (engine->n_sources == 0)
+ return FALSE;
- g_set_error (error, G_FILE_ERROR,
- g_file_error_from_errno (saved_errno),
- "open '%s': %s", filename, g_strerror (saved_errno));
- g_free (filename);
+ if (engine->sources[0]->writable == FALSE)
+ return FALSE;
+
+ /* Ignore locks in the first source.
+ *
+ * Either it is writable and therefore ignoring locks is the right
+ * thing to do, or it's non-writable and we caught that case above.
+ */
+ for (i = 1; i < engine->n_sources; i++)
+ if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
return FALSE;
- }
- allocated = 4;
- *bus_types = g_new (gchar, allocated);
- *names = g_new (gchar *, allocated);
- *n_dbs = 0;
+ return TRUE;
+}
- /* quick and dirty is good enough for now */
- while (fgets (line, sizeof line, f))
- {
- const gchar *end;
- const gchar *sep;
+gboolean
+dconf_engine_is_writable (DConfEngine *engine,
+ const gchar *key)
+{
+ gboolean writable;
- end = strchr (line, '\n');
+ dconf_engine_acquire_sources (engine);
+ writable = dconf_engine_is_writable_internal (engine, key);
+ dconf_engine_release_sources (engine);
- if (end == NULL)
- g_error ("long line in %s", filename);
+ return writable;
+}
- if (end == line)
- continue;
+static gboolean
+dconf_engine_find_key_in_queue (GQueue *queue,
+ const gchar *key,
+ GVariant **value)
+{
+ GList *node;
- if (line[0] == '#')
- continue;
+ /* Tail to head... */
+ for (node = g_queue_peek_tail_link (queue); node; node = node->prev)
+ if (dconf_changeset_get (node->data, key, value))
+ return TRUE;
- if (*n_dbs == allocated)
- {
- allocated *= 2;
- *names = g_renew (gchar *, *names, allocated);
- *bus_types = g_renew (gchar, *bus_types, allocated);
- }
+ return FALSE;
+}
+
+GVariant *
+dconf_engine_read (DConfEngine *engine,
+ DConfChangesetList *read_through,
+ const gchar *key)
+{
+ GVariant *value = NULL;
+ gint lock_level = 0;
+ gint i;
+
+ dconf_engine_acquire_sources (engine);
- sep = strchr (line, ':');
+ /* There are a number of situations that this function has to deal
+ * with and they interact in unusual ways. We attempt to write the
+ * rules for all cases here:
+ *
+ * With respect to the steady-state condition with no locks:
+ *
+ * This is the case where there are no changes queued, no
+ * read_through and no locks.
+ *
+ * The value returned is the one from the lowest-index source that
+ * contains that value.
+ *
+ * With respect to locks:
+ *
+ * If a lock is present (except in source #0 where it is ignored)
+ * then we will only return a value found in the source where the
+ * lock was present, or a higher-index source (following the normal
+ * rule that sources with lower indexes take priority).
+ *
+ * This statement includes read_through and queued changes. If a
+ * lock is found, we will ignore those.
+ *
+ * With respect to read_through and queued changed:
+ *
+ * We only consider read_through and queued changes in the event
+ * that we have a writable source. This will possibly cause us to
+ * ignore read_through and will have no real effect on the queues
+ * (since they will be empty anyway if we have no writable source).
+ *
+ * We only consider read_through and queued changes in the event
+ * that we have not found any locks.
+ *
+ * If there is a non-NULL value found in read_through or the queued
+ * changes then we will return that value.
+ *
+ * If there is a NULL value (ie: a reset) found in read_through or
+ * the queued changes then we will only ignore any value found in
+ * the first source (which must be writable, or else we would not
+ * have been considering read_through and the queues). This is
+ * consistent with the fact that a reset will unset any value found
+ * in this source but will not affect values found in lower sources.
+ *
+ * Put another way: if a non-writable source contains a value for a
+ * particular key then it is impossible for this function to return
+ * NULL.
+ *
+ * We implement the above rules as follows. We have three state
+ * tracking variables:
+ *
+ * - lock_level: records if and where we found a lock
+ *
+ * - found_key: records if we found the key in any queue
+ *
+ * - value: records the value of the found key (NULL for resets)
+ *
+ * We take these steps:
+ *
+ * 1. check for lockdown. If we find a lock then we prevent any
+ * other sources (including read_through and pending/in-flight)
+ * from affecting the value of the key.
+ *
+ * We record the result of this in the lock_level variable. Zero
+ * means that no locks were found. Non-zero means that a lock was
+ * found in the source with the index given by the variable.
+ *
+ * 2. check the uncommited changes in the read_through list as the
+ * highest priority. This is only done if we have a writable
+ * source and no locks were found.
+ *
+ * If we found an entry in the read_through then we set
+ * 'found_key' to TRUE and set 'value' to the value that we found
+ * (which will be NULL in the case of finding a reset request).
+ *
+ * 3. check our pending and in-flight "fast" changes (in that order).
+ * This is only done if we have a writable source and no locks
+ * were found. It is also only done if we did not find the key in
+ * the read_through.
+ *
+ * 4. check the first source, if there is one.
+ *
+ * This is only done if 'found_key' is FALSE. If 'found_key' is
+ * TRUE then it means that the first database was writable and we
+ * either found a value that will replace it (value != NULL) or
+ * found a pending reset (value == NULL) that will unset it.
+ *
+ * We only actually do this step if we have a writable first
+ * source and no locks found, otherwise we just let step 5 do all
+ * the checking.
+ *
+ * 5. check the remaining sources.
+ *
+ * We do this until we have value != NULL. Even if found_key was
+ * TRUE, the reset that was requested will not have affected the
+ * lower-level databases.
+ */
- if (sep)
+ /* Step 1. Check for locks.
+ *
+ * Note: i > 0 (strictly). Ignore locks for source #0.
+ */
+ for (i = engine->n_sources - 1; i > 0; i--)
+ if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
+ {
+ lock_level = i;
+ break;
+ }
+
+ /* Only do steps 2 to 4 if we have no locks and we have a writable source. */
+ if (!lock_level && engine->n_sources != 0 && engine->sources[0]->writable)
+ {
+ gboolean found_key = FALSE;
+
+ /* Step 2. Check read_through. */
+ if (read_through)
+ found_key = dconf_engine_find_key_in_queue (&read_through->queue, key, &value);
+
+ /* Step 3. Check queued changes if we didn't find it in read_through.
+ *
+ * NB: We may want to optimise this to avoid taking the lock in
+ * the case that we know both queues are empty.
+ */
+ if (!found_key)
{
- /* strings MUST be 'user-db' or 'system-db'. we do the check
- * this way here merely because it is the fastest.
+ dconf_engine_lock_queues (engine);
+
+ /* Check the pending queue first because those were submitted
+ * more recently.
*/
- (*bus_types)[*n_dbs] = (line[0] == 'u') ? 'e' : 'y';
- (*names)[*n_dbs] = g_strndup (sep + 1, end - (sep + 1));
- }
- else
- {
- /* default is for first DB to be user and rest to be system */
- (*bus_types)[*n_dbs] = (*n_dbs == 0) ? 'e' : 'y';
- (*names)[*n_dbs] = g_strndup (line, end - line);
+ found_key = dconf_engine_find_key_in_queue (&engine->pending, key, &value) ||
+ dconf_engine_find_key_in_queue (&engine->in_flight, key, &value);
+
+ dconf_engine_unlock_queues (engine);
}
- (*n_dbs)++;
+ /* Step 4. Check the first source. */
+ if (!found_key && engine->sources[0]->values)
+ value = gvdb_table_get_value (engine->sources[0]->values, key);
+
+ /* We already checked source #0 (or ignored it, as appropriate).
+ *
+ * Abuse the lock_level variable to get step 5 to skip this one.
+ */
+ lock_level = 1;
}
- *bus_types = g_renew (gchar, *bus_types, *n_dbs);
- *names = g_renew (gchar *, *names, *n_dbs);
- g_free (filename);
- fclose (f);
+ /* Step 5. Check the remaining sources, until value != NULL. */
+ for (i = lock_level; value == NULL && i < engine->n_sources; i++)
+ {
+ if (engine->sources[i]->values == NULL)
+ continue;
- return TRUE;
+ if ((value = gvdb_table_get_value (engine->sources[i]->values, key)))
+ break;
+ }
+
+ dconf_engine_release_sources (engine);
+
+ return value;
}
-DConfEngine *
-dconf_engine_new (const gchar *profile)
+gchar **
+dconf_engine_list (DConfEngine *engine,
+ const gchar *dir,
+ gint *length)
{
- DConfEngine *engine;
+ GHashTable *results;
+ GHashTableIter iter;
+ gchar **list;
+ gint n_items;
+ gpointer key;
gint i;
- engine = g_slice_new (DConfEngine);
- g_mutex_init (&engine->lock);
+ /* This function is unreliable in the presence of pending changes.
+ * Here's why:
+ *
+ * Consider the case that we list("/a/") and a pending request has a
+ * reset request recorded for "/a/b/c". The question of if "b/"
+ * should appear in the output rests on if "/a/b/d" also exists.
+ *
+ * Put another way: If "/a/b/c" is the only key in "/a/b/" then
+ * resetting it would mean that "/a/b/" stops existing (and we should
+ * not include it in the output). If there are others keys then it
+ * will continue to exist and we should include it.
+ *
+ * Instead of trying to sort this out, we just ignore the pending
+ * requests and report what the on-disk file says.
+ */
+
+ results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
- if (profile == NULL)
- profile = getenv ("DCONF_PROFILE");
+ dconf_engine_acquire_sources (engine);
- if (profile)
+ for (i = 0; i < engine->n_sources; i++)
{
- GError *error = NULL;
+ gchar **partial_list;
+ gint j;
- if (!dconf_engine_load_profile (profile, &engine->bus_types, &engine->names, &engine->n_dbs, &error))
- g_error ("Error loading dconf profile '%s': %s\n",
- profile, error->message);
- }
- else
- {
- if (!dconf_engine_load_profile ("user", &engine->bus_types, &engine->names, &engine->n_dbs, NULL))
+ partial_list = gvdb_table_list (engine->sources[i]->values, dir);
+
+ if (partial_list != NULL)
{
- engine->names = g_new (gchar *, 1);
- engine->names[0] = g_strdup ("user");
- engine->bus_types = g_strdup ("e");
- engine->n_dbs = 1;
+ for (j = 0; partial_list[j]; j++)
+ /* Steal the keys from the list. */
+ g_hash_table_add (results, partial_list[j]);
+
+ /* Free only the list. */
+ g_free (partial_list);
}
}
- if (strcmp (engine->names[0], "-") == 0)
+ dconf_engine_release_sources (engine);
+
+ n_items = g_hash_table_size (results);
+ list = g_new (gchar *, n_items + 1);
+
+ i = 0;
+ g_hash_table_iter_init (&iter, results);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
{
- g_free (engine->names[0]);
- engine->names[0] = NULL;
+ g_hash_table_iter_steal (&iter);
+ list[i++] = key;
}
+ list[i] = NULL;
+ g_assert_cmpint (i, ==, n_items);
- engine->object_paths = g_new (gchar *, engine->n_dbs);
- engine->gvdbs = g_new0 (GvdbTable *, engine->n_dbs);
- engine->lock_tables = g_new0 (GvdbTable *, engine->n_dbs);
- engine->shm = g_new0 (guint8 *, engine->n_dbs);
- engine->state = 0;
-
- for (i = 0; i < engine->n_dbs; i++)
- if (engine->names[i])
- engine->object_paths[i] = g_strjoin (NULL,
- "/ca/desrt/dconf/Writer/",
- engine->names[i],
- NULL);
- else
- engine->object_paths[i] = NULL;
+ if (length)
+ *length = n_items;
- dconf_engine_setup (engine);
+ g_hash_table_unref (results);
- return engine;
+ return list;
}
-void
-dconf_engine_free (DConfEngine *engine)
+typedef void (* DConfEngineCallHandleCallback) (DConfEngine *engine,
+ gpointer handle,
+ GVariant *parameter,
+ const GError *error);
+
+struct _DConfEngineCallHandle
{
- gint i;
+ DConfEngine *engine;
+ DConfEngineCallHandleCallback callback;
+ const GVariantType *expected_reply;
+};
- for (i = 0; i < engine->n_dbs; i++)
- {
- g_free (engine->object_paths[i]);
- g_free (engine->names[i]);
+static gpointer
+dconf_engine_call_handle_new (DConfEngine *engine,
+ DConfEngineCallHandleCallback callback,
+ const GVariantType *expected_reply,
+ gsize size)
+{
+ DConfEngineCallHandle *handle;
- if (engine->gvdbs[i])
- gvdb_table_unref (engine->gvdbs[i]);
+ g_assert (engine != NULL);
+ g_assert (callback != NULL);
+ g_assert (size >= sizeof (DConfEngineCallHandle));
- if (engine->lock_tables[i])
- gvdb_table_unref (engine->lock_tables[i]);
+ handle = g_malloc0 (size);
+ handle->engine = dconf_engine_ref (engine);
+ handle->callback = callback;
+ handle->expected_reply = expected_reply;
- if (engine->shm[i])
- munmap (engine->shm[i], 1);
- }
+ return handle;
+}
+const GVariantType *
+dconf_engine_call_handle_get_expected_type (DConfEngineCallHandle *handle)
+{
+ return handle->expected_reply;
+}
- g_mutex_clear (&engine->lock);
+void
+dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
+ GVariant *parameter,
+ const GError *error)
+{
+ if (handle == NULL)
+ return;
- g_free (engine->object_paths);
- g_free (engine->bus_types);
- g_free (engine->names);
- g_free (engine->gvdbs);
- g_free (engine->lock_tables);
- g_free (engine->shm);
+ (* handle->callback) (handle->engine, handle, parameter, error);
+}
- g_slice_free (DConfEngine, engine);
+static void
+dconf_engine_call_handle_free (DConfEngineCallHandle *handle)
+{
+ dconf_engine_unref (handle->engine);
+ g_free (handle);
}
+/* returns floating */
static GVariant *
-dconf_engine_read_internal (DConfEngine *engine,
- const gchar *key,
- gboolean user,
- gboolean system)
+dconf_engine_make_match_rule (DConfEngineSource *source,
+ const gchar *path)
{
- GVariant *value = NULL;
- gint lowest;
- gint limit;
- gint i;
+ GVariant *params;
+ gchar *rule;
- g_mutex_lock (&engine->lock);
+ rule = g_strdup_printf ("type='signal',"
+ "interface='ca.desrt.dconf.Writer',"
+ "path='%s',"
+ "arg0path='%s'",
+ source->object_path,
+ path);
- dconf_engine_refresh (engine);
+ params = g_variant_new ("(s)", rule);
- /* Bound the search space depending on the databases that we are
- * interested in.
- */
- limit = system ? engine->n_dbs : 1;
- lowest = user ? 0 : 1;
-
- /* We want i equal to the index of the highest database containing a
- * lock, or i == lowest if there is no lock. For that reason, we
- * don't actually check the lowest database for a lock. That makes
- * sense, because even if it had a lock, it would not change our
- * search policy (which would be to check the lowest one first).
- *
- * Note that we intentionally dishonour 'limit' here -- we want to
- * ensure that values in the user database are always ignored when
- * locks are present.
- */
- for (i = MAX (engine->n_dbs - 1, lowest); lowest < i; i--)
- if (engine->lock_tables[i] != NULL &&
- gvdb_table_has_value (engine->lock_tables[i], key))
- break;
+ g_free (rule);
- while (i < limit && value == NULL)
- {
- if (engine->gvdbs[i] != NULL)
- value = gvdb_table_get_value (engine->gvdbs[i], key);
- i++;
- }
+ return params;
+}
- g_mutex_unlock (&engine->lock);
+typedef struct
+{
+ DConfEngineCallHandle handle;
- return value;
-}
+ guint64 state;
+ gint pending;
+} OutstandingWatch;
-GVariant *
-dconf_engine_read (DConfEngine *engine,
- const gchar *key)
+static void
+dconf_engine_watch_established (DConfEngine *engine,
+ gpointer handle,
+ GVariant *reply,
+ const GError *error)
{
- return dconf_engine_read_internal (engine, key, TRUE, TRUE);
+ OutstandingWatch *ow = handle;
+
+ /* ignore errors */
+
+ if (--ow->pending)
+ /* more on the way... */
+ return;
+
+ if (ow->state != dconf_engine_get_state (engine))
+ {
+ /* Our recorded state does not match the current state. Something
+ * must have changed while our watch requests were on the wire.
+ *
+ * We don't know what changed, so we can just say that potentially
+ * everything changed. This case is very rare, anyway...
+ */
+ dconf_engine_change_notify (engine, "/", NULL, engine->user_data, NULL);
+ }
+
+ dconf_engine_call_handle_free (handle);
}
-GVariant *
-dconf_engine_read_default (DConfEngine *engine,
- const gchar *key)
+void
+dconf_engine_watch_fast (DConfEngine *engine,
+ const gchar *path)
{
- return dconf_engine_read_internal (engine, key, FALSE, TRUE);
+ OutstandingWatch *ow;
+ gint i;
+
+ if (engine->n_sources == 0)
+ return;
+
+ /* It's possible (although rare) that the dconf database could change
+ * while our match rule is on the wire.
+ *
+ * Since we returned immediately (suggesting to the user that the
+ * watch was already established) we could have a race.
+ *
+ * To deal with this, we use the current state counter to ensure that nothing
+ * changes while the watch requests are on the wire.
+ */
+ ow = dconf_engine_call_handle_new (engine, dconf_engine_watch_established,
+ G_VARIANT_TYPE_UNIT, sizeof (OutstandingWatch));
+ ow->state = dconf_engine_get_state (engine);
+ ow->pending = engine->n_sources;
+
+ for (i = 0; i < engine->n_sources; i++)
+ dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch",
+ dconf_engine_make_match_rule (engine->sources[i], path),
+ &ow->handle, NULL);
}
-GVariant *
-dconf_engine_read_no_default (DConfEngine *engine,
- const gchar *key)
+void
+dconf_engine_unwatch_fast (DConfEngine *engine,
+ const gchar *path)
{
- return dconf_engine_read_internal (engine, key, TRUE, FALSE);
+ gint i;
+
+ for (i = 0; i < engine->n_sources; i++)
+ dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch",
+ dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL);
}
static void
-dconf_engine_make_match_rule (DConfEngine *engine,
- DConfEngineMessage *dcem,
- const gchar *name,
- const gchar *method_name)
+dconf_engine_handle_match_rule_sync (DConfEngine *engine,
+ const gchar *method_name,
+ const gchar *path)
{
gint i;
- dcem->bus_name = "org.freedesktop.DBus";
- dcem->object_path = "/org/freedesktop/DBus";
- dcem->interface_name = "org.freedesktop.DBus";
- dcem->method_name = method_name;
+ /* We need not hold any locks here because we are only touching static
+ * things: the number of sources, and static properties of each source
+ * itself.
+ *
+ * This function silently ignores all errors.
+ */
- dcem->parameters = g_new (GVariant *, engine->n_dbs + 1);
- for (i = 0; i < engine->n_dbs; i++)
+ for (i = 0; i < engine->n_sources; i++)
{
- gchar *rule;
-
- rule = g_strdup_printf ("type='signal',"
- "interface='ca.desrt.dconf.Writer',"
- "path='%s',"
- "arg0path='%s'",
- engine->object_paths[i],
- name);
- dcem->parameters[i] = g_variant_new ("(s)", rule);
- g_variant_ref_sink (dcem->parameters[i]);
- g_free (rule);
- }
- dcem->parameters[i] = NULL;
+ GVariant *result;
+
+ result = dconf_engine_dbus_call_sync_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus", method_name,
+ dconf_engine_make_match_rule (engine->sources[i], path),
+ G_VARIANT_TYPE_UNIT, NULL);
- dcem->bus_types = engine->bus_types;
- dcem->n_messages = engine->n_dbs;
- dcem->reply_type = G_VARIANT_TYPE_UNIT;
+ if (result)
+ g_variant_unref (result);
+ }
}
void
-dconf_engine_watch (DConfEngine *engine,
- const gchar *name,
- DConfEngineMessage *dcem)
+dconf_engine_watch_sync (DConfEngine *engine,
+ const gchar *path)
{
- dconf_engine_make_match_rule (engine, dcem, name, "AddMatch");
+ dconf_engine_handle_match_rule_sync (engine, "AddMatch", path);
}
void
-dconf_engine_unwatch (DConfEngine *engine,
- const gchar *name,
- DConfEngineMessage *dcem)
+dconf_engine_unwatch_sync (DConfEngine *engine,
+ const gchar *path)
{
- dconf_engine_make_match_rule (engine, dcem, name, "RemoveMatch");
+ dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path);
}
-gboolean
-dconf_engine_is_writable (DConfEngine *engine,
- const gchar *name)
+typedef struct
{
- gboolean writable = TRUE;
+ DConfEngineCallHandle handle;
- /* Only check if we have more than one database */
- if (engine->n_dbs > 1)
- {
- gint i;
+ DConfChangeset *change;
+} OutstandingChange;
- g_mutex_lock (&engine->lock);
+static GVariant *
+dconf_engine_prepare_change (DConfEngine *engine,
+ DConfChangeset *change)
+{
+ GVariant *serialised;
- dconf_engine_refresh (engine);
+ serialised = dconf_changeset_serialise (change);
- /* Don't check for locks in the top database (i == 0). */
- for (i = engine->n_dbs - 1; 0 < i; i--)
- if (engine->lock_tables[i] != NULL &&
- gvdb_table_has_value (engine->lock_tables[i], name))
- {
- writable = FALSE;
- break;
- }
+ return g_variant_new_from_data (G_VARIANT_TYPE ("(ay)"),
+ g_variant_get_data (serialised), g_variant_get_size (serialised), TRUE,
+ (GDestroyNotify) g_variant_unref, g_variant_ref_sink (serialised));
+}
- g_mutex_unlock (&engine->lock);
- }
+/* This function promotes changes from the pending queue to the
+ * in-flight queue by sending the appropriate D-Bus message.
+ *
+ * Of course, this is only possible when there are pending items and
+ * room in the in-flight queue. For this reason, this function gets
+ * called in two situations:
+ *
+ * - an item has been added to the pending queue (due to an API call)
+ *
+ * - an item has been removed from the inflight queue (due to a D-Bus
+ * reply having been received)
+ *
+ * It will move a maximum of one item.
+ */
+static void dconf_engine_manage_queue (DConfEngine *engine);
- return writable;
+static void
+dconf_engine_emit_changes (DConfEngine *engine,
+ DConfChangeset *changeset)
+{
+ const gchar *prefix;
+ const gchar * const *changes;
+
+ if (dconf_changeset_describe (changeset, &prefix, &changes, NULL))
+ dconf_engine_change_notify (engine, prefix, changes, NULL, engine->user_data);
}
-/* be conservative and fast: false negatives are OK */
-static gboolean
-is_dbusable (GVariant *value)
+static void
+dconf_engine_change_completed (DConfEngine *engine,
+ gpointer handle,
+ GVariant *reply,
+ const GError *error)
{
- const gchar *type;
+ OutstandingChange *oc = handle;
+ DConfChangeset *expected;
- type = g_variant_get_type_string (value);
+ dconf_engine_lock_queues (engine);
- /* maybe definitely won't work.
- * variant? too lazy to check inside...
+ /* D-Bus guarantees ordered delivery of messages.
+ *
+ * The dconf-service handles requests in-order.
+ *
+ * The reply we just received should therefore be at the head of
+ * our 'in flight' queue.
*/
- if (strchr (type, 'v') || strchr (type, 'm'))
- return FALSE;
+ expected = g_queue_pop_head (&engine->in_flight);
+ g_assert (expected && oc->change == expected);
- /* XXX: we could also check for '{}' not inside an array...
- * but i'm not sure we want to support that anyway.
+ /* We just popped a change from the in-flight queue, possibly
+ * making room for another to be added. Check that.
*/
+ dconf_engine_manage_queue (engine);
+ dconf_engine_unlock_queues (engine);
- /* this will avoid any too-deeply-nested limits */
- return strlen (type) < 32;
-}
-
-static GVariant *
-fake_maybe (GVariant *value)
-{
- GVariantBuilder builder;
-
- g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
-
- if (value != NULL)
+ /* Deal with the reply we got. */
+ if (reply)
{
- if (is_dbusable (value))
- g_variant_builder_add (&builder, "v", value);
+ /* The write worked.
+ *
+ * We already sent a change notification for this item when we
+ * added it to the pending queue and we don't want to send another
+ * one again. At the same time, it's very likely that we're just
+ * about to receive a change signal from the service.
+ *
+ * The tag sent as part of the reply to the Change call will be
+ * the same tag as on the change notification signal. Record that
+ * tag so that we can ignore the signal when it comes.
+ *
+ * last_handled is only ever touched from the worker thread
+ */
+ g_free (engine->last_handled);
+ g_variant_get (reply, "(s)", &engine->last_handled);
+ }
- else
- {
- GVariant *variant;
- GVariant *ay;
-
- variant = g_variant_new_variant (value);
- ay = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING,
- g_variant_get_data (variant),
- g_variant_get_size (variant),
- TRUE,
- (GDestroyNotify) g_variant_unref,
- variant);
- g_variant_builder_add (&builder, "v", ay);
-
- g_variant_builder_add (&builder, "v",
- g_variant_new_string ("serialised GVariant"));
- }
+ if (error)
+ {
+ /* Some kind of unexpected failure occured while attempting to
+ * commit the change.
+ *
+ * There's not much we can do here except to drop our local copy
+ * of the change (and notify that it is gone) and print the error
+ * message as a warning.
+ */
+ g_warning ("failed to commit changes to dconf: %s", error->message);
+ dconf_engine_emit_changes (engine, oc->change);
}
- return g_variant_builder_end (&builder);
+ dconf_changeset_unref (oc->change);
+ dconf_engine_call_handle_free (handle);
}
static void
-dconf_engine_dcem (DConfEngine *engine,
- DConfEngineMessage *dcem,
- const gchar *method_name,
- const gchar *format_string,
- ...)
+dconf_engine_manage_queue (DConfEngine *engine)
{
- va_list ap;
+ if (!g_queue_is_empty (&engine->pending) && g_queue_get_length (&engine->in_flight) < MAX_IN_FLIGHT)
+ {
+ OutstandingChange *oc;
+ GVariant *parameters;
- dcem->bus_name = "ca.desrt.dconf";
- dcem->object_path = engine->object_paths[0];
- dcem->interface_name = "ca.desrt.dconf.Writer";
- dcem->method_name = method_name;
- dcem->parameters = g_new (GVariant *, 2);
- dcem->n_messages = 1;
+ oc = dconf_engine_call_handle_new (engine, dconf_engine_change_completed,
+ G_VARIANT_TYPE ("(s)"), sizeof (OutstandingChange));
- va_start (ap, format_string);
- dcem->parameters[0] = g_variant_new_va (format_string, NULL, &ap);
- g_variant_ref_sink (dcem->parameters[0]);
- dcem->parameters[1] = NULL;
- va_end (ap);
+ oc->change = g_queue_pop_head (&engine->pending);
- dcem->bus_types = engine->bus_types;
- dcem->reply_type = G_VARIANT_TYPE ("(s)");
+ parameters = dconf_engine_prepare_change (engine, oc->change);
+
+ dconf_engine_dbus_call_async_func (engine->sources[0]->bus_type,
+ engine->sources[0]->bus_name,
+ engine->sources[0]->object_path,
+ "ca.desrt.dconf.Writer", "Change",
+ parameters, &oc->handle, NULL);
+
+ g_queue_push_tail (&engine->in_flight, oc->change);
+ }
+
+ if (g_queue_is_empty (&engine->in_flight))
+ {
+ /* The in-flight queue should not be empty if we have changes
+ * pending...
+ */
+ g_assert (g_queue_is_empty (&engine->pending));
+
+ g_cond_broadcast (&engine->queue_cond);
+ }
}
-gboolean
-dconf_engine_write (DConfEngine *engine,
- const gchar *name,
- GVariant *value,
- DConfEngineMessage *dcem,
- GError **error)
+static gboolean
+dconf_engine_is_writable_changeset_predicate (const gchar *key,
+ GVariant *value,
+ gpointer user_data)
{
- dconf_engine_dcem (engine, dcem,
- "Write", "(s@av)",
- name, fake_maybe (value));
+ DConfEngine *engine = user_data;
- return TRUE;
+ /* Resets absolutely always succeed -- even in the case that there is
+ * not even a writable database.
+ */
+ return value == NULL || dconf_engine_is_writable_internal (engine, key);
}
-gboolean
-dconf_engine_write_many (DConfEngine *engine,
- const gchar *prefix,
- const gchar * const *keys,
- GVariant **values,
- DConfEngineMessage *dcem,
- GError **error)
+static gboolean
+dconf_engine_changeset_changes_only_writable_keys (DConfEngine *engine,
+ DConfChangeset *changeset,
+ GError **error)
{
- GVariantBuilder builder;
- gsize i;
+ gboolean success = TRUE;
- g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sav)"));
+ dconf_engine_acquire_sources (engine);
- for (i = 0; keys[i]; i++)
- g_variant_builder_add (&builder, "(s@av)",
- keys[i], fake_maybe (values[i]));
+ if (!dconf_changeset_all (changeset, dconf_engine_is_writable_changeset_predicate, engine))
+ {
+ g_set_error_literal (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE,
+ "The operation attempted to modify one or more non-writable keys");
+ success = FALSE;
+ }
- dconf_engine_dcem (engine, dcem, "WriteMany", "(sa(sav))", prefix, &builder);
+ dconf_engine_release_sources (engine);
- return TRUE;
+ return success;
}
-gchar **
-dconf_engine_list (DConfEngine *engine,
- const gchar *dir,
- gint *length)
+gboolean
+dconf_engine_change_fast (DConfEngine *engine,
+ DConfChangeset *changeset,
+ GError **error)
{
- gchar **list;
+ GList *node;
+
+ if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
+ return FALSE;
- g_mutex_lock (&engine->lock);
+ /* Check for duplicates in the pending queue.
+ *
+ * Note: order doesn't really matter here since "similarity" is an
+ * equivalence class and we've ensured that there are no pairwise
+ * similar changes in the queue already (ie: at most we will have only
+ * one similar item to the one we are adding).
+ */
+ dconf_engine_lock_queues (engine);
- dconf_engine_refresh (engine);
+ for (node = g_queue_peek_head_link (&engine->pending); node; node = node->next)
+ {
+ DConfChangeset *queued_change = node->data;
- if (engine->gvdbs[0])
- list = gvdb_table_list (engine->gvdbs[0], dir);
- else
- list = NULL;
+ if (dconf_changeset_is_similar_to (changeset, queued_change))
+ {
+ /* We found a similar item in the queue.
+ *
+ * We want to drop the one that's in the queue already since
+ * we want our new (more recent) change to take precedence.
+ *
+ * The pending queue owned the changeset, so free it.
+ */
+ g_queue_delete_link (&engine->pending, node);
+ dconf_changeset_unref (queued_change);
- if (list == NULL)
- list = g_new0 (char *, 1);
+ /* There will only have been one, so stop looking. */
+ break;
+ }
+ }
- if (length)
- *length = g_strv_length (list);
+ /* No matter what we're going to queue up this change, so put it in
+ * the pending queue now.
+ *
+ * There may be room in the in_flight queue, so we try to manage the
+ * queue right away in order to try to promote it there (which causes
+ * the D-Bus message to actually be sent).
+ *
+ * The change might get tossed before being sent if the loop above
+ * finds it on a future call.
+ */
+ g_queue_push_tail (&engine->pending, dconf_changeset_ref (changeset));
+ dconf_engine_manage_queue (engine);
- g_mutex_unlock (&engine->lock);
+ dconf_engine_unlock_queues (engine);
- return list;
+ /* Emit the signal after dropping the lock to avoid deadlock on re-entry. */
+ dconf_engine_emit_changes (engine, changeset);
+
+ return TRUE;
}
gboolean
-dconf_engine_decode_notify (DConfEngine *engine,
- const gchar *anti_expose,
- const gchar **path,
- const gchar ***rels,
- guint bus_type,
- const gchar *sender,
- const gchar *iface,
- const gchar *method,
- GVariant *body)
-{
- if (strcmp (iface, "ca.desrt.dconf.Writer") || strcmp (method, "Notify"))
+dconf_engine_change_sync (DConfEngine *engine,
+ DConfChangeset *changeset,
+ gchar **tag,
+ GError **error)
+{
+ GVariant *reply;
+
+ if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
return FALSE;
- if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
+ /* we know that we have at least one source because we checked writability */
+ reply = dconf_engine_dbus_call_sync_func (engine->sources[0]->bus_type,
+ engine->sources[0]->bus_name,
+ engine->sources[0]->object_path,
+ "ca.desrt.dconf.Writer", "Change",
+ dconf_engine_prepare_change (engine, changeset),
+ G_VARIANT_TYPE ("(s)"), error);
+
+ if (reply == NULL)
return FALSE;
- if (anti_expose)
+ /* g_variant_get() is okay with NULL tag */
+ g_variant_get (reply, "(s)", tag);
+ g_variant_unref (reply);
+
+ return TRUE;
+}
+
+void
+dconf_engine_handle_dbus_signal (GBusType type,
+ const gchar *sender,
+ const gchar *path,
+ const gchar *member,
+ GVariant *body)
+{
+ if (g_str_equal (member, "Notify"))
{
- const gchar *ae;
+ const gchar *prefix;
+ const gchar **changes;
+ const gchar *tag;
+ GSList *engines;
+
+ if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
+ return;
- g_variant_get_child (body, 2, "&s", &ae);
+ g_variant_get (body, "(&s^a&s&s)", &prefix, &changes, &tag);
+
+ g_mutex_lock (&dconf_engine_global_lock);
+ engines = g_slist_copy_deep (dconf_engine_global_list, (GCopyFunc) dconf_engine_ref, NULL);
+ g_mutex_unlock (&dconf_engine_global_lock);
+
+ while (engines)
+ {
+ DConfEngine *engine = engines->data;
+
+ /* It's possible that this incoming change notify is for a
+ * change that we already announced to the client when we
+ * placed it in the pending queue.
+ *
+ * Check last_handled to determine if we should ignore it.
+ */
+ if (!engine->last_handled || !g_str_equal (engine->last_handled, tag))
+ dconf_engine_change_notify (engine, prefix, changes, tag, engine->user_data);
+
+ engines = g_slist_delete_link (engines, engines);
+
+ dconf_engine_unref (engine);
+ }
- if (strcmp (ae, anti_expose) == 0)
- return FALSE;
+ g_free (changes);
}
- g_variant_get (body, "(&s^a&ss)", path, rels, NULL);
+ else if (g_str_equal (member, "WritabilityNotify"))
+ {
+ if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
+ return;
- return TRUE;
+ g_warning ("Need to handle writability changes"); /* XXX */
+ }
}
gboolean
-dconf_engine_decode_writability_notify (const gchar **path,
- const gchar *iface,
- const gchar *method,
- GVariant *body)
+dconf_engine_has_outstanding (DConfEngine *engine)
{
- if (strcmp (iface, "ca.desrt.dconf.Writer") ||
- strcmp (method, "WritabilityNotify"))
- return FALSE;
+ gboolean has;
- if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
- return FALSE;
+ /* The in-flight queue will never be empty unless the pending queue is
+ * also empty, so we only really need to check one of them...
+ */
+ dconf_engine_lock_queues (engine);
+ has = !g_queue_is_empty (&engine->in_flight);
+ dconf_engine_unlock_queues (engine);
- g_variant_get_child (body, 0, "&s", path);
+ return has;
+}
- return TRUE;
+void
+dconf_engine_sync (DConfEngine *engine)
+{
+ dconf_engine_lock_queues (engine);
+ while (!g_queue_is_empty (&engine->in_flight))
+ g_cond_wait (&engine->queue_cond, &engine->queue_lock);
+ dconf_engine_unlock_queues (engine);
}
diff --git a/engine/dconf-engine.h b/engine/dconf-engine.h
index 25de43d..0128f8b 100644
--- a/engine/dconf-engine.h
+++ b/engine/dconf-engine.h
@@ -22,123 +22,145 @@
#ifndef __dconf_engine_h__
#define __dconf_engine_h__
-#include <glib.h>
+#include "dconf-changeset-list.h"
+
+#include <gio/gio.h>
typedef struct _DConfEngine DConfEngine;
-/**
- * DConfEngineMessage:
- *
- * This structure represents a number of DBus method call messages that #DConfEngine would like to send.
- *
- * #DConfEngine itself is unaware of a particular DBus or main loop implementation. As such, all requests are
- * synchronous and non-blocking, but most of them produce a #DConfEngineMessage describing messages that must be
- * sent in order for the operation to be completed.
- *
- * @bus_name, @object_path, @interface_name, @method_name specify the respective header fields of the method
- * call. These are always equal for all of the calls contained within a single #DConfEngineMessage.
- *
- * @reply_type is the expected reply type of the method call. This is also the same for all calls contained
- * within a single #DConfEngineMessage.
- *
- * @n_messages is the number of messages to send.
- *
- * @bus_types and @parameters are both arrays, of length @n_messages. Each element of @bus_type is the bus type
- * to send each method call on and each of @parameters is the body of that call. The reason that there may be
- * several messages is that a single dconf "watch" operation may need to send multiple DBus "AddMatch" calls
- * (and usually to multiple busses).
+typedef struct _DConfEngineCallHandle DConfEngineCallHandle;
+
+typedef enum
+{
+ DCONF_ERROR_FAILED,
+ DCONF_ERROR_NOT_WRITABLE
+} DConfEngineError;
+
+#define DCONF_ERROR (g_quark_from_static_string ("dconf error quark"))
+
+/* These functions need to be implemented by the client library */
+G_GNUC_INTERNAL
+void dconf_engine_dbus_init_for_testing (void);
+
+/* Sends a D-Bus message.
*
- * Each element in @bus_types is either 'y' for system bus or 'e' for session bus.
+ * When the reply comes back, the client library should call
+ * dconf_engine_handle_dbus_reply with the given user_data.
*
- * A #DConfEngineMessage is always stack-allocated by the caller. It must be cleared using
- * dconf_engine_message_destroy() when done. It may be copied using dconf_engine_message_copy().
+ * This is called with the engine lock held. Re-entering the engine
+ * from this function will cause a deadlock.
*/
-typedef struct
-{
- const gchar *bus_name;
- const gchar *object_path;
- const gchar *interface_name;
- const gchar *method_name;
+G_GNUC_INTERNAL
+gboolean dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error);
- gint n_messages;
- GVariant **parameters;
- const gchar *bus_types;
+/* Sends a D-Bus message, synchronously.
+ *
+ * The lock is never held when calling this function (for the sake of
+ * not blocking requests in other threads) but you should have no reason
+ * to re-enter, so don't.
+ */
+G_GNUC_INTERNAL
+GVariant * dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *expected_type,
+ GError **error);
- const GVariantType *reply_type;
-} DConfEngineMessage;
+/* Notifies that a change occured.
+ *
+ * The engine lock is never held when calling this function so it is
+ * safe to run user callbacks or emit signals from this function.
+ */
+G_GNUC_INTERNAL
+void dconf_engine_change_notify (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *changes,
+ const gchar *tag,
+ gpointer user_data);
+/* These functions are implemented by the engine */
G_GNUC_INTERNAL
-void dconf_engine_message_copy (DConfEngineMessage *orig,
- DConfEngineMessage *copy);
+const GVariantType * dconf_engine_call_handle_get_expected_type (DConfEngineCallHandle *handle);
G_GNUC_INTERNAL
-void dconf_engine_message_destroy (DConfEngineMessage *message);
+void dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
+ GVariant *parameters,
+ const GError *error);
G_GNUC_INTERNAL
-DConfEngine * dconf_engine_new (const gchar *profile);
-G_GNUC_INTERNAL
-DConfEngine * dconf_engine_new_for_db (const gchar *db_name);
+void dconf_engine_handle_dbus_signal (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *signal_name,
+ GVariant *parameters);
+
G_GNUC_INTERNAL
-guint64 dconf_engine_get_state (DConfEngine *engine);
+DConfEngine * dconf_engine_new (gpointer user_data,
+ GDestroyNotify free_func);
G_GNUC_INTERNAL
-void dconf_engine_free (DConfEngine *engine);
+void dconf_engine_unref (DConfEngine *engine);
+/* Read API: always handled immediately */
G_GNUC_INTERNAL
-GVariant * dconf_engine_read (DConfEngine *engine,
- const gchar *key);
+guint64 dconf_engine_get_state (DConfEngine *engine);
+
G_GNUC_INTERNAL
-GVariant * dconf_engine_read_default (DConfEngine *engine,
+gboolean dconf_engine_is_writable (DConfEngine *engine,
const gchar *key);
+
G_GNUC_INTERNAL
-GVariant * dconf_engine_read_no_default (DConfEngine *engine,
+GVariant * dconf_engine_read (DConfEngine *engine,
+ DConfChangesetList *read_through,
const gchar *key);
+
G_GNUC_INTERNAL
gchar ** dconf_engine_list (DConfEngine *engine,
- const gchar *path,
+ const gchar *dir,
gint *length);
+/* "Fast" API: all calls return immediately and look like they succeeded (from a local viewpoint) */
G_GNUC_INTERNAL
-void dconf_engine_get_service_info (DConfEngine *engine,
- const gchar **bus_type,
- const gchar **destination,
- const gchar **object_path);
+void dconf_engine_watch_fast (DConfEngine *engine,
+ const gchar *path);
+
G_GNUC_INTERNAL
-gboolean dconf_engine_is_writable (DConfEngine *engine,
- const gchar *name);
+void dconf_engine_unwatch_fast (DConfEngine *engine,
+ const gchar *path);
+
G_GNUC_INTERNAL
-gboolean dconf_engine_write (DConfEngine *engine,
- const gchar *key,
- GVariant *value,
- DConfEngineMessage *message,
+gboolean dconf_engine_change_fast (DConfEngine *engine,
+ DConfChangeset *changeset,
GError **error);
+
+/* Synchronous API: all calls block until completed */
G_GNUC_INTERNAL
-gboolean dconf_engine_write_many (DConfEngine *engine,
- const gchar *prefix,
- const gchar * const *keys,
- GVariant **values,
- DConfEngineMessage *message,
+void dconf_engine_watch_sync (DConfEngine *engine,
+ const gchar *path);
+
+G_GNUC_INTERNAL
+void dconf_engine_unwatch_sync (DConfEngine *engine,
+ const gchar *path);
+
+G_GNUC_INTERNAL
+gboolean dconf_engine_change_sync (DConfEngine *engine,
+ DConfChangeset *changeset,
+ gchar **tag,
GError **error);
G_GNUC_INTERNAL
-void dconf_engine_watch (DConfEngine *engine,
- const gchar *name,
- DConfEngineMessage *message);
-G_GNUC_INTERNAL
-void dconf_engine_unwatch (DConfEngine *engine,
- const gchar *name,
- DConfEngineMessage *message);
-G_GNUC_INTERNAL
-gboolean dconf_engine_decode_notify (DConfEngine *engine,
- const gchar *anti_expose,
- const gchar **prefix,
- const gchar ***keys,
- guint bus_type,
- const gchar *sender,
- const gchar *interface,
- const gchar *member,
- GVariant *body);
-G_GNUC_INTERNAL
-gboolean dconf_engine_decode_writability_notify (const gchar **path,
- const gchar *iface,
- const gchar *method,
- GVariant *body);
+gboolean dconf_engine_has_outstanding (DConfEngine *engine);
+G_GNUC_INTERNAL
+void dconf_engine_sync (DConfEngine *engine);
+
+/* Asynchronous API: not implemented yet (and maybe never?) */
+
#endif /* __dconf_engine_h__ */
diff --git a/gdbus/.gitignore b/gdbus/.gitignore
new file mode 100644
index 0000000..0269bef
--- /dev/null
+++ b/gdbus/.gitignore
@@ -0,0 +1,4 @@
+libdconf-gdbus-filter.a
+libdconf-gdbus-filter-shared.a
+libdconf-gdbus-thread.a
+libdconf-gdbus-thread-shared.a
diff --git a/gdbus/Makefile.am b/gdbus/Makefile.am
new file mode 100644
index 0000000..cee235c
--- /dev/null
+++ b/gdbus/Makefile.am
@@ -0,0 +1,21 @@
+include $(top_srcdir)/Makefile.gtester
+
+noinst_LIBRARIES = \
+ libdconf-gdbus-thread.a \
+ libdconf-gdbus-thread-shared.a \
+ libdconf-gdbus-filter.a \
+ libdconf-gdbus-filter-shared.a
+
+libdconf_gdbus_thread_a_CFLAGS = $(gio_CFLAGS)
+libdconf_gdbus_thread_a_SOURCES = \
+ dconf-gdbus-thread.c
+
+libdconf_gdbus_thread_shared_a_CFLAGS = $(libdconf_gdbus_thread_a_CFLAGS) -fPIC -DPIC
+libdconf_gdbus_thread_shared_a_SOURCES = $(libdconf_gdbus_thread_a_SOURCES)
+
+libdconf_gdbus_filter_a_CFLAGS = $(gio_CFLAGS)
+libdconf_gdbus_filter_a_SOURCES = \
+ dconf-gdbus-filter.c
+
+libdconf_gdbus_filter_shared_a_CFLAGS = $(libdconf_gdbus_filter_a_CFLAGS) -fPIC -DPIC
+libdconf_gdbus_filter_shared_a_SOURCES = $(libdconf_gdbus_filter_a_SOURCES)
diff --git a/gdbus/dconf-gdbus-filter.c b/gdbus/dconf-gdbus-filter.c
new file mode 100644
index 0000000..e1447b1
--- /dev/null
+++ b/gdbus/dconf-gdbus-filter.c
@@ -0,0 +1,311 @@
+#include "../engine/dconf-engine.h"
+
+
+
+
+typedef struct
+{
+ gpointer data; /* either GDBusConnection or GError */
+ guint is_error;
+ guint waiting_for_serial;
+ GQueue queue;
+} ConnectionState;
+
+typedef struct
+{
+ guint32 serial;
+ DConfEngineCallHandle *handle;
+} DConfGDBusCall;
+
+static ConnectionState connections[3];
+static GMutex dconf_gdbus_lock;
+
+static GBusType
+connection_state_get_bus_type (ConnectionState *state)
+{
+ return state - connections;
+}
+
+static gboolean
+connection_state_ensure_success (ConnectionState *state,
+ GError **error)
+{
+ if (state->is_error)
+ {
+ if (error)
+ *error = g_error_copy (state->data);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GDBusConnection *
+connection_state_get_connection (ConnectionState *state)
+{
+ g_assert (!state->is_error);
+
+ return state->data;
+}
+
+/* This function can be slow (as compared to the one below). */
+static void
+dconf_gdbus_handle_reply (ConnectionState *state,
+ GDBusMessage *message)
+{
+ DConfEngineCallHandle *handle;
+ GError *error = NULL;
+ GVariant *body;
+
+ g_mutex_lock (&dconf_gdbus_lock);
+ {
+ DConfGDBusCall *call;
+
+ call = g_queue_pop_head (&state->queue);
+ g_assert_cmpuint (g_dbus_message_get_reply_serial (message), ==, call->serial);
+ handle = call->handle;
+
+ g_slice_free (DConfGDBusCall, call);
+
+ call = g_queue_peek_head (&state->queue);
+ if (call)
+ g_atomic_int_set (&state->waiting_for_serial, call->serial);
+ else
+ g_atomic_int_set (&state->waiting_for_serial, -1);
+ }
+ g_mutex_unlock (&dconf_gdbus_lock);
+
+ body = g_dbus_message_get_body (message);
+
+ if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_ERROR)
+ {
+ const GVariantType *first_child_type;
+ const gchar *error_message = NULL;
+
+ first_child_type = g_variant_type_first (g_variant_get_type (body));
+
+ if (g_variant_type_equal (first_child_type, G_VARIANT_TYPE_STRING))
+ g_variant_get_child (body, 0, "&s", &error_message);
+
+ error = g_dbus_error_new_for_dbus_error (g_dbus_message_get_error_name (message), error_message);
+ body = NULL;
+ }
+
+ dconf_engine_call_handle_reply (handle, body, error);
+
+ if (error)
+ g_error_free (error);
+}
+
+/* We optimise for this function being super-efficient since it gets run
+ * on every single D-Bus message in or out.
+ *
+ * We want to bail out as quickly as possible in the case that this
+ * message does not interest us. That means we should not hold locks or
+ * anything like that.
+ *
+ * In the case that this message _does_ interest us (which should be
+ * rare) we can take a lot more time.
+ */
+static GDBusMessage *
+dconf_gdbus_filter_function (GDBusConnection *connection,
+ GDBusMessage *message,
+ gboolean incoming,
+ gpointer user_data)
+{
+ ConnectionState *state = user_data;
+
+ if (incoming)
+ {
+ switch (g_dbus_message_get_message_type (message))
+ {
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ {
+ const gchar *interface;
+
+ interface = g_dbus_message_get_interface (message);
+ if (interface && g_str_equal (interface, "ca.desrt.dconf.Writer"))
+ dconf_engine_handle_dbus_signal (connection_state_get_bus_type (state),
+ g_dbus_message_get_sender (message),
+ g_dbus_message_get_path (message),
+ g_dbus_message_get_member (message),
+ g_dbus_message_get_body (message));
+
+ /* Others could theoretically be interested in this... */
+ }
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ if G_UNLIKELY (g_dbus_message_get_reply_serial (message) == g_atomic_int_get (&state->waiting_for_serial))
+ {
+ /* This is definitely for us. */
+ dconf_gdbus_handle_reply (state, message);
+
+ /* Nobody else should be interested in it. */
+ g_clear_object (&message);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return message;
+}
+
+static ConnectionState *
+dconf_gdbus_get_connection_state (GBusType bus_type,
+ GError **error)
+{
+ ConnectionState *state;
+
+ g_assert (bus_type < G_N_ELEMENTS (connections));
+
+ state = &connections[bus_type];
+
+ if (g_once_init_enter (&state->data))
+ {
+ GDBusConnection *connection;
+ GError *error = NULL;
+ gpointer result;
+
+ /* This will only block the first time...
+ *
+ * Optimising this away is probably not worth the effort.
+ */
+ connection = g_bus_get_sync (bus_type, NULL, &error);
+
+ if (connection)
+ {
+ g_dbus_connection_add_filter (connection, dconf_gdbus_filter_function, state, NULL);
+ result = connection;
+ state->is_error = FALSE;
+ }
+ else
+ {
+ result = error;
+ state->is_error = TRUE;
+ }
+
+ g_once_init_leave (&state->data, result);
+ }
+
+ if (!connection_state_ensure_success (state, error))
+ return FALSE;
+
+ return state;
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error)
+{
+ ConnectionState *state;
+ GDBusMessage *message;
+ DConfGDBusCall *call;
+ gboolean success;
+
+ state = dconf_gdbus_get_connection_state (bus_type, error);
+
+ if (state == NULL)
+ {
+ g_variant_unref (g_variant_ref_sink (parameters));
+ return FALSE;
+ }
+
+ message = g_dbus_message_new_method_call (bus_name, object_path, interface_name, method_name);
+ g_dbus_message_set_body (message, parameters);
+
+ g_mutex_lock (&dconf_gdbus_lock);
+ {
+ volatile guint *serial_ptr;
+ guint my_serial;
+
+ /* We need to set the serial in call->serial. Sometimes we also
+ * need to set it in state->waiting_for_serial (in the case that no
+ * other items are queued yet).
+ *
+ * g_dbus_connection_send_message() only has one out_serial parameter
+ * so we can only set one of them atomically. If needed, we elect
+ * to set the waiting_for_serial because that is the one that is
+ * accessed from the filter function without holding the lock.
+ *
+ * The serial number in the call structure is only accessed after the
+ * lock is acquired which allows us to take our time setting it (for
+ * as long as we're still holding the lock).
+ *
+ * In the case that waiting_for_serial should not be set we just use
+ * a local variable and use that to fill call->serial.
+ *
+ * Also: the queue itself isn't accessed until after the lock is
+ * taken, so we can delay adding the call to the queue until we know
+ * that the sending of the message was successful.
+ */
+
+ if (g_queue_is_empty (&state->queue))
+ serial_ptr = &state->waiting_for_serial;
+ else
+ serial_ptr = &my_serial;
+
+ success = g_dbus_connection_send_message (connection_state_get_connection (state), message,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE, serial_ptr, error);
+
+ if (success)
+ {
+ call = g_slice_new (DConfGDBusCall);
+
+ call->handle = handle;
+ call->serial = *serial_ptr;
+
+ g_queue_push_tail (&state->queue, call);
+ }
+ }
+ g_mutex_unlock (&dconf_gdbus_lock);
+
+ g_object_unref (message);
+
+ return success;
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GError **error)
+{
+ ConnectionState *state;
+
+ state = dconf_gdbus_get_connection_state (bus_type, error);
+
+ if (state == NULL)
+ {
+ g_variant_unref (g_variant_ref_sink (parameters));
+
+ return NULL;
+ }
+
+ return g_dbus_connection_call_sync (connection_state_get_connection (state),
+ bus_name, object_path, interface_name, method_name, parameters, reply_type,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+}
+
+#ifndef PIC
+void
+dconf_engine_dbus_init_for_testing (void)
+{
+ g_type_init ();
+}
+#endif
diff --git a/gdbus/dconf-gdbus-thread.c b/gdbus/dconf-gdbus-thread.c
new file mode 100644
index 0000000..e3d69d8
--- /dev/null
+++ b/gdbus/dconf-gdbus-thread.c
@@ -0,0 +1,357 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "../engine/dconf-engine.h"
+
+/* We interact with GDBus using a worker thread just for dconf.
+ *
+ * We want to have a worker thread that's not the main thread for one
+ * main reason: we don't want to have all of our incoming signals and
+ * method call replies being delivered via the default main context
+ * (which may blocked or simply not running at all).
+ *
+ * The only question is if we should have our own thread or share the
+ * GDBus worker thread. This file takes the approach that we should
+ * have our own thread. See "dconf-gdbus-filter.c" for an approach that
+ * shares the worker thread with GDBus.
+ *
+ * We gain at least one advantage here that we cannot gain any other way
+ * (including sharing a worker thread with GDBus): fast startup.
+ *
+ * The first thing that happens when GSettings comes online is a D-Bus
+ * call to establish a watch. We have to bring up the GDBusConnection.
+ * There are two ways to do that: sync and async.
+ *
+ * We can't do either of those in GDBus's worker thread (since it
+ * doesn't exist yet). We can't do async in the main thread because the
+ * user may not be running the mainloop (as is the case for the
+ * commandline tool, for example).
+ *
+ * That leaves only one option: synchronous initialisation in the main
+ * thread. That's what the "dconf-gdbus-filter" variant of this code
+ * does, and it's slower because of it.
+ *
+ * If we have our own worker thread then we can punt synchronous
+ * initialisation of the bus to it and return immediately.
+ *
+ * We also gain the advantage that the dconf worker thread and the GDBus
+ * worker thread can both be doing work at the same time. This
+ * advantage is probably quite marginal (and is likely outweighed by the
+ * cost of all the punting around of messages between threads).
+ */
+
+typedef struct
+{
+ GBusType bus_type;
+ const gchar *bus_name;
+ const gchar *object_path;
+ const gchar *interface_name;
+ const gchar *method_name;
+ GVariant *parameters;
+ DConfEngineCallHandle *handle;
+} DConfGDBusCall;
+
+static gpointer
+dconf_gdbus_worker_thread (gpointer user_data)
+{
+ GMainContext *context = user_data;
+
+ g_main_context_push_thread_default (context);
+
+ for (;;)
+ g_main_context_iteration (context, TRUE);
+
+ /* srsly, gcc? */
+ return NULL;
+}
+
+static GMainContext *
+dconf_gdbus_get_worker_context (void)
+{
+ static GMainContext *worker_context;
+
+ if (g_once_init_enter (&worker_context))
+ {
+ GMainContext *context;
+
+ context = g_main_context_new ();
+ g_thread_new ("dconf worker", dconf_gdbus_worker_thread, context);
+ g_once_init_leave (&worker_context, context);
+ }
+
+ return worker_context;
+}
+
+static void
+dconf_gdbus_signal_handler (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+ dconf_engine_handle_dbus_signal (bus_type, sender_name, object_path, signal_name, parameters);
+}
+
+/* The code to create and initialise the GDBusConnection for a
+ * particular bus type is more complicated than it should be.
+ *
+ * The complication comes from the fact that we must call
+ * g_dbus_connection_signal_subscribe() from the thread in which the
+ * signal handler will run (which in our case is the worker thread).
+ * g_main_context_push_thread_default() attempts to acquire the context,
+ * preventing us from temporarily pushing the worker's context just for
+ * the sake of setting up the subscription.
+ *
+ * We therefore always create the bus connection from the worker thread.
+ * For requests that are already in the worker thread this is a pretty
+ * simple affair.
+ *
+ * For requests in other threads (ie: synchronous calls) we have to poke
+ * the worker to instantiate the bus for us (if it doesn't already
+ * exist). We do that by using g_main_context_invoke() to schedule a
+ * dummy request in the worker and then we wait on a GCond until we see
+ * that the bus has been created.
+ *
+ * An attempt to get a particular bus can go one of two ways:
+ *
+ * - success: we end up with a GDBusConnection.
+ *
+ * - failure: we end up with a GError.
+ *
+ * One way or another we put the result in dconf_gdbus_get_bus_data[] so
+ * that we only have one pointer value to check. We know what type of
+ * result it is by dconf_gdbus_get_bus_is_error[].
+ */
+
+static GMutex dconf_gdbus_get_bus_lock;
+static GCond dconf_gdbus_get_bus_cond;
+static gpointer dconf_gdbus_get_bus_data[5];
+static gboolean dconf_gdbus_get_bus_is_error[5];
+
+static GDBusConnection *
+dconf_gdbus_get_bus_common (GBusType bus_type,
+ const GError **error)
+{
+ if (dconf_gdbus_get_bus_is_error[bus_type])
+ {
+ if (error)
+ *error = dconf_gdbus_get_bus_data[bus_type];
+
+ return NULL;
+ }
+
+ return dconf_gdbus_get_bus_data[bus_type];
+}
+
+static GDBusConnection *
+dconf_gdbus_get_bus_in_worker (GBusType bus_type,
+ const GError **error)
+{
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
+
+ /* We're in the worker thread and only the worker thread can ever set
+ * this variable so there is no need to take a lock.
+ */
+ if (dconf_gdbus_get_bus_data[bus_type] == NULL)
+ {
+ GDBusConnection *connection;
+ GError *error = NULL;
+ gpointer result;
+
+ connection = g_bus_get_sync (bus_type, NULL, &error);
+
+ if (connection)
+ {
+ g_dbus_connection_signal_subscribe (connection, NULL, "ca.desrt.dconf.Writer",
+ NULL, NULL, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ dconf_gdbus_signal_handler, GINT_TO_POINTER (bus_type), NULL);
+ dconf_gdbus_get_bus_is_error[bus_type] = FALSE;
+ result = connection;
+ }
+ else
+ {
+ dconf_gdbus_get_bus_is_error[bus_type] = TRUE;
+ result = error;
+ }
+
+ g_assert (result != NULL);
+
+ /* It's possible that another thread was waiting for us to do
+ * this on its behalf. Wake it up.
+ *
+ * The other thread cannot actually wake up until we release the
+ * mutex below so we have a guarantee that this CPU will have
+ * flushed all outstanding writes. The other CPU has to acquire
+ * the lock so it cannot have done any out-of-order reads either.
+ */
+ g_mutex_lock (&dconf_gdbus_get_bus_lock);
+ dconf_gdbus_get_bus_data[bus_type] = result;
+ g_cond_broadcast (&dconf_gdbus_get_bus_cond);
+ g_mutex_unlock (&dconf_gdbus_get_bus_lock);
+ }
+
+ return dconf_gdbus_get_bus_common (bus_type, error);
+}
+
+static void
+dconf_gdbus_method_call_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (source);
+ DConfEngineCallHandle *handle = user_data;
+ GError *error = NULL;
+ GVariant *reply;
+
+ reply = g_dbus_connection_call_finish (connection, result, &error);
+ dconf_engine_call_handle_reply (handle, reply, error);
+ g_clear_pointer (&reply, g_variant_unref);
+ g_clear_error (&error);
+}
+
+static gboolean
+dconf_gdbus_method_call (gpointer user_data)
+{
+ DConfGDBusCall *call = user_data;
+ GDBusConnection *connection;
+ const GError *error = NULL;
+
+ connection = dconf_gdbus_get_bus_in_worker (call->bus_type, &error);
+
+ if (connection)
+ g_dbus_connection_call (connection, call->bus_name, call->object_path, call->interface_name,
+ call->method_name, call->parameters, NULL, G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, dconf_gdbus_method_call_done, call->handle);
+
+ else
+ dconf_engine_call_handle_reply (call->handle, NULL, error);
+
+ g_variant_unref (call->parameters);
+ g_slice_free (DConfGDBusCall, call);
+
+ return FALSE;
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error)
+{
+ DConfGDBusCall *call;
+
+ call = g_slice_new (DConfGDBusCall);
+ call->bus_type = bus_type;
+ call->bus_name = bus_name;
+ call->object_path = object_path;
+ call->interface_name = interface_name;
+ call->method_name = method_name;
+ call->parameters = g_variant_ref_sink (parameters);
+ call->handle = handle;
+
+ g_main_context_invoke (dconf_gdbus_get_worker_context (), dconf_gdbus_method_call, call);
+
+ return TRUE;
+}
+
+/* Dummy function to force the bus into existence in the worker. */
+static gboolean
+dconf_gdbus_summon_bus (gpointer user_data)
+{
+ GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+ dconf_gdbus_get_bus_in_worker (bus_type, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static GDBusConnection *
+dconf_gdbus_get_bus_for_sync (GBusType bus_type,
+ const GError **error)
+{
+ g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
+
+ /* I'm not 100% sure we have to lock as much as we do here, but let's
+ * play it safe.
+ *
+ * This codepath is only hit on synchronous calls anyway. You're
+ * probably not doing those if you care a lot about performance.
+ */
+ g_mutex_lock (&dconf_gdbus_get_bus_lock);
+ if (dconf_gdbus_get_bus_data[bus_type] == NULL)
+ {
+ g_main_context_invoke (dconf_gdbus_get_worker_context (),
+ dconf_gdbus_summon_bus,
+ GINT_TO_POINTER (bus_type));
+
+ while (dconf_gdbus_get_bus_data[bus_type] == NULL)
+ g_cond_wait (&dconf_gdbus_get_bus_cond, &dconf_gdbus_get_bus_lock);
+ }
+ g_mutex_unlock (&dconf_gdbus_get_bus_lock);
+
+ return dconf_gdbus_get_bus_common (bus_type, error);
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GError **error)
+{
+ const GError *inner_error = NULL;
+ GDBusConnection *connection;
+
+ connection = dconf_gdbus_get_bus_for_sync (bus_type, &inner_error);
+
+ if (connection == NULL)
+ {
+ g_variant_unref (g_variant_ref_sink (parameters));
+
+ if (error)
+ *error = g_error_copy (inner_error);
+
+ return NULL;
+ }
+
+ return g_dbus_connection_call_sync (connection, bus_name, object_path, interface_name, method_name,
+ parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+}
+
+#ifndef PIC
+void
+dconf_engine_dbus_init_for_testing (void)
+{
+ g_type_init ();
+}
+#endif
diff --git a/gsettings/Makefile.am b/gsettings/Makefile.am
index ace7da0..2a2be8f 100644
--- a/gsettings/Makefile.am
+++ b/gsettings/Makefile.am
@@ -1,16 +1,18 @@
-AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -fPIC -DPIC
-INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(gio_CFLAGS)
+include $(top_srcdir)/Makefile.gtester
giomodules_PROGRAMS = libdconfsettings.so
-libdconfsettings_so_LDADD = $(gio_LIBS)
+libdconfsettings_so_LDADD = \
+ ../engine/libdconf-engine-shared.a \
+ ../gvdb/libgvdb-shared.a \
+ ../gdbus/libdconf-gdbus-thread-shared.a \
+ ../common/libdconf-common-hidden.a \
+ ../shm/libdconf-shm-shared.a \
+ $(gio_LIBS)
+
+libdconfsettings_so_CFLAGS = $(gio_CFLAGS) -fPIC -DPIC
libdconfsettings_so_LDFLAGS = -shared
libdconfsettings_so_SOURCES = \
- ../engine/dconf-engine.c \
- ../common/dconf-shmdir.c \
- ../gvdb/gvdb-reader.c \
- dconfcontext.h \
- dconfcontext.c \
dconfsettingsbackend.c
uninstall-hook:
@@ -22,3 +24,6 @@ install-data-hook:
if test -z "$(DESTDIR)" -a "$(gio_QUERYMODULES)" != "no" ; then \
$(gio_QUERYMODULES) $(giomodulesdir) ; \
fi
+
+TESTS = abicheck.sh
+EXTRA_DIST = abicheck.sh
diff --git a/gsettings/abicheck.sh b/gsettings/abicheck.sh
new file mode 100755
index 0000000..c7b8154
--- /dev/null
+++ b/gsettings/abicheck.sh
@@ -0,0 +1,2 @@
+nm --dynamic --defined-only libdconfsettings.so > public-abi
+test "`cat public-abi | cut -f 3 -d ' ' | grep -v ^_ | grep -v ^g_io_module | wc -l`" -eq 0 && rm public-abi
diff --git a/gsettings/dconfcontext.c b/gsettings/dconfcontext.c
deleted file mode 100644
index 5e11980..0000000
--- a/gsettings/dconfcontext.c
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "dconfcontext.h"
-
-static gpointer
-dconf_context_thread (gpointer data)
-{
- GMainContext *context = data;
- GMainLoop *loop;
-
- g_main_context_push_thread_default (context);
- loop = g_main_loop_new (context, FALSE);
- g_main_loop_run (loop);
-
- g_assert_not_reached ();
-}
-
-GMainContext *
-dconf_context_get (void)
-{
- static GMainContext *context;
- static gsize initialised;
-
- if (g_once_init_enter (&initialised))
- {
- GThread *thread;
-
- context = g_main_context_new ();
- thread = g_thread_new ("dconf worker", dconf_context_thread, context);
- g_thread_unref (thread);
- g_once_init_leave (&initialised, 1);
- }
-
- return context;
-}
diff --git a/gsettings/dconfcontext.h b/gsettings/dconfcontext.h
deleted file mode 100644
index 42d40a6..0000000
--- a/gsettings/dconfcontext.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef _dconfcontext_h_
-#define _dconfcontext_h_
-
-#include <glib.h>
-
-G_GNUC_INTERNAL
-GMainContext * dconf_context_get (void);
-
-#endif
diff --git a/gsettings/dconfsettingsbackend.c b/gsettings/dconfsettingsbackend.c
index cdf0550..11c123c 100644
--- a/gsettings/dconfsettingsbackend.c
+++ b/gsettings/dconfsettingsbackend.c
@@ -21,419 +21,21 @@
#define G_SETTINGS_ENABLE_BACKEND
#include <gio/gsettingsbackend.h>
-#include <dconf-engine.h>
+#include "../engine/dconf-engine.h"
#include <gio/gio.h>
#include <string.h>
-#include "dconfcontext.h"
-
typedef GSettingsBackendClass DConfSettingsBackendClass;
-typedef struct _Outstanding Outstanding;
typedef struct
{
GSettingsBackend backend;
-
- GDBusConnection *session_bus;
- GDBusConnection *system_bus;
- guint session_subscription;
- guint system_subscription;
-
- Outstanding *outstanding;
- gchar *anti_expose_tag;
-
- DConfEngine *engine;
- GMutex lock;
- GCond sync_cond;
- gint sync_waiters;
+ DConfEngine *engine;
} DConfSettingsBackend;
static GType dconf_settings_backend_get_type (void);
-G_DEFINE_TYPE (DConfSettingsBackend,
- dconf_settings_backend,
- G_TYPE_SETTINGS_BACKEND)
-
-static void
-dconf_settings_backend_signal (GDBusConnection *connection,
- const gchar *sender_name,
- const gchar *object_path,
- const gchar *interface_name,
- const gchar *signal_name,
- GVariant *parameters,
- gpointer user_data)
-{
- DConfSettingsBackend *dcsb = user_data;
- const gchar *anti_expose;
- const gchar **rels;
- const gchar *path;
- gchar bus_type;
-
- if (connection == dcsb->session_bus)
- {
- anti_expose = dcsb->anti_expose_tag;
- bus_type = 'e';
- }
-
- else if (connection == dcsb->system_bus)
- {
- anti_expose = NULL;
- bus_type = 'y';
- }
-
- else
- g_assert_not_reached ();
-
- if (dconf_engine_decode_notify (dcsb->engine, anti_expose,
- &path, &rels, bus_type,
- sender_name, interface_name,
- signal_name, parameters))
- {
- GSettingsBackend *backend = G_SETTINGS_BACKEND (dcsb);
-
- if (!g_str_has_suffix (path, "/"))
- g_settings_backend_changed (backend, path, NULL);
-
- else if (rels[0] == NULL)
- g_settings_backend_path_changed (backend, path, NULL);
-
- else
- g_settings_backend_keys_changed (backend, path, rels, NULL);
-
- g_free (rels);
- }
-
- if (dconf_engine_decode_writability_notify (&path, interface_name,
- signal_name, parameters))
- {
- GSettingsBackend *backend = G_SETTINGS_BACKEND (dcsb);
-
- if (g_str_has_suffix (path, "/"))
- {
- g_settings_backend_path_writable_changed (backend, path);
- g_settings_backend_path_changed (backend, path, NULL);
- }
-
- else
- {
- g_settings_backend_writable_changed (backend, path);
- g_settings_backend_changed (backend, path, NULL);
- }
- }
-}
-
-static void
-dconf_settings_backend_send (DConfSettingsBackend *dcsb,
- DConfEngineMessage *dcem,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- GDBusConnection *connection;
- gint i;
-
- for (i = 0; i < dcem->n_messages; i++)
- {
- GError *error = NULL;
-
- switch (dcem->bus_types[i])
- {
- case 'e':
- if (dcsb->session_bus == NULL && callback)
- {
- dcsb->session_bus =
- g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
-
- if (dcsb->session_bus != NULL)
- dcsb->session_subscription =
- g_dbus_connection_signal_subscribe (dcsb->session_bus, NULL,
- "ca.desrt.dconf.Writer",
- NULL, NULL, NULL,
- G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
- dconf_settings_backend_signal,
- dcsb, NULL);
- }
- connection = dcsb->session_bus;
- break;
-
- case 'y':
- if (dcsb->system_bus == NULL && callback)
- {
- dcsb->system_bus =
- g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
-
- if (dcsb->system_bus != NULL)
- dcsb->system_subscription =
- g_dbus_connection_signal_subscribe (dcsb->system_bus, NULL,
- "ca.desrt.dconf.Writer",
- NULL, NULL, NULL,
- G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
- dconf_settings_backend_signal,
- dcsb, NULL);
- }
- connection = dcsb->system_bus;
- break;
-
- default:
- g_assert_not_reached ();
- }
-
- if (connection == NULL && callback != NULL)
- {
- g_assert (error != NULL);
-
- g_warning ("%s", error->message);
- g_error_free (error);
-
- callback (NULL, NULL, user_data);
- }
-
- if (connection != NULL)
- g_dbus_connection_call (connection,
- dcem->bus_name,
- dcem->object_path,
- dcem->interface_name,
- dcem->method_name,
- dcem->parameters[i],
- dcem->reply_type,
- 0, 120000, NULL, callback, user_data);
- }
-}
-
-static GVariant *
-dconf_settings_backend_send_finish (GObject *source,
- GAsyncResult *result)
-{
- GError *error = NULL;
- GVariant *reply;
-
- if (source == NULL)
- return NULL;
-
- reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
- result, &error);
-
- if (reply == NULL)
- {
- /* This should only be hit in the case that something is seriously
- * wrong with the installation (ie: the service can't be started,
- * etc). Bug #641768 requests some notification of these kinds of
- * situations in the context of the gsettings(1) commandline tool,
- * so a g_warning() is appropriate here.
- */
-
- g_warning ("%s", error->message);
- g_error_free (error);
- }
-
- return reply;
-}
-
-struct _Outstanding
-{
- Outstanding *next;
-
- DConfSettingsBackend *dcsb;
- DConfEngineMessage dcem;
-
- gchar *set_key;
- GVariant *set_value;
- GTree *tree;
-};
-
-static void
-dconf_settings_backend_outstanding_returned (GObject *source,
- GAsyncResult *result,
- gpointer user_data)
-{
- Outstanding *outstanding = user_data;
- DConfSettingsBackend *dcsb;
- GVariant *reply;
-
- dcsb = outstanding->dcsb;
-
- /* One way or another we no longer need this hooked into the list.
- */
- g_mutex_lock (&dcsb->lock);
- {
- Outstanding **tmp;
-
- for (tmp = &dcsb->outstanding; tmp; tmp = &(*tmp)->next)
- if (*tmp == outstanding)
- {
- *tmp = outstanding->next;
- break;
- }
-
- if (dcsb->outstanding == NULL && dcsb->sync_waiters)
- g_cond_broadcast (&dcsb->sync_cond);
- }
- g_mutex_unlock (&dcsb->lock);
-
- reply = dconf_settings_backend_send_finish (source, result);
-
- if (reply)
- {
- /* Success.
- *
- * We want to ensure that we don't emit an extra change
- * notification signal when we see the signal that the service is
- * about to send, so store the tag so we know to ignore it when
- * the signal comes.
- *
- * No thread safety issue here since this variable is only
- * accessed from the worker thread.
- */
- g_free (dcsb->anti_expose_tag);
- g_variant_get_child (reply, 0, "s", &dcsb->anti_expose_tag);
- g_variant_unref (reply);
- }
- else
- {
- /* An error of some kind.
- *
- * We already removed the outstanding entry from the list, so the
- * unmodified database is now visible to the client. Change
- * notify so that they see it.
- */
- if (outstanding->set_key)
- g_settings_backend_changed (G_SETTINGS_BACKEND (dcsb),
- outstanding->set_key, NULL);
-
- else
- g_settings_backend_changed_tree (G_SETTINGS_BACKEND (dcsb),
- outstanding->tree, NULL);
- }
-
- dconf_engine_message_destroy (&outstanding->dcem);
- g_object_unref (outstanding->dcsb);
- g_free (outstanding->set_key);
-
- if (outstanding->set_value)
- g_variant_unref (outstanding->set_value);
-
- if (outstanding->tree)
- g_tree_unref (outstanding->tree);
-
- g_slice_free (Outstanding, outstanding);
-}
-
-static gboolean
-dconf_settings_backend_send_outstanding (gpointer data)
-{
- Outstanding *outstanding = data;
-
- dconf_settings_backend_send (outstanding->dcsb,
- &outstanding->dcem,
- dconf_settings_backend_outstanding_returned,
- outstanding);
-
- return FALSE;
-}
-
-static void
-dconf_settings_backend_queue (DConfSettingsBackend *dcsb,
- DConfEngineMessage *dcem,
- const gchar *set_key,
- GVariant *set_value,
- GTree *tree)
-{
- Outstanding *outstanding;
-
- outstanding = g_slice_new (Outstanding);
- outstanding->dcsb = g_object_ref (dcsb);
- outstanding->dcem = *dcem;
-
- outstanding->set_key = g_strdup (set_key);
- outstanding->set_value = set_value ? g_variant_ref_sink (set_value) : NULL;
- outstanding->tree = tree ? g_tree_ref (tree) : NULL;
-
- g_mutex_lock (&dcsb->lock);
- outstanding->next = dcsb->outstanding;
- dcsb->outstanding = outstanding;
- g_mutex_unlock (&dcsb->lock);
-
- g_main_context_invoke (dconf_context_get (),
- dconf_settings_backend_send_outstanding,
- outstanding);
-}
-
-static gboolean
-dconf_settings_backend_scan_outstanding_tree (GTree *tree,
- const gchar *key,
- gsize key_length,
- gpointer *value)
-{
- gchar *mykey;
-
- mykey = g_alloca (key_length + 1);
- memcpy (mykey, key, key_length + 1);
-
- while (!g_tree_lookup_extended (tree, mykey, NULL, value) &&
- --key_length)
- {
- while (mykey[key_length - 1] != '/')
- key_length--;
-
- mykey[key_length] = '\0';
- }
-
- return key_length != 0;
-}
-
-static gboolean
-dconf_settings_backend_scan_outstanding (DConfSettingsBackend *backend,
- const gchar *key,
- GVariant **value)
-{
- gboolean found = FALSE;
- Outstanding *node;
- gsize length;
-
- length = strlen (key);
-
- if G_LIKELY (backend->outstanding == NULL)
- return FALSE;
-
- g_mutex_lock (&backend->lock);
-
- for (node = backend->outstanding; node; node = node->next)
- {
- if (node->set_key)
- {
- if (strcmp (key, node->set_key) == 0)
- {
- if (node->set_value != NULL)
- *value = g_variant_ref (node->set_value);
- else
- *value = NULL;
-
- found = TRUE;
- break;
- }
- }
-
- else
- {
- gpointer result;
-
- if (dconf_settings_backend_scan_outstanding_tree (node->tree, key,
- length, &result))
- {
- if (result)
- *value = g_variant_ref (result);
- else
- *value = NULL;
-
- found = TRUE;
- break;
- }
- }
- }
-
- g_mutex_unlock (&backend->lock);
-
- return found;
-}
+G_DEFINE_TYPE (DConfSettingsBackend, dconf_settings_backend, G_TYPE_SETTINGS_BACKEND)
static GVariant *
dconf_settings_backend_read (GSettingsBackend *backend,
@@ -443,17 +45,9 @@ dconf_settings_backend_read (GSettingsBackend *backend,
{
DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
- if (!default_value)
- {
- GVariant *value;
-
- if (dconf_settings_backend_scan_outstanding (dcsb, key, &value))
- return value;
+ /* XXX default value */
- return dconf_engine_read (dcsb->engine, key);
- }
- else
- return dconf_engine_read_default (dcsb->engine, key);
+ return dconf_engine_read (dcsb->engine, NULL, key);
}
static gboolean
@@ -463,15 +57,26 @@ dconf_settings_backend_write (GSettingsBackend *backend,
gpointer origin_tag)
{
DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
- DConfEngineMessage dcem;
+ DConfChangeset *change;
+ gboolean success;
- if (!dconf_engine_write (dcsb->engine, key, value, &dcem, NULL))
- return FALSE;
+ change = dconf_changeset_new ();
+ dconf_changeset_set (change, key, value);
- dconf_settings_backend_queue (dcsb, &dcem, key, value, NULL);
- g_settings_backend_changed (backend, key, origin_tag);
+ success = dconf_engine_change_fast (dcsb->engine, change, NULL);
+ dconf_changeset_unref (change);
- return TRUE;
+ return success;
+}
+
+static gboolean
+dconf_settings_backend_add_to_changeset (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ dconf_changeset_set (data, key, value);
+
+ return FALSE;
}
static gboolean
@@ -480,24 +85,14 @@ dconf_settings_backend_write_tree (GSettingsBackend *backend,
gpointer origin_tag)
{
DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
- DConfEngineMessage dcem;
- const gchar **keys;
- GVariant **values;
+ DConfChangeset *change;
gboolean success;
- gchar *prefix;
- g_settings_backend_flatten_tree (tree, &prefix, &keys, &values);
-
- if ((success = dconf_engine_write_many (dcsb->engine, prefix,
- keys, values, &dcem, NULL)))
- {
- dconf_settings_backend_queue (dcsb, &dcem, NULL, NULL, tree);
- g_settings_backend_keys_changed (backend, prefix, keys, origin_tag);
- }
+ change= dconf_changeset_new ();
+ g_tree_foreach (tree, dconf_settings_backend_add_to_changeset, change);
- g_free (prefix);
- g_free (values);
- g_free (keys);
+ success = dconf_engine_change_fast (dcsb->engine, change, NULL);
+ dconf_changeset_unref (change);
return success;
}
@@ -519,111 +114,13 @@ dconf_settings_backend_get_writable (GSettingsBackend *backend,
return dconf_engine_is_writable (dcsb->engine, name);
}
-typedef struct
-{
- DConfSettingsBackend *dcsb;
- guint64 state;
- gchar *name;
- gint outstanding;
-} OutstandingWatch;
-
-static OutstandingWatch *
-outstanding_watch_new (DConfSettingsBackend *dcsb,
- const gchar *name)
-{
- OutstandingWatch *watch;
-
- watch = g_slice_new (OutstandingWatch);
- watch->dcsb = g_object_ref (dcsb);
- watch->state = dconf_engine_get_state (dcsb->engine);
- watch->outstanding = 0;
- watch->name = g_strdup (name);
-
- return watch;
-}
-
-static void
-outstanding_watch_free (OutstandingWatch *watch)
-{
- if (--watch->outstanding == 0)
- {
- g_object_unref (watch->dcsb);
- g_free (watch->name);
-
- g_slice_free (OutstandingWatch, watch);
- }
-}
-
-static void
-add_match_done (GObject *source,
- GAsyncResult *result,
- gpointer user_data)
-{
- OutstandingWatch *watch = user_data;
- GError *error = NULL;
- GVariant *reply;
-
- /* couldn't connect to DBus */
- if (source == NULL)
- {
- outstanding_watch_free (watch);
- return;
- }
-
- reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
- result, &error);
-
- if (reply == NULL)
- {
- g_critical ("DBus AddMatch for dconf path '%s': %s",
- watch->name, error->message);
- outstanding_watch_free (watch);
- g_error_free (error);
-
- return;
- }
-
- else
- g_variant_unref (reply); /* it is just an empty tuple */
-
- /* In the normal case we can just free everything and be done.
- *
- * There is a fleeting chance, however, that the database has changed
- * in the meantime. In that case we can only assume that the subject
- * of this watch changed in that time period and emit a signal to that
- * effect.
- */
- if (dconf_engine_get_state (watch->dcsb->engine) != watch->state)
- g_settings_backend_path_changed (G_SETTINGS_BACKEND (watch->dcsb),
- watch->name, NULL);
-
- outstanding_watch_free (watch);
-}
-
-static gboolean
-dconf_settings_backend_subscribe_context_func (gpointer data)
-{
- OutstandingWatch *watch = data;
- DConfEngineMessage dcem;
-
- dconf_engine_watch (watch->dcsb->engine, watch->name, &dcem);
- watch->outstanding = dcem.n_messages;
-
- dconf_settings_backend_send (watch->dcsb, &dcem, add_match_done, watch);
- dconf_engine_message_destroy (&dcem);
-
- return FALSE;
-}
-
static void
dconf_settings_backend_subscribe (GSettingsBackend *backend,
const gchar *name)
{
DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
- g_main_context_invoke (dconf_context_get (),
- dconf_settings_backend_subscribe_context_func,
- outstanding_watch_new (dcsb, name));
+ dconf_engine_watch_fast (dcsb->engine, name);
}
static void
@@ -631,11 +128,8 @@ dconf_settings_backend_unsubscribe (GSettingsBackend *backend,
const gchar *name)
{
DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
- DConfEngineMessage dcem;
- dconf_engine_unwatch (dcsb->engine, name, &dcem);
- dconf_settings_backend_send (dcsb, &dcem, NULL, NULL);
- dconf_engine_message_destroy (&dcem);
+ dconf_engine_unwatch_fast (dcsb->engine, name);
}
static void
@@ -643,30 +137,46 @@ dconf_settings_backend_sync (GSettingsBackend *backend)
{
DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
- if (!dcsb->outstanding)
- return;
-
- g_mutex_lock (&dcsb->lock);
+ dconf_engine_sync (dcsb->engine);
+}
- dcsb->sync_waiters++;
- while (dcsb->outstanding)
- g_cond_wait (&dcsb->sync_cond, &dcsb->lock);
- dcsb->sync_waiters--;
+static void
+dconf_settings_backend_free_weak_ref (gpointer data)
+{
+ GWeakRef *weak_ref = data;
- g_mutex_unlock (&dcsb->lock);
+ g_weak_ref_clear (weak_ref);
+ g_slice_free (GWeakRef, weak_ref);
}
static void
dconf_settings_backend_init (DConfSettingsBackend *dcsb)
{
- dcsb->engine = dconf_engine_new (NULL);
- g_mutex_init (&dcsb->lock);
- g_cond_init (&dcsb->sync_cond);
+ GWeakRef *weak_ref;
+
+ weak_ref = g_slice_new (GWeakRef);
+ g_weak_ref_init (weak_ref, dcsb);
+ dcsb->engine = dconf_engine_new (weak_ref, dconf_settings_backend_free_weak_ref);
+}
+
+static void
+dconf_settings_backend_finalize (GObject *object)
+{
+ DConfSettingsBackend *dcsb = (DConfSettingsBackend *) object;
+
+ dconf_engine_unref (dcsb->engine);
+
+ G_OBJECT_CLASS (dconf_settings_backend_parent_class)
+ ->finalize (object);
}
static void
dconf_settings_backend_class_init (GSettingsBackendClass *class)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = dconf_settings_backend_finalize;
+
class->read = dconf_settings_backend_read;
class->write = dconf_settings_backend_write;
class->write_tree = dconf_settings_backend_write_tree;
@@ -697,3 +207,32 @@ g_io_module_query (void)
{
return g_strsplit (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, "!", 0);
}
+
+void
+dconf_engine_change_notify (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *changes,
+ const gchar *tag,
+ gpointer user_data)
+{
+ GWeakRef *weak_ref = user_data;
+ DConfSettingsBackend *dcsb;
+
+ dcsb = g_weak_ref_get (weak_ref);
+
+ if (dcsb == NULL)
+ return;
+
+ if (changes[0] == NULL)
+ return;
+
+ if (changes[1] == NULL)
+ {
+ if (g_str_has_suffix (prefix, "/"))
+ g_settings_backend_path_changed (G_SETTINGS_BACKEND (dcsb), prefix, NULL);
+ else
+ g_settings_backend_changed (G_SETTINGS_BACKEND (dcsb), prefix, NULL);
+ }
+ else
+ g_settings_backend_keys_changed (G_SETTINGS_BACKEND (dcsb), prefix, changes, NULL);
+}
diff --git a/gvdb/.gitignore b/gvdb/.gitignore
new file mode 100644
index 0000000..8b5dee6
--- /dev/null
+++ b/gvdb/.gitignore
@@ -0,0 +1,2 @@
+libgvdb.a
+libgvdb-shared.a
diff --git a/gvdb/Makefile.am b/gvdb/Makefile.am
index 47e77c8..24aaf1e 100644
--- a/gvdb/Makefile.am
+++ b/gvdb/Makefile.am
@@ -1,6 +1,14 @@
-EXTRA_DIST = \
+include $(top_srcdir)/Makefile.gtester
+
+noinst_LIBRARIES = libgvdb.a libgvdb-shared.a
+
+libgvdb_a_CFLAGS = $(glib_CFLAGS)
+libgvdb_a_SOURCES = \
gvdb-format.h \
- gvdb-reader.h \
- gvdb-reader.c \
gvdb-builder.h \
- gvdb-builder.c
+ gvdb-builder.c \
+ gvdb-reader.h \
+ gvdb-reader.c
+
+libgvdb_shared_a_CFLAGS = $(libgvdb_a_CFLAGS) -fPIC -DPIC
+libgvdb_shared_a_SOURCES = $(libgvdb_a_SOURCES)
diff --git a/gvdb/gvdb-builder.c b/gvdb/gvdb-builder.c
index 91adec6..c63d117 100644
--- a/gvdb/gvdb-builder.c
+++ b/gvdb/gvdb-builder.c
@@ -340,6 +340,13 @@ file_builder_allocate_for_hash (FileBuilder *fb,
#undef chunk
memset (*bloom_filter, 0, n_bloom_words * sizeof (guint32_le));
+
+ /* NOTE - the code to actually fill in the bloom filter here is missing.
+ * Patches welcome!
+ *
+ * http://en.wikipedia.org/wiki/Bloom_filter
+ * http://0pointer.de/blog/projects/bloom.html
+ */
}
static void
diff --git a/gvdb/gvdb-reader.c b/gvdb/gvdb-reader.c
index 57816af..062c427 100644
--- a/gvdb/gvdb-reader.c
+++ b/gvdb/gvdb-reader.c
@@ -30,7 +30,10 @@ struct _GvdbTable {
const gchar *data;
gsize size;
- GMappedFile *mapped;
+ gpointer user_data;
+ GvdbRefFunc ref_user_data;
+ GDestroyNotify unref_user_data;
+
gboolean byteswapped;
gboolean trusted;
@@ -123,6 +126,64 @@ gvdb_table_setup_root (GvdbTable *file,
file->n_hash_items = size / sizeof (struct gvdb_hash_item);
}
+static GvdbTable *
+new_from_data (const void *data,
+ gsize data_len,
+ gboolean trusted,
+ gpointer user_data,
+ GvdbRefFunc ref,
+ GDestroyNotify unref,
+ const char *filename,
+ GError **error)
+{
+ const struct gvdb_header *header;
+ GvdbTable *file;
+
+ file = g_slice_new0 (GvdbTable);
+ file->data = data;
+ file->size = data_len;
+ file->trusted = trusted;
+ file->ref_count = 1;
+ file->ref_user_data = ref;
+ file->unref_user_data = unref;
+ file->user_data = user_data;
+
+ if (file->size < sizeof (struct gvdb_header))
+ goto invalid;
+
+ header = (gpointer) file->data;
+
+ if (header->signature[0] == GVDB_SIGNATURE0 &&
+ header->signature[1] == GVDB_SIGNATURE1 &&
+ guint32_from_le (header->version) == 0)
+ file->byteswapped = FALSE;
+
+ else if (header->signature[0] == GVDB_SWAPPED_SIGNATURE0 &&
+ header->signature[1] == GVDB_SWAPPED_SIGNATURE1 &&
+ guint32_from_le (header->version) == 0)
+ file->byteswapped = TRUE;
+
+ else
+ goto invalid;
+
+ gvdb_table_setup_root (file, &header->root);
+
+ return file;
+
+invalid:
+ if (filename)
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s: invalid header", filename);
+ else
+ g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "invalid gvdb header");
+
+ g_slice_free (GvdbTable, file);
+
+ if (unref)
+ unref (user_data);
+
+ return NULL;
+}
+
/**
* gvdb_table_new:
* @filename: the path to the hash file
@@ -133,7 +194,7 @@ gvdb_table_setup_root (GvdbTable *file,
* Creates a new #GvdbTable from the contents of the file found at
* @filename.
*
- * The only time this function fails is if the file can not be opened.
+ * The only time this function fails is if the file cannot be opened.
* In that case, the #GError that is returned will be an error from
* g_mapped_file_new().
*
@@ -149,46 +210,52 @@ gvdb_table_new (const gchar *filename,
GError **error)
{
GMappedFile *mapped;
- GvdbTable *file;
if ((mapped = g_mapped_file_new (filename, FALSE, error)) == NULL)
return NULL;
- file = g_slice_new0 (GvdbTable);
- file->data = g_mapped_file_get_contents (mapped);
- file->size = g_mapped_file_get_length (mapped);
- file->trusted = trusted;
- file->mapped = mapped;
- file->ref_count = 1;
-
- if (sizeof (struct gvdb_header) <= file->size)
- {
- const struct gvdb_header *header = (gpointer) file->data;
-
- if (header->signature[0] == GVDB_SIGNATURE0 &&
- header->signature[1] == GVDB_SIGNATURE1 &&
- guint32_from_le (header->version) == 0)
- file->byteswapped = FALSE;
-
- else if (header->signature[0] == GVDB_SWAPPED_SIGNATURE0 &&
- header->signature[1] == GVDB_SWAPPED_SIGNATURE1 &&
- guint32_from_le (header->version) == 0)
- file->byteswapped = TRUE;
-
- else
- {
- g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
- "%s: invalid header", filename);
- g_slice_free (GvdbTable, file);
- g_mapped_file_unref (mapped);
-
- return NULL;
- }
-
- gvdb_table_setup_root (file, &header->root);
- }
+ return new_from_data (g_mapped_file_get_contents (mapped),
+ g_mapped_file_get_length (mapped),
+ trusted,
+ mapped,
+ (GvdbRefFunc)g_mapped_file_ref,
+ (GDestroyNotify)g_mapped_file_unref,
+ filename,
+ error);
+}
- return file;
+/**
+ * gvdb_table_new_from_data:
+ * @data: the data
+ * @data_len: the length of @data in bytes
+ * @trusted: if the contents of @data are trusted
+ * @user_data: User supplied data that owns @data
+ * @ref: Ref function for @user_data
+ * @unref: Unref function for @user_data
+ * @returns: a new #GvdbTable
+ *
+ * Creates a new #GvdbTable from the data in @data.
+ *
+ * An empty or otherwise corrupted data is considered to be a valid
+ * #GvdbTable with no entries.
+ *
+ * You should call gvdb_table_unref() on the return result when you no
+ * longer require it.
+ **/
+GvdbTable *
+gvdb_table_new_from_data (const void *data,
+ gsize data_len,
+ gboolean trusted,
+ gpointer user_data,
+ GvdbRefFunc ref,
+ GDestroyNotify unref,
+ GError **error)
+{
+ return new_from_data (data, data_len,
+ trusted,
+ user_data, ref, unref,
+ NULL,
+ error);
}
static gboolean
@@ -281,18 +348,6 @@ gvdb_table_lookup (GvdbTable *file,
return NULL;
}
-static const struct gvdb_hash_item *
-gvdb_table_get_item (GvdbTable *table,
- guint32_le item_no)
-{
- guint32 item_no_native = guint32_from_le (item_no);
-
- if G_LIKELY (item_no_native < table->n_hash_items)
- return table->hash_items + item_no_native;
-
- return NULL;
-}
-
static gboolean
gvdb_table_list_from_item (GvdbTable *table,
const struct gvdb_hash_item *item,
@@ -311,6 +366,155 @@ gvdb_table_list_from_item (GvdbTable *table,
return TRUE;
}
+/**
+ * gvdb_table_get_names:
+ * @table: a #GvdbTable
+ * @length: the number of items returned, or %NULL
+ *
+ * Gets a list of all names contained in @table.
+ *
+ * No call to gvdb_table_get_table(), gvdb_table_list() or
+ * gvdb_table_get_value() will succeed unless it is for one of the
+ * names returned by this function.
+ *
+ * Note that some names that are returned may still fail for all of the
+ * above calls in the case of the corrupted file. Note also that the
+ * returned strings may not be utf8.
+ *
+ * Returns: a %NULL-terminated list of strings, of length @length
+ **/
+gchar **
+gvdb_table_get_names (GvdbTable *table,
+ gint *length)
+{
+ gchar **names;
+ gint n_names;
+ gint filled;
+ gint total;
+ gint i;
+
+ /* We generally proceed by iterating over the list of items in the
+ * hash table (in order of appearance) recording them into an array.
+ *
+ * Each item has a parent item (except root items). The parent item
+ * forms part of the name of the item. We could go fetching the
+ * parent item chain at the point that we encounter each item but then
+ * we would need to implement some sort of recursion along with checks
+ * for self-referential items.
+ *
+ * Instead, we do a number of passes. Each pass will build up one
+ * level of names (starting from the root). We continue to do passes
+ * until no more items are left. The first pass will only add root
+ * items and each further pass will only add items whose direct parent
+ * is an item added in the immediately previous pass. It's also
+ * possible that items get filled if they follow their parent within a
+ * particular pass.
+ *
+ * At most we will have a number of passes equal to the depth of the
+ * tree. Self-referential items will never be filled in (since their
+ * parent will have never been filled in). We continue until we have
+ * a pass that fills in no additional items.
+ *
+ * This takes an O(n) algorithm and turns it into O(n*m) where m is
+ * the depth of the tree, but in all sane cases the tree won't be very
+ * deep and the constant factor of this algorithm is lower (and the
+ * complexity of coding it, as well).
+ */
+
+ n_names = table->n_hash_items;
+ names = g_new0 (gchar *, n_names + 1);
+
+ /* 'names' starts out all-NULL. On each pass we record the number
+ * of items changed from NULL to non-NULL in 'filled' so we know if we
+ * should repeat the loop. 'total' counts the total number of items
+ * filled. If 'total' ends up equal to 'n_names' then we know that
+ * 'names' has been completely filled.
+ */
+
+ total = 0;
+ do
+ {
+ /* Loop until we have filled no more entries */
+ filled = 0;
+
+ for (i = 0; i < n_names; i++)
+ {
+ const struct gvdb_hash_item *item = &table->hash_items[i];
+ const gchar *name;
+ gsize name_length;
+ guint32 parent;
+
+ /* already got it on a previous pass */
+ if (names[i] != NULL)
+ continue;
+
+ parent = guint32_from_le (item->parent);
+
+ if (parent == 0xffffffffu)
+ {
+ /* it's a root item */
+ name = gvdb_table_item_get_key (table, item, &name_length);
+
+ if (name != NULL)
+ {
+ names[i] = g_strndup (name, name_length);
+ filled++;
+ }
+ }
+
+ else if (parent < n_names && names[parent] != NULL)
+ {
+ /* It's a non-root item whose parent was filled in already.
+ *
+ * Calculate the name of this item by combining it with
+ * its parent name.
+ */
+ name = gvdb_table_item_get_key (table, item, &name_length);
+
+ if (name != NULL)
+ {
+ const gchar *parent_name = names[parent];
+ gsize parent_length;
+ gchar *fullname;
+
+ parent_length = strlen (parent_name);
+ fullname = g_malloc (parent_length + name_length + 1);
+ memcpy (fullname, parent_name, parent_length);
+ memcpy (fullname + parent_length, name, name_length);
+ fullname[parent_length + name_length] = '\0';
+ names[i] = fullname;
+ filled++;
+ }
+ }
+ }
+
+ total += filled;
+ }
+ while (filled && total < n_names);
+
+ /* If the table was corrupted then 'names' may have holes in it.
+ * Collapse those.
+ */
+ if G_UNLIKELY (total != n_names)
+ {
+ GPtrArray *fixed_names;
+
+ fixed_names = g_ptr_array_new ();
+ for (i = 0; i < n_names; i++)
+ if (names[i] != NULL)
+ g_ptr_array_add (fixed_names, names[i]);
+
+ g_free (names);
+ n_names = fixed_names->len;
+ g_ptr_array_add (fixed_names, NULL);
+ names = (gchar **) g_ptr_array_free (fixed_names, FALSE);
+ }
+
+ if (length)
+ *length = n_names;
+
+ return names;
+}
/**
* gvdb_table_list:
@@ -390,7 +594,15 @@ gboolean
gvdb_table_has_value (GvdbTable *file,
const gchar *key)
{
- return gvdb_table_lookup (file, key, 'v') != NULL;
+ static const struct gvdb_hash_item *item;
+ gsize size;
+
+ item = gvdb_table_lookup (file, key, 'v');
+
+ if (item == NULL)
+ return FALSE;
+
+ return gvdb_table_dereference (file, &item->value.pointer, 8, &size) != NULL;
}
static GVariant *
@@ -408,8 +620,8 @@ gvdb_table_value_from_item (GvdbTable *table,
variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT,
data, size, table->trusted,
- (GDestroyNotify) g_mapped_file_unref,
- g_mapped_file_ref (table->mapped));
+ table->unref_user_data,
+ table->ref_user_data ? table->ref_user_data (table->user_data) : table->user_data);
value = g_variant_get_variant (variant);
g_variant_unref (variant);
@@ -510,7 +722,9 @@ gvdb_table_get_table (GvdbTable *file,
return NULL;
new = g_slice_new0 (GvdbTable);
- new->mapped = g_mapped_file_ref (file->mapped);
+ new->user_data = file->ref_user_data ? file->ref_user_data (file->user_data) : file->user_data;
+ new->ref_user_data = file->ref_user_data;
+ new->unref_user_data = file->unref_user_data;
new->byteswapped = file->byteswapped;
new->trusted = file->trusted;
new->data = file->data;
@@ -550,7 +764,8 @@ gvdb_table_unref (GvdbTable *file)
{
if (g_atomic_int_dec_and_test (&file->ref_count))
{
- g_mapped_file_unref (file->mapped);
+ if (file->unref_user_data)
+ file->unref_user_data (file->user_data);
g_slice_free (GvdbTable, file);
}
}
@@ -571,105 +786,3 @@ gvdb_table_is_valid (GvdbTable *table)
{
return !!*table->data;
}
-
-/**
- * gvdb_table_walk:
- * @table: a #GvdbTable
- * @key: a key corresponding to a list
- * @open_func: the #GvdbWalkOpenFunc
- * @value_func: the #GvdbWalkValueFunc
- * @close_func: the #GvdbWalkCloseFunc
- * @user_data: data to pass to the callbacks
- *
- * Looks up the list at @key and iterate over the items in it.
- *
- * First, @open_func is called to signal that we are starting to iterate over
- * the list. Then the list is iterated. When all items in the list have been
- * iterated over, the @close_func is called.
- *
- * When iterating, if a given item in the list is a value then @value_func is
- * called.
- *
- * If a given item in the list is itself a list then @open_func is called. If
- * that function returns %TRUE then the walk begins iterating the items in the
- * sublist, until there are no more items, at which point a matching
- * @close_func call is made. If @open_func returns %FALSE then no iteration of
- * the sublist occurs and no corresponding @close_func call is made.
- **/
-void
-gvdb_table_walk (GvdbTable *table,
- const gchar *key,
- GvdbWalkOpenFunc open_func,
- GvdbWalkValueFunc value_func,
- GvdbWalkCloseFunc close_func,
- gpointer user_data)
-{
- const struct gvdb_hash_item *item;
- const guint32_le *pointers[64];
- const guint32_le *enders[64];
- gsize name_lengths[64];
- gint index = 0;
-
- item = gvdb_table_lookup (table, key, 'L');
- name_lengths[0] = 0;
- pointers[0] = NULL;
- enders[0] = NULL;
- goto start_here;
-
- while (index)
- {
- close_func (name_lengths[index], user_data);
- index--;
-
- while (pointers[index] < enders[index])
- {
- const gchar *name;
- gsize name_len;
-
- item = gvdb_table_get_item (table, *pointers[index]++);
- start_here:
-
- if (item != NULL &&
- (name = gvdb_table_item_get_key (table, item, &name_len)))
- {
- if (item->type == 'L')
- {
- if (open_func (name, name_len, user_data))
- {
- guint length;
-
- index++;
- g_assert (index < 64);
-
- gvdb_table_list_from_item (table, item,
- &pointers[index],
- &length);
- enders[index] = pointers[index] + length;
- name_lengths[index] = name_len;
- }
- }
- else if (item->type == 'v')
- {
- GVariant *value;
-
- value = gvdb_table_value_from_item (table, item);
-
- if (value != NULL)
- {
- if (table->byteswapped)
- {
- GVariant *tmp;
-
- tmp = g_variant_byteswap (value);
- g_variant_unref (value);
- value = tmp;
- }
-
- value_func (name, name_len, value, user_data);
- g_variant_unref (value);
- }
- }
- }
- }
- }
-}
diff --git a/gvdb/gvdb-reader.h b/gvdb/gvdb-reader.h
index e6921e9..d6984ac 100644
--- a/gvdb/gvdb-reader.h
+++ b/gvdb/gvdb-reader.h
@@ -26,6 +26,8 @@
typedef struct _GvdbTable GvdbTable;
+typedef gpointer (*GvdbRefFunc) (gpointer data);
+
G_BEGIN_DECLS
G_GNUC_INTERNAL
@@ -33,10 +35,20 @@ GvdbTable * gvdb_table_new (const g
gboolean trusted,
GError **error);
G_GNUC_INTERNAL
+GvdbTable * gvdb_table_new_from_data (const void *data,
+ gsize data_len,
+ gboolean trusted,
+ gpointer user_data,
+ GvdbRefFunc ref,
+ GDestroyNotify unref,
+ GError **error);
+G_GNUC_INTERNAL
GvdbTable * gvdb_table_ref (GvdbTable *table);
G_GNUC_INTERNAL
void gvdb_table_unref (GvdbTable *table);
-
+G_GNUC_INTERNAL
+gchar ** gvdb_table_get_names (GvdbTable *table,
+ gint *length);
G_GNUC_INTERNAL
gchar ** gvdb_table_list (GvdbTable *table,
const gchar *key);
@@ -53,28 +65,9 @@ GVariant * gvdb_table_get_value (GvdbTab
G_GNUC_INTERNAL
gboolean gvdb_table_has_value (GvdbTable *table,
const gchar *key);
-
G_GNUC_INTERNAL
gboolean gvdb_table_is_valid (GvdbTable *table);
-typedef void (*GvdbWalkValueFunc) (const gchar *name,
- gsize name_len,
- GVariant *value,
- gpointer user_data);
-typedef gboolean (*GvdbWalkOpenFunc) (const gchar *name,
- gsize name_len,
- gpointer user_data);
-typedef void (*GvdbWalkCloseFunc) (gsize name_len,
- gpointer user_data);
-
-G_GNUC_INTERNAL
-void gvdb_table_walk (GvdbTable *table,
- const gchar *key,
- GvdbWalkOpenFunc open_func,
- GvdbWalkValueFunc value_func,
- GvdbWalkCloseFunc close_func,
- gpointer user_data);
-
G_END_DECLS
#endif /* __gvdb_reader_h__ */
diff --git a/service/Makefile.am b/service/Makefile.am
index d3eddf7..fe3afaa 100644
--- a/service/Makefile.am
+++ b/service/Makefile.am
@@ -1,14 +1,17 @@
-AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb -I$(top_srcdir)/common -Wall -Wmissing-prototypes -Wwrite-strings
+include $(top_srcdir)/Makefile.gtester
libexec_PROGRAMS = dconf-service
dbusservice_DATA = ca.desrt.dconf.service
-dconf_service_LDADD = $(gio_LIBS)
+dconf_service_CFLAGS = $(gio_CFLAGS)
+dconf_service_LDADD = \
+ ../common/libdconf-common.a \
+ ../gvdb/libgvdb.a \
+ ../shm/libdconf-shm.a \
+ $(gio_LIBS)
+
dconf_service_SOURCES = \
- ../gvdb/gvdb-builder.c \
- ../gvdb/gvdb-reader.c \
- ../common/dconf-shmdir.c \
dconf-interfaces.h \
dconf-interfaces.c \
dconf-rebuilder.h \
diff --git a/service/dconf-interfaces.c b/service/dconf-interfaces.c
index c3c57c0..45c6593 100644
--- a/service/dconf-interfaces.c
+++ b/service/dconf-interfaces.c
@@ -21,6 +21,7 @@
#include "dconf-interfaces.h"
+static const GDBusArgInfo payload_arg = { -1, (gchar *) "payload", (gchar *) "ay" };
static const GDBusArgInfo name_arg = { -1, (gchar *) "name", (gchar *) "s" };
static const GDBusArgInfo path_arg = { -1, (gchar *) "path", (gchar *) "s" };
static const GDBusArgInfo names_arg = { -1, (gchar *) "names", (gchar *) "as" };
@@ -28,6 +29,8 @@ static const GDBusArgInfo tag_arg = { -1, (gchar *) "tag", (gchar *) "s" };
static const GDBusArgInfo value_arg = { -1, (gchar *) "value", (gchar *) "av" };
static const GDBusArgInfo values_arg = { -1, (gchar *) "values", (gchar *) "a(sav)" };
+static const GDBusArgInfo *change_in[] = { &payload_arg, NULL };
+static const GDBusArgInfo *change_out[] = { &tag_arg, NULL };
static const GDBusArgInfo *write_in[] = { &name_arg, &value_arg, NULL };
static const GDBusArgInfo *write_out[] = { &tag_arg, NULL };
static const GDBusArgInfo *many_in[] = { &path_arg, &values_arg, NULL };
@@ -35,6 +38,12 @@ static const GDBusArgInfo *many_out[] = { &tag_arg, NULL };
static const GDBusArgInfo *blame_out[] = { &tag_arg, NULL };
static const GDBusArgInfo *notify_args[] = { &path_arg, &names_arg, &tag_arg, NULL };
+static const GDBusMethodInfo change_method = {
+ -1, (gchar *) "Change",
+ (GDBusArgInfo **) change_in,
+ (GDBusArgInfo **) change_out
+};
+
static const GDBusMethodInfo write_method = {
-1, (gchar *) "Write",
(GDBusArgInfo **) write_in,
@@ -58,12 +67,8 @@ static const GDBusMethodInfo blame_method = {
(GDBusArgInfo **) blame_out
};
-static const GDBusPropertyInfo shmdir_property = {
- -1, (gchar *) "ShmDirectory", (gchar *) "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE
-};
-
static const GDBusMethodInfo *writer_methods[] = {
- &write_method, &writemany_method, NULL
+ &change_method, &write_method, &writemany_method, NULL
};
static const GDBusSignalInfo *writer_signals[] = {
@@ -74,10 +79,6 @@ static const GDBusMethodInfo *writer_info_methods[] = {
&blame_method, NULL
};
-static const GDBusPropertyInfo *writer_info_properties[] = {
- &shmdir_property, NULL
-};
-
const GDBusInterfaceInfo ca_desrt_dconf_Writer = {
-1, (gchar *) "ca.desrt.dconf.Writer",
(GDBusMethodInfo **) writer_methods,
@@ -89,5 +90,5 @@ const GDBusInterfaceInfo ca_desrt_dconf_WriterInfo = {
-1, (gchar *) "ca.desrt.dconf.WriterInfo",
(GDBusMethodInfo **) writer_info_methods,
(GDBusSignalInfo **) NULL,
- (GDBusPropertyInfo **) writer_info_properties
+ (GDBusPropertyInfo **) NULL
};
diff --git a/service/dconf-rebuilder.c b/service/dconf-rebuilder.c
index d7f4cb3..3f170d6 100644
--- a/service/dconf-rebuilder.c
+++ b/service/dconf-rebuilder.c
@@ -23,44 +23,36 @@
#include <string.h>
-#include "gvdb-reader.h"
-#include "gvdb-builder.h"
-
-typedef struct
-{
- const gchar *prefix;
- gint prefix_len;
-
- GHashTable *table;
- const gchar *const*keys;
- GVariant *const*values;
- gint n_items;
- gint index;
-
- gchar name[4096];
- gint name_len;
-} DConfRebuilderState;
+#include "../gvdb/gvdb-reader.h"
+#include "../gvdb/gvdb-builder.h"
+#include "../common/dconf-paths.h"
static GvdbItem *
-dconf_rebuilder_get_parent (GHashTable *table,
- gchar *key,
- gint length)
+dconf_rebuilder_get_parent (GHashTable *table,
+ const gchar *key)
{
GvdbItem *grandparent, *parent;
+ gchar *parent_name;
+ gint len;
- if (length == 1)
+ if (g_str_equal (key, "/"))
return NULL;
- while (key[--length - 1] != '/');
- key[length] = '\0';
+ len = strlen (key);
+ if (key[len - 1] == '/')
+ len--;
+
+ while (key[len - 1] != '/')
+ len--;
- parent = g_hash_table_lookup (table, key);
+ parent_name = g_strndup (key, len);
+ parent = g_hash_table_lookup (table, parent_name);
if (parent == NULL)
{
- parent = gvdb_hash_table_insert (table, key);
+ parent = gvdb_hash_table_insert (table, parent_name);
- grandparent = dconf_rebuilder_get_parent (table, key, length);
+ grandparent = dconf_rebuilder_get_parent (table, parent_name);
if (grandparent != NULL)
gvdb_item_set_parent (parent, grandparent);
@@ -69,146 +61,107 @@ dconf_rebuilder_get_parent (GHashTable *table,
return parent;
}
-static void
-dconf_rebuilder_insert (GHashTable *table,
- const gchar *key,
- GVariant *value)
+gboolean
+dconf_rebuilder_rebuild (const gchar *filename,
+ const gchar *prefix,
+ const gchar * const *keys,
+ GVariant * const *values,
+ int n_items,
+ GError **error)
{
- GvdbItem *item;
- gchar *mykey;
- gint length;
-
- length = strlen (key);
- mykey = g_alloca (length);
- memcpy (mykey, key, length);
-
- g_assert (g_hash_table_lookup (table, key) == NULL);
- item = gvdb_hash_table_insert (table, key);
-
- gvdb_item_set_parent (item,
- dconf_rebuilder_get_parent (table, mykey, length));
+ GHashTable *table;
+ gboolean success;
+ GHashTable *new;
+ GvdbTable *old;
+ gint i;
- gvdb_item_set_value (item, value);
-}
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
-static void
-dconf_rebuilder_put_item (DConfRebuilderState *state)
-{
- if (state->values[state->index] != NULL)
+ /* read in the old values */
+ if ((old = gvdb_table_new (filename, FALSE, NULL)))
{
- gchar *fullname;
- GVariant *ouch;
-
- fullname = g_strconcat (state->prefix, state->keys[state->index], NULL);
- ouch = g_variant_get_variant (state->values[state->index]);
- dconf_rebuilder_insert (state->table, fullname, ouch);
- g_variant_unref (ouch);
- g_free (fullname);
- }
+ gchar **names;
+ gint n_names;
+ gint i;
- state->index++;
-}
+ names = gvdb_table_get_names (old, &n_names);
+ for (i = 0; i < n_names; i++)
+ {
+ if (dconf_is_key (names[i], NULL))
+ {
+ GVariant *value;
-static gboolean
-dconf_rebuilder_walk_name (DConfRebuilderState *state,
- const gchar *name,
- gsize name_len)
-{
- gint cmp;
-
- g_assert (state->name_len + name_len < sizeof state->name - 1);
- memcpy (state->name + state->name_len, name, name_len);
- state->name[state->name_len + name_len] = '\0';
+ value = gvdb_table_get_value (old, names[i]);
- if (state->index == state->n_items)
- return TRUE;
+ if (value != NULL)
+ {
+ g_hash_table_insert (table, names[i], value);
+ names[i] = NULL;
+ }
+ }
- if (state->name_len + name_len < state->prefix_len ||
- memcmp (state->name, state->prefix, state->prefix_len) != 0)
- return TRUE;
+ g_free (names[i]);
+ }
- while ((cmp = strcmp (state->name + state->prefix_len,
- state->keys[state->index])) > 0)
- {
- dconf_rebuilder_put_item (state);
-
- if (state->index == state->n_items)
- return TRUE;
+ gvdb_table_unref (old);
+ g_free (names);
}
- return cmp != 0;
-}
-
-static void
-dconf_rebuilder_walk_value (const gchar *name,
- gsize name_len,
- GVariant *value,
- gpointer user_data)
-{
- DConfRebuilderState *state = user_data;
-
- if (dconf_rebuilder_walk_name (state, name, name_len))
- dconf_rebuilder_insert (state->table, state->name, value);
-
- else
- dconf_rebuilder_put_item (state);
-}
-
-static gboolean
-dconf_rebuilder_walk_open (const gchar *name,
- gsize name_len,
- gpointer user_data)
-{
- DConfRebuilderState *state = user_data;
-
- if (dconf_rebuilder_walk_name (state, name, name_len))
+ /* apply the requested changes */
+ for (i = 0; i < n_items; i++)
{
- state->name_len += name_len;
- return TRUE;
+ gchar *path = g_strconcat (prefix, keys[i], NULL);
+
+ /* Check if we are performing a path reset */
+ if (g_str_has_suffix (path, "/"))
+ {
+ GHashTableIter iter;
+ gpointer key;
+
+ g_assert (values[i] == NULL);
+
+ /* A path reset is really a request to delete all keys that
+ * has a name starting with the reset path.
+ */
+ g_hash_table_iter_init (&iter, table);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ if (g_str_has_prefix (key, path))
+ g_hash_table_iter_remove (&iter);
+ }
+
+ if (values[i] != NULL)
+ g_hash_table_insert (table, g_strdup (path), g_variant_ref (values[i]));
+ else
+ g_hash_table_remove (table, path);
+
+ g_free (path);
}
- return FALSE;
-}
-
-static void
-dconf_rebuilder_walk_close (gsize name_len,
- gpointer user_data)
-{
- DConfRebuilderState *state = user_data;
+ /* convert back to GVDB format */
+ {
+ GHashTableIter iter;
+ gpointer key, value;
- state->name_len -= name_len;
-}
+ new = gvdb_hash_table_new (NULL, NULL);
-gboolean
-dconf_rebuilder_rebuild (const gchar *filename,
- const gchar *prefix,
- const gchar *const*keys,
- GVariant *const*values,
- int n_items,
- GError **error)
-{
- DConfRebuilderState state = { prefix, strlen (prefix),
- 0, keys, values, n_items };
- gboolean success;
- GvdbTable *old;
+ g_hash_table_iter_init (&iter, table);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GvdbItem *item;
- state.table = gvdb_hash_table_new (NULL, NULL);
-
- if ((old = gvdb_table_new (filename, FALSE, NULL)))
- {
- gvdb_table_walk (old, "/",
- dconf_rebuilder_walk_open,
- dconf_rebuilder_walk_value,
- dconf_rebuilder_walk_close,
- &state);
- gvdb_table_unref (old);
- }
+ g_assert (g_hash_table_lookup (new, key) == NULL);
+ item = gvdb_hash_table_insert (new, key);
+ gvdb_item_set_parent (item, dconf_rebuilder_get_parent (new, key));
+ gvdb_item_set_value (item, value);
+ }
+ }
- while (state.index != state.n_items)
- dconf_rebuilder_put_item (&state);
+ /* write the new file */
+ success = gvdb_table_write_contents (new, filename, FALSE, error);
- success = gvdb_table_write_contents (state.table, filename, FALSE, error);
- g_hash_table_unref (state.table);
+ /* clean up */
+ g_hash_table_unref (table);
+ g_hash_table_unref (new);
return success;
}
diff --git a/service/dconf-state.c b/service/dconf-state.c
index a30054c..79e9753 100644
--- a/service/dconf-state.c
+++ b/service/dconf-state.c
@@ -1,7 +1,5 @@
#include "dconf-state.h"
-#include "dconf-shmdir.h"
-
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
@@ -51,19 +49,6 @@ dconf_state_init_session (DConfState *state)
g_error ("Can not create directory '%s': %s",
state->db_dir, g_strerror (errno));
}
-
- state->shm_dir = dconf_shmdir_from_environment ();
-
- if (state->shm_dir == NULL)
- {
- const gchar *tmpdir = g_get_tmp_dir ();
- gchar *shmdir;
-
- shmdir = g_build_filename (tmpdir, "dconf.XXXXXX", NULL);
-
- if ((state->shm_dir = mkdtemp (shmdir)) == NULL)
- g_error ("Can not create reasonable shm directory");
- }
}
static gboolean
diff --git a/service/dconf-state.h b/service/dconf-state.h
index 6a2ac58..f68ac3e 100644
--- a/service/dconf-state.h
+++ b/service/dconf-state.h
@@ -11,7 +11,6 @@ typedef struct
GMainLoop *main_loop;
guint64 serial;
gchar *db_dir;
- gchar *shm_dir;
gchar *id;
} DConfState;
diff --git a/service/dconf-writer.c b/service/dconf-writer.c
index 3f2aeee..c6cda13 100644
--- a/service/dconf-writer.c
+++ b/service/dconf-writer.c
@@ -23,6 +23,7 @@
#include "dconf-rebuilder.h"
#include "dconf-state.h"
+#include "../shm/dconf-shm.h"
#include <stdlib.h>
#include <unistd.h>
@@ -35,7 +36,6 @@ struct OPAQUE_TYPE__DConfWriter
DConfState *state;
gchar *name;
gchar *path;
- gchar *shm;
};
/* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_"
@@ -77,26 +77,6 @@ dconf_writer_list_existing (void)
return (gchar **) g_ptr_array_free (array, FALSE);
}
-static void
-dconf_writer_touch_shm (DConfWriter *writer)
-{
- gchar one = 1;
- gint fd;
-
- fd = open (writer->shm, O_WRONLY);
-
- if (fd >= 0)
- {
- write (fd, &one, sizeof one);
- close (fd);
-
- unlink (writer->shm);
- }
-
- else if (errno != ENOENT)
- unlink (writer->shm);
-}
-
gboolean
dconf_writer_write (DConfWriter *writer,
const gchar *name,
@@ -106,7 +86,7 @@ dconf_writer_write (DConfWriter *writer,
if (!dconf_rebuilder_rebuild (writer->path, "", &name, &value, 1, error))
return FALSE;
- dconf_writer_touch_shm (writer);
+ dconf_shm_flag (writer->name);
return TRUE;
}
@@ -123,7 +103,30 @@ dconf_writer_write_many (DConfWriter *writer,
values, n_items, error))
return FALSE;
- dconf_writer_touch_shm (writer);
+ dconf_shm_flag (writer->name);
+
+ return TRUE;
+}
+
+gboolean
+dconf_writer_change (DConfWriter *writer,
+ DConfChangeset *change,
+ GError **error)
+{
+ const gchar * const *keys;
+ GVariant * const *values;
+ const gchar *prefix;
+ gint n_items;
+
+ n_items = dconf_changeset_describe (change, &prefix, &keys, &values);
+
+ if (!n_items)
+ return TRUE;
+
+ if (!dconf_rebuilder_rebuild (writer->path, prefix, keys, values, n_items, error))
+ return FALSE;
+
+ dconf_shm_flag (writer->name);
return TRUE;
}
@@ -149,7 +152,6 @@ dconf_writer_new (DConfState *state,
writer = g_slice_new (DConfWriter);
writer->state = state;
writer->path = g_build_filename (state->db_dir, name, NULL);
- writer->shm = g_build_filename (state->shm_dir, name, NULL);
writer->name = g_strdup (name);
return writer;
diff --git a/service/dconf-writer.h b/service/dconf-writer.h
index c469d1b..8f391fe 100644
--- a/service/dconf-writer.h
+++ b/service/dconf-writer.h
@@ -22,6 +22,7 @@
#ifndef __dconf_writer_h__
#define __dconf_writer_h__
+#include "../common/dconf-changeset.h"
#include "dconf-state.h"
typedef struct OPAQUE_TYPE__DConfWriter DConfWriter;
@@ -42,4 +43,8 @@ gboolean dconf_writer_write_many (DConfWr
gsize n_items,
GError **error);
+gboolean dconf_writer_change (DConfWriter *writer,
+ DConfChangeset *change,
+ GError **error);
+
#endif /* __dconf_writer_h__ */
diff --git a/service/service.c b/service/service.c
index 84ede50..22011d3 100644
--- a/service/service.c
+++ b/service/service.c
@@ -26,6 +26,7 @@
#include <stdio.h>
#include "dconf-interfaces.h"
+#include "../common/dconf-changeset.h"
#include "dconf-writer.h"
#include "dconf-state.h"
@@ -47,7 +48,7 @@ emit_notify_signal (GDBusConnection *connection,
if (n_keys > 1)
{
const gchar *last_reset = NULL;
- gint last_reset_len;
+ gint last_reset_len = 0;
gint i;
for (i = 0; i < n_keys; i++)
@@ -95,7 +96,29 @@ emit_notify_signal (GDBusConnection *connection,
}
static void
-unwrap_maybe (GVariant **ptr)
+emit_notify_signal_change (GDBusConnection *connection,
+ DConfWriter *writer,
+ gchar *tag,
+ DConfChangeset *change)
+{
+ const gchar *path;
+ const gchar * const *names;
+
+ if (dconf_changeset_describe (change, &path, &names, NULL))
+ {
+ gchar *obj;
+
+ obj = g_strjoin (NULL, "/ca/desrt/dconf/Writer/", dconf_writer_get_name (writer), NULL);
+ g_dbus_connection_emit_signal (connection, NULL, obj,
+ "ca.desrt.dconf.Writer", "Notify",
+ g_variant_new ("(s^ass)", path, names, tag),
+ NULL);
+ g_free (obj);
+ }
+}
+
+static void
+unwrap_maybe_and_variant (GVariant **ptr)
{
GVariant *array, *child;
gsize n_children;
@@ -109,11 +132,12 @@ unwrap_maybe (GVariant **ptr)
child = NULL;
break;
case 1: default:
- child = g_variant_get_child_value (array, 0);
+ g_variant_get_child (array, 0, "v", &child);
break;
case 2:
{
GVariant *untrusted;
+ GVariant *trusted;
GVariant *ay;
g_variant_get_child (array, 0, "v", &ay);
@@ -130,8 +154,10 @@ unwrap_maybe (GVariant **ptr)
FALSE,
(GDestroyNotify) g_variant_unref, ay);
g_variant_ref_sink (untrusted);
- child = g_variant_get_normal_form (untrusted);
+ trusted = g_variant_get_normal_form (untrusted);
g_variant_unref (untrusted);
+
+ g_variant_get (trusted, "v", &child);
}
}
@@ -230,7 +256,39 @@ method_call (GDBusConnection *connection,
if G_UNLIKELY (state->blame_mode)
gather_blame_info (state, connection, sender, object_path, method_name, parameters);
- if (strcmp (method_name, "Write") == 0)
+ if (strcmp (method_name, "Change") == 0)
+ {
+ DConfChangeset *change;
+ GError *error = NULL;
+ GVariant *args;
+ GVariant *tmp;
+ gchar *tag;
+
+ tmp = g_variant_new_from_data (G_VARIANT_TYPE ("a{smv}"),
+ g_variant_get_data (parameters), g_variant_get_size (parameters), FALSE,
+ (GDestroyNotify) g_variant_unref, g_variant_ref (parameters));
+ g_variant_ref_sink (tmp);
+ args = g_variant_get_normal_form (tmp);
+ g_variant_unref (tmp);
+
+ change = dconf_changeset_deserialise (args);
+ g_variant_unref (args);
+
+ if (!dconf_writer_change (writer, change, &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_error_free (error);
+ return;
+ }
+
+ tag = dconf_state_get_tag (state);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", tag));
+ emit_notify_signal_change (connection, writer, tag, change);
+ dconf_changeset_unref (change);
+ g_free (tag);
+ }
+
+ else if (strcmp (method_name, "Write") == 0)
{
GError *error = NULL;
GVariant *keyvalue;
@@ -244,7 +302,7 @@ method_call (GDBusConnection *connection,
g_variant_get (parameters, "(@s@av)", &keyvalue, &value);
key = g_variant_get_string (keyvalue, &key_length);
g_variant_unref (keyvalue);
- unwrap_maybe (&value);
+ unwrap_maybe_and_variant (&value);
if (key[0] != '/' || strstr (key, "//"))
{
@@ -312,7 +370,7 @@ method_call (GDBusConnection *connection,
values = g_new (GVariant *, length);
while (g_variant_iter_next (iter, "(&s@av)", &keys[i], &values[i]))
{
- unwrap_maybe (&values[i]);
+ unwrap_maybe_and_variant (&values[i]);
if (keys[i][0] == '/' || strstr (keys[i], "//") ||
(i > 0 && !(strcmp (keys[i - 1], keys[i]) < 0)))
@@ -389,24 +447,6 @@ writer_info_method (GDBusConnection *connection,
g_assert_not_reached ();
}
-static GVariant *
-writer_info_get_property (GDBusConnection *connection,
- const gchar *sender,
- const gchar *object_path,
- const gchar *interface_name,
- const gchar *property_name,
- GError **error,
- gpointer user_data)
-{
- DConfState *state = user_data;
-
- /* debugging... */
- if G_UNLIKELY (state->blame_mode)
- gather_blame_info (state, connection, sender, object_path, "GetProperty", NULL);
-
- return g_variant_new_string (state->shm_dir);
-}
-
static const GDBusInterfaceVTable *
subtree_dispatch (GDBusConnection *connection,
const gchar *sender,
@@ -448,7 +488,7 @@ subtree_dispatch (GDBusConnection *connection,
else if (strcmp (interface_name, "ca.desrt.dconf.WriterInfo") == 0)
{
static const GDBusInterfaceVTable vtable = {
- writer_info_method, writer_info_get_property, NULL
+ writer_info_method, NULL
};
*out_user_data = state;
diff --git a/shm/.gitignore b/shm/.gitignore
new file mode 100644
index 0000000..16d8712
--- /dev/null
+++ b/shm/.gitignore
@@ -0,0 +1,2 @@
+libdconf-shm.a
+libdconf-shm-shared.a
diff --git a/shm/Makefile.am b/shm/Makefile.am
new file mode 100644
index 0000000..ffa2fd3
--- /dev/null
+++ b/shm/Makefile.am
@@ -0,0 +1,11 @@
+include $(top_srcdir)/Makefile.gtester
+
+noinst_LIBRARIES = libdconf-shm.a libdconf-shm-shared.a
+
+libdconf_shm_a_CFLAGS = $(glib_CFLAGS)
+libdconf_shm_a_SOURCES = \
+ dconf-shm.h \
+ dconf-shm.c
+
+libdconf_shm_shared_a_CFLAGS = $(libdconf_shm_a_CFLAGS) -fPIC -DPIC
+libdconf_shm_shared_a_SOURCES = $(libdconf_shm_a_SOURCES)
diff --git a/shm/dconf-shm.c b/shm/dconf-shm.c
new file mode 100644
index 0000000..10bfec9
--- /dev/null
+++ b/shm/dconf-shm.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-shm.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static gchar *
+dconf_shm_get_shmdir (void)
+{
+ static gchar *shmdir;
+
+ if (g_once_init_enter (&shmdir))
+ g_once_init_leave (&shmdir, g_build_filename (g_get_user_runtime_dir (), "dconf", NULL));
+
+ return shmdir;
+}
+
+void
+dconf_shm_close (guint8 *shm)
+{
+ if (shm)
+ munmap (shm, 1);
+}
+
+guint8 *
+dconf_shm_open (const gchar *name)
+{
+ const gchar *shmdir;
+ gchar *filename;
+ void *memory;
+ gint fd;
+
+ shmdir = dconf_shm_get_shmdir ();
+ filename = g_build_filename (shmdir, name, NULL);
+ memory = NULL;
+ fd = -1;
+
+ if (g_mkdir_with_parents (shmdir, 0700) != 0)
+ {
+ g_critical ("unable to create directory '%s': %s. dconf will not work properly.", shmdir, g_strerror (errno));
+ goto out;
+ }
+
+ fd = open (filename, O_RDWR | O_CREAT, 0600);
+ if (fd == -1)
+ {
+ g_critical ("unable to create file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
+ goto out;
+ }
+
+ /* fruncate(fd, 1) is not sufficient because it does not actually
+ * ensure that the space is available (which could give a SIGBUS
+ * later).
+ *
+ * posix_fallocate() is also problematic because it is implemented in
+ * a racy way in the libc if unavailable for a particular filesystem
+ * (as is the case for tmpfs, which is where we probably are).
+ *
+ * By writing to the second byte in the file we ensure we don't
+ * overwrite the first byte (which is the one we care about).
+ */
+ if (pwrite (fd, "", 1, 1) != 1)
+ {
+ g_critical ("failed to allocate file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
+ goto out;
+ }
+
+ memory = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
+ g_assert (memory != MAP_FAILED);
+ g_assert (memory != NULL);
+
+ out:
+ g_free (filename);
+ close (fd);
+
+ return memory;
+}
+
+void
+dconf_shm_flag (const gchar *name)
+{
+ const gchar *shmdir;
+ gchar *filename;
+ gint fd;
+
+ shmdir = dconf_shm_get_shmdir ();
+ filename = g_build_filename (shmdir, name, NULL);
+
+ /* We need O_RDWR for PROT_WRITE.
+ *
+ * This is probably due to the fact that some architectures can't make
+ * write-only mappings (so they end up being readable as well).
+ */
+ fd = open (filename, O_RDWR);
+ if (fd >= 0)
+ {
+ /* In theory we could have opened the file after a client created
+ * it but before they called pwrite(). Do the pwrite() ourselves
+ * to make sure (so we don't get SIGBUS in a moment).
+ *
+ * If this fails then it will probably fail for the client too.
+ * If it doesn't then there's not really much we can do...
+ */
+ if (pwrite (fd, "", 1, 1) == 1)
+ {
+ guint8 *shm;
+
+ /* It would ahve been easier for us to do write(fd, "\1", 1);
+ * but this causes problems on kernels (ie: OpenBSD) that
+ * don't sync up their filesystem cache with mmap()ed regions.
+ *
+ * Using mmap() works everywhere.
+ */
+ shm = mmap (NULL, 1, PROT_WRITE, MAP_SHARED, fd, 0);
+ g_assert (shm != MAP_FAILED);
+
+ *shm = 1;
+
+ munmap (shm, 1);
+ }
+
+ close (fd);
+
+ unlink (filename);
+ }
+
+ g_free (filename);
+}
diff --git a/shm/dconf-shm.h b/shm/dconf-shm.h
new file mode 100644
index 0000000..c1f136c
--- /dev/null
+++ b/shm/dconf-shm.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_shm_h__
+#define __dconf_shm_h__
+
+#include <glib.h>
+
+G_GNUC_INTERNAL
+guint8 * dconf_shm_open (const gchar *name);
+G_GNUC_INTERNAL
+void dconf_shm_close (guint8 *shm);
+G_GNUC_INTERNAL
+void dconf_shm_flag (const gchar *name);
+
+static inline gboolean
+dconf_shm_is_flagged (const guint8 *shm)
+{
+ return shm == NULL || *shm != 0;
+}
+
+#endif /* __dconf_shm_h__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index e4f23e5..9e74fd0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,3 +1,13 @@
-paths
-gsettings
+libdconf-mock.a
+
+changeset
+client
dbus1
+engine
+gdbus-filter
+gdbus-thread
+gsettings
+gvdb
+libdbus-1
+paths
+shm
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3b66beb..09592e0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,11 +1,106 @@
-AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings
-INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/engine -I$(top_srcdir)/client $(gio_CFLAGS) -I$(top_srcdir)/dbus-1 $(dbus_CFLAGS)
+include $(top_srcdir)/Makefile.gtester
-noinst_PROGRAMS = paths gsettings dbus1
+noinst_LIBRARIES = libdconf-mock.a
+noinst_PROGRAMS = $(TEST_PROGS) gsettings dbus1
+libdconf_mock_a_CFLAGS = $(glib_CFLAGS)
+libdconf_mock_a_SOURCES = \
+ dconf-mock-dbus.c \
+ dconf-mock-gvdb.c \
+ dconf-mock-shm.c
+
+gsettings_CFLAGS = $(gio_CFLAGS)
gsettings_LDADD = $(gio_LIBS)
+dbus1_CFLAGS = $(glib_CFLAGS) $(dbus_CFLAGS)
dbus1_LDADD = -L../dbus-1 -ldconf-dbus-1 $(glib_LIBS)
-paths_LDADD = $(gio_LIBS)
-paths_SOURCES = \
- ../common/dconf-paths.c \
- paths.c
+
+TEST_PROGS += paths
+paths_CFLAGS = $(glib_CFLAGS)
+paths_LDADD = \
+ ../common/libdconf-common.a \
+ $(gio_LIBS)
+paths_SOURCES = paths.c
+
+TEST_PROGS += changeset
+changeset_CFLAGS = $(glib_CFLAGS)
+changeset_LDADD = \
+ ../common/libdconf-common.a \
+ $(glib_LIBS)
+changeset_SOURCES = changeset.c
+
+TEST_PROGS += shm
+shm_CFLAGS = $(glib_CFLAGS)
+shm_LDADD = \
+ ../shm/libdconf-shm.a \
+ $(glib_LIBS) \
+ -ldl
+shm_SOURCES = \
+ shm.c \
+ tmpdir.h \
+ tmpdir.c
+
+TEST_PROGS += gvdb
+gvdb_CFLAGS = $(glib_CFLAGS) -DSRCDIR=\"$(abs_srcdir)\"
+gvdb_LDADD = \
+ ../gvdb/libgvdb.a \
+ $(glib_LIBS)
+gvdb_SOURCES = gvdb.c
+EXTRA_DIST = \
+ gvdbs/empty_gvdb \
+ gvdbs/example_gvdb \
+ gvdbs/example_gvdb.big-endian \
+ gvdbs/file_empty \
+ gvdbs/file_too_small \
+ gvdbs/invalid_header \
+ gvdbs/nested_gvdb
+
+TEST_PROGS += gdbus-thread
+gdbus_thread_CFLAGS = $(gio_CFLAGS) -DDBUS_BACKEND=\"/gdbus/thread\"
+gdbus_thread_LDADD = \
+ ../gdbus/libdconf-gdbus-thread.a \
+ $(gio_LIBS)
+gdbus_thread_SOURCES = dbus.c
+
+TEST_PROGS += gdbus-filter
+gdbus_filter_CFLAGS = $(gio_CFLAGS) -DDBUS_BACKEND=\"/gdbus/filter\"
+gdbus_filter_LDADD = \
+ ../gdbus/libdconf-gdbus-filter.a \
+ $(gio_LIBS)
+gdbus_filter_SOURCES = dbus.c
+
+TEST_PROGS += libdbus-1
+libdbus_1_CFLAGS = $(gio_CFLAGS) -DDBUS_BACKEND=\"/libdbus-1\"
+libdbus_1_LDADD = \
+ ../dbus-1/libdconf-libdbus-1.a \
+ $(glib_LIBS) \
+ $(dbus_LIBS)
+libdbus_1_SOURCES = dbus.c
+
+TEST_PROGS += engine
+engine_CFLAGS = $(glib_CFLAGS) -DSRCDIR=\"$(abs_srcdir)\"
+engine_LDADD = \
+ ../engine/libdconf-engine.a \
+ ../common/libdconf-common.a \
+ libdconf-mock.a \
+ $(glib_LIBS) \
+ -ldl
+engine_SOURCES = engine.c
+EXTRA_DIST += \
+ profile/broken-profile \
+ profile/colourful \
+ profile/dos \
+ profile/empty-profile \
+ profile/many-sources \
+ profile/no-newline-longline \
+ profile/test-profile \
+ profile/will-never-exist
+
+TEST_PROGS += client
+client_CFLAGS = $(gio_CFLAGS) -DSRCDIR=\"$(abs_srcdir)\"
+client_LDADD = \
+ ../client/libdconf-client.a \
+ ../engine/libdconf-engine.a \
+ ../common/libdconf-common.a \
+ libdconf-mock.a \
+ $(gio_LIBS)
+client_SOURCES = client.c
diff --git a/tests/changeset.c b/tests/changeset.c
new file mode 100644
index 0000000..17d77fa
--- /dev/null
+++ b/tests/changeset.c
@@ -0,0 +1,398 @@
+#include "../common/dconf-changeset.h"
+
+static gboolean
+should_not_run (const gchar *key,
+ GVariant *value,
+ gpointer user_data)
+{
+ g_assert_not_reached ();
+}
+
+static gboolean
+is_null (const gchar *key,
+ GVariant *value,
+ gpointer user_data)
+{
+ return value == NULL;
+}
+
+static gboolean
+is_not_null (const gchar *key,
+ GVariant *value,
+ gpointer user_data)
+{
+ return value != NULL;
+}
+
+static void
+test_basic (void)
+{
+ DConfChangeset *changeset;
+ gboolean result;
+ GVariant *value;
+ gint n_items;
+
+ changeset = dconf_changeset_new ();
+ dconf_changeset_ref (changeset);
+ dconf_changeset_all (changeset, should_not_run, NULL);
+ n_items = dconf_changeset_describe (changeset, NULL, NULL, NULL);
+ g_assert_cmpint (n_items, ==, 0);
+ dconf_changeset_unref (changeset);
+ dconf_changeset_unref (changeset);
+
+ changeset = dconf_changeset_new_write ("/value/a", NULL);
+ result = dconf_changeset_all (changeset, is_null, NULL);
+ g_assert (result);
+ result = dconf_changeset_all (changeset, is_not_null, NULL);
+ g_assert (!result);
+
+ result = dconf_changeset_get (changeset, "/value/a", &value);
+ g_assert (result);
+ g_assert (value == NULL);
+
+ result = dconf_changeset_get (changeset, "/value/b", &value);
+ g_assert (!result);
+
+ dconf_changeset_set (changeset, "/value/b", g_variant_new_int32 (123));
+ result = dconf_changeset_all (changeset, is_null, NULL);
+ g_assert (!result);
+ result = dconf_changeset_all (changeset, is_not_null, NULL);
+ g_assert (!result);
+
+ result = dconf_changeset_get (changeset, "/value/a", &value);
+ g_assert (result);
+ g_assert (value == NULL);
+
+ result = dconf_changeset_get (changeset, "/value/b", &value);
+ g_assert (result);
+ g_assert_cmpint (g_variant_get_int32 (value), ==, 123);
+ g_variant_unref (value);
+
+ dconf_changeset_set (changeset, "/value/a", g_variant_new_string ("a string"));
+ result = dconf_changeset_all (changeset, is_null, NULL);
+ g_assert (!result);
+ result = dconf_changeset_all (changeset, is_not_null, NULL);
+ g_assert (result);
+
+ result = dconf_changeset_get (changeset, "/value/a", &value);
+ g_assert (result);
+ g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "a string");
+ g_variant_unref (value);
+
+ result = dconf_changeset_get (changeset, "/value/b", &value);
+ g_assert (result);
+ g_assert_cmpint (g_variant_get_int32 (value), ==, 123);
+ g_variant_unref (value);
+
+ dconf_changeset_unref (changeset);
+}
+
+static void
+test_similarity (void)
+{
+ DConfChangeset *a, *b;
+
+ a = dconf_changeset_new ();
+ b = dconf_changeset_new ();
+
+ g_assert (dconf_changeset_is_similar_to (a, b));
+ g_assert (dconf_changeset_is_similar_to (b, a));
+
+ dconf_changeset_set (a, "/value/a", g_variant_new_int32 (0));
+ g_assert (!dconf_changeset_is_similar_to (a, b));
+ g_assert (!dconf_changeset_is_similar_to (b, a));
+
+ /* different values for the same key are still the same */
+ dconf_changeset_set (b, "/value/a", g_variant_new_int32 (1));
+ g_assert (dconf_changeset_is_similar_to (a, b));
+ g_assert (dconf_changeset_is_similar_to (b, a));
+
+ /* make sure even a NULL is counted as different */
+ dconf_changeset_set (a, "/value/b", NULL);
+ g_assert (!dconf_changeset_is_similar_to (a, b));
+ g_assert (!dconf_changeset_is_similar_to (b, a));
+
+ dconf_changeset_set (b, "/value/b", NULL);
+ g_assert (dconf_changeset_is_similar_to (a, b));
+ g_assert (dconf_changeset_is_similar_to (b, a));
+
+ /* different types are still the same */
+ dconf_changeset_set (b, "/value/a", g_variant_new_uint32 (222));
+ g_assert (dconf_changeset_is_similar_to (a, b));
+ g_assert (dconf_changeset_is_similar_to (b, a));
+
+ dconf_changeset_set (a, "/value/c", NULL);
+ dconf_changeset_set (b, "/value/d", NULL);
+ g_assert (!dconf_changeset_is_similar_to (a, b));
+ g_assert (!dconf_changeset_is_similar_to (b, a));
+
+ dconf_changeset_unref (a);
+ dconf_changeset_unref (b);
+}
+
+static void
+test_describe (void)
+{
+ DConfChangeset *changeset;
+ const gchar * const *keys;
+ GVariant * const *values;
+ const gchar *prefix;
+ gint n_items;
+ gint i;
+
+ /* test zero items */
+ changeset = dconf_changeset_new ();
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 0);
+ dconf_changeset_unref (changeset);
+
+ /* test one NULL item */
+ changeset = dconf_changeset_new_write ("/value/a", NULL);
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 1);
+ g_assert_cmpstr (prefix, ==, "/value/a");
+ g_assert_cmpstr (keys[0], ==, "");
+ g_assert (keys[1] == NULL);
+ g_assert (values[0] == NULL);
+
+
+ /* Check again */
+ prefix = NULL;
+ keys = NULL;
+ values = NULL;
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 1);
+ g_assert_cmpstr (prefix, ==, "/value/a");
+ g_assert_cmpstr (keys[0], ==, "");
+ g_assert (keys[1] == NULL);
+ g_assert (values[0] == NULL);
+ dconf_changeset_unref (changeset);
+
+ /* test one non-NULL item */
+ changeset = dconf_changeset_new_write ("/value/a", g_variant_new_int32 (55));
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 1);
+ g_assert_cmpstr (prefix, ==, "/value/a");
+ g_assert_cmpstr (keys[0], ==, "");
+ g_assert (keys[1] == NULL);
+ g_assert_cmpint (g_variant_get_int32 (values[0]), ==, 55);
+ dconf_changeset_unref (changeset);
+
+ /* test many items */
+ changeset = dconf_changeset_new ();
+ for (i = 0; i < 100; i++)
+ {
+ gchar key[80];
+
+ g_snprintf (key, sizeof key, "/test/value/%2d", i);
+
+ dconf_changeset_set (changeset, key, g_variant_new_int32 (i));
+ }
+
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, i);
+ g_assert_cmpstr (prefix, ==, "/test/value/");
+ for (i = 0; i < 100; i++)
+ {
+ gchar key[80];
+
+ g_snprintf (key, sizeof key, "%2d", i);
+
+ g_assert_cmpstr (keys[i], ==, key);
+ g_assert_cmpint (g_variant_get_int32 (values[i]), ==, i);
+ }
+ g_assert (keys[n_items] == NULL);
+ dconf_changeset_unref (changeset);
+
+ /* test many items with common names */
+ changeset = dconf_changeset_new ();
+ for (i = 0; i < 100; i++)
+ {
+ gchar key[80];
+
+ g_snprintf (key, sizeof key, "/test/value/aaa%02d", i);
+
+ dconf_changeset_set (changeset, key, g_variant_new_int32 (i));
+ }
+
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, i);
+ g_assert_cmpstr (prefix, ==, "/test/value/");
+ for (i = 0; i < 100; i++)
+ {
+ gchar key[80];
+
+ g_snprintf (key, sizeof key, "aaa%02d", i);
+
+ g_assert_cmpstr (keys[i], ==, key);
+ g_assert_cmpint (g_variant_get_int32 (values[i]), ==, i);
+ }
+ g_assert (keys[n_items] == NULL);
+ dconf_changeset_unref (changeset);
+
+ /* test several values in different directories */
+ changeset = dconf_changeset_new ();
+ dconf_changeset_set (changeset, "/value/reset/", NULL);
+ dconf_changeset_set (changeset, "/value/int/a", g_variant_new_int32 (123));
+ dconf_changeset_set (changeset, "/value/string", g_variant_new_string ("bar"));
+ dconf_changeset_set (changeset, "/value/string/a", g_variant_new_string ("foo"));
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 4);
+ g_assert_cmpstr (prefix, ==, "/value/");
+ g_assert_cmpstr (keys[0], ==, "int/a");
+ g_assert_cmpint (g_variant_get_int32 (values[0]), ==, 123);
+ g_assert_cmpstr (keys[1], ==, "reset/");
+ g_assert (values[1] == NULL);
+ g_assert_cmpstr (keys[2], ==, "string");
+ g_assert_cmpstr (g_variant_get_string (values[2], NULL), ==, "bar");
+ g_assert_cmpstr (keys[3], ==, "string/a");
+ g_assert_cmpstr (g_variant_get_string (values[3], NULL), ==, "foo");
+ g_assert (keys[4] == NULL);
+ dconf_changeset_unref (changeset);
+
+ /* test a couple of values in very different directories */
+ changeset = dconf_changeset_new_write ("/a/deep/directory/", NULL);
+ dconf_changeset_set (changeset, "/another/deep/directory/", NULL);
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 2);
+ g_assert_cmpstr (prefix, ==, "/");
+ g_assert_cmpstr (keys[0], ==, "a/deep/directory/");
+ g_assert_cmpstr (keys[1], ==, "another/deep/directory/");
+ g_assert (keys[2] == NULL);
+ g_assert (values[0] == NULL);
+ g_assert (values[1] == NULL);
+ dconf_changeset_unref (changeset);
+
+ /* one more similar case, but with the first letter different */
+ changeset = dconf_changeset_new_write ("/deep/directory/", NULL);
+ dconf_changeset_set (changeset, "/another/deep/directory/", NULL);
+ n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+ g_assert_cmpint (n_items, ==, 2);
+ g_assert_cmpstr (prefix, ==, "/");
+ g_assert_cmpstr (keys[0], ==, "another/deep/directory/");
+ g_assert_cmpstr (keys[1], ==, "deep/directory/");
+ g_assert (keys[2] == NULL);
+ g_assert (values[0] == NULL);
+ g_assert (values[1] == NULL);
+ dconf_changeset_unref (changeset);
+}
+
+static void
+test_reset (void)
+{
+ DConfChangeset *changeset;
+
+ changeset = dconf_changeset_new ();
+ g_assert (!dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* set a value */
+ dconf_changeset_set (changeset, "/value/a", g_variant_new_boolean (TRUE));
+ g_assert (dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* record the reset */
+ dconf_changeset_set (changeset, "/value/", NULL);
+ g_assert (!dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* write it back */
+ dconf_changeset_set (changeset, "/value/a", g_variant_new_boolean (TRUE));
+ g_assert (dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* reset again */
+ dconf_changeset_set (changeset, "/value/", NULL);
+ g_assert (!dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* write again */
+ dconf_changeset_set (changeset, "/value/a", g_variant_new_boolean (TRUE));
+ g_assert (dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* reset a different way */
+ dconf_changeset_set (changeset, "/value/a", g_variant_new_boolean (TRUE));
+ g_assert (dconf_changeset_get (changeset, "/value/a", NULL));
+
+ /* write last time */
+ dconf_changeset_set (changeset, "/value/a", g_variant_new_boolean (TRUE));
+ g_assert (dconf_changeset_get (changeset, "/value/a", NULL));
+
+ dconf_changeset_unref (changeset);
+}
+
+static gboolean
+has_same_value (const gchar *key,
+ GVariant *value,
+ gpointer user_data)
+{
+ DConfChangeset *other = user_data;
+ GVariant *other_value;
+ gboolean success;
+
+ success = dconf_changeset_get (other, key, &other_value);
+ g_assert (success);
+
+ if (value == NULL)
+ g_assert (other_value == NULL);
+ else
+ {
+ g_assert (g_variant_equal (value, other_value));
+ g_variant_unref (other_value);
+ }
+
+ return TRUE;
+}
+
+static void
+test_serialisation (DConfChangeset *changes)
+{
+ GVariant *serialised;
+ DConfChangeset *copy;
+
+ serialised = dconf_changeset_serialise (changes);
+ copy = dconf_changeset_deserialise (serialised);
+ g_variant_unref (serialised);
+
+ g_assert (dconf_changeset_is_similar_to (copy, changes));
+ g_assert (dconf_changeset_is_similar_to (changes, copy));
+ g_assert (dconf_changeset_all (copy, has_same_value, changes));
+ g_assert (dconf_changeset_all (changes, has_same_value, copy));
+
+ dconf_changeset_unref (copy);
+}
+
+static void
+test_serialiser (void)
+{
+ DConfChangeset *changeset;
+
+ changeset = dconf_changeset_new ();
+ test_serialisation (changeset);
+
+ dconf_changeset_set (changeset, "/some/value", g_variant_new_int32 (333));
+ test_serialisation (changeset);
+
+ dconf_changeset_set (changeset, "/other/value", NULL);
+ test_serialisation (changeset);
+
+ dconf_changeset_set (changeset, "/other/value", g_variant_new_int32 (55));
+ test_serialisation (changeset);
+
+ dconf_changeset_set (changeset, "/other/", NULL);
+ test_serialisation (changeset);
+
+ dconf_changeset_set (changeset, "/", NULL);
+ test_serialisation (changeset);
+
+ dconf_changeset_unref (changeset);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/changeset/basic", test_basic);
+ g_test_add_func ("/changeset/similarity", test_similarity);
+ g_test_add_func ("/changeset/describe", test_describe);
+ g_test_add_func ("/changeset/reset", test_reset);
+ g_test_add_func ("/changeset/serialiser", test_serialiser);
+
+ return g_test_run ();
+}
diff --git a/tests/client.c b/tests/client.c
new file mode 100644
index 0000000..ad0115d
--- /dev/null
+++ b/tests/client.c
@@ -0,0 +1,179 @@
+#define _BSD_SOURCE
+#include "../client/dconf-client.h"
+#include "../engine/dconf-engine.h"
+#include <string.h>
+#include <stdlib.h>
+
+extern GQueue outstanding_call_handles;
+static GThread *main_thread;
+
+static void
+test_lifecycle (void)
+{
+ DConfClient *client;
+ GWeakRef weak;
+
+ client = dconf_client_new ();
+ g_weak_ref_init (&weak, client);
+ g_object_unref (client);
+
+ g_assert (g_weak_ref_get (&weak) == NULL);
+ g_weak_ref_clear (&weak);
+}
+
+static gboolean changed_was_called;
+
+static void
+changed (DConfClient *client,
+ const gchar *prefix,
+ const gchar * const *changes,
+ const gchar *tag,
+ gpointer user_data)
+{
+ g_assert (g_thread_self () == main_thread);
+
+ changed_was_called = TRUE;
+}
+
+static void
+check_and_free (GVariant *to_check,
+ GVariant *expected)
+{
+ if (expected)
+ {
+ g_variant_ref_sink (expected);
+ g_assert (to_check);
+
+ g_assert (g_variant_equal (to_check, expected));
+ g_variant_unref (to_check);
+ g_variant_unref (expected);
+ }
+ else
+ g_assert (to_check == NULL);
+}
+
+static void
+queue_up_100_writes (DConfClient *client)
+{
+ gint i;
+
+ /* We send 100 writes, letting them pile up.
+ * At no time should there be more than 2 writes on the wire.
+ */
+ for (i = 0; i < 100; i++)
+ {
+ changed_was_called = FALSE;
+ dconf_client_write_fast (client, "/test/value", g_variant_new_int32 (i), NULL);
+ g_assert (changed_was_called);
+
+ /* We should always see the most recently written value. */
+ check_and_free (dconf_client_read (client, "/test/value"), g_variant_new_int32 (i));
+ }
+
+ g_assert_cmpint (g_queue_get_length (&outstanding_call_handles), ==, 2);
+}
+
+static void
+fail_one_call (void)
+{
+ DConfEngineCallHandle *handle;
+ GError *error;
+
+ error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "--expected error from testcase--");
+ handle = g_queue_pop_head (&outstanding_call_handles);
+ dconf_engine_call_handle_reply (handle, NULL, error);
+ g_error_free (error);
+}
+
+static void
+log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ if (strstr (message, "--expected error from testcase--"))
+ return;
+
+ g_log_default_handler (log_domain, log_level, message, user_data);
+}
+
+static gboolean
+fatal_log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ if (strstr (message, "--expected error from testcase--"))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+test_fast (void)
+{
+ DConfClient *client;
+ gint i;
+
+ g_log_set_default_handler (log_handler, NULL);
+ g_test_log_set_fatal_handler (fatal_log_handler, NULL);
+
+ client = dconf_client_new ();
+ g_signal_connect (client, "changed", G_CALLBACK (changed), NULL);
+
+ queue_up_100_writes (client);
+
+ /* Start indicating that the writes failed.
+ *
+ * For the first failures, we should continue to see the most recently
+ * written value (99).
+ *
+ * After we fail that last one, we should see NULL returned.
+ *
+ * Each time, we should see a change notify.
+ */
+
+ for (i = 0; g_queue_get_length (&outstanding_call_handles) > 1; i++)
+ {
+ changed_was_called = FALSE;
+ fail_one_call ();
+ g_assert (changed_was_called);
+
+ check_and_free (dconf_client_read (client, "/test/value"), g_variant_new_int32 (99));
+ }
+
+ /* Because of the pending-merging logic, we should only have had to
+ * fail two calls.
+ */
+ g_assert (i == 2);
+
+ /* Fail the last call. */
+ changed_was_called = FALSE;
+ fail_one_call ();
+ g_assert (changed_was_called);
+
+ /* Should read back now as NULL */
+ check_and_free (dconf_client_read (client, "/test/value"), NULL);
+
+ /* Cleanup */
+ g_signal_handlers_disconnect_by_func (client, changed, NULL);
+ g_object_unref (client);
+}
+
+int
+main (int argc, char **argv)
+{
+ setenv ("DCONF_PROFILE", SRCDIR "/profile/will-never-exist", TRUE);
+
+ main_thread = g_thread_self ();
+
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_type_init ();
+
+ g_test_add_func ("/client/lifecycle", test_lifecycle);
+ g_test_add_func ("/client/basic-fast", test_fast);
+
+ return g_test_run ();
+}
diff --git a/tests/dbus.c b/tests/dbus.c
new file mode 100644
index 0000000..d411c06
--- /dev/null
+++ b/tests/dbus.c
@@ -0,0 +1,423 @@
+#include <string.h>
+#include <glib.h>
+#include <stdlib.h>
+
+/* Test the DBus communicaton code.
+ */
+
+#include "../engine/dconf-engine.h"
+
+static gboolean okay_in_main;
+static GThread *main_thread;
+static GThread *dbus_thread;
+static GQueue async_call_success_queue;
+static GQueue async_call_error_queue;
+static GMutex async_call_queue_lock;
+static gboolean signal_was_received;
+
+static void
+wait_for_queue_to_empty (GQueue *queue)
+{
+ okay_in_main = TRUE;
+
+ while (TRUE)
+ {
+ gboolean is_empty;
+
+ g_mutex_lock (&async_call_queue_lock);
+ is_empty = g_queue_is_empty (queue);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ if (is_empty)
+ return;
+
+ g_main_context_iteration (NULL, TRUE);
+ }
+
+ okay_in_main = FALSE;
+}
+
+static gboolean
+just_wake (gpointer user_data)
+{
+ return G_SOURCE_REMOVE;
+}
+
+static void
+signal_if_queue_is_empty (GQueue *queue)
+{
+ gboolean is_empty;
+
+ g_mutex_lock (&async_call_queue_lock);
+ is_empty = g_queue_is_empty (queue);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ if (is_empty)
+ g_idle_add (just_wake, NULL);
+}
+
+const GVariantType *
+dconf_engine_call_handle_get_expected_type (DConfEngineCallHandle *handle)
+{
+ return (GVariantType *) handle;
+}
+
+void
+dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
+ GVariant *parameters,
+ const GError *error)
+{
+ DConfEngineCallHandle *expected_handle;
+
+ /* Ensure that messages are never delivered in the main thread except
+ * by way of a mainloop (ie: not during sync calls).
+ *
+ * It's okay if they are delivered in another thread at the same time
+ * as a sync call is happening in the main thread, though...
+ */
+ g_assert (g_thread_self () != main_thread || okay_in_main);
+
+ /* Make sure that we only ever receive D-Bus calls from a single
+ * thread.
+ */
+ if (!dbus_thread)
+ dbus_thread = g_thread_self ();
+ g_assert (g_thread_self () == dbus_thread);
+
+ /* This is the passing case. */
+ if (parameters != NULL)
+ {
+ g_mutex_lock (&async_call_queue_lock);
+ g_assert (g_queue_is_empty (&async_call_error_queue));
+ expected_handle = g_queue_pop_head (&async_call_success_queue);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ g_assert (parameters != NULL);
+ g_assert (error == NULL);
+ g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(s)")));
+
+ g_assert (expected_handle == handle);
+ g_variant_type_free ((GVariantType *) handle);
+
+ signal_if_queue_is_empty (&async_call_success_queue);
+ }
+ else
+ {
+ g_mutex_lock (&async_call_queue_lock);
+ g_assert (g_queue_is_empty (&async_call_success_queue));
+ expected_handle = g_queue_pop_head (&async_call_error_queue);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ g_assert (parameters == NULL);
+ g_assert (error != NULL);
+
+ g_assert (expected_handle == handle);
+ g_variant_type_free ((GVariantType *) handle);
+
+ signal_if_queue_is_empty (&async_call_error_queue);
+ }
+}
+
+void
+dconf_engine_handle_dbus_signal (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *signal_name,
+ GVariant *parameters)
+{
+ g_assert (g_thread_self () != main_thread || okay_in_main);
+
+ if (!dbus_thread)
+ dbus_thread = g_thread_self ();
+ g_assert (g_thread_self () == dbus_thread);
+
+ if (g_str_equal (signal_name, "TestSignal"))
+ {
+ GVariant *expected;
+
+ expected = g_variant_parse (NULL, "('1', ['2', '3'])", NULL, NULL, NULL);
+ g_assert (g_variant_equal (parameters, expected));
+ g_variant_unref (expected);
+
+ signal_was_received = TRUE;
+ g_idle_add (just_wake, NULL);
+ }
+}
+
+static void
+test_creation_error (void)
+{
+ /* Sync with 'error' */
+ if (g_test_trap_fork (0, 0))
+ {
+ GError *error = NULL;
+ GVariant *reply;
+
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", "some nonsense", 1);
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(as)"), &error);
+
+ g_assert (reply == NULL);
+ g_assert (error != NULL);
+ g_assert (strstr (error->message, "some nonsense"));
+ exit (0);
+ }
+
+ g_test_trap_assert_passed ();
+
+ /* Sync without 'error' */
+ if (g_test_trap_fork (0, 0))
+ {
+ GVariant *reply;
+
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", "some nonsense", 1);
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(as)"), NULL);
+
+ g_assert (reply == NULL);
+ exit (0);
+ }
+
+ g_test_trap_assert_passed ();
+
+ /* Async */
+ if (g_test_trap_fork (0, 0))
+ {
+ DConfEngineCallHandle *handle;
+ GError *error = NULL;
+ gboolean success;
+
+ g_setenv ("DBUS_SESSION_BUS_ADDRESS", "some nonsense", 1);
+
+ handle = (gpointer) g_variant_type_new ("(s)");
+ g_mutex_lock (&async_call_queue_lock);
+ g_queue_push_tail (&async_call_error_queue, handle);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ success = dconf_engine_dbus_call_async_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), handle, &error);
+
+ /* This could either fail immediately or asynchronously, depending
+ * on how the backend is setup.
+ */
+ if (success)
+ {
+ g_assert_no_error (error);
+
+ wait_for_queue_to_empty (&async_call_error_queue);
+ }
+ else
+ g_assert (error != NULL);
+
+ exit (0);
+ }
+
+ g_test_trap_assert_passed ();
+}
+
+static void
+test_sync_call_success (void)
+{
+ GError *error = NULL;
+ gchar *session_id;
+ gchar *system_id;
+ GVariant *reply;
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(as)"), &error);
+
+ g_assert_no_error (error);
+ g_assert (reply != NULL);
+ g_assert (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(as)")));
+ g_variant_unref (reply);
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(s)"), &error);
+
+ g_assert_no_error (error);
+ g_assert (reply != NULL);
+ g_assert (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(s)")));
+ g_variant_get (reply, "(s)", &session_id);
+ g_variant_unref (reply);
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SYSTEM,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(s)"), &error);
+
+ g_assert_no_error (error);
+ g_assert (reply != NULL);
+ g_assert (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(s)")));
+ g_variant_get (reply, "(s)", &system_id);
+ g_variant_unref (reply);
+
+ /* Make sure we actually saw two separate buses */
+ g_assert_cmpstr (session_id, !=, system_id);
+ g_free (session_id);
+ g_free (system_id);
+}
+
+static void
+test_sync_call_error (void)
+{
+ GError *error = NULL;
+ GVariant *reply;
+
+ /* Test receiving errors from the other side */
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("(s)", ""), G_VARIANT_TYPE_UNIT, &error);
+ g_assert (reply == NULL);
+ g_assert (error != NULL);
+ g_assert (strstr (error->message, "org.freedesktop.DBus.Error.InvalidArgs"));
+ g_clear_error (&error);
+
+ /* Test reply type errors */
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(u)"), &error);
+ g_assert (reply == NULL);
+ g_assert (error != NULL);
+ g_assert (strstr (error->message, " type "));
+ g_clear_error (&error);
+}
+
+static void
+test_async_call_success (void)
+{
+ gint i;
+
+ for (i = 0; i < 1000; i++)
+ {
+ DConfEngineCallHandle *handle;
+ GError *error = NULL;
+ gboolean success;
+
+ handle = (gpointer) g_variant_type_new ("(s)");
+ g_mutex_lock (&async_call_queue_lock);
+ g_queue_push_tail (&async_call_success_queue, handle);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ success = dconf_engine_dbus_call_async_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), handle, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ }
+
+ wait_for_queue_to_empty (&async_call_success_queue);
+}
+
+static void
+test_async_call_error (void)
+{
+ DConfEngineCallHandle *handle;
+ GError *error = NULL;
+ gboolean success;
+
+ handle = (gpointer) g_variant_type_new ("(s)");
+
+ g_mutex_lock (&async_call_queue_lock);
+ g_queue_push_tail (&async_call_error_queue, handle);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ success = dconf_engine_dbus_call_async_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("(s)", ""), handle, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ wait_for_queue_to_empty (&async_call_error_queue);
+}
+
+static void
+test_sync_during_async (void)
+{
+ DConfEngineCallHandle *handle;
+ GError *error = NULL;
+ gboolean success;
+ GVariant *reply;
+
+ handle = (gpointer) g_variant_type_new ("(s)");
+ g_mutex_lock (&async_call_queue_lock);
+ g_queue_push_tail (&async_call_success_queue, handle);
+ g_mutex_unlock (&async_call_queue_lock);
+
+ success = dconf_engine_dbus_call_async_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetId",
+ g_variant_new ("()"), handle, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames",
+ g_variant_new ("()"), G_VARIANT_TYPE ("(as)"), &error);
+ g_assert_no_error (error);
+ g_assert (reply != NULL);
+ g_variant_unref (reply);
+
+ wait_for_queue_to_empty (&async_call_success_queue);
+}
+
+static gboolean
+did_not_receive_signal (gpointer user_data)
+{
+ g_assert_not_reached ();
+}
+
+static void
+test_signal_receipt (void)
+{
+ GError *error = NULL;
+ GVariant *reply;
+ gint status;
+ guint id;
+
+ reply = dconf_engine_dbus_call_sync_func (G_BUS_TYPE_SESSION,
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "AddMatch",
+ g_variant_new ("(s)", "type='signal',interface='ca.desrt.dconf.Writer'"),
+ G_VARIANT_TYPE_UNIT, &error);
+ g_assert_no_error (error);
+ g_assert (reply != NULL);
+ g_variant_unref (reply);
+
+ status = system ("gdbus emit --session "
+ "--object-path /ca/desrt/dconf/Writer/testcase "
+ "--signal ca.desrt.dconf.Writer.TestSignal "
+ "\"'1'\" \"['2', '3']\"");
+ g_assert_cmpint (status, ==, 0);
+
+ id = g_timeout_add (30000, did_not_receive_signal, NULL);
+ while (!signal_was_received)
+ g_main_context_iteration (NULL, FALSE);
+ g_source_remove (id);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ main_thread = g_thread_self ();
+
+ dconf_engine_dbus_init_for_testing ();
+
+ /* test_creation_error absolutely must come first */
+ if (!g_str_equal (DBUS_BACKEND, "/libdbus-1"))
+ g_test_add_func (DBUS_BACKEND "/creation/error", test_creation_error);
+
+ g_test_add_func (DBUS_BACKEND "/sync-call/success", test_sync_call_success);
+ g_test_add_func (DBUS_BACKEND "/sync-call/error", test_sync_call_error);
+ g_test_add_func (DBUS_BACKEND "/async-call/success", test_async_call_success);
+ g_test_add_func (DBUS_BACKEND "/async-call/error", test_async_call_error);
+ g_test_add_func (DBUS_BACKEND "/sync-during-async", test_sync_during_async);
+ g_test_add_func (DBUS_BACKEND "/signal-receipt", test_signal_receipt);
+
+ return g_test_run ();
+}
diff --git a/tests/dbus1.c b/tests/dbus1.c
index 1220160..9bb2d7a 100644
--- a/tests/dbus1.c
+++ b/tests/dbus1.c
@@ -17,7 +17,7 @@
* Author: Ryan Lortie <desrt@desrt.ca>
**/
-#include <dconf-dbus-1.h>
+#include "../dbus-1/dconf-dbus-1.h"
#include <stdbool.h>
#include <unistd.h>
diff --git a/tests/dconf-mock-dbus.c b/tests/dconf-mock-dbus.c
new file mode 100644
index 0000000..772e9ed
--- /dev/null
+++ b/tests/dconf-mock-dbus.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "../engine/dconf-engine.h"
+
+GQueue outstanding_call_handles;
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ DConfEngineCallHandle *handle,
+ GError **error)
+{
+ g_queue_push_tail (&outstanding_call_handles, handle);
+
+ return TRUE;
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType bus_type,
+ const gchar *bus_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GError **error)
+{
+ g_assert_not_reached ();
+}
diff --git a/tests/dconf-mock-gvdb.c b/tests/dconf-mock-gvdb.c
new file mode 100644
index 0000000..cb639e3
--- /dev/null
+++ b/tests/dconf-mock-gvdb.c
@@ -0,0 +1,195 @@
+#include "../gvdb/gvdb-reader.h"
+#include "dconf-mock.h"
+
+/* The global dconf_mock_gvdb_tables hashtable is modified all the time
+ * so we need to hold the lock while we access it.
+ *
+ * The hashtables contained within it are never modified, however. They
+ * can be safely accessed without a lock.
+ */
+
+static GHashTable *dconf_mock_gvdb_tables;
+static GMutex dconf_mock_gvdb_lock;
+
+typedef struct
+{
+ GVariant *value;
+ GvdbTable *table;
+} DConfMockGvdbItem;
+
+struct _GvdbTable
+{
+ GHashTable *table;
+ gboolean is_valid;
+ gboolean top_level;
+ gint ref_count;
+};
+
+static void
+dconf_mock_gvdb_item_free (gpointer data)
+{
+ DConfMockGvdbItem *item = data;
+
+ if (item->value)
+ g_variant_unref (item->value);
+
+ if (item->table)
+ gvdb_table_unref (item->table);
+
+ g_slice_free (DConfMockGvdbItem, item);
+}
+
+static void
+dconf_mock_gvdb_init (void)
+{
+ if (dconf_mock_gvdb_tables == NULL)
+ dconf_mock_gvdb_tables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gvdb_table_unref);
+}
+
+GvdbTable *
+dconf_mock_gvdb_table_new (void)
+{
+ GvdbTable *table;
+
+ table = g_slice_new (GvdbTable);
+ table->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, dconf_mock_gvdb_item_free);
+ table->ref_count = 1;
+ table->is_valid = TRUE;
+
+ return table;
+}
+
+void
+dconf_mock_gvdb_table_insert (GvdbTable *table,
+ const gchar *name,
+ GVariant *value,
+ GvdbTable *subtable)
+{
+ DConfMockGvdbItem *item;
+
+ g_assert (value == NULL || subtable == NULL);
+
+ if (subtable)
+ subtable->top_level = FALSE;
+
+ item = g_slice_new (DConfMockGvdbItem);
+ item->value = value ? g_variant_ref_sink (value) : NULL;
+ item->table = subtable;
+
+ g_hash_table_insert (table->table, g_strdup (name), item);
+}
+
+void
+dconf_mock_gvdb_install (const gchar *filename,
+ GvdbTable *table)
+{
+ g_mutex_lock (&dconf_mock_gvdb_lock);
+ dconf_mock_gvdb_init ();
+
+ if (table)
+ {
+ table->top_level = TRUE;
+ g_hash_table_insert (dconf_mock_gvdb_tables, g_strdup (filename), table);
+ }
+ else
+ g_hash_table_remove (dconf_mock_gvdb_tables, filename);
+
+ g_mutex_unlock (&dconf_mock_gvdb_lock);
+}
+
+void
+gvdb_table_unref (GvdbTable *table)
+{
+ if (g_atomic_int_dec_and_test (&table->ref_count))
+ {
+ g_hash_table_unref (table->table);
+ g_slice_free (GvdbTable, table);
+ }
+}
+
+GvdbTable *
+gvdb_table_ref (GvdbTable *table)
+{
+ g_atomic_int_inc (&table->ref_count);
+
+ return table;
+}
+
+GvdbTable *
+gvdb_table_get_table (GvdbTable *table,
+ const gchar *key)
+{
+ DConfMockGvdbItem *item;
+ GvdbTable *subtable;
+
+ item = g_hash_table_lookup (table->table, key);
+
+ if (item && item->table)
+ subtable = gvdb_table_ref (item->table);
+ else
+ subtable = NULL;
+
+ return subtable;
+}
+
+gboolean
+gvdb_table_has_value (GvdbTable *table,
+ const gchar *key)
+{
+ DConfMockGvdbItem *item;
+
+ item = g_hash_table_lookup (table->table, key);
+
+ return item && item->value;
+}
+
+GVariant *
+gvdb_table_get_value (GvdbTable *table,
+ const gchar *key)
+{
+ GHashTable *hash_table = (GHashTable *) table;
+ DConfMockGvdbItem *item;
+
+ item = g_hash_table_lookup (hash_table, key);
+
+ return item ? g_variant_ref (item->value) : NULL;
+}
+
+gchar **
+gvdb_table_list (GvdbTable *table,
+ const gchar *key)
+{
+ g_assert_not_reached ();
+}
+
+GvdbTable *
+gvdb_table_new (const gchar *filename,
+ gboolean trusted,
+ GError **error)
+{
+ GvdbTable *table;
+
+ g_mutex_lock (&dconf_mock_gvdb_lock);
+ dconf_mock_gvdb_init ();
+ table = g_hash_table_lookup (dconf_mock_gvdb_tables, filename);
+ if (table)
+ gvdb_table_ref (table);
+ g_mutex_unlock (&dconf_mock_gvdb_lock);
+
+ if (table == NULL)
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "this gvdb does not exist");
+
+ return table;
+}
+
+gboolean
+gvdb_table_is_valid (GvdbTable *table)
+{
+ return table->is_valid;
+}
+
+void
+dconf_mock_gvdb_table_invalidate (GvdbTable *table)
+{
+ table->is_valid = FALSE;
+}
diff --git a/tests/dconf-mock-shm.c b/tests/dconf-mock-shm.c
new file mode 100644
index 0000000..588667e
--- /dev/null
+++ b/tests/dconf-mock-shm.c
@@ -0,0 +1,124 @@
+#include "../shm/dconf-shm.h"
+
+#include "dconf-mock.h"
+
+typedef struct
+{
+ guint8 flagged;
+ gint ref_count;
+} DConfMockShm;
+
+static GHashTable *dconf_mock_shm_table;
+static GMutex dconf_mock_shm_lock;
+static GString *dconf_mock_shm_log;
+
+static void
+dconf_mock_shm_unref (gpointer data)
+{
+ DConfMockShm *shm = data;
+
+ if (g_atomic_int_dec_and_test (&shm->ref_count))
+ g_slice_free (DConfMockShm, shm);
+}
+
+static DConfMockShm *
+dconf_mock_shm_ref (DConfMockShm *shm)
+{
+ g_atomic_int_inc (&shm->ref_count);
+
+ return shm;
+}
+
+guint8 *
+dconf_shm_open (const gchar *name)
+{
+ DConfMockShm *shm;
+
+ g_mutex_lock (&dconf_mock_shm_lock);
+
+ if G_UNLIKELY (dconf_mock_shm_table == NULL)
+ {
+ dconf_mock_shm_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, dconf_mock_shm_unref);
+ dconf_mock_shm_log = g_string_new (NULL);
+ }
+
+ shm = g_hash_table_lookup (dconf_mock_shm_table, name);
+ if (shm == NULL)
+ {
+ shm = g_slice_new0 (DConfMockShm);
+ g_hash_table_insert (dconf_mock_shm_table, g_strdup (name), dconf_mock_shm_ref (shm));
+ }
+
+ /* before unlocking... */
+ dconf_mock_shm_ref (shm);
+
+ g_string_append_printf (dconf_mock_shm_log, "open %s;", name);
+
+ g_mutex_unlock (&dconf_mock_shm_lock);
+
+ return &shm->flagged;
+}
+
+void
+dconf_shm_close (guint8 *shm)
+{
+ if (shm)
+ {
+ g_mutex_lock (&dconf_mock_shm_lock);
+ g_string_append (dconf_mock_shm_log, "close;");
+ g_mutex_unlock (&dconf_mock_shm_lock);
+
+ dconf_mock_shm_unref (shm);
+ }
+}
+
+gint
+dconf_mock_shm_flag (const gchar *name)
+{
+ DConfMockShm *shm;
+ gint count = 0;
+
+ g_mutex_lock (&dconf_mock_shm_lock);
+ shm = g_hash_table_lookup (dconf_mock_shm_table, name);
+ if (shm)
+ {
+ shm->flagged = 1;
+ count = shm->ref_count;
+ g_hash_table_remove (dconf_mock_shm_table, name);
+ }
+ g_mutex_unlock (&dconf_mock_shm_lock);
+
+ return count;
+}
+
+void
+dconf_mock_shm_reset (void)
+{
+ g_mutex_lock (&dconf_mock_shm_lock);
+ if (dconf_mock_shm_table != NULL)
+ {
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, dconf_mock_shm_table);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ DConfMockShm *shm = value;
+
+ g_assert_cmpint (shm->ref_count, ==, 1);
+ g_hash_table_iter_remove (&iter);
+ }
+
+ g_string_truncate (dconf_mock_shm_log, 0);
+ }
+ g_mutex_unlock (&dconf_mock_shm_lock);
+}
+
+void
+dconf_mock_shm_assert_log (const gchar *expected_log)
+{
+ g_mutex_lock (&dconf_mock_shm_lock);
+ g_assert_cmpstr (dconf_mock_shm_log->str, ==, expected_log);
+ g_string_truncate (dconf_mock_shm_log, 0);
+ g_mutex_unlock (&dconf_mock_shm_lock);
+}
diff --git a/tests/dconf-mock.h b/tests/dconf-mock.h
new file mode 100644
index 0000000..7c8fc0b
--- /dev/null
+++ b/tests/dconf-mock.h
@@ -0,0 +1,19 @@
+#ifndef __dconf_mock_h__
+#define __dconf_mock_h__
+
+#include "../gvdb/gvdb-reader.h"
+
+void dconf_mock_shm_reset (void);
+gint dconf_mock_shm_flag (const gchar *name);
+void dconf_mock_shm_assert_log (const gchar *expected_log);
+
+GvdbTable * dconf_mock_gvdb_table_new (void);
+void dconf_mock_gvdb_table_insert (GvdbTable *table,
+ const gchar *name,
+ GVariant *value,
+ GvdbTable *subtable);
+void dconf_mock_gvdb_table_invalidate (GvdbTable *table);
+void dconf_mock_gvdb_install (const gchar *filename,
+ GvdbTable *table);
+
+#endif
diff --git a/tests/engine.c b/tests/engine.c
new file mode 100644
index 0000000..37a3e0f
--- /dev/null
+++ b/tests/engine.c
@@ -0,0 +1,427 @@
+#define _GNU_SOURCE
+
+#include "../engine/dconf-engine.h"
+#include "../engine/dconf-engine-profile.h"
+#include "dconf-mock.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+/* Interpose to catch fopen("/etc/dconf/profile/user") */
+static const gchar *filename_to_replace;
+static const gchar *filename_to_replace_it_with;
+
+FILE *
+fopen (const char *filename,
+ const char *mode)
+{
+ static FILE * (*real_fopen) (const char *, const char *);
+
+ if (!real_fopen)
+ real_fopen = dlsym (RTLD_NEXT, "fopen");
+
+ if (filename_to_replace && g_str_equal (filename, filename_to_replace))
+ {
+ /* Crash if this file was unexpectedly opened */
+ g_assert (filename_to_replace_it_with != NULL);
+ filename = filename_to_replace_it_with;
+ }
+
+ return (* real_fopen) (filename, mode);
+}
+
+static GThread *main_thread;
+
+void
+dconf_engine_change_notify (DConfEngine *engine,
+ const gchar *prefix,
+ const gchar * const *changes,
+ const gchar *tag,
+ gpointer user_data)
+{
+ /* ignore */
+}
+
+static void
+verify_and_free (DConfEngineSource **sources,
+ gint n_sources,
+ const gchar * const *expected_names,
+ gint n_expected)
+{
+ gint i;
+
+ g_assert_cmpint (n_sources, ==, n_expected);
+
+ g_assert ((sources == NULL) == (n_sources == 0));
+
+ for (i = 0; i < n_sources; i++)
+ {
+ g_assert_cmpstr (sources[i]->name, ==, expected_names[i]);
+ dconf_engine_source_free (sources[i]);
+ }
+
+ g_free (sources);
+}
+
+static void
+test_five_times (const gchar *filename,
+ gint n_expected,
+ ...)
+{
+ const gchar **expected_names;
+ DConfEngineSource **sources;
+ gint n_sources;
+ va_list ap;
+ gint i;
+
+ expected_names = g_new (const gchar *, n_expected);
+ va_start (ap, n_expected);
+ for (i = 0; i < n_expected; i++)
+ expected_names[i] = va_arg (ap, const gchar *);
+ va_end (ap);
+
+ /* first try by supplying the profile filename via the API */
+ g_assert (g_getenv ("DCONF_PROFILE") == NULL);
+ g_assert (filename_to_replace == NULL);
+ sources = dconf_engine_profile_open (filename, &n_sources);
+ verify_and_free (sources, n_sources, expected_names, n_expected);
+
+ /* next try supplying it via the environment */
+ g_setenv ("DCONF_PROFILE", filename, TRUE);
+ g_assert (filename_to_replace == NULL);
+ sources = dconf_engine_profile_open (NULL, &n_sources);
+ verify_and_free (sources, n_sources, expected_names, n_expected);
+ g_unsetenv ("DCONF_PROFILE");
+
+ /* next try supplying a profile name via API and intercepting fopen */
+ filename_to_replace = "/etc/dconf/profile/myprofile";
+ filename_to_replace_it_with = filename;
+ g_assert (g_getenv ("DCONF_PROFILE") == NULL);
+ sources = dconf_engine_profile_open ("myprofile", &n_sources);
+ verify_and_free (sources, n_sources, expected_names, n_expected);
+ filename_to_replace = NULL;
+
+ /* next try the same, via the environment */
+ g_setenv ("DCONF_PROFILE", "myprofile", TRUE);
+ filename_to_replace = "/etc/dconf/profile/myprofile";
+ filename_to_replace_it_with = filename;
+ sources = dconf_engine_profile_open (NULL, &n_sources);
+ verify_and_free (sources, n_sources, expected_names, n_expected);
+ g_unsetenv ("DCONF_PROFILE");
+ filename_to_replace = NULL;
+
+ /* next try to have dconf pick it up as the default user profile */
+ filename_to_replace = "/etc/dconf/profile/user";
+ filename_to_replace_it_with = filename;
+ g_assert (g_getenv ("DCONF_PROFILE") == NULL);
+ sources = dconf_engine_profile_open (NULL, &n_sources);
+ verify_and_free (sources, n_sources, expected_names, n_expected);
+ filename_to_replace = NULL;
+
+ filename_to_replace_it_with = NULL;
+ g_free (expected_names);
+}
+
+static void
+test_profile_parser (void)
+{
+ DConfEngineSource **sources;
+ gint n_sources;
+
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+
+ sources = dconf_engine_profile_open (SRCDIR "/profile/this-file-does-not-exist", &n_sources);
+ g_assert_cmpint (n_sources, ==, 0);
+ g_assert (sources == NULL);
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+ g_test_trap_assert_stderr ("*WARNING*: unable to open named profile*");
+
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+
+ sources = dconf_engine_profile_open (SRCDIR "/profile/broken-profile", &n_sources);
+ g_assert_cmpint (n_sources, ==, 0);
+ g_assert (sources == NULL);
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+ g_test_trap_assert_stderr ("*WARNING*: unknown dconf database*unknown dconf database*");
+
+ test_five_times (SRCDIR "/profile/empty-profile", 0);
+ test_five_times (SRCDIR "/profile/test-profile", 1, "test");
+ test_five_times (SRCDIR "/profile/colourful", 4,
+ "user",
+ "other",
+ "verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname",
+ "nonewline");
+ test_five_times (SRCDIR "/profile/dos", 2, "user", "site");
+ test_five_times (SRCDIR "/profile/no-newline-longline", 0);
+ test_five_times (SRCDIR "/profile/many-sources", 10,
+ "user", "local", "room", "floor", "building",
+ "site", "region", "division", "country", "global");
+
+ /* finally, test that we get the default profile if the user profile
+ * file cannot be located and we do not specify another profile.
+ */
+ filename_to_replace = "/etc/dconf/profile/user";
+ filename_to_replace_it_with = SRCDIR "/profile/this-file-does-not-exist";
+ g_assert (g_getenv ("DCONF_PROFILE") == NULL);
+ sources = dconf_engine_profile_open (NULL, &n_sources);
+ filename_to_replace = NULL;
+ g_assert_cmpint (n_sources, ==, 1);
+ g_assert_cmpstr (sources[0]->name, ==, "user");
+ dconf_engine_source_free (sources[0]);
+ g_free (sources);
+
+ dconf_mock_shm_reset ();
+}
+
+static gpointer
+test_signal_threadsafety_worker (gpointer user_data)
+{
+ gint *finished = user_data;
+ gint i;
+
+ for (i = 0; i < 20000; i++)
+ {
+ DConfEngine *engine;
+
+ engine = dconf_engine_new (NULL, NULL);
+ dconf_engine_unref (engine);
+ }
+
+ g_atomic_int_inc (finished);
+
+ return NULL;
+}
+
+static void
+test_signal_threadsafety (void)
+{
+#define N_WORKERS 4
+ GVariant *parameters;
+ gint finished = 0;
+ gint i;
+
+ parameters = g_variant_new_parsed ("('/test/key', [''], 'tag')");
+ g_variant_ref_sink (parameters);
+
+ for (i = 0; i < N_WORKERS; i++)
+ g_thread_unref (g_thread_new ("testcase worker", test_signal_threadsafety_worker, &finished));
+
+ while (g_atomic_int_get (&finished) < N_WORKERS)
+ dconf_engine_handle_dbus_signal (G_BUS_TYPE_SESSION,
+ ":1.2.3",
+ "/ca/desrt/dconf/Writer/user",
+ "Notify", parameters);
+ g_variant_unref (parameters);
+
+ dconf_mock_shm_reset ();
+}
+
+static void
+test_user_source (void)
+{
+ DConfEngineSource *source;
+ GvdbTable *table;
+ GvdbTable *locks;
+ gboolean reopened;
+
+ /* Create the source from a clean slate */
+ source = dconf_engine_source_new ("user-db:user");
+ g_assert (source != NULL);
+ g_assert (source->values == NULL);
+ g_assert (source->locks == NULL);
+
+ /* Refresh it the first time.
+ * This should cause it to open the shm.
+ * FALSE should be returned because there is no database file.
+ */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+ dconf_mock_shm_assert_log ("open user;");
+
+ /* Try to refresh it. There must be no IO at this point. */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+ dconf_mock_shm_assert_log ("");
+
+ /* Add a real database. */
+ table = dconf_mock_gvdb_table_new ();
+ dconf_mock_gvdb_table_insert (table, "/values/int32", g_variant_new_int32 (123456), NULL);
+ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
+
+ /* Try to refresh it again.
+ * Because we didn't flag the change there must still be no IO.
+ */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+ g_assert (source->values == NULL);
+ g_assert (source->locks == NULL);
+ dconf_mock_shm_assert_log ("");
+
+ /* Now flag it and reopen. */
+ dconf_mock_shm_flag ("user");
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values != NULL);
+ g_assert (source->locks == NULL);
+ g_assert (gvdb_table_has_value (source->values, "/values/int32"));
+ dconf_mock_shm_assert_log ("close;open user;");
+
+ /* Do it again -- should get the same result, after some IO */
+ dconf_mock_shm_flag ("user");
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values != NULL);
+ g_assert (source->locks == NULL);
+ dconf_mock_shm_assert_log ("close;open user;");
+
+ /* "Delete" the gvdb and make sure dconf notices after a flag */
+ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
+ dconf_mock_shm_flag ("user");
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values == NULL);
+ g_assert (source->locks == NULL);
+ dconf_mock_shm_assert_log ("close;open user;");
+
+ /* Add a gvdb with a lock */
+ table = dconf_mock_gvdb_table_new ();
+ locks = dconf_mock_gvdb_table_new ();
+ dconf_mock_gvdb_table_insert (table, "/values/int32", g_variant_new_int32 (123456), NULL);
+ dconf_mock_gvdb_table_insert (locks, "/values/int32", g_variant_new_boolean (TRUE), NULL);
+ dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
+ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
+
+ /* Reopen and check if we have the lock */
+ dconf_mock_shm_flag ("user");
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values != NULL);
+ g_assert (source->locks != NULL);
+ g_assert (gvdb_table_has_value (source->values, "/values/int32"));
+ g_assert (gvdb_table_has_value (source->locks, "/values/int32"));
+ dconf_mock_shm_assert_log ("close;open user;");
+
+ /* Reopen one last time */
+ dconf_mock_shm_flag ("user");
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values != NULL);
+ g_assert (source->locks != NULL);
+ dconf_mock_shm_assert_log ("close;open user;");
+
+ dconf_engine_source_free (source);
+ dconf_mock_shm_assert_log ("close;");
+
+ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
+ dconf_mock_shm_reset ();
+}
+
+static void
+test_system_source (void)
+{
+ DConfEngineSource *source;
+ GvdbTable *first_table;
+ GvdbTable *next_table;
+ gboolean reopened;
+
+ source = dconf_engine_source_new ("system-db:site");
+ g_assert (source != NULL);
+
+ /* Check to see that we get the warning about the missing file. */
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+
+ /* Failing to open should return FALSE from refresh */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+ g_assert (source->values == NULL);
+
+ /* Attempt the reopen to make sure we don't get two warnings.
+ * We should see FALSE again since we go from NULL to NULL.
+ */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+
+ /* Create the file after the fact and make sure it opens properly */
+ first_table = dconf_mock_gvdb_table_new ();
+ dconf_mock_gvdb_install ("/etc/dconf/db/site", first_table);
+
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values != NULL);
+
+ dconf_engine_source_free (source);
+
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+ /* Check that we only saw the warning, but only one time. */
+ g_test_trap_assert_stderr ("*this gvdb does not exist; expect degraded performance*");
+ g_test_trap_assert_stderr_unmatched ("*degraded*degraded*");
+
+ /* Create the file before the first refresh attempt */
+ first_table = dconf_mock_gvdb_table_new ();
+ dconf_mock_gvdb_install ("/etc/dconf/db/site", first_table);
+ /* Hang on to a copy for ourselves for below... */
+ gvdb_table_ref (first_table);
+
+ /* See that we get the database. */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values == first_table);
+
+ /* Do a refresh, make sure there is no change. */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+ g_assert (source->values == first_table);
+
+ /* Replace the table on "disk" but don't invalidate the old one */
+ next_table = dconf_mock_gvdb_table_new ();
+ dconf_mock_gvdb_install ("/etc/dconf/db/site", next_table);
+
+ /* Make sure the old table remains open (ie: no IO performed) */
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+ g_assert (source->values == first_table);
+
+ /* Now mark the first table invalid and reopen */
+ dconf_mock_gvdb_table_invalidate (first_table);
+ gvdb_table_unref (first_table);
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (reopened);
+ g_assert (source->values == next_table);
+
+ /* Remove the file entirely and do the same thing */
+ dconf_mock_gvdb_install ("/etc/dconf/db/site", NULL);
+ reopened = dconf_engine_source_refresh (source);
+ g_assert (!reopened);
+
+ dconf_engine_source_free (source);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_setenv ("XDG_CONFIG_HOME", "/HOME/.config", TRUE);
+ g_unsetenv ("DCONF_PROFILE");
+
+ main_thread = g_thread_self ();
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/engine/profile-parser", test_profile_parser);
+ g_test_add_func ("/engine/signal-threadsafety", test_signal_threadsafety);
+ g_test_add_func ("/engine/sources/user", test_user_source);
+ g_test_add_func ("/engine/sources/system", test_system_source);
+
+ return g_test_run ();
+}
diff --git a/tests/gsettings.c b/tests/gsettings.c
index 5695fa7..89ebb4c 100644
--- a/tests/gsettings.c
+++ b/tests/gsettings.c
@@ -299,7 +299,7 @@ verify_consistency (void)
else
g_print ("(%d)", g_hash_table_size (implicit));
- g_assert (g_hash_table_size (explicit) == g_hash_table_size (implicit));
+ g_assert_cmpint (g_hash_table_size (explicit), ==, g_hash_table_size (implicit));
g_hash_table_iter_init (&iter, explicit);
while (g_hash_table_iter_next (&iter, &key, &value))
{
diff --git a/tests/gvdb.c b/tests/gvdb.c
new file mode 100644
index 0000000..7c80ee5
--- /dev/null
+++ b/tests/gvdb.c
@@ -0,0 +1,432 @@
+#include <glib.h>
+#include "../gvdb/gvdb-reader.h"
+
+static void
+test_reader_open_error (void)
+{
+ GError *error = NULL;
+ GvdbTable *table;
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/does_not_exist", TRUE, &error);
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
+ g_assert (table == NULL);
+ g_clear_error (&error);
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/file_empty", TRUE, &error);
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
+ g_assert (table == NULL);
+ g_clear_error (&error);
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/invalid_header", TRUE, &error);
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
+ g_assert (table == NULL);
+ g_clear_error (&error);
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/file_too_small", TRUE, &error);
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
+ g_assert (table == NULL);
+ g_clear_error (&error);
+}
+
+static void
+test_reader_empty (void)
+{
+ const gchar * strings[] = { "", "value", "/value", ".", NULL};
+ GError *error = NULL;
+ GvdbTable *table;
+ gchar **names;
+ gint n_names;
+ gint i;
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/empty_gvdb", TRUE, &error);
+ g_assert_no_error (error);
+ g_assert (table != NULL);
+
+ table = gvdb_table_ref (table);
+ gvdb_table_unref (table);
+
+ g_assert (gvdb_table_is_valid (table));
+
+ names = gvdb_table_get_names (table, &n_names);
+ g_assert_cmpint (n_names, ==, 0);
+ g_assert_cmpint (g_strv_length (names), ==, 0);
+ g_strfreev (names);
+
+ names = gvdb_table_get_names (table, NULL);
+ g_assert_cmpint (g_strv_length (names), ==, 0);
+ g_strfreev (names);
+
+ for (i = 0; strings[i]; i++)
+ {
+ const gchar *key = strings[i];
+ GvdbTable *sub;
+ GVariant *val;
+ gboolean has;
+ gchar **list;
+
+ sub = gvdb_table_get_table (table, key);
+ g_assert (sub == NULL);
+
+ has = gvdb_table_has_value (table, key);
+ g_assert (!has);
+
+ val = gvdb_table_get_value (table, key);
+ g_assert (val == NULL);
+
+ val = gvdb_table_get_raw_value (table, key);
+ g_assert (val == NULL);
+
+ list = gvdb_table_list (table, key);
+ g_assert (list == NULL);
+ }
+
+ gvdb_table_unref (table);
+}
+
+static void
+verify_table (GvdbTable *table)
+{
+ GVariant *value;
+ gchar **list;
+ gint n_names;
+ gboolean has;
+
+ /* We could not normally expect these to be in a particular order but
+ * we are using a specific test file that we know to be layed out this
+ * way...
+ *
+ * It's pure luck that they happened to be layed out in this nice way.
+ */
+ list = gvdb_table_get_names (table, &n_names);
+ g_assert_cmpint (n_names, ==, g_strv_length (list));
+ g_assert_cmpint (n_names, ==, 5);
+ g_assert_cmpstr (list[0], ==, "/");
+ g_assert_cmpstr (list[1], ==, "/values/");
+ g_assert_cmpstr (list[2], ==, "/values/boolean");
+ g_assert_cmpstr (list[3], ==, "/values/string");
+ g_assert_cmpstr (list[4], ==, "/values/int32");
+ g_strfreev (list);
+
+ list = gvdb_table_list (table, "/");
+ g_assert (list != NULL);
+ g_assert_cmpint (g_strv_length (list), ==, 1);
+ g_assert_cmpstr (list[0], ==, "values/");
+ g_strfreev (list);
+
+ list = gvdb_table_list (table, "/values/");
+ g_assert (list != NULL);
+ g_assert_cmpint (g_strv_length (list), ==, 3);
+ g_assert_cmpstr (list[0], ==, "boolean");
+ g_assert_cmpstr (list[1], ==, "int32");
+ g_assert_cmpstr (list[2], ==, "string");
+ g_strfreev (list);
+
+ /* A directory is not a value */
+ has = gvdb_table_has_value (table, "/");
+ g_assert (!has);
+ has = gvdb_table_has_value (table, "/values/");
+ g_assert (!has);
+
+ has = gvdb_table_has_value (table, "/int32");
+ g_assert (!has);
+ has = gvdb_table_has_value (table, "values/int32");
+ g_assert (!has);
+ has = gvdb_table_has_value (table, "/values/int32");
+ g_assert (has);
+
+ value = gvdb_table_get_value (table, "/");
+ g_assert (value == NULL);
+ value = gvdb_table_get_value (table, "/values/");
+ g_assert (value == NULL);
+ value = gvdb_table_get_value (table, "/int32");
+ g_assert (value == NULL);
+ value = gvdb_table_get_value (table, "values/int32");
+ g_assert (value == NULL);
+
+ value = gvdb_table_get_value (table, "/values/boolean");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN));
+ g_assert (g_variant_get_boolean (value));
+ g_variant_unref (value);
+
+ value = gvdb_table_get_raw_value (table, "/values/boolean");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN));
+ g_assert (g_variant_get_boolean (value));
+ g_variant_unref (value);
+
+ value = gvdb_table_get_value (table, "/values/int32");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_INT32));
+ g_assert_cmpint (g_variant_get_int32 (value), ==, 0x44332211);
+ g_variant_unref (value);
+
+ value = gvdb_table_get_value (table, "/values/string");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING));
+ g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "a string");
+ g_variant_unref (value);
+
+ value = gvdb_table_get_raw_value (table, "/values/string");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING));
+ g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "a string");
+ g_variant_unref (value);
+}
+
+static void
+test_reader_values (void)
+{
+ GError *error = NULL;
+ GvdbTable *table;
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/example_gvdb", TRUE, &error);
+ g_assert_no_error (error);
+ verify_table (table);
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+ {
+ GVariant *value;
+
+ value = gvdb_table_get_raw_value (table, "/values/int32");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_INT32));
+ g_assert_cmpint (g_variant_get_int32 (value), ==, 0x11223344);
+ g_variant_unref (value);
+ }
+#endif
+
+ gvdb_table_unref (table);
+}
+
+static void
+test_reader_values_bigendian (void)
+{
+ GError *error = NULL;
+ GvdbTable *table;
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/example_gvdb.big-endian", TRUE, &error);
+ g_assert_no_error (error);
+ verify_table (table);
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ {
+ GVariant *value;
+
+ value = gvdb_table_get_raw_value (table, "/values/int32");
+ g_assert (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_INT32));
+ g_assert_cmpint (g_variant_get_int32 (value), ==, 0x11223344);
+ g_variant_unref (value);
+ }
+#endif
+
+ gvdb_table_unref (table);
+}
+
+static void
+test_nested (void)
+{
+ GError *error = NULL;
+ GvdbTable *table;
+ GvdbTable *locks;
+ gchar **names;
+ gint n_names;
+ gboolean has;
+
+ table = gvdb_table_new (SRCDIR "/gvdbs/nested_gvdb", TRUE, &error);
+ g_assert_no_error (error);
+
+ /* Note the more-random ordering here compared with above. */
+ names = gvdb_table_get_names (table, &n_names);
+ g_assert_cmpint (n_names, ==, g_strv_length (names));
+ g_assert_cmpint (n_names, ==, 6);
+ g_assert_cmpstr (names[0], ==, "/values/boolean");
+ g_assert_cmpstr (names[1], ==, "/");
+ g_assert_cmpstr (names[2], ==, "/values/int32");
+ g_assert_cmpstr (names[3], ==, ".locks");
+ g_assert_cmpstr (names[4], ==, "/values/");
+ g_assert_cmpstr (names[5], ==, "/values/string");
+ g_strfreev (names);
+
+ locks = gvdb_table_get_table (table, "/");
+ g_assert (locks == NULL);
+ locks = gvdb_table_get_table (table, "/values/");
+ g_assert (locks == NULL);
+ locks = gvdb_table_get_table (table, "/values/int32");
+ g_assert (locks == NULL);
+
+ locks = gvdb_table_get_table (table, ".locks");
+ g_assert (locks != NULL);
+
+ has = gvdb_table_has_value (locks, "/first/lck");
+ g_assert (!has);
+
+ has = gvdb_table_has_value (locks, "/first/lock");
+ g_assert (has);
+
+ has = gvdb_table_has_value (locks, "/second");
+ g_assert (has);
+
+ gvdb_table_unref (table);
+ gvdb_table_unref (locks);
+}
+
+/* This function exercises the API against @table but does not do any
+ * asserts on unexpected values (although it will assert on inconsistent
+ * values returned by the API).
+ */
+static void
+inspect_carefully (GvdbTable *table)
+{
+ const gchar * key_names[] = {
+ "/", "/values/", "/int32", "values/int32",
+ "/values/int32", "/values/boolean", "/values/string",
+ ".locks", "/first/lock", "/second", NULL
+ };
+ gint found_items;
+ gchar **names;
+ gint n_names;
+ gint i;
+
+ found_items = 0;
+ for (i = 0; key_names[i]; i++)
+ {
+ const gchar *key = key_names[i];
+ GvdbTable *subtable;
+ GVariant *value;
+ gchar **list;
+ gboolean has;
+
+ has = gvdb_table_has_value (table, key);
+
+ list = gvdb_table_list (table, key);
+ g_assert (!has || list == NULL);
+ if (list)
+ {
+ gchar *joined = g_strjoinv (",", list);
+ g_strfreev (list);
+ g_free (joined);
+ found_items++;
+ }
+
+ value = gvdb_table_get_value (table, key);
+ g_assert_cmpint (value != NULL, ==, has);
+ if (value)
+ {
+ gchar *printed = g_variant_print (value, FALSE);
+ g_variant_unref (value);
+ g_free (printed);
+ found_items++;
+ }
+
+ value = gvdb_table_get_raw_value (table, key);
+ g_assert_cmpint (value != NULL, ==, has);
+ if (value)
+ {
+ gchar *printed = g_variant_print (value, FALSE);
+ g_variant_unref (value);
+ g_free (printed);
+ }
+
+ subtable = gvdb_table_get_table (table, key);
+ g_assert (!has || subtable == NULL);
+ if (subtable)
+ {
+ inspect_carefully (subtable);
+ gvdb_table_unref (subtable);
+ found_items++;
+ }
+ }
+
+ names = gvdb_table_get_names (table, &n_names);
+ g_assert_cmpint (n_names, ==, g_strv_length (names));
+ g_assert_cmpint (found_items, <=, n_names);
+ g_free (g_strjoinv (" ", names));
+ g_strfreev (names);
+}
+
+static void
+test_corrupted (gconstpointer user_data)
+{
+ gint percentage = GPOINTER_TO_INT (user_data);
+ GError *error = NULL;
+ GMappedFile *mapped;
+
+ mapped = g_mapped_file_new (SRCDIR "/gvdbs/nested_gvdb", FALSE, &error);
+ g_assert_no_error (error);
+ g_assert (mapped);
+
+ if (percentage)
+ {
+ GvdbTable *table;
+ const gchar *orig;
+ gsize length;
+ gchar *copy;
+ gint i;
+
+ orig = g_mapped_file_get_contents (mapped);
+ length = g_mapped_file_get_length (mapped);
+ copy = g_memdup (orig, length);
+
+ for (i = 0; i < 10000; i++)
+ {
+ gint j;
+
+ /* Make a broken copy, but leave the signature intact so that
+ * we don't get too many boring trivial failures.
+ */
+ for (j = 8; j < length; j++)
+ if (g_test_rand_int_range (0, 100) < percentage)
+ copy[j] = g_test_rand_int_range (0, 256);
+ else
+ copy[j] = orig[j];
+
+ table = gvdb_table_new_from_data (copy, length, FALSE, NULL, NULL, NULL, &error);
+
+ /* If we damaged the header, it may not open */
+ if (table)
+ {
+ inspect_carefully (table);
+ gvdb_table_unref (table);
+ }
+ else
+ {
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
+ g_clear_error (&error);
+ }
+ }
+
+ g_free (copy);
+ }
+ else
+ {
+ GvdbTable *table;
+
+ table = gvdb_table_new_from_data (g_mapped_file_get_contents (mapped),
+ g_mapped_file_get_length (mapped),
+ FALSE, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (table);
+
+ inspect_carefully (table);
+ gvdb_table_unref (table);
+ }
+
+ g_mapped_file_unref (mapped);
+}
+
+int
+main (int argc, char **argv)
+{
+ gint i;
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/gvdb/reader/open-error", test_reader_open_error);
+ g_test_add_func ("/gvdb/reader/empty", test_reader_empty);
+ g_test_add_func ("/gvdb/reader/values", test_reader_values);
+ g_test_add_func ("/gvdb/reader/values/big-endian", test_reader_values_bigendian);
+ g_test_add_func ("/gvdb/reader/nested", test_nested);
+ for (i = 0; i < 20; i++)
+ {
+ gchar test_name[80];
+ g_snprintf (test_name, sizeof test_name, "/gvdb/reader/corrupted/%d%%", i);
+ g_test_add_data_func (test_name, GINT_TO_POINTER (i), test_corrupted);
+ }
+
+ return g_test_run ();
+}
diff --git a/tests/gvdbs/empty_gvdb b/tests/gvdbs/empty_gvdb
new file mode 100644
index 0000000..c700bdb
--- /dev/null
+++ b/tests/gvdbs/empty_gvdb
Binary files differ
diff --git a/tests/gvdbs/example_gvdb b/tests/gvdbs/example_gvdb
new file mode 100644
index 0000000..73b098e
--- /dev/null
+++ b/tests/gvdbs/example_gvdb
Binary files differ
diff --git a/tests/gvdbs/example_gvdb.big-endian b/tests/gvdbs/example_gvdb.big-endian
new file mode 100644
index 0000000..c729546
--- /dev/null
+++ b/tests/gvdbs/example_gvdb.big-endian
Binary files differ
diff --git a/tests/gvdbs/file_empty b/tests/gvdbs/file_empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gvdbs/file_empty
diff --git a/tests/gvdbs/file_too_small b/tests/gvdbs/file_too_small
new file mode 100644
index 0000000..b54857d
--- /dev/null
+++ b/tests/gvdbs/file_too_small
@@ -0,0 +1 @@
+GVariant
diff --git a/tests/gvdbs/invalid_header b/tests/gvdbs/invalid_header
new file mode 100644
index 0000000..25335ec
--- /dev/null
+++ b/tests/gvdbs/invalid_header
@@ -0,0 +1 @@
+GVARIANT________________ \ No newline at end of file
diff --git a/tests/gvdbs/nested_gvdb b/tests/gvdbs/nested_gvdb
new file mode 100644
index 0000000..b593e38
--- /dev/null
+++ b/tests/gvdbs/nested_gvdb
Binary files differ
diff --git a/tests/paths.c b/tests/paths.c
index a95530e..5774333 100644
--- a/tests/paths.c
+++ b/tests/paths.c
@@ -1,4 +1,4 @@
-#include <dconf-paths.h>
+#include "../common/dconf-paths.h"
static void
test_paths (void)
@@ -16,6 +16,7 @@ test_paths (void)
#define relkey 020 | rel
#define reldir 040 | rel
+ { NULL, invalid },
{ "", reldir },
{ "/", dir },
@@ -75,26 +76,23 @@ test_paths (void)
const gchar *string = cases[i].string;
guint flags;
- flags = (dconf_is_path (string, NULL) ? 001 : 000) |
- (dconf_is_key (string, NULL) ? 002 : 000) |
- (dconf_is_dir (string, NULL) ? 004 : 000) |
- (dconf_is_rel (string, NULL) ? 010 : 000) |
- (dconf_is_rel_key (string, NULL) ? 020 : 000) |
- (dconf_is_rel_dir (string, NULL) ? 040 : 000);
-
- if (flags != cases[i].flags)
- {
- g_print ("case %i: string '%s' should be %o but is %o",
- i, string, cases[i].flags, flags);
- g_assert_not_reached ();
- }
+ flags = (dconf_is_path (string, NULL) ? 001 : 000) |
+ (dconf_is_key (string, NULL) ? 002 : 000) |
+ (dconf_is_dir (string, NULL) ? 004 : 000) |
+ (dconf_is_rel_path (string, NULL) ? 010 : 000) |
+ (dconf_is_rel_key (string, NULL) ? 020 : 000) |
+ (dconf_is_rel_dir (string, NULL) ? 040 : 000);
+
+ g_assert_cmphex (flags, ==, cases[i].flags);
}
}
int
-main (void)
+main (int argc, char **argv)
{
- test_paths ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/paths", test_paths);
- return 0;
+ return g_test_run ();
}
diff --git a/tests/profile/broken-profile b/tests/profile/broken-profile
new file mode 100644
index 0000000..d293cf0
--- /dev/null
+++ b/tests/profile/broken-profile
@@ -0,0 +1,2 @@
+a b c d e
+f g h i j
diff --git a/tests/profile/colourful b/tests/profile/colourful
new file mode 100644
index 0000000..5a69bb5
--- /dev/null
+++ b/tests/profile/colourful
@@ -0,0 +1,13 @@
+# this is an interesting dconf profile file
+ # it shows some of the possible weird things you can do
+
+ user-db:user # comments can be like this
+
+# we can have comments that are extremely long. we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.
+
+ system-db:other # we can have comments that are extremely long. we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.we can have comments that are extremely long.
+
+system-db:verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname
+
+# there is no newline after this last line
+system-db:nonewline \ No newline at end of file
diff --git a/tests/profile/dos b/tests/profile/dos
new file mode 100644
index 0000000..cc38458
--- /dev/null
+++ b/tests/profile/dos
@@ -0,0 +1,2 @@
+user-db:user
+system-db:site
diff --git a/tests/profile/empty-profile b/tests/profile/empty-profile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/profile/empty-profile
diff --git a/tests/profile/many-sources b/tests/profile/many-sources
new file mode 100644
index 0000000..15fe5ca
--- /dev/null
+++ b/tests/profile/many-sources
@@ -0,0 +1,10 @@
+user-db:user
+system-db:local
+system-db:room
+system-db:floor
+system-db:building
+system-db:site
+system-db:region
+system-db:division
+system-db:country
+system-db:global
diff --git a/tests/profile/no-newline-longline b/tests/profile/no-newline-longline
new file mode 100644
index 0000000..93193aa
--- /dev/null
+++ b/tests/profile/no-newline-longline
@@ -0,0 +1 @@
+# this long line has no newline at the end. this long line has no newline at the end. this long line has no newline at the end. this long line has no newline at the end. this long line has no newline at the end. this long line has no newline at the end. this long line has no newline at the end. this long line has no newline at the end. \ No newline at end of file
diff --git a/tests/profile/test-profile b/tests/profile/test-profile
new file mode 100644
index 0000000..3a511c2
--- /dev/null
+++ b/tests/profile/test-profile
@@ -0,0 +1 @@
+user-db:test
diff --git a/tests/profile/will-never-exist b/tests/profile/will-never-exist
new file mode 100644
index 0000000..bcff7a6
--- /dev/null
+++ b/tests/profile/will-never-exist
@@ -0,0 +1 @@
+user-db:will-never-exist
diff --git a/tests/shm.c b/tests/shm.c
new file mode 100644
index 0000000..86e3b34
--- /dev/null
+++ b/tests/shm.c
@@ -0,0 +1,170 @@
+#define _GNU_SOURCE
+#include "../common/dconf-paths.h"
+#include <glib/gstdio.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "../shm/dconf-shm.h"
+#include "tmpdir.h"
+
+static void
+test_mkdir_fail (void)
+{
+ guint8 *shm;
+
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ gchar *evil;
+ gint fd;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+
+ evil = g_build_filename (g_get_user_runtime_dir (), "dconf", NULL);
+ fd = open (evil, O_WRONLY | O_CREAT, 0600);
+ close (fd);
+
+ shm = dconf_shm_open ("foo");
+ g_assert (shm == NULL);
+
+ g_unlink (evil);
+ g_free (evil);
+
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+ g_test_trap_assert_stderr ("*unable to create directory*");
+}
+
+static void
+test_close_null (void)
+{
+ dconf_shm_close (NULL);
+}
+
+static void
+test_open_and_flag (void)
+{
+ guint8 *shm;
+
+ shm = dconf_shm_open ("foo");
+ g_assert (shm != NULL);
+ g_assert (!dconf_shm_is_flagged (shm));
+ dconf_shm_flag ("foo");
+ g_assert (dconf_shm_is_flagged (shm));
+ dconf_shm_close (shm);
+}
+
+static void
+test_invalid_name (void)
+{
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ guint8 *shm;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+
+ shm = dconf_shm_open ("foo/bar");
+ g_assert (shm == NULL);
+ g_assert (dconf_shm_is_flagged (shm));
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+ g_test_trap_assert_stderr ("*unable to create*foo/bar*");
+}
+
+static void
+test_flag_nonexistent (void)
+{
+ dconf_shm_flag ("does-not-exist");
+}
+
+static gboolean should_fail_pwrite;
+/* interpose */
+ssize_t
+pwrite (int fd, const void *buf, size_t count, off_t offset)
+{
+ static ssize_t (* real_pwrite) (int, const void *, size_t, off_t);
+
+ if (!real_pwrite)
+ real_pwrite = dlsym (RTLD_NEXT, "pwrite");
+
+ if (should_fail_pwrite)
+ {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ return (* real_pwrite) (fd, buf, count, offset);
+}
+
+static void
+test_out_of_space_open (void)
+{
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ guint8 *shm;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+ should_fail_pwrite = TRUE;
+
+ shm = dconf_shm_open ("foo");
+ g_assert (shm == NULL);
+ g_assert (dconf_shm_is_flagged (shm));
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+ g_test_trap_assert_stderr ("*failed to allocate*foo*");
+}
+
+static void
+test_out_of_space_flag (void)
+{
+ if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+ should_fail_pwrite = TRUE;
+
+ dconf_shm_flag ("foo");
+ exit (0);
+ }
+ g_test_trap_assert_passed ();
+}
+
+int
+main (int argc, char **argv)
+{
+ gchar *temp;
+ gint status;
+
+ temp = dconf_test_create_tmpdir ();
+
+ g_setenv ("XDG_RUNTIME_DIR", temp, TRUE);
+ /* This currently works, but it is possible that one day GLib will
+ * read the XDG_RUNTIME_DIR variable (and cache its value) as a
+ * side-effect of the dconf_test_create_tmpdir() call above.
+ *
+ * This assert will quickly uncover the problem in that case...
+ */
+ g_assert_cmpstr (g_get_user_runtime_dir (), ==, temp);
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/shm/mkdir-fail", test_mkdir_fail);
+ g_test_add_func ("/shm/close-null", test_close_null);
+ g_test_add_func ("/shm/open-and-flag", test_open_and_flag);
+ g_test_add_func ("/shm/invalid-name", test_invalid_name);
+ g_test_add_func ("/shm/flag-nonexistent", test_flag_nonexistent);
+ g_test_add_func ("/shm/out-of-space-open", test_out_of_space_open);
+ g_test_add_func ("/shm/out-of-space-flag", test_out_of_space_flag);
+
+ status = g_test_run ();
+
+ dconf_test_remove_tmpdir (temp);
+ g_free (temp);
+
+ return status;
+}
diff --git a/tests/tmpdir.c b/tests/tmpdir.c
new file mode 100644
index 0000000..b8c8745
--- /dev/null
+++ b/tests/tmpdir.c
@@ -0,0 +1,53 @@
+#include "tmpdir.h"
+
+#include <glib/gstdio.h>
+#include "../common/dconf-paths.h"
+#include <string.h>
+
+gchar *
+dconf_test_create_tmpdir (void)
+{
+ GError *error = NULL;
+ gchar *temp;
+
+ temp = g_dir_make_tmp ("dconf-testcase.XXXXXX", &error);
+ g_assert_no_error (error);
+ g_assert (temp != NULL);
+
+ return temp;
+}
+
+static void
+rm_rf (const gchar *file)
+{
+ GDir *dir;
+
+ dir = g_dir_open (file, 0, NULL);
+ if (dir)
+ {
+ const gchar *basename;
+
+ while ((basename = g_dir_read_name (dir)))
+ {
+ gchar *fullname;
+
+ fullname = g_build_filename (file, basename, NULL);
+ rm_rf (fullname);
+ g_free (fullname);
+ }
+
+ g_dir_close (dir);
+ g_rmdir (file);
+ }
+
+ else
+ /* excess paranoia -- only unlink if we're really really sure */
+ if (strstr (file, "/dconf-testcase") && !strstr (file, ".."))
+ g_unlink (file);
+}
+
+void
+dconf_test_remove_tmpdir (const gchar *tmpdir)
+{
+ rm_rf (tmpdir);
+}
diff --git a/tests/tmpdir.h b/tests/tmpdir.h
new file mode 100644
index 0000000..6e9dae8
--- /dev/null
+++ b/tests/tmpdir.h
@@ -0,0 +1,9 @@
+#ifndef __dconf_tmpdir_h__
+#define __dconf_tmpdir_h__
+
+#include <glib.h>
+
+gchar *dconf_test_create_tmpdir (void);
+void dconf_test_remove_tmpdir (const gchar *tmpdir);
+
+#endif /* __dconf_tmpdir_h__ */
diff --git a/trim-lcov.py b/trim-lcov.py
new file mode 100755
index 0000000..c1709c3
--- /dev/null
+++ b/trim-lcov.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+
+# This script removes branch and/or line coverage data for lines that
+# contain a particular substring.
+#
+# In the interest of "fairness" it removes all branch or coverage data
+# when a match is found -- not just negative data. It is therefore
+# likely that running this script will actually reduce the total number
+# of lines and branches that are marked as covered (in absolute terms).
+#
+# This script intentionally avoids checking for errors. Any exceptions
+# will trigger make to fail.
+#
+# Author: Ryan Lortie <desrt@desrt.ca>
+
+import sys
+
+line_suppress = ['g_assert_not_reached']
+branch_suppress = ['g_assert', 'g_return_if_fail', 'g_return_val_if_fail', 'G_DEFINE_TYPE']
+
+def check_suppress(suppressions, source, data):
+ line, _, rest = data.partition(',')
+ line = int(line) - 1
+
+ assert line < len(source)
+
+ for suppression in suppressions:
+ if suppression in source[line]:
+ return True
+
+ return False
+
+source = []
+for line in sys.stdin:
+ line = line[:-1]
+
+ keyword, _, rest = line.partition(':')
+
+ # Source file
+ if keyword == 'SF':
+ source = file(rest).readlines()
+
+ # Branch coverage data
+ elif keyword == 'BRDA':
+ if check_suppress(branch_suppress, source, rest):
+ continue
+
+ # Line coverage data
+ elif keyword == 'DA':
+ if check_suppress(line_suppress, source, rest):
+ continue
+
+ print line