diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/core/config-kconfig.h.cmake | 1 | ||||
-rw-r--r-- | src/core/kconfig.cpp | 55 | ||||
-rw-r--r-- | src/core/kconfig_p.h | 2 | ||||
-rw-r--r-- | src/core/kconfigbase.h | 14 | ||||
-rw-r--r-- | src/core/kconfigdata.cpp | 7 | ||||
-rw-r--r-- | src/core/kconfigdata.h | 7 | ||||
-rw-r--r-- | src/core/kconfigwatcher.cpp | 107 | ||||
-rw-r--r-- | src/core/kconfigwatcher.h | 68 |
9 files changed, 260 insertions, 11 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f06c803f..28aad4f8 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -10,8 +10,11 @@ set(libkconfigcore_SRCS kcoreconfigskeleton.cpp kauthorized.cpp kemailsettings.cpp + kconfigwatcher.cpp ) +configure_file(config-kconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kconfig.h ) + add_library(KF5ConfigCore ${libkconfigcore_SRCS}) generate_export_header(KF5ConfigCore BASE_NAME KConfigCore) add_library(KF5::ConfigCore ALIAS KF5ConfigCore) @@ -24,6 +27,11 @@ target_compile_definitions(KF5ConfigCore target_include_directories(KF5ConfigCore INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/KConfigCore>") target_link_libraries(KF5ConfigCore PUBLIC Qt5::Core) + +if(KCONFIG_USE_DBUS) + target_link_libraries(KF5ConfigCore PRIVATE Qt5::DBus) +endif() + if(WIN32) target_link_libraries(KF5ConfigCore PRIVATE ${KDEWIN_LIBRARIES}) endif() @@ -44,6 +52,7 @@ ecm_generate_headers(KConfigCore_HEADERS KCoreConfigSkeleton KEMailSettings ConversionCheck + KConfigWatcher REQUIRED_HEADERS KConfigCore_HEADERS ) @@ -68,6 +77,7 @@ if (PythonModuleGeneration_FOUND) kcoreconfigskeleton.h kemailsettings.h conversioncheck.h + kconfigwatcher.h ) endif() diff --git a/src/core/config-kconfig.h.cmake b/src/core/config-kconfig.h.cmake new file mode 100644 index 00000000..a4a7519d --- /dev/null +++ b/src/core/config-kconfig.h.cmake @@ -0,0 +1 @@ +#cmakedefine01 KCONFIG_USE_DBUS diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp index e4c9935b..df3ad471 100644 --- a/src/core/kconfig.cpp +++ b/src/core/kconfig.cpp @@ -23,6 +23,8 @@ #include "kconfig.h" #include "kconfig_p.h" +#include "config-kconfig.h" + #include <cstdlib> #include <fcntl.h> @@ -54,6 +56,12 @@ static inline int pclose(FILE *stream) #include <QBasicMutex> #include <QMutexLocker> +#if KCONFIG_USE_DBUS +#include <QDBusMessage> +#include <QDBusConnection> +#include <QDBusMetaType> +#endif + bool KConfigPrivate::mappingsRegistered = false; Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes. @@ -424,6 +432,9 @@ bool KConfig::sync() return false; } + QHash<QString, QByteArrayList> notifyGroupsLocal; + QHash<QString, QByteArrayList> notifyGroupsGlobal; + if (d->bDirty && d->mBackend) { const QByteArray utf8Locale(locale().toUtf8()); @@ -439,16 +450,20 @@ bool KConfig::sync() // Rewrite global/local config only if there is a dirty entry in it. bool writeGlobals = false; bool writeLocals = false; - Q_FOREACH (const KEntry &e, d->entryMap) { + + for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) { + auto e = it.value(); if (e.bDirty) { if (e.bGlobal) { writeGlobals = true; + if (e.bNotify) { + notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; + } } else { writeLocals = true; - } - - if (writeGlobals && writeLocals) { - break; + if (e.bNotify) { + notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; + } } } } @@ -485,9 +500,35 @@ bool KConfig::sync() d->mBackend->unlock(); } } + + if (!notifyGroupsLocal.isEmpty()) { + d->notifyClients(notifyGroupsLocal, QStringLiteral("/") + name()); + } + if (!notifyGroupsGlobal.isEmpty()) { + d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals")); + } + return !d->bDirty; } +void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path) +{ +#if KCONFIG_USE_DBUS + qDBusRegisterMetaType<QByteArrayList>(); + + qDBusRegisterMetaType<QHash<QString, QByteArrayList>>(); + + QDBusMessage message = QDBusMessage::createSignal(path, + QStringLiteral("org.kde.kconfig.notify"), + QStringLiteral("ConfigChanged")); + message.setArguments({QVariant::fromValue(changes)}); + QDBusConnection::sessionBus().send(message); +#else + Q_UNUSED(changes) + Q_UNUSED(path) +#endif +} + void KConfig::markAsClean() { Q_D(KConfig); @@ -497,6 +538,7 @@ void KConfig::markAsClean() const KEntryMapIterator theEnd = d->entryMap.end(); for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) { it->bDirty = false; + it->bNotify = false; } } @@ -874,6 +916,9 @@ KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags) if (flags & KConfig::Localized) { options |= KEntryMap::EntryLocalized; } + if (flags & KConfig::Notify) { + options |= KEntryMap::EntryNotify; + } return options; } diff --git a/src/core/kconfig_p.h b/src/core/kconfig_p.h index dfb3875a..7433ab2f 100644 --- a/src/core/kconfig_p.h +++ b/src/core/kconfig_p.h @@ -60,6 +60,8 @@ public: QSet<QByteArray> allSubGroups(const QByteArray &parentGroup) const; bool hasNonDeletedEntries(const QByteArray &group) const; + void notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path); + static QString expandString(const QString &value); protected: diff --git a/src/core/kconfigbase.h b/src/core/kconfigbase.h index b7403de1..62a0cc31 100644 --- a/src/core/kconfigbase.h +++ b/src/core/kconfigbase.h @@ -58,13 +58,19 @@ public: */ Localized = 0x04, /**< + * Notify remote KConfigWatchers of changes (requires DBus support) + * Implied persistent + * @since 5.51 + */ + Notify = 0x08 | Persistent, + /**< * Add the locale tag to the key when writing it. */ Normal = Persistent - /**< - * Save the entry to the application specific config file without - * a locale tag. This is the default. - */ + /**< + * Save the entry to the application specific config file without + * a locale tag. This is the default. + */ }; Q_DECLARE_FLAGS(WriteConfigFlags, WriteConfigFlag) diff --git a/src/core/kconfigdata.cpp b/src/core/kconfigdata.cpp index 6ef6af07..d80b7d07 100644 --- a/src/core/kconfigdata.cpp +++ b/src/core/kconfigdata.cpp @@ -134,6 +134,8 @@ bool KEntryMap::setEntry(const QByteArray &group, const QByteArray &key, const Q e.mValue = value; e.bDirty = e.bDirty || (options & EntryDirty); + e.bNotify = e.bNotify || (options & EntryNotify); + e.bGlobal = (options & EntryGlobal); //we can't use || here, because changes to entries in //kdeglobals would be written to kdeglobals instead //of the local config file, regardless of the globals flag @@ -269,6 +271,8 @@ bool KEntryMap::getEntryOption(const QMap< KEntryKey, KEntry >::ConstIterator &i return it->bDeleted; case EntryExpansion: return it->bExpand; + case EntryNotify: + return it->bNotify; default: break; // fall through } @@ -296,6 +300,9 @@ void KEntryMap::setEntryOption(QMap< KEntryKey, KEntry >::Iterator it, KEntryMap case EntryExpansion: it->bExpand = bf; break; + case EntryNotify: + it->bNotify = bf; + break; default: break; // fall through } diff --git a/src/core/kconfigdata.h b/src/core/kconfigdata.h index f84e51e3..2a5c643d 100644 --- a/src/core/kconfigdata.h +++ b/src/core/kconfigdata.h @@ -37,7 +37,7 @@ struct KEntry { KEntry() : mValue(), bDirty(false), bGlobal(false), bImmutable(false), bDeleted(false), bExpand(false), bReverted(false), - bLocalizedCountry(false) {} + bLocalizedCountry(false), bNotify(false) {} /** @internal */ QByteArray mValue; /** @@ -69,11 +69,13 @@ struct KEntry { * if @c true the value references language and country, e.g. "de_DE". **/ bool bLocalizedCountry: 1; + + bool bNotify: 1; }; // These operators are used to check whether an entry which is about // to be written equals the previous value. As such, this intentionally -// omits the dirty flag from the comparison. +// omits the dirty/notify flag from the comparison. inline bool operator ==(const KEntry &k1, const KEntry &k2) { return k1.bGlobal == k2.bGlobal && k1.bImmutable == k2.bImmutable @@ -172,6 +174,7 @@ public: EntryExpansion = 16, EntryRawKey = 32, EntryLocalizedCountry = 64, + EntryNotify = 128, EntryDefault = (SearchDefaults << 16), EntryLocalized = (SearchLocalized << 16) }; diff --git a/src/core/kconfigwatcher.cpp b/src/core/kconfigwatcher.cpp new file mode 100644 index 00000000..96120c6a --- /dev/null +++ b/src/core/kconfigwatcher.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2018 David Edmundson <davidedmundson@kde.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kconfigwatcher.h" + +#include "config-kconfig.h" + +#if KCONFIG_USE_DBUS +#include <QDBusConnection> +#include <QDBusMessage> +#include <QDBusMetaType> +#endif + +#include <QDebug> +#include <QThreadStorage> +#include <QHash> + +class KConfigWatcherPrivate { +public: + KSharedConfig::Ptr m_config; +}; + +KConfigWatcher::Ptr KConfigWatcher::create(const KSharedConfig::Ptr &config) +{ + static QThreadStorage<QHash<KSharedConfig*, QWeakPointer<KConfigWatcher>>> watcherList; + + auto c = config.data(); + KConfigWatcher::Ptr watcher; + + if (!watcherList.localData().contains(c)) { + watcher = KConfigWatcher::Ptr(new KConfigWatcher(config)); + + watcherList.localData().insert(c, watcher.toWeakRef()); + + QObject::connect(watcher.data(), &QObject::destroyed, [c]() { + watcherList.localData().remove(c); + }); + } + return watcherList.localData().value(c).toStrongRef(); +} + +KConfigWatcher::KConfigWatcher(const KSharedConfig::Ptr &config): + QObject (nullptr), + d(new KConfigWatcherPrivate) +{ + Q_ASSERT(config); +#if KCONFIG_USE_DBUS + + qDBusRegisterMetaType<QByteArrayList>(); + qDBusRegisterMetaType<QHash<QString, QByteArrayList>>(); + + d->m_config = config; + + QStringList watchedPaths; + watchedPaths <<QStringLiteral("/") + d->m_config->name(); + for (const QString file: d->m_config->additionalConfigSources()) { + watchedPaths << QStringLiteral("/") + file; + } + if (d->m_config->openFlags() & KConfig::IncludeGlobals) { + watchedPaths << QStringLiteral("/kdeglobals"); + } + + for(const QString &path: qAsConst(watchedPaths)) { + QDBusConnection::sessionBus().connect(QString(), + path, + QStringLiteral("org.kde.kconfig.notify"), + QStringLiteral("ConfigChanged"), + this, + SLOT(onConfigChangeNotification(QHash<QString, QByteArrayList>))); + } +#else + qWarning() << "Use of KConfigWatcher without DBus support. You will not receive updates" +#endif +} + +void KConfigWatcher::onConfigChangeNotification(const QHash<QString, QByteArrayList> &changes) +{ + //should we ever need it we can determine the file changed with QDbusContext::message().path(), but it doesn't seem too useful + + d->m_config->reparseConfiguration(); + + for(auto it = changes.constBegin(); it != changes.constEnd(); it++) { + KConfigGroup group = d->m_config->group(QString());//top level group + const auto parts = it.key().split(QLatin1Char('\x1d')); //magic char, see KConfig + for(const QString &groupName: parts) { + group = group.group(groupName); + } + emit configChanged(group, it.value()); + } +} + diff --git a/src/core/kconfigwatcher.h b/src/core/kconfigwatcher.h new file mode 100644 index 00000000..b73d3fc2 --- /dev/null +++ b/src/core/kconfigwatcher.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018 David Edmundson <davidedmundson@kde.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KCONFIGWATCHER_H +#define KCONFIGWATCHER_H + +#include <QObject> + +#include <KSharedConfig> +#include <KConfigGroup> + +#include <kconfigcore_export.h> + +class KConfigWatcherPrivate; + +/* + * Notifies when another client has updated this config file with the Notify flag set. + * @since 5.51 + */ +class KCONFIGCORE_EXPORT KConfigWatcher: public QObject +{ + Q_OBJECT +public: + typedef QSharedPointer<KConfigWatcher> Ptr; + + /* + * Instantiate a ConfigWatcher for a given config + * + * @note any additional config sources should be set before this point. + */ + static Ptr create(const KSharedConfig::Ptr &config); + +Q_SIGNALS: + /** + * Emitted when a config group has changed + * The config will be reloaded before this signal is emitted + * + * @arg group the config group that has changed + * @arg names a list of entries that have changed within that group + */ + void configChanged(const KConfigGroup &group, const QByteArrayList &names); + +private Q_SLOTS: + void onConfigChangeNotification(const QHash<QString, QByteArrayList> &changes); + +private: + KConfigWatcher(const KSharedConfig::Ptr &config); + Q_DISABLE_COPY(KConfigWatcher) + KConfigWatcherPrivate *const d; +}; + +#endif |