/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <sal/config.h>

#include <cassert>
#include <cstddef>
#include <cstring>
#include <forward_list>
#include <limits>
#include <vector>

extern "C" {
    // <https://bugzilla.gnome.org/show_bug.cgi?id=754245>
    // "common/dconf-changeset.h etc. lack extern "C" wrapper for C++"
#include <dconf/dconf.h>
}

#include <com/sun/star/uno/Sequence.hxx>
#include <o3tl/safeint.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>

#include "data.hxx"
#include "dconf.hxx"
#include "groupnode.hxx"
#include "localizedpropertynode.hxx"
#include "localizedvaluenode.hxx"
#include "nodemap.hxx"
#include "propertynode.hxx"
#include "setnode.hxx"

// component-data is encoded in dconf as follows:
//
// * The node hierarchy (starting at component nodes with names like
//   "org.openoffice.Setup") maps to dconf paths underneath
//   "/org/libreoffice/registry/".
//
// * Component, group, set, and localized property nodes map to dconf dirs,
//   while property and localized value nodes map to dconf keys.
//
// * The names of nodes that are not set elements are used directly as dconf
//   path segments.  (The syntax for node names is any non-empty sequences of
//   any Unicode scalar values except U+0000--0008, U+000B--000C, U+000E--001F,
//   U+002F SOLIDUS, and U+FFFE--FFFF.  TODO: "<aruiz> sberg, in general I think
//   it'd be nice if you used path separators instead of dots though, they have
//   meaning in dconf/gvdb world :-)"?)
//
// * The names of set element nodes are encoded as dconf path segments as
//   follows: each occurrence of U+0000 NULL is replace by the three characters
//   "\00", each occurrence of U+002F SOLIDUS is replaced by the three
//   characters "\2F", and each occurrence of U+005C REVERSE SOLIDUS is replaced
//   by the three characters "\5C".
//
// * Set elements (which must themselves be either sets or groups) map to
//   "indirection" dconf dirs as follows:
//
// ** The dir must contain a key named "op" of string type, with a value of
//    "fuse", "replace", or "remove".
//
// ** If "op" is "fuse" or "replace", the dir must contain exactly the following
//    further keys and dirs:
//
// *** The dir must contain a key named "template" of string type, containing
//     the full template name, encoded as follows: each occurrence of U+0000
//     NULL is replace by the three characters "\00" and each occurrence of
//     U+005C REVERSE SOLIDUS is replaced by the three characters "\5C".
//
// *** The dir must contain a dir named "content" that contains the set
//     element's (i.e., set or group node's) real content.
//
// ** If "op" is "remove", the dir must contain no further keys or dirs.
//
// * Property and localized property value "fuse" operations map to GVariant
//   instances as follows:
//
// ** Non-nillable boolean values map to GVariant boolean instances.
//
// ** Non-nillable short values map to GVariant int16 instances.
//
// ** Non-nillable int values map to GVariant int32 instances.
//
// ** Non-nillable long values map to GVariant int64 instances.
//
// ** Non-nillable double values map to GVariant double instances.
//
// ** Non-nillable string values map to GVariant string instances, with the
//    following encoding: each occurrence of U+0000 NULL is replace by the three
//    characters "\00" and each occurrence of U+005C REVERSE SOLIDUS is replaced
//    by the three characters "\5C".
//
// ** Non-nillable hexbinary values map to GVariant byte array instances.
//
// ** Non-nillable list values recursively map to GVariant array instances.
//
// ** Nillable values recursively map to GVariant maybe instances.
//
// * Property "remove" operations map to GVariant instances of empty tuple type.
//
// Finalization:  The component-update.dtd allows for finalization of
// oor:component-data, node, and prop elements, while dconf allows for locking
// of individual keys.  That does not match, but just mark the individual Node
// instances that correspond to individual dconf keys as finalized for
// non-writable dconf keys.
//
// TODO: support "mandatory" and "external"?

namespace configmgr::dconf {

namespace {

template<typename T> class GObjectHolder {
public:
    explicit GObjectHolder(T * object): object_(object) {}

    ~GObjectHolder() {
        if (object_ != nullptr) {
            g_object_unref(object_);
        }
    }

    T * get() const { return object_; }

private:
    GObjectHolder(GObjectHolder const &) = delete;
    GObjectHolder& operator =(GObjectHolder const &) = delete;

    T * object_;
};

class GVariantHolder {
public:
    explicit GVariantHolder(GVariant * variant = nullptr): variant_(variant) {}

    ~GVariantHolder() { unref(); }

    void reset(GVariant * variant) {
        unref();
        variant_ = variant;
    }

    void release() { variant_ = nullptr; }

    GVariant * get() const { return variant_; }

private:
    GVariantHolder(GVariantHolder const &) = delete;
    GVariantHolder& operator =(GVariantHolder const &) = delete;

    void unref() {
        if (variant_ != nullptr) {
            g_variant_unref(variant_);
        }
    }

    GVariant * variant_;
};

class GVariantTypeHolder {
public:
    explicit GVariantTypeHolder(GVariantType * type): type_(type) {}

    ~GVariantTypeHolder() {
        if (type_ != nullptr) {
            g_variant_type_free(type_);
        }
    }

    GVariantType * get() const { return type_; }

private:
    GVariantTypeHolder(GVariantTypeHolder const &) = delete;
    GVariantTypeHolder& operator =(GVariantTypeHolder const &) = delete;

    GVariantType * type_;
};

class StringArrayHolder {
public:
    explicit StringArrayHolder(gchar ** array): array_(array) {}

