From a50d999d0741ec2ce57d513c949e39283b4561b7 Mon Sep 17 00:00:00 2001 From: Aleksei Nikiforov Date: Fri, 14 Aug 2020 15:28:00 +0300 Subject: [PATCH] KConfig: notify on every configuration file change via DBUS Also add utilities for listening to the broadcasted changes and replaying changes. --- kconfig/src/CMakeLists.txt | 3 + kconfig/src/core/CMakeLists.txt | 3 + kconfig/src/core/kconfig.cpp | 26 +++ kconfig/src/core/kconfig.h | 4 + kconfig/src/core/kconfig_p.h | 1 + kconfig/src/core/kconfigbase.cpp | 198 ++++++++++++++++ kconfig/src/core/kconfigbase.h | 23 ++ kconfig/src/core/kconfiggroup.cpp | 139 +++++++++++- kconfig/src/core/kconfiggroup.h | 2 + kconfig/src/core/kconfigserialization.cpp | 136 +++++++++++ kconfig/src/core/kconfigserialization.h | 24 ++ kconfig/src/kconf_watcher/CMakeLists.txt | 15 ++ kconfig/src/kconf_watcher/kconf_apply.cpp | 240 ++++++++++++++++++++ kconfig/src/kconf_watcher/kconf_watcher.cpp | 135 +++++++++++ kconfig/src/kconf_watcher/kconf_watcher_p.h | 18 ++ 15 files changed, 965 insertions(+), 2 deletions(-) create mode 100644 kconfig/src/core/kconfigserialization.cpp create mode 100644 kconfig/src/core/kconfigserialization.h create mode 100644 kconfig/src/kconf_watcher/CMakeLists.txt create mode 100644 kconfig/src/kconf_watcher/kconf_apply.cpp create mode 100644 kconfig/src/kconf_watcher/kconf_watcher.cpp create mode 100644 kconfig/src/kconf_watcher/kconf_watcher_p.h diff --git a/kconfig/src/CMakeLists.txt b/kconfig/src/CMakeLists.txt index 151c324..2f211df 100644 --- a/kconfig/src/CMakeLists.txt +++ b/kconfig/src/CMakeLists.txt @@ -4,6 +4,9 @@ if(TARGET Qt5::Gui) endif() add_subdirectory(kconfig_compiler) add_subdirectory(kconf_update) +if(KCONFIG_USE_DBUS) + add_subdirectory(kconf_watcher) +endif() add_subdirectory(kreadconfig) ecm_qt_install_logging_categories( diff --git a/kconfig/src/core/CMakeLists.txt b/kconfig/src/core/CMakeLists.txt index eb16d03..c80a400 100644 --- a/kconfig/src/core/CMakeLists.txt +++ b/kconfig/src/core/CMakeLists.txt @@ -11,6 +11,7 @@ set(libkconfigcore_SRCS kauthorized.cpp kemailsettings.cpp kconfigwatcher.cpp + kconfigserialization.cpp ) ecm_qt_declare_logging_category(libkconfigcore_SRCS @@ -65,6 +66,7 @@ ecm_generate_headers(KConfigCore_HEADERS KEMailSettings ConversionCheck KConfigWatcher + KConfigSerialization REQUIRED_HEADERS KConfigCore_HEADERS ) diff --git a/kconfig/src/core/kconfig.cpp b/kconfig/src/core/kconfig.cpp index 7f53847..f4250e0 100644 --- a/kconfig/src/core/kconfig.cpp +++ b/kconfig/src/core/kconfig.cpp @@ -60,6 +60,7 @@ KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::Standar , bFileImmutable(false) , bForceGlobal(false) , bSuppressGlobal(false) + , bUpdateNotificationEnabled(true) , configState(KConfigBase::NoAccess) { const bool isTestMode = QStandardPaths::isTestModeEnabled(); @@ -886,6 +887,19 @@ KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags) } void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags) +{ + if (isUpdateNotificationEnabled()) + { + sendNotification(nullptr, QByteArray(), QStringList(), + QStringList(QString::fromUtf8(aGroup)), + name(), locationType(), flags, + EntryNotificationType::DeleteGroup); + } + + deleteGroupImplWithoutNotification(aGroup, flags); +} + +void KConfig::deleteGroupImplWithoutNotification(const QByteArray &aGroup, WriteConfigFlags flags) { Q_D(KConfig); KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted; @@ -1002,6 +1016,18 @@ QStandardPaths::StandardLocation KConfig::locationType() const return d->resourceType; } +bool KConfig::isUpdateNotificationEnabledImpl() const +{ + Q_D(const KConfig); + return d->bUpdateNotificationEnabled; +} + +void KConfig::setUpdateNotificationEnabledImpl(bool new_state) +{ + Q_D(KConfig); + d->bUpdateNotificationEnabled = new_state; +} + void KConfig::virtual_hook(int /*id*/, void * /*data*/) { /* nothing */ diff --git a/kconfig/src/core/kconfig.h b/kconfig/src/core/kconfig.h index 792a9a6..7f6638d 100644 --- a/kconfig/src/core/kconfig.h +++ b/kconfig/src/core/kconfig.h @@ -370,6 +370,8 @@ protected: const KConfigGroup groupImpl(const QByteArray &b) const override; void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags = Normal) override; bool isGroupImmutableImpl(const QByteArray &aGroup) const override; + bool isUpdateNotificationEnabledImpl() const override; + void setUpdateNotificationEnabledImpl(bool new_state) override; friend class KConfigGroup; friend class KConfigGroupPrivate; @@ -384,6 +386,8 @@ protected: KConfig(KConfigPrivate &d); + void deleteGroupImplWithoutNotification(const QByteArray &group, WriteConfigFlags flags = Normal); + private: friend class KConfigTest; diff --git a/kconfig/src/core/kconfig_p.h b/kconfig/src/core/kconfig_p.h index b201df1..b013775 100644 --- a/kconfig/src/core/kconfig_p.h +++ b/kconfig/src/core/kconfig_p.h @@ -65,6 +65,7 @@ private: bool bFileImmutable : 1; bool bForceGlobal : 1; bool bSuppressGlobal : 1; + bool bUpdateNotificationEnabled : 1; static bool mappingsRegistered; diff --git a/kconfig/src/core/kconfigbase.cpp b/kconfig/src/core/kconfigbase.cpp index 9eee642..169c2a8 100644 --- a/kconfig/src/core/kconfigbase.cpp +++ b/kconfig/src/core/kconfigbase.cpp @@ -9,10 +9,27 @@ #include "kconfigbase.h" +#include "config-kconfig.h" #include "kconfiggroup.h" #include +#if KCONFIG_USE_DBUS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kconfig.h" +#include "kconfigserialization.h" +#endif // KCONFIG_USE_DBUS + +bool KConfigBase::s_notification_warn_once = true; + bool KConfigBase::hasGroup(const QString &group) const { return hasGroupImpl(group.toUtf8()); @@ -88,6 +105,16 @@ bool KConfigBase::isGroupImmutable(const char *aGroup) const return isGroupImmutableImpl(QByteArray(aGroup)); } +bool KConfigBase::isUpdateNotificationEnabled() const +{ + return isUpdateNotificationEnabledImpl(); +} + +void KConfigBase::setUpdateNotificationEnabled(bool new_state) +{ + setUpdateNotificationEnabledImpl(new_state); +} + KConfigBase::~KConfigBase() { } @@ -99,3 +126,174 @@ KConfigBase::KConfigBase() void KConfigBase::virtual_hook(int, void *) { } + +void KConfigBase::sendNotification(const char *key, const QByteArray &value, + const QStringList &entry_paths, const QStringList &groups, + const QString &filename, QStandardPaths::StandardLocation location_type, + WriteConfigFlags flags, EntryNotificationType notification_type) +{ +#if KCONFIG_USE_DBUS + { + static QMutex s_globalsInitMutex; + static bool s_globalsInitialized = false; + static bool s_notificationsGloballyEnabled; + + { // lock + QMutexLocker globalsInitMutexLock(&s_globalsInitMutex); + + if (!s_globalsInitialized) + { + KConfig globals_config(QStringLiteral("kdeglobals"), KConfig::NoGlobals); + KConfigGroup alt_group = globals_config.group(QStringLiteral("ALT")); + s_notificationsGloballyEnabled = alt_group.readEntry(QStringLiteral("EnableDbusNotifications"), false); + s_globalsInitialized = true; + } + } // unlock + + if (!s_notificationsGloballyEnabled) + { + return; + } + } + + if (!QDBusConnection::sessionBus().isConnected()) + { + if (s_notification_warn_once) + { + qDebug() << QStringLiteral( + "Cannot connect to the D-Bus session bus.\n" + "Please check your system settings and try again.\n"); + s_notification_warn_once = false; + } + + return; + } + + QStringList message_values_list; + + message_values_list << QStringLiteral("version=1"); + + switch (notification_type) + { + case EntryNotificationType::Entry: + message_values_list << QStringLiteral("event=write_entry"); + break; + + case EntryNotificationType::PathEntry: + message_values_list << QStringLiteral("event=write_path_entry"); + break; + + case EntryNotificationType::PathArrayEntry: + message_values_list << QStringLiteral("event=write_path_array_entry"); + break; + + case EntryNotificationType::DeleteEntry: + message_values_list << QStringLiteral("event=delete_entry"); + break; + + case EntryNotificationType::DeleteGroup: + message_values_list << QStringLiteral("event=delete_group"); + break; + } + + { + qint64 initlen = sysconf(_SC_GETPW_R_SIZE_MAX); + + struct passwd result; + struct passwd *resultp; + + QVector buffer; + buffer.resize((initlen == -1) ? 1024 : initlen); + + uid_t uid = geteuid(); + + int e; + + while ((e = getpwuid_r(uid, &result, buffer.data(), buffer.size(), &resultp)) == ERANGE) + { + buffer.resize(buffer.size() * 2); + } + + if (e != 0) + { + qDebug() << QStringLiteral("Failed to serialize config entry: getpwuid_r function failed"); + return; + } + + if (resultp != nullptr) + { + message_values_list << QStringLiteral("username=%1").arg(QString::fromLocal8Bit(resultp->pw_name)); + } + + message_values_list << QStringLiteral("uid=%1").arg(uid); + } + + message_values_list << QStringLiteral("filename=%1").arg(filename); + + { + bool ok = false; + QString location_type_string = serializeStandardLocation(location_type, ok); + if (!ok) + { + qDebug() << QStringLiteral("Failed to serialize config entry: failed to serialize QStandardPaths::StandardLocation enum"); + return; + } + message_values_list << QStringLiteral("location_type=%1").arg(location_type_string); + } + + { + bool ok = false; + QString flags_string = serializeWriteConfigFlags(flags, ok); + if (!ok) + { + qDebug() << QStringLiteral("Failed to serialize config entry: failed to serialize KConfigBase::WriteConfigFlags"); + return; + } + + message_values_list << QStringLiteral("flags=%1").arg(flags_string); + } + + message_values_list << QStringLiteral("group_name=%1").arg(groups.join(QLatin1Char('|'))); + + switch (notification_type) + { + case EntryNotificationType::Entry: + case EntryNotificationType::PathEntry: + case EntryNotificationType::PathArrayEntry: + case EntryNotificationType::DeleteEntry: + message_values_list << QStringLiteral("entry_name=%1").arg(QString::fromLocal8Bit(key)); + break; + + default: + break; + } + + switch (notification_type) + { + case EntryNotificationType::Entry: + case EntryNotificationType::PathEntry: + message_values_list << QStringLiteral("entry_value=%1").arg(QString::fromLocal8Bit(value.isNull() ? QByteArray("") : value)); + break; + + default: + break; + } + + if (notification_type == EntryNotificationType::PathArrayEntry) + { + int idx = 1; + auto iter = entry_paths.begin(); + for (; iter != entry_paths.end(); ++iter, ++idx) + { + message_values_list << QStringLiteral("entry_path_%1=%2").arg(idx).arg(*iter); + } + } + + QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/"), + QStringLiteral("org.alt.kde.kconfig.extra.notify"), + QStringLiteral("ConfigChanged")); + + message << message_values_list; + QDBusConnection::sessionBus().send(message); +#endif +} diff --git a/kconfig/src/core/kconfigbase.h b/kconfig/src/core/kconfigbase.h index 79af885..54642d4 100644 --- a/kconfig/src/core/kconfigbase.h +++ b/kconfig/src/core/kconfigbase.h @@ -15,6 +15,7 @@ #include #include +#include class KConfigGroup; class KConfigBasePrivate; @@ -218,6 +219,9 @@ public: */ bool isGroupImmutable(const char *group) const; + bool isUpdateNotificationEnabled() const; + void setUpdateNotificationEnabled(bool new_state); + protected: KConfigBase(); @@ -232,10 +236,29 @@ protected: /// @param group name of group, encoded in UTF-8 virtual bool isGroupImmutableImpl(const QByteArray &group) const = 0; + virtual bool isUpdateNotificationEnabledImpl() const = 0; + virtual void setUpdateNotificationEnabledImpl(bool new_state) = 0; + /** Virtual hook, used to add new "virtual" functions while maintaining * binary compatibility. Unused in this class. */ virtual void virtual_hook(int id, void *data); + + enum class EntryNotificationType + { + Entry, + PathEntry, + PathArrayEntry, + DeleteEntry, + DeleteGroup, + }; + + void sendNotification(const char *pKey, const QByteArray &value, + const QStringList &entry_paths, const QStringList &groups, + const QString &filename, QStandardPaths::StandardLocation location_type, + WriteConfigFlags pFlags, EntryNotificationType notification_type); + + static bool s_notification_warn_once; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KConfigBase::WriteConfigFlags) diff --git a/kconfig/src/core/kconfiggroup.cpp b/kconfig/src/core/kconfiggroup.cpp --- a/kconfig/src/core/kconfiggroup.cpp +++ b/kconfig/src/core/kconfiggroup.cpp @@ -16,6 +16,7 @@ #include "kconfigdata_p.h" #include "ksharedconfig.h" +#include #include #include #include @@ -40,6 +41,7 @@ public: , mName(name) , bImmutable(isImmutable) , bConst(isConst) + , bUpdateNotificationEnabled(owner->isUpdateNotificationEnabled()) { } @@ -49,6 +51,7 @@ public: , mName(name) , bImmutable(name.isEmpty() ? owner->isImmutable() : owner->isGroupImmutable(name)) , bConst(false) + , bUpdateNotificationEnabled(owner->isUpdateNotificationEnabled()) { } @@ -58,6 +61,7 @@ public: , mName(name) , bImmutable(isImmutable) , bConst(isConst) + , bUpdateNotificationEnabled(parent->isUpdateNotificationEnabled()) { if (!parent->d->mName.isEmpty()) { mParent = parent->d; @@ -70,6 +74,7 @@ public: , mName(name) , bImmutable(isImmutable) , bConst(other->bConst) + , bUpdateNotificationEnabled(other->bUpdateNotificationEnabled) { if (!other->mName.isEmpty()) { mParent = const_cast(other); @@ -84,6 +89,7 @@ public: /* bitfield */ const bool bImmutable : 1; // is this group immutable? const bool bConst : 1; // is this group read-only? + bool bUpdateNotificationEnabled: 1; QByteArray fullName() const { @@ -109,6 +115,24 @@ public: return fullName() + '\x1d' + aGroup; } + QStringList fullNameList() const + { + if (!mParent) { + return QStringList(QString::fromUtf8(name())); + } + return mParent->fullNameList(mName); + } + + QStringList fullNameList(const QByteArray &aGroup) const + { + if (mName.isEmpty()) { + return QStringList(QString::fromUtf8(aGroup)); + } + QStringList result = fullNameList(); + result.append(QString::fromUtf8(aGroup)); + return result; + } + static QExplicitlySharedDataPointer create(KConfigBase *master, const QByteArray &name, bool isImmutable, bool isConst) { QExplicitlySharedDataPointer data; @@ -470,6 +494,7 @@ static inline bool writeEntryGui(KConfig KConfigGroup::KConfigGroup(KConfigBase *master, const QString &_group) : d(KConfigGroupPrivate::create(master, _group.toUtf8(), master->isGroupImmutable(_group), false)) { + } KConfigGroup::KConfigGroup(KConfigBase *master, const char *_group) @@ -559,7 +584,23 @@ void KConfigGroup::deleteGroup(WriteConf Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroup", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroup", "deleting a read-only group"); - config()->deleteGroup(d->fullName(), flags); + if (isUpdateNotificationEnabled()) + { + KConfig *parent_config = config(); + if (parent_config != nullptr) + { + sendNotification(nullptr, QByteArray(""), QStringList(), + d->fullNameList(), + parent_config->name(), parent_config->locationType(), flags, + EntryNotificationType::DeleteGroup); + } + else + { + qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found"); + } + } + + config()->deleteGroupImplWithoutNotification(d->fullName(), flags); } #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 0) @@ -859,6 +900,22 @@ void KConfigGroup::writeEntry(const char Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); + if (isUpdateNotificationEnabled()) + { + KConfig *parent_config = config(); + if (parent_config != nullptr) + { + sendNotification(key, value.isNull() ? QByteArray("") : value, QStringList(), + d->fullNameList(), + parent_config->name(), parent_config->locationType(), flags, + EntryNotificationType::Entry); + } + else + { + qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found"); + } + } + config()->d_func()->putData(d->fullName(), key, value.isNull() ? QByteArray("") : value, flags); } @@ -1085,6 +1142,22 @@ void KConfigGroup::writePathEntry(const Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group"); + if (isUpdateNotificationEnabled()) + { + KConfig *parent_config = config(); + if (parent_config != nullptr) + { + sendNotification(pKey, path.toLocal8Bit(), QStringList(), + d->fullNameList(), + parent_config->name(), parent_config->locationType(), pFlags, + EntryNotificationType::PathEntry); + } + else + { + qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found"); + } + } + config()->d_func()->putData(d->fullName(), pKey, translatePath(path).toUtf8(), pFlags, true); } @@ -1104,6 +1177,22 @@ void KConfigGroup::writePathEntry(const list << translatePath(path).toUtf8(); } + if (isUpdateNotificationEnabled()) + { + KConfig *parent_config = config(); + if (parent_config != nullptr) + { + sendNotification(pKey, QByteArray(), value, + d->fullNameList(), + parent_config->name(), parent_config->locationType(), pFlags, + EntryNotificationType::PathArrayEntry); + } + else + { + qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found"); + } + } + config()->d_func()->putData(d->fullName(), pKey, KConfigGroupPrivate::serializeList(list), pFlags, true); } @@ -1112,6 +1201,22 @@ void KConfigGroup::deleteEntry(const cha Q_ASSERT_X(isValid(), "KConfigGroup::deleteEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteEntry", "deleting from a read-only group"); + if (isUpdateNotificationEnabled()) + { + KConfig *parent_config = config(); + if (parent_config != nullptr) + { + sendNotification(key, QByteArray(), QStringList(), + d->fullNameList(), + parent_config->name(), parent_config->locationType(), flags, + EntryNotificationType::DeleteEntry); + } + else + { + qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found"); + } + } + config()->d_func()->putData(d->fullName(), key, QByteArray(), flags); } @@ -1221,7 +1326,23 @@ void KConfigGroup::deleteGroupImpl(const Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroupImpl", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroupImpl", "deleting from a read-only group"); - config()->deleteGroup(d->fullName(b), flags); + if (isUpdateNotificationEnabled()) + { + KConfig *parent_config = config(); + if (parent_config != nullptr) + { + sendNotification(nullptr, QByteArray(), QStringList(), + d->fullNameList(b), + parent_config->name(), parent_config->locationType(), flags, + EntryNotificationType::DeleteGroup); + } + else + { + qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found"); + } + } + + config()->deleteGroupImplWithoutNotification(d->fullName(b), flags); } bool KConfigGroup::isGroupImmutableImpl(const QByteArray &b) const @@ -1235,6 +1356,20 @@ bool KConfigGroup::isGroupImmutableImpl( return config()->isGroupImmutable(d->fullName(b)); } +bool KConfigGroup::isUpdateNotificationEnabledImpl() const +{ + Q_ASSERT_X(isValid(), "KConfigGroup::isUpdateNotificationEnabledImpl", "accessing an invalid group"); + + return d->bUpdateNotificationEnabled; +} + +void KConfigGroup::setUpdateNotificationEnabledImpl(bool new_state) +{ + Q_ASSERT_X(isValid(), "KConfigGroup::setUpdateNotificationEnabledImpl", "accessing an invalid group"); + + d->bUpdateNotificationEnabled = new_state; +} + void KConfigGroup::copyTo(KConfigBase *other, WriteConfigFlags pFlags) const { Q_ASSERT_X(isValid(), "KConfigGroup::copyTo", "accessing an invalid group"); diff --git a/kconfig/src/core/kconfiggroup.h b/kconfig/src/core/kconfiggroup.h index 5c125dd..000cbdb 100644 --- a/kconfig/src/core/kconfiggroup.h +++ b/kconfig/src/core/kconfiggroup.h @@ -705,6 +705,8 @@ protected: const KConfigGroup groupImpl(const QByteArray &b) const override; void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags) override; bool isGroupImmutableImpl(const QByteArray &aGroup) const override; + bool isUpdateNotificationEnabledImpl() const override; + void setUpdateNotificationEnabledImpl(bool new_state) override; private: QExplicitlySharedDataPointer d; diff --git a/kconfig/src/core/kconfigserialization.cpp b/kconfig/src/core/kconfigserialization.cpp new file mode 100644 index 0000000..299ebf7 --- /dev/null +++ b/kconfig/src/core/kconfigserialization.cpp @@ -0,0 +1,136 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2020 Aleksei Nikiforov + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "kconfigserialization.h" + +#include +#include +#include +#include +#include + +static const QMap s_standardLocationEnumValues = +{ + { QStandardPaths::DesktopLocation, QStringLiteral("DesktopLocation") }, + { QStandardPaths::DocumentsLocation, QStringLiteral("DocumentsLocation") }, + { QStandardPaths::FontsLocation, QStringLiteral("FontsLocation") }, + { QStandardPaths::ApplicationsLocation, QStringLiteral("ApplicationsLocation") }, + { QStandardPaths::MusicLocation, QStringLiteral("MusicLocation") }, + { QStandardPaths::MoviesLocation, QStringLiteral("MoviesLocation") }, + { QStandardPaths::PicturesLocation, QStringLiteral("PicturesLocation") }, + { QStandardPaths::TempLocation, QStringLiteral("TempLocation") }, + { QStandardPaths::HomeLocation, QStringLiteral("HomeLocation") }, + { QStandardPaths::AppLocalDataLocation, QStringLiteral("AppLocalDataLocation") }, + { QStandardPaths::CacheLocation, QStringLiteral("CacheLocation") }, + { QStandardPaths::GenericDataLocation, QStringLiteral("GenericDataLocation") }, + { QStandardPaths::RuntimeLocation, QStringLiteral("RuntimeLocation") }, + { QStandardPaths::ConfigLocation, QStringLiteral("ConfigLocation") }, + { QStandardPaths::DownloadLocation, QStringLiteral("DownloadLocation") }, + { QStandardPaths::GenericCacheLocation, QStringLiteral("GenericCacheLocation") }, + { QStandardPaths::GenericConfigLocation, QStringLiteral("GenericConfigLocation") }, + { QStandardPaths::AppDataLocation, QStringLiteral("AppDataLocation") }, + { QStandardPaths::AppConfigLocation, QStringLiteral("AppConfigLocation") }, +}; + +static const QMap s_writeConfigFlagsEnumValues = +{ + { QStringLiteral("Persistent"), KConfigBase::Persistent }, + { QStringLiteral("Global"), KConfigBase::Global }, + { QStringLiteral("Localized"), KConfigBase::Localized }, + { QStringLiteral("Notify"), KConfigBase::Notify }, + { QStringLiteral("Normal"), KConfigBase::Normal }, +}; + +QString serializeStandardLocation(QStandardPaths::StandardLocation value, bool &ok) +{ + auto iter = s_standardLocationEnumValues.find(value); + if (iter != s_standardLocationEnumValues.end()) + { + ok = true; + return iter.value(); + } + else + { + ok = false; + return QString(); + } +} + +QStandardPaths::StandardLocation deserializeStandardLocation(const QString &value, bool &ok) +{ + static QMap s_standardLocationEnumDeserializationMap; + static QMutex s_standardLocationEnumDeserializationMapMutex; + + { // lock + QMutexLocker mapLock(&s_standardLocationEnumDeserializationMapMutex); + + if (s_standardLocationEnumDeserializationMap.isEmpty()) + { + for (auto iter = s_standardLocationEnumValues.begin(); iter != s_standardLocationEnumValues.end(); ++iter) + { + s_standardLocationEnumDeserializationMap[iter.value()] = iter.key(); + } + } + } // unlock + + auto iter = s_standardLocationEnumDeserializationMap.find(value); + if (iter != s_standardLocationEnumDeserializationMap.end()) + { + ok = true; + return iter.value(); + } + else + { + ok = false; + return QStandardPaths::StandardLocation(); + } +} + +QString serializeWriteConfigFlags(const KConfigBase::WriteConfigFlags &value, bool &ok) +{ + QStringList result; + KConfigBase::WriteConfigFlags reconstructed_value; + + for (auto values_iter = s_writeConfigFlagsEnumValues.begin(); values_iter != s_writeConfigFlagsEnumValues.end(); ++values_iter) + { + if (value.testFlag(values_iter.value())) + { + result.push_back(values_iter.key()); + reconstructed_value |= values_iter.value(); + } + } + + ok = (value == reconstructed_value); + + return result.join(QChar::fromLatin1('|'));; +} + +KConfigBase::WriteConfigFlags deserializeWriteConfigFlags(const QString &value, bool &ok) +{ + KConfigBase::WriteConfigFlags result; + + QStringList values_list = value.split(QChar::fromLatin1('|')); + + bool no_unknown_values = true; + + for (const auto &value_iter: values_list) + { + auto map_iter = s_writeConfigFlagsEnumValues.find(value_iter); + if (map_iter != s_writeConfigFlagsEnumValues.end()) + { + result |= map_iter.value(); + } + else + { + no_unknown_values = false; + } + } + + ok = no_unknown_values; + + return result; +} diff --git a/kconfig/src/core/kconfigserialization.h b/kconfig/src/core/kconfigserialization.h new file mode 100644 index 0000000..52a6da0 --- /dev/null +++ b/kconfig/src/core/kconfigserialization.h @@ -0,0 +1,24 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2020 Aleksei Nikiforov + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KCONFIGSERIALIZATION_H +#define KCONFIGSERIALIZATION_H + +#include "kconfigbase.h" + +#include + +#include +#include + +KCONFIGCORE_EXPORT QString serializeStandardLocation(QStandardPaths::StandardLocation value, bool &ok); +KCONFIGCORE_EXPORT QStandardPaths::StandardLocation deserializeStandardLocation(const QString &value, bool &ok); + +KCONFIGCORE_EXPORT QString serializeWriteConfigFlags(const KConfigBase::WriteConfigFlags &value, bool &ok); +KCONFIGCORE_EXPORT KConfigBase::WriteConfigFlags deserializeWriteConfigFlags(const QString &value, bool &ok); + +#endif // KCONFIGSERIALIZATION_H diff --git a/kconfig/src/kconf_watcher/CMakeLists.txt b/kconfig/src/kconf_watcher/CMakeLists.txt new file mode 100644 index 0000000..2d1e5fe --- /dev/null +++ b/kconfig/src/kconf_watcher/CMakeLists.txt @@ -0,0 +1,15 @@ +include(ECMMarkNonGuiExecutable) + +add_executable(kconf_watcher kconf_watcher.cpp) +target_link_libraries(kconf_watcher Qt5::Core Qt5::DBus) +ecm_mark_nongui_executable(kconf_watcher) + +install(TARGETS kconf_watcher ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) + +########### next target ############### + +add_executable(kconf_apply kconf_apply.cpp) +target_link_libraries(kconf_apply Qt5::Core KF5::ConfigCore) +ecm_mark_nongui_executable(kconf_apply) + +install(TARGETS kconf_apply ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kconfig/src/kconf_watcher/kconf_apply.cpp b/kconfig/src/kconf_watcher/kconf_apply.cpp new file mode 100644 index 0000000..0684640 --- /dev/null +++ b/kconfig/src/kconf_watcher/kconf_apply.cpp @@ -0,0 +1,240 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2020 Aleksei Nikiforov + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +enum class event_type +{ + write_entry = 0, + write_path_entry, + write_path_array_entry, + delete_entry, + delete_group, +}; + +static const QMap string_to_event_type_map = +{ + { QStringLiteral("write_entry"), event_type::write_entry }, + { QStringLiteral("write_path_entry"), event_type::write_path_entry }, + { QStringLiteral("write_path_array_entry"), event_type::write_path_array_entry }, + { QStringLiteral("delete_entry"), event_type::delete_entry }, + { QStringLiteral("delete_group"), event_type::delete_group }, +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + app.setApplicationVersion(QStringLiteral("1.0")); + + QTextStream stdin_stream(stdin, QIODevice::ReadOnly); + + bool in_message = false; + QMap message_data; + + while (!stdin_stream.atEnd()) + { + QString line = stdin_stream.readLine(); + + if (in_message) + { + if (line == QStringLiteral("message_end")) + { + do + { + if (message_data[QStringLiteral("version")] != QStringLiteral("1")) + { + break; + } + + event_type event_type_value; + QString filename; + QStandardPaths::StandardLocation standard_location; + KConfigBase::WriteConfigFlags write_config_flags; + QStringList group_names; + QString entry_name; + QString entry_value; + + // TODO: process uid/username, use it for optional filtering + + { + auto iter = string_to_event_type_map.find(message_data[QStringLiteral("event")]); + if (iter == string_to_event_type_map.end()) + { + break; + } + + event_type_value = iter.value(); + } + + { + auto iter = message_data.find(QStringLiteral("filename")); + if (iter == message_data.end()) + { + break; + } + + filename = iter.value(); + } + + { + auto iter = message_data.find(QStringLiteral("location_type"));; + if (iter == message_data.end()) + { + break; + } + + bool ok = false; + standard_location = deserializeStandardLocation(iter.value(), ok); + if (!ok) + { + break; + } + } + + { + auto iter = message_data.find(QStringLiteral("flags"));; + if (iter == message_data.end()) + { + break; + } + + bool ok = false; + write_config_flags = deserializeWriteConfigFlags(iter.value(), ok); + if (!ok) + { + break; + } + } + + { + auto iter = message_data.find(QStringLiteral("group_name"));; + if (iter == message_data.end()) + { + break; + } + + group_names = iter.value().split(QLatin1Char('|')); + if (group_names.isEmpty()) + { + break; + } + } + + { + auto iter = message_data.find(QStringLiteral("entry_name"));; + if ((iter == message_data.end()) + && ((event_type_value == event_type::write_entry) + || (event_type_value == event_type::write_path_entry) + || (event_type_value == event_type::write_path_array_entry) + || (event_type_value == event_type::delete_entry))) + { + break; + } + + if (iter != message_data.end()) + { + entry_name = iter.value(); + } + } + + { + auto iter = message_data.find(QStringLiteral("entry_value"));; + if ((iter == message_data.end()) + && ((event_type_value == event_type::write_entry) + || (event_type_value == event_type::write_path_entry))) + { + break; + } + + if (iter != message_data.end()) + { + entry_value = iter.value(); + } + } + + KConfig config(filename, KConfig::FullConfig, standard_location); + config.setUpdateNotificationEnabled(false); + + auto group_name_iter = group_names.begin(); + KConfigGroup config_group = config.group(*group_name_iter); + + for (++group_name_iter; group_name_iter != group_names.end(); ++group_name_iter) + { + config_group = config_group.group(*group_name_iter); + } + + switch (event_type_value) + { + case event_type::write_entry: + config_group.writeEntry(entry_name, entry_value, write_config_flags); + break; + + case event_type::write_path_entry: + config_group.writePathEntry(entry_name, entry_value, write_config_flags); + break; + + case event_type::write_path_array_entry: + { + QStringList entries_array; + + for (int idx = 1; ; ++idx) + { + auto iter = message_data.find(QStringLiteral("entry_path_%1").arg(idx)); + if (iter == message_data.end()) + { + break; + } + + entries_array.push_back(iter.value()); + } + + config_group.writePathEntry(entry_name, entries_array, write_config_flags); + } + break; + + case event_type::delete_entry: + config_group.deleteEntry(entry_name, write_config_flags); + break; + + case event_type::delete_group: + config_group.deleteGroup(write_config_flags); + break; + } + } while (false); + + in_message = false; + message_data.clear(); + } + else + { + // if '=' character is present and not first character in line + int index = line.indexOf(QLatin1Char('=')); + if (index > 0) + { + message_data[line.left(index)] = line.mid(index + 1); + } + } + } + else + { + if (line == QStringLiteral("message_begin")) + { + in_message = true; + } + } + } + + return 0; +} diff --git a/kconfig/src/kconf_watcher/kconf_watcher.cpp b/kconfig/src/kconf_watcher/kconf_watcher.cpp new file mode 100644 index 0000000..47103f8 --- /dev/null +++ b/kconfig/src/kconf_watcher/kconf_watcher.cpp @@ -0,0 +1,135 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2020 Aleksei Nikiforov + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kconf_watcher_p.h" + +static int sigintFd[2]; +static int sigtermFd[2]; + +void intSignalHandler(int) +{ + char a = 1; + ::write(sigintFd[0], &a, sizeof(a)); +} + +void termSignalHandler(int) +{ + char a = 1; + ::write(sigtermFd[0], &a, sizeof(a)); +} + +void qtHandleSigInt(QSocketNotifier ¬ifier) +{ + notifier.setEnabled(false); + + char tmp; + ::read(sigintFd[1], &tmp, sizeof(tmp)); + + QCoreApplication::quit(); + + notifier.setEnabled(true); +} + +void qtHandleSigTerm(QSocketNotifier ¬ifier) +{ + notifier.setEnabled(false); + + char tmp; + ::read(sigtermFd[1], &tmp, sizeof(tmp)); + + QCoreApplication::quit(); + + notifier.setEnabled(true); +} + +void DBusPrinter::messageSlot(const QStringList &values) +{ + static qint64 message_idx = 1; + fprintf(stdout, "message %" PRIi64 "\n", message_idx++); + fprintf(stdout, "message_begin\n"); + + for (const auto &item: values) + { + fprintf(stdout, "%s\n", item.toLocal8Bit().data()); + } + + fprintf(stdout, "message_end\n\n"); + fflush(stdout); +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + app.setApplicationVersion(QStringLiteral("1.0")); + + if (!QDBusConnection::sessionBus().isConnected()) + { + fprintf(stderr, + "Cannot connect to the D-Bus session bus.\n" + "Please check your system settings and try again.\n"); + + return -1; + } + + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd)) + { + fprintf(stderr, "Couldn't create INT socketpair\n"); + return -1; + } + + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermFd)) + { + fprintf(stderr, "Couldn't create TERM socketpair\n"); + return -1; + } + + QSocketNotifier sigIntNotifier(sigintFd[1], QSocketNotifier::Read); + QObject::connect(&sigIntNotifier, &QSocketNotifier::activated, [&sigIntNotifier] () { qtHandleSigInt(sigIntNotifier); }); + + QSocketNotifier sigTermNotifier(sigtermFd[1], QSocketNotifier::Read); + QObject::connect(&sigTermNotifier, &QSocketNotifier::activated, [&sigTermNotifier] () { qtHandleSigTerm(sigTermNotifier); }); + + { + struct sigaction sigaction_int, sigaction_term; + + sigaction_int.sa_handler = &intSignalHandler; + sigemptyset(&sigaction_int.sa_mask); + sigaction_int.sa_flags = 0; + sigaction_int.sa_flags |= SA_RESTART; + sigaction(SIGINT, &sigaction_int, 0); + + sigaction_term.sa_handler = &termSignalHandler; + sigemptyset(&sigaction_term.sa_mask); + sigaction_term.sa_flags = 0; + sigaction_term.sa_flags |= SA_RESTART; + sigaction(SIGTERM, &sigaction_term, 0); + } + + DBusPrinter dbus_printer; + QDBusConnection::sessionBus().connect( + QString(), + QStringLiteral("/"), + QStringLiteral("org.alt.kde.kconfig.extra.notify"), + QStringLiteral("ConfigChanged"), + &dbus_printer, + SLOT(messageSlot(QStringList))); + + return app.exec(); +} diff --git a/kconfig/src/kconf_watcher/kconf_watcher_p.h b/kconfig/src/kconf_watcher/kconf_watcher_p.h new file mode 100644 index 0000000..cb7d38d --- /dev/null +++ b/kconfig/src/kconf_watcher/kconf_watcher_p.h @@ -0,0 +1,18 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2020 Aleksei Nikiforov + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include + +class QStringList; + +class DBusPrinter: public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void messageSlot(const QStringList &values); +}; -- 2.29.3