    ~StringArrayHolder() { g_strfreev(array_); }

    gchar ** get() const { return array_; }

private:
    StringArrayHolder(StringArrayHolder const &) = delete;
    StringArrayHolder& operator =(StringArrayHolder const &) = delete;

    gchar ** array_;
};

class ChangesetHolder {
public:
    explicit ChangesetHolder(DConfChangeset * changeset):
        changeset_(changeset)
    {}

    ~ChangesetHolder() {
        if (changeset_ != nullptr) {
            dconf_changeset_unref(changeset_);
        }
    }

    DConfChangeset * get() const { return changeset_; }

private:
    ChangesetHolder(ChangesetHolder const &) = delete;
    ChangesetHolder& operator =(ChangesetHolder const &) = delete;

    DConfChangeset * changeset_;
};

OString getRoot() {
    return "/org/libreoffice/registry";
}

bool decode(OUString * string, bool slash) {
    for (sal_Int32 i = 0;; ++i) {
        i = string->indexOf('\\', i);
        if (i == -1) {
            return true;
        }
        if (string->match("00", i + 1)) {
            *string = string->replaceAt(i, 3, OUString(u'\0'));
        } else if (slash && string->match("2F", i + 1)) {
            *string = string->replaceAt(i, 3, "/");
        } else if (string->match("5C", i + 1)) {
            *string = string->replaceAt(i + 1, 2, "");
        } else {
            SAL_WARN("configmgr.dconf", "bad escape in " << *string);
            return false;
        }
    }
}

bool getBoolean(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_BOOLEAN)) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match boolean property");
        return false;
    }
    *value <<= bool(g_variant_get_boolean(variant.get()));
    return true;
}

bool getShort(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT16)) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match short property");
        return false;
    }
    *value <<= sal_Int16(g_variant_get_int16(variant.get()));
    return true;
}

bool getInt(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT32)) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match int property");
        return false;
    }
    *value <<= sal_Int32(g_variant_get_int32(variant.get()));
    return true;
}

bool getLong(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT64)) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match long property");
        return false;
    }
    *value <<= sal_Int64(g_variant_get_int64(variant.get()));
    return true;
}

bool getDouble(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_DOUBLE)) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match double property");
        return false;
    }
    *value <<= double(g_variant_get_double(variant.get()));
    return true;
}

bool getStringValue(
    OString const & key, GVariantHolder const & variant, OUString * value)
{
    assert(value != nullptr);
    if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_STRING)) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match string property");
        return false;
    }
    gsize n;
    char const * p = g_variant_get_string(variant.get(), &n);
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long string value for key " << key);
        return false;
    }
    if (!rtl_convertStringToUString(
            &value->pData, p, static_cast<sal_Int32>(n), RTL_TEXTENCODING_UTF8,
            (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
             | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
             | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
    {
        SAL_WARN("configmgr.dconf", "non--UTF-8 string value for key " << key);
        return false;
    }
    return decode(value, false);
}

bool getString(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    OUString v;
    if (!getStringValue(key, variant, &v)) {
        return false;
    }
    *value <<= v;
    return true;
}

bool getHexbinaryValue(
    OString const & key, GVariantHolder const & variant,
    css::uno::Sequence<sal_Int8> * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "ay") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match hexbinary property");
        return false;
    }
    gsize n;
    gconstpointer p = g_variant_get_fixed_array(
        variant.get(), &n, sizeof (guchar));
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long hexbinary value for key " << key);
        return false;
    }
    value->realloc(static_cast<sal_Int32>(n));
    static_assert(sizeof (sal_Int8) == sizeof (guchar), "size mismatch");
    std::memcpy(value->getArray(), p, n * sizeof (guchar));
        // assuming that n * sizeof (guchar) is small enough for std::size_t
    return true;
}

bool getHexbinary(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    css::uno::Sequence<sal_Int8> v;
    if (!getHexbinaryValue(key, variant, &v)) {
        return false;
    }
    *value <<= v;
    return true;
}

bool getBooleanList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "ab") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match boolean list property");
        return false;
    }
    gsize n;
    gconstpointer p = g_variant_get_fixed_array(
        variant.get(), &n, sizeof (guchar));
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long boolean list for key " << key);
        return false;
    }
    css::uno::Sequence<sal_Bool> v(static_cast<sal_Int32>(n));
    static_assert(sizeof (sal_Bool) == sizeof (guchar), "size mismatch");
    std::memcpy(v.getArray(), p, n * sizeof (guchar));
        // assuming that n * sizeof (guchar) is small enough for std::size_t
    *value <<= v;
    return true;
}

bool getShortList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "an") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match short list property");
        return false;
    }
    gsize n;
    gconstpointer p = g_variant_get_fixed_array(
        variant.get(), &n, sizeof (gint16));
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long short list for key " << key);
        return false;
    }
    css::uno::Sequence<sal_Int16> v(static_cast<sal_Int32>(n));
    static_assert(sizeof (sal_Int16) == sizeof (gint16), "size mismatch");
    std::memcpy(v.getArray(), p, n * sizeof (gint16));
        // assuming that n * sizeof (gint16) is small enough for std::size_t
    *value <<= v;
    return true;
}

bool getIntList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "ai") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match int list property");
        return false;
    }
    gsize n;
    gconstpointer p = g_variant_get_fixed_array(
        variant.get(), &n, sizeof (gint32));
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long int list for key " << key);
        return false;
    }
    css::uno::Sequence<sal_Int32> v(static_cast<sal_Int32>(n));
    static_assert(sizeof (sal_Int32) == sizeof (gint32), "size mismatch");
    std::memcpy(v.getArray(), p, n * sizeof (gint32));
        // assuming that n * sizeof (gint32) is small enough for std::size_t
    *value <<= v;
    return true;
}

bool getLongList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "ax") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match long list property");
        return false;
    }
    gsize n;
    gconstpointer p = g_variant_get_fixed_array(
        variant.get(), &n, sizeof (gint64));
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long long list for key " << key);
        return false;
    }
    css::uno::Sequence<sal_Int64> v(static_cast<sal_Int32>(n));
    static_assert(sizeof (sal_Int64) == sizeof (gint64), "size mismatch");
    std::memcpy(v.getArray(), p, n * sizeof (gint64));
        // assuming that n * sizeof (gint64) is small enough for std::size_t
    *value <<= v;
    return true;
}

bool getDoubleList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "ad") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match double list property");
        return false;
    }
    gsize n;
    gconstpointer p = g_variant_get_fixed_array(
        variant.get(), &n, sizeof (gdouble));
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long double list for key " << key);
        return false;
    }
    css::uno::Sequence<double> v(static_cast<sal_Int32>(n));
    static_assert(std::is_same<double, gdouble>::value, "type mismatch");
    std::memcpy(v.getArray(), p, n * sizeof (gdouble));
        // assuming that n * sizeof (gdouble) is small enough for std::size_t
    *value <<= v;
    return true;
}

bool getStringList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "as") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match string list property");
        return false;
    }
    gsize n = g_variant_n_children(variant.get());
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long string list for key " << key);
        return false;
    }
    css::uno::Sequence<OUString> v(static_cast<sal_Int32>(n));
    for (gsize i = 0; i != n; ++i) {
        GVariantHolder c(g_variant_get_child_value(variant.get(), i));
        if (!getStringValue(key, c, v.getArray() + i)) {
            return false;
        }
    }
    *value <<= v;
    return true;
}

bool getHexbinaryList(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    assert(value != nullptr);
    if (std::strcmp(g_variant_get_type_string(variant.get()), "aay") != 0) {
        SAL_WARN(
            "configmgr.dconf",
            "bad key " << key << " does not match hexbinary list property");
        return false;
    }
    gsize n = g_variant_n_children(variant.get());
    if (n > o3tl::make_unsigned(
            std::numeric_limits<sal_Int32>::max()))
    {
        SAL_WARN("configmgr.dconf", "too long hexbinary list for key " << key);
        return false;
    }
    css::uno::Sequence<css::uno::Sequence<sal_Int8>> v(
        static_cast<sal_Int32>(n));
    for (gsize i = 0; i != n; ++i) {
        GVariantHolder c(g_variant_get_child_value(variant.get(), i));
        if (!getHexbinaryValue(key, c, v.getArray() + i)) {
            return false;
        }
    }
    *value <<= v;
    return true;
}

bool getAny(
    OString const & key, GVariantHolder const & variant, css::uno::Any * value)
{
    char const * t = g_variant_get_type_string(variant.get());
    if (std::strcmp(t, "b") == 0) {
        return getBoolean(key, variant, value);
    }
    if (std::strcmp(t, "n") == 0) {
        return getShort(key, variant, value);
    }
    if (std::strcmp(t, "i") == 0) {
        return getInt(key, variant, value);
    }
    if (std::strcmp(t, "x") == 0) {
        return getLong(key, variant, value);
    }
    if (std::strcmp(t, "d") == 0) {
        return getDouble(key, variant, value);
    }
    if (std::strcmp(t, "s") == 0) {
        return getString(key, variant, value);
    }
    if (std::strcmp(t, "ay") == 0) {
        return getHexbinary(key, variant, value);
    }
    if (std::strcmp(t, "ab") == 0) {
        return getBooleanList(key, variant, value);
    }
    if (std::strcmp(t, "an") == 0) {
        return getShortList(key, variant, value);
    }
    if (std::strcmp(t, "ai") == 0) {
        return getIntList(key, variant, value);
    }
    if (std::strcmp(t, "ax") == 0) {
        return getLongList(key, variant, value);
    }
    if (std::strcmp(t, "ad") == 0) {
        return getDoubleList(key, variant, value);
    }
    if (std::strcmp(t, "as") == 0) {
        return getStringList(key, variant, value);
    }
    if (std::strcmp(t, "aay") == 0) {
        return getHexbinaryList(key, variant, value);
    }
    SAL_WARN(
        "configmgr.dconf", "bad key " << key << " does not match any property");
    return false;
}

enum class ReadValue { Error, Value, Remove };

ReadValue readValue(
    GObjectHolder<DConfClient> const & client, OString const & path, Type type,
    bool nillable, bool removable, css::uno::Any * value)
{
    assert(value != nullptr);
    assert(!value->hasValue());
    assert(!path.endsWith("/"));
    GVariantHolder v(dconf_client_read(client.get(), path.getStr()));
    if (v.get() == nullptr) {
        SAL_WARN("configmgr.dconf", "cannot read key " << path);
        return ReadValue::Error;
    }
    if (removable && std::strcmp(g_variant_get_type_string(v.get()), "()") == 0)
    {
        return ReadValue::Remove;
    }
    bool nil;
    if (nillable) {
        if (g_variant_classify(v.get()) != G_VARIANT_CLASS_MAYBE) {
            SAL_WARN(
                "configmgr.dconf",
                "bad key " << path << " does not match nillable property");
        }
        v.reset(g_variant_get_maybe(v.get()));
        nil = v.get() == nullptr;
    } else {
        nil = false;
    }
    if (!nil) {
        switch (type) {
        case TYPE_ANY:
            if (!getAny(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_BOOLEAN:
            if (!getBoolean(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_SHORT:
            if (!getShort(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_INT:
            if (!getInt(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_LONG:
            if (!getLong(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_DOUBLE:
            if (!getDouble(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_STRING:
            if (!getString(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_HEXBINARY:
            if (!getHexbinary(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_BOOLEAN_LIST:
            if (!getBooleanList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_SHORT_LIST:
            if (!getShortList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_INT_LIST:
            if (!getIntList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_LONG_LIST:
            if (!getLongList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_DOUBLE_LIST:
            if (!getDoubleList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_STRING_LIST:
            if (!getStringList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        case TYPE_HEXBINARY_LIST:
            if (!getHexbinaryList(path, v, value)) {
                return ReadValue::Error;
            }
            break;
        default:
            assert(false); // cannot happen
        }
    }
    return ReadValue::Value;
}

void finalize(
    GObjectHolder<DConfClient> const & client, OString const & path,
    rtl::Reference<Node> const & node, int layer)
{
    if (!dconf_client_is_writable(client.get(), path.getStr())) {
        node->setFinalized(layer);
    }
}

void readDir(
    Data & data, int layer, rtl::Reference<Node> const & node,
    NodeMap & members, GObjectHolder<DConfClient> const & client,
    OString const & dir)
{
    StringArrayHolder a(dconf_client_list(client.get(), dir.getStr(), nullptr));
    for (char const * const * p = a.get(); *p != nullptr; ++p) {
        std::size_t n = std::strlen(*p);
        if (n > o3tl::make_unsigned(
                std::numeric_limits<sal_Int32>::max()))
        {
            SAL_WARN("configmgr.dconf", "too long dir/key in dir " << dir);
            continue;
        }
        OString s(*p, static_cast<sal_Int32>(n));
        OString path(dir + s);
        OUString name;
        if (!rtl_convertStringToUString(
                &name.pData, s.getStr(), s.getLength(), RTL_TEXTENCODING_UTF8,
                (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
                 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
                 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
        {
            SAL_WARN("configmgr.dconf", "non--UTF-8 dir/key in dir " << dir);
            continue;
        }
        bool isDir = name.endsWith("/", &name);
        OUString templ;
        bool remove;
        bool replace;
        if (node.is() && node->kind() == Node::KIND_SET) {
            if (!isDir) {
                SAL_WARN(
                    "configmgr.dconf",
                    "bad key " << path << " does not match set element");
                continue;
            }
            if (!decode(&name, true)) {
                continue;
            }
            enum class Op { None, Fuse, Replace, Remove };
            Op op = Op::None;
            bool content = false;
            bool bad = false;
            StringArrayHolder a2(
                dconf_client_list(client.get(), path.getStr(), nullptr));
            for (char const * const * p2 = a2.get(); *p2 != nullptr; ++p2) {
                if (std::strcmp(*p2, "op") == 0) {
                    OString path2(path + "op");
                    GVariantHolder v(
                        dconf_client_read(client.get(), path2.getStr()));
                    if (v.get() == nullptr) {
                        SAL_WARN(
                            "configmgr.dconf", "cannot read key " << path2);
                        bad = true;
                        break;
                    }
                    OUString ops;
                    if (!getStringValue(path2, v, &ops)) {
                        bad = true;
                        break;
                    }
                    if (ops == "fuse") {
                        op = Op::Fuse;
                    } else if (ops == "replace") {
                        op = Op::Replace;
                    } else if (ops == "remove") {
                        op = Op::Remove;
                    } else {
                        SAL_WARN(
                            "configmgr.dconf",
                            "bad key " << path2 << " value " << ops);
                        bad = true;
                        break;
                    }
                } else if (std::strcmp(*p2, "template") == 0) {
                    OString path2(path + "template");
                    GVariantHolder v(
                        dconf_client_read(client.get(), path2.getStr()));
                    if (v.get() == nullptr) {
                        SAL_WARN(
                            "configmgr.dconf", "cannot read key " << path2);
                        bad = true;
                        break;
                    }
                    if (!getStringValue(path2, v, &templ)) {
                        bad = true;
                        break;
                    }
                    if (!static_cast<SetNode *>(node.get())->
                        isValidTemplate(templ))
                    {
                        SAL_WARN(
                            "configmgr.dconf",
                            "bad key " << path2 << " value " << templ
                                << " denotes unsupported set element template");
                        bad = true;
                        break;
                    }
                } else if (std::strcmp(*p2, "content/") == 0) {
                    content = true;
                } else {
                    SAL_WARN(
                        "configmgr.dconf",
                        "bad dir/key " << p2
                            << " in set element indirection dir " << path);
                    bad = true;
                    break;
                }
            }
            if (bad) {
                continue;
            }
            switch (op) {
            default: // case Op::None:
                SAL_WARN(
                    "configmgr.dconf",
                    "bad set element indirection dir " << path
                        << " missing \"op\" key");
                continue;
            case Op::Fuse:
            case Op::Replace:
                if (templ.isEmpty() || !content) {
                    SAL_WARN(
                        "configmgr.dconf",
                        "missing \"content\" and/or \"template\" dir/key in "
                            "\"op\" = \"fuse\"/\"remove\" set element"
                            " indirection dir " << path);
                    continue;
                }
                path += "content/";
                remove = false;
                replace = op == Op::Replace;
                break;
            case Op::Remove:
                if (!templ.isEmpty() || content) {
                    SAL_WARN(
                        "configmgr.dconf",
                        "bad \"content\" and/or \"template\" dir/key in \"op\" "
                            "= \"remove\" set element indirection dir "
                            << path);
                    continue;
                }
                remove = true;
                replace = false;
                break;
            }
        } else {
            remove = false;
            replace = false;
        }
        rtl::Reference<Node> member(members.findNode(layer, name));
        bool insert = !member.is();
        if (!remove) {
            if (replace || insert) {
                if (!node.is()) {
                    SAL_WARN("configmgr.dconf", "bad unmatched " << path);
                    continue;
                }
                switch (node->kind()) {
                case Node::KIND_LOCALIZED_PROPERTY:
                    member.set(new LocalizedValueNode(layer));
                    break;
                case Node::KIND_GROUP:
                    if (!static_cast<GroupNode *>(node.get())->isExtensible()) {
                        SAL_WARN("configmgr.dconf", "bad unmatched " << path);
                        continue;
                    }
                    member.set(
                        new PropertyNode(
                            layer, TYPE_ANY, true, css::uno::Any(), true));
                    break;
                case Node::KIND_SET:
                    assert(!templ.isEmpty());
                    member = data.getTemplate(layer, templ);
                    if (!member.is()) {
                        SAL_WARN(
                            "configmgr.dconf",
                            "bad " << path << " denoting undefined template "
                                << templ);
                        continue;
                    }
                    member = member->clone(true);
                    break;
                default:
                    assert(false); // cannot happen
                }
            } else if (!templ.isEmpty() && templ != member->getTemplateName()) {
                SAL_WARN(
                    "configmgr.dconf",
                    "bad " << path
                        << " denoting set element of non-matching template "
                        << member->getTemplateName());
                continue;
            }
        }
        if (member.is()) {
            if (member->getFinalized() < layer) {
                continue;
            }
            switch (member->kind()) {
            case Node::KIND_PROPERTY:
                {
                    if (isDir) {
                        SAL_WARN(
                            "configmgr.dconf",
                            "bad dir " << path << " does not match property");
                        continue;
                    }
                    rtl::Reference<PropertyNode> prop(
                        static_cast<PropertyNode *>(member.get()));
                    css::uno::Any value;
                    switch (readValue(
                                client, path, prop->getStaticType(),
                                prop->isNillable(), prop->isExtension(),
                                &value))
                    {
                    case ReadValue::Error:
                        continue;
                    case ReadValue::Value:
                        prop->setValue(layer, value);
                        finalize(client, path, member, layer);
                        break;
                    case ReadValue::Remove:
                        remove = true;
                        break;
                    }
                    break;
                }
            case Node::KIND_LOCALIZED_VALUE:
                {
                    if (isDir) {
                        SAL_WARN(
                            "configmgr.dconf",
                            "bad dir " << path
                                << " does not match localized value");
                        continue;
                    }
                    assert(
                        node.is()
                        && node->kind() == Node::KIND_LOCALIZED_PROPERTY);
                    rtl::Reference<LocalizedPropertyNode> locProp(
                        static_cast<LocalizedPropertyNode *>(node.get()));
                    css::uno::Any value;
                    if (readValue(
                            client, path, locProp->getStaticType(),
                            locProp->isNillable(), false, &value)
                        == ReadValue::Error)
                    {
                        continue;
                    }
                    static_cast<LocalizedValueNode *>(member.get())->setValue(
                        layer, value);
                    finalize(client, path, member, layer);
                    break;
                }
            case Node::KIND_LOCALIZED_PROPERTY:
            case Node::KIND_GROUP:
            case Node::KIND_SET:
                if (!isDir) {
                    SAL_WARN(
                        "configmgr.dconf",
                        "bad key " << path
                            << " does not match localized property, group, or"
                            " set, respectively");
                    continue;
                }
                assert(path.endsWith("/"));
                readDir(
                    data, layer, member, member->getMembers(), client, path);
                break;
            default:
                assert(false); // cannot happen
            }
        }
        if (remove) {
            if (!(member.is() && member->getMandatory())) {
                members.erase(name);
            }
        } else if (replace) {
            members.erase(name);
            members.insert(NodeMap::value_type(name, member));
        } else if (insert) {
            members.insert(NodeMap::value_type(name, member));
        }
    }
}

OString encodeSegment(OUString const & name, bool setElement) {
    if (!setElement) {
        return name.toUtf8();
    }
    OUStringBuffer buf;
    for (sal_Int32 i = 0; i != name.getLength(); ++i) {
        sal_Unicode c = name[i];
        switch (c) {
        case '\0':
            buf.append("\\00");
            break;
        case '/':
            buf.append("\\2F");
            break;
        case '\\':
            buf.append("\\5C");
            break;
        default:
            buf.append(c);
        }
    }
    return buf.makeStringAndClear().toUtf8();
}

OString encodeString(OUString const & value) {
    OUStringBuffer buf;
    for (sal_Int32 i = 0; i != value.getLength(); ++i) {
        sal_Unicode c = value[i];
        switch (c) {
        case '\0':
            buf.append("\\00");
            break;
        case '\\':
            buf.append("\\5C");
            break;
        default:
            buf.append(c);
        }
    }
    return buf.makeStringAndClear().toUtf8();
}

bool addProperty(
    ChangesetHolder const & changeset, OString const & pathRepresentation,
    Type type, bool nillable, css::uno::Any const & value)
{
    Type dynType = getDynamicType(value);
    assert(dynType != TYPE_ERROR);
    if (type == TYPE_ANY) {
        type = dynType;
    }
    GVariantHolder v;
    std::forward_list<GVariantHolder> children;
    if (dynType == TYPE_NIL) {
        switch (type) {
        case TYPE_BOOLEAN:
            v.reset(g_variant_new_maybe(G_VARIANT_TYPE_BOOLEAN, nullptr));
            break;
        case TYPE_SHORT:
            v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT16, nullptr));
            break;
        case TYPE_INT:
            v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT32, nullptr));
            break;
        case TYPE_LONG:
            v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT64, nullptr));
            break;
        case TYPE_DOUBLE:
            v.reset(g_variant_new_maybe(G_VARIANT_TYPE_DOUBLE, nullptr));
            break;
        case TYPE_STRING:
            v.reset(g_variant_new_maybe(G_VARIANT_TYPE_STRING, nullptr));
            break;
        case TYPE_HEXBINARY:
        case TYPE_BOOLEAN_LIST:
        case TYPE_SHORT_LIST:
        case TYPE_INT_LIST:
        case TYPE_LONG_LIST:
        case TYPE_DOUBLE_LIST:
        case TYPE_STRING_LIST:
        case TYPE_HEXBINARY_LIST:
            {
                static char const * const typeString[
                    TYPE_HEXBINARY_LIST - TYPE_HEXBINARY + 1]
                    = { "ay", "ab", "an", "ai", "ax", "ad", "as", "aay" };
                GVariantTypeHolder ty(
                    g_variant_type_new(typeString[type - TYPE_HEXBINARY]));
                if (ty.get() == nullptr) {
                    SAL_WARN("configmgr.dconf", "g_variant_type_new failed");
                    return false;
                }
                v.reset(g_variant_new_maybe(ty.get(), nullptr));
                break;
            }
        default:
            assert(false); // this cannot happen
            break;
        }
        if (v.get() == nullptr) {
            SAL_WARN("configmgr.dconf", "g_variant_new_maybe failed");
            return false;
        }
    } else {
        switch (type) {
        case TYPE_BOOLEAN:
            v.reset(g_variant_new_boolean(value.get<bool>()));
            break;
        case TYPE_SHORT:
            v.reset(g_variant_new_int16(value.get<sal_Int16>()));
            break;
        case TYPE_INT:
            v.reset(g_variant_new_int32(value.get<sal_Int32>()));
            break;
        case TYPE_LONG:
            v.reset(g_variant_new_int64(value.get<sal_Int64>()));
            break;
        case TYPE_DOUBLE:
            v.reset(g_variant_new_double(value.get<double>()));
            break;
        case TYPE_STRING:
            v.reset(
                g_variant_new_string(
                    encodeString(value.get<OUString>()).getStr()));
            break;
        case TYPE_HEXBINARY:
            {
                css::uno::Sequence<sal_Int8> seq(
                    value.get<css::uno::Sequence<sal_Int8>>());
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                static_assert(
                    sizeof (sal_Int8) == sizeof (guchar), "size mismatch");
                v.reset(
                    g_variant_new_fixed_array(
                        G_VARIANT_TYPE_BYTE, seq.getConstArray(),
                        seq.getLength(), sizeof (sal_Int8)));
                break;
            }
        case TYPE_BOOLEAN_LIST:
            {
                css::uno::Sequence<sal_Bool> seq(
                    value.get<css::uno::Sequence<sal_Bool>>());
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                static_assert(sizeof (sal_Bool) == 1, "size mismatch");
                v.reset(
                    g_variant_new_fixed_array(
                        G_VARIANT_TYPE_BOOLEAN, seq.getConstArray(),
                        seq.getLength(), sizeof (sal_Bool)));
                break;
            }
        case TYPE_SHORT_LIST:
            {
                css::uno::Sequence<sal_Int16> seq(
                    value.get<css::uno::Sequence<sal_Int16>>());
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                static_assert(
                    sizeof (sal_Int16) == sizeof (gint16), "size mismatch");
                v.reset(
                    g_variant_new_fixed_array(
                        G_VARIANT_TYPE_INT16, seq.getConstArray(),
                        seq.getLength(), sizeof (sal_Int16)));
                    //TODO: endian-ness?
                break;
            }
        case TYPE_INT_LIST:
            {
                css::uno::Sequence<sal_Int32> seq(
                    value.get<css::uno::Sequence<sal_Int32>>());
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                static_assert(
                    sizeof (sal_Int32) == sizeof (gint32), "size mismatch");
                v.reset(
                    g_variant_new_fixed_array(
                        G_VARIANT_TYPE_INT32, seq.getConstArray(),
                        seq.getLength(), sizeof (sal_Int32)));
                    //TODO: endian-ness?
                break;
            }
        case TYPE_LONG_LIST:
            {
                css::uno::Sequence<sal_Int64> seq(
                    value.get<css::uno::Sequence<sal_Int64>>());
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                static_assert(
                    sizeof (sal_Int64) == sizeof (gint64), "size mismatch");
                v.reset(
                    g_variant_new_fixed_array(
                        G_VARIANT_TYPE_INT64, seq.getConstArray(),
                        seq.getLength(), sizeof (sal_Int64)));
                    //TODO: endian-ness?
                break;
            }
        case TYPE_DOUBLE_LIST:
            {
                css::uno::Sequence<double> seq(
                    value.get<css::uno::Sequence<double>>());
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                static_assert(
                    sizeof (double) == sizeof (gdouble), "size mismatch");
                v.reset(
                    g_variant_new_fixed_array(
                        G_VARIANT_TYPE_DOUBLE, seq.getConstArray(),
                        seq.getLength(), sizeof (double)));
                    //TODO: endian-ness?
                break;
            }
        case TYPE_STRING_LIST:
            {
                const css::uno::Sequence<OUString> seq(
                    value.get<css::uno::Sequence<OUString>>());
                std::vector<GVariant *> vs;
                for (OUString const & s : seq) {
                    children.emplace_front(
                        g_variant_new_string(encodeString(s).getStr()));
                    if (children.front().get() == nullptr) {
                        SAL_WARN(
                            "configmgr.dconf", "g_variant_new_string failed");
                        return false;
                    }
                    vs.push_back(children.front().get());
                }
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                v.reset(
                    g_variant_new_array(
                        G_VARIANT_TYPE_STRING, vs.data(), seq.getLength()));
                break;
            }
        case TYPE_HEXBINARY_LIST:
            {
                const css::uno::Sequence<css::uno::Sequence<sal_Int8>> seqSeq(
                    value.get<
                        css::uno::Sequence<css::uno::Sequence<sal_Int8>>>());
                std::vector<GVariant *> vs;
                for (css::uno::Sequence<sal_Int8> const & seq : seqSeq) {
                    static_assert(
                        sizeof(sal_Int32) <= sizeof(gsize),
                        "G_MAXSIZE too small");
                    static_assert(
                        sizeof (sal_Int8) == sizeof (guchar), "size mismatch");
                    children.emplace_front(
                        g_variant_new_fixed_array(
                            G_VARIANT_TYPE_BYTE, seq.getConstArray(),
                            seq.getLength(), sizeof (sal_Int8)));
                    if (children.front().get() == nullptr) {
                        SAL_WARN(
                            "configmgr.dconf",
                            "g_variant_new_fixed_array failed");
                        return false;
                    }
                    vs.push_back(children.front().get());
                }
                GVariantTypeHolder ty(g_variant_type_new("aay"));
                if (ty.get() == nullptr) {
                    SAL_WARN("configmgr.dconf", "g_variant_type_new failed");
                    return false;
                }
                static_assert(
                    sizeof(sal_Int32) <= sizeof(gsize),
                    "G_MAXSIZE too small");
                v.reset(
                    g_variant_new_array(ty.get(), vs.data(), seqSeq.getLength()));
                break;
            }
        default:
            assert(false); // this cannot happen
            break;
        }
        if (v.get() == nullptr) {
            SAL_WARN("configmgr.dconf", "GVariant creation failed");
            return false;
        }
        if (nillable) {
            GVariantHolder v1(g_variant_new_maybe(nullptr, v.get()));
            if (v1.get() == nullptr) {
                SAL_WARN("configmgr.dconf", "g_variant_new_maybe failed");
                return false;
            }
            v.release();
            v.reset(v1.get());
            v1.release();
        }
    }
    dconf_changeset_set(
        changeset.get(), pathRepresentation.getStr(), v.get());
    for (auto & i: children) {
        i.release();
    }
    v.release();
    return true;
}

bool addNode(
    Components & components, ChangesetHolder const & changeset,
    rtl::Reference<Node> const & parent, OString const & pathRepresentation,
    rtl::Reference<Node> const & node)
{
    switch (node->kind()) {
    case Node::KIND_PROPERTY:
        {
            PropertyNode * prop = static_cast<PropertyNode *>(node.get());
            if (!addProperty(
                    changeset, pathRepresentation, prop->getStaticType(),
                    prop->isNillable(), prop->getValue(components)))
            {
                return false;
            }
            break;
        }
    case Node::KIND_LOCALIZED_VALUE:
        {
            //TODO: name.isEmpty()?
            LocalizedPropertyNode * locprop
                = static_cast<LocalizedPropertyNode *>(parent.get());
            if (!addProperty(
                    changeset, pathRepresentation,
                    locprop->getStaticType(), locprop->isNillable(),
                    static_cast<LocalizedValueNode *>(node.get())->getValue()))
            {
                return false;
            }
            break;
        }
    case Node::KIND_LOCALIZED_PROPERTY:
    case Node::KIND_GROUP:
    case Node::KIND_SET:
        for (auto const & i: node->getMembers()) {
            OUString templ(i.second->getTemplateName());
            OString path(
                pathRepresentation + "/"
                + encodeSegment(i.first, !templ.isEmpty()));
            if (!templ.isEmpty()) {
                path += "/";
                GVariantHolder v(g_variant_new_string("replace"));
                if (v.get() == nullptr) {
                    SAL_WARN("configmgr.dconf", "g_variant_new_string failed");
                    return false;
                }
                dconf_changeset_set(
                    changeset.get(), OString(path + "op").getStr(), v.get());
                v.release();
                v.reset(g_variant_new_string(encodeString(templ).getStr()));
                if (v.get() == nullptr) {
                    SAL_WARN("configmgr.dconf", "g_variant_new_string failed");
                    return false;
                }
                dconf_changeset_set(
                    changeset.get(), OString(path + "template").getStr(),
                    v.get());
                v.release();
                path += "content";
            }
            if (!addNode(components, changeset, parent, path, i.second)) {
                return false;
            }
        }
        break;
    case Node::KIND_ROOT:
        assert(false); // this cannot happen
        break;
    }
    return true;
}

bool addModifications(
    Components & components, ChangesetHolder const & changeset,
    OString const & parentPathRepresentation,
    rtl::Reference<Node> const & parent, OUString const & nodeName,
    rtl::Reference<Node> const & node,
    Modifications::Node const & modifications)
{
    // It is never necessary to write oor:finalized or oor:mandatory attributes,
    // as they cannot be set via the UNO API.
    if (modifications.children.empty()) {
        assert(parent.is());
            // components themselves have no parent but must have children
        if (node.is()) {
            OUString templ(node->getTemplateName());
            OString path(
                parentPathRepresentation + "/"
                + encodeSegment(nodeName, !templ.isEmpty()));
            if (!templ.isEmpty()) {
                path += "/";
                GVariantHolder v(g_variant_new_string("replace"));
                if (v.get() == nullptr) {
                    SAL_WARN("configmgr.dconf", "g_variant_new_string failed");
                    return false;
                }
                dconf_changeset_set(
                    changeset.get(), OString(path + "op").getStr(), v.get());
                v.release();
                v.reset(g_variant_new_string(encodeString(templ).getStr()));
                if (v.get() == nullptr) {
                    SAL_WARN("configmgr.dconf", "g_variant_new_string failed");
                    return false;
                }
                dconf_changeset_set(
                    changeset.get(), OString(path + "template").getStr(),
                    v.get());
                v.release();
                path += "content";
            }
            if (!addNode(components, changeset, parent, path, node)) {
                return false;
            }
        } else {
            switch (parent->kind()) {
            case Node::KIND_LOCALIZED_PROPERTY:
            case Node::KIND_GROUP:
                {
                    GVariantHolder v(g_variant_new_tuple(nullptr, 0));
                    if (v.get() == nullptr) {
                        SAL_WARN(
                            "configmgr.dconf", "g_variant_new_tuple failed");
                        return false;
                    }
                    OString path(parentPathRepresentation);
                    if (!nodeName.isEmpty()) { // KIND_LOCALIZED_PROPERTY
                        path += "/" + encodeSegment(nodeName, false);
                    }
                    dconf_changeset_set(
                        changeset.get(), path.getStr(), v.get());
                    v.release();
                    break;
                }
            case Node::KIND_SET:
                {
                    OString path(
                        parentPathRepresentation + "/"
                        + encodeSegment(nodeName, true) + "/");
                    GVariantHolder v(g_variant_new_string("remove"));
                    if (v.get() == nullptr) {
                        SAL_WARN(
                            "configmgr.dconf", "g_variant_new_string failed");
                        return false;
                    }
                    dconf_changeset_set(
                        changeset.get(), OString(path + "op").getStr(),
                        v.get());
                    v.release();
                    dconf_changeset_set(
                        changeset.get(), OString(path + "template").getStr(),
                        nullptr);
                    dconf_changeset_set(
                        changeset.get(), OString(path + "content/").getStr(),
                        nullptr);
                    break;
                }
            default:
                assert(false); // this cannot happen
                break;
            }
        }
    } else {
        assert(node.is());
        OUString templ(node->getTemplateName());
        OString path(
            parentPathRepresentation + "/"
            + encodeSegment(nodeName, !templ.isEmpty()));
        if (!templ.isEmpty()) {
            path += "/";
            GVariantHolder v(g_variant_new_string("fuse"));
            if (v.get() == nullptr) {
                SAL_WARN("configmgr.dconf", "g_variant_new_string failed");
                return false;
            }
            dconf_changeset_set(
                changeset.get(), OString(path + "op").getStr(), v.get());
            v.release();
            v.reset(g_variant_new_string(encodeString(templ).getStr()));
            if (v.get() == nullptr) {
                SAL_WARN("configmgr.dconf", "g_variant_new_string failed");
                return false;
            }
            dconf_changeset_set(
                changeset.get(), OString(path + "template").getStr(), v.get());
            v.release();
            path += "content";
        }
        for (auto const & i: modifications.children) {
            if (!addModifications(
                    components, changeset, path, node, i.first,
                    node->getMember(i.first), i.second))
            {
                return false;
            }
        }
    }
    return true;
}

}

void readLayer(Data & data, int layer) {
    GObjectHolder<DConfClient> client(dconf_client_new());
    if (client.get() == nullptr) {
        SAL_WARN("configmgr.dconf", "dconf_client_new failed");
        return;
    }
    readDir(
        data, layer, rtl::Reference<Node>(), data.getComponents(), client,
        getRoot() + "/");
}

void writeModifications(Components & components, Data & data) {
    GObjectHolder<DConfClient> client(dconf_client_new());
    if (client.get() == nullptr) {
        SAL_WARN("configmgr.dconf", "dconf_client_new failed");
    }
    ChangesetHolder cs(dconf_changeset_new());
    if (cs.get() == nullptr) {
        SAL_WARN("configmgr.dconf", "dconf_changeset_new failed");
        return;
    }
    for (auto const & i: data.modifications.getRoot().children) {
        if (!addModifications(
                components, cs, getRoot(), rtl::Reference<Node>(), i.first,
                data.getComponents().findNode(Data::NO_LAYER, i.first),
                i.second))
        {
            return;
        }
    }
    if (!dconf_client_change_sync(
            client.get(), cs.get(), nullptr, nullptr, nullptr))
    {
        //TODO: GError
        SAL_WARN("configmgr.dconf", "dconf_client_change_sync failed");
        return;
    }
    data.modifications.clear();
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */