diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp index a0d757e..e4c9935 100644 --- a/src/core/kconfig.cpp +++ b/src/core/kconfig.cpp @@ -1,998 +1,1004 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997-1999 Matthias Kalle Dalheimer This library 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 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfig.h" #include "kconfig_p.h" #include #include #ifdef _MSC_VER static inline FILE *popen(const char *cmd, const char *mode) { return _popen(cmd, mode); } static inline int pclose(FILE *stream) { return _pclose(stream); } #else #include #endif #include "kconfigbackend_p.h" #include "kconfiggroup.h" #include #include #include #include #include #include #include #include #include #include #include bool KConfigPrivate::mappingsRegistered = false; Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes. static QBasicMutex s_globalFilesMutex; Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals"))) #ifndef Q_OS_WIN static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive; #else static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive; #endif KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType) : openFlags(flags), resourceType(resourceType), mBackend(nullptr), bDynamicBackend(true), bDirty(false), bReadDefaults(false), bFileImmutable(false), bForceGlobal(false), bSuppressGlobal(false), configState(KConfigBase::NoAccess) { static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1); if (use_etc_kderc.load() < 0) { use_etc_kderc.store( !qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests } if (use_etc_kderc.load()) { etc_kderc = #ifdef Q_OS_WIN QFile::decodeName(qgetenv("WINDIR") + "/kde5rc"); #else QStringLiteral("/etc/kde5rc"); #endif if (!QFileInfo(etc_kderc).isReadable()) { use_etc_kderc.store(false); etc_kderc.clear(); } } // if (!mappingsRegistered) { // KEntryMap tmp; // if (!etc_kderc.isEmpty()) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(etc_kderc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults); // } // const QString kde5rc(QDir::home().filePath(".kde5rc")); // if (KStandardDirs::checkAccess(kde5rc, R_OK)) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(kde5rc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions()); // } // KConfigBackend::registerMappings(tmp); // mappingsRegistered = true; // } setLocale(QLocale().name()); } bool KConfigPrivate::lockLocal() { if (mBackend) { return mBackend->lock(); } // anonymous object - pretend we locked it return true; } void KConfigPrivate::copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const { KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap; const int len = source.length(); const bool sameName = (destination == source); // we keep this bool outside the foreach loop so that if // the group is empty, we don't end up marking the other config // as dirty erroneously bool dirtied = false; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const QByteArray &group = entryMapIt.key().mGroup; if (!group.startsWith(source)) { // nothing to do continue; } // don't copy groups that start with the same prefix, but are not sub-groups if (group.length() > len && group[len] != '\x1d') { continue; } KEntryKey newKey = entryMapIt.key(); if (flags & KConfigBase::Localized) { newKey.bLocal = true; } if (!sameName) { newKey.mGroup.replace(0, len, destination); } KEntry entry = entryMap[ entryMapIt.key() ]; dirtied = entry.bDirty = flags & KConfigBase::Persistent; if (flags & KConfigBase::Global) { entry.bGlobal = true; } otherMap[newKey] = entry; } if (dirtied) { otherGroup->config()->d_ptr->bDirty = true; } } QString KConfigPrivate::expandString(const QString &value) { QString aValue = value; // check for environment variables and make necessary translations int nDollarPos = aValue.indexOf(QLatin1Char('$')); while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) { // there is at least one $ if (aValue[nDollarPos + 1] == QLatin1Char('(')) { int nEndPos = nDollarPos + 1; // the next character is not $ while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char(')'))) { nEndPos++; } nEndPos++; QString cmd = aValue.mid(nDollarPos + 2, nEndPos - nDollarPos - 3); QString result; // FIXME: wince does not have pipes #ifndef _WIN32_WCE FILE *fs = popen(QFile::encodeName(cmd).data(), "r"); if (fs) { QTextStream ts(fs, QIODevice::ReadOnly); result = ts.readAll().trimmed(); pclose(fs); } #endif aValue.replace(nDollarPos, nEndPos - nDollarPos, result); nDollarPos += result.length(); } else if (aValue[nDollarPos + 1] != QLatin1Char('$')) { int nEndPos = nDollarPos + 1; // the next character is not $ QStringRef aVarName; if (aValue[nEndPos] == QLatin1Char('{')) { while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) { nEndPos++; } nEndPos++; aVarName = aValue.midRef(nDollarPos + 2, nEndPos - nDollarPos - 3); } else { while (nEndPos <= aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) { nEndPos++; } aVarName = aValue.midRef(nDollarPos + 1, nEndPos - nDollarPos - 1); } QString env; if (!aVarName.isEmpty()) { #ifdef Q_OS_WIN if (aVarName == QLatin1String("HOME")) { env = QDir::homePath(); } else #endif { QByteArray pEnv = qgetenv(aVarName.toLatin1().constData()); if (!pEnv.isEmpty()) { env = QString::fromLocal8Bit(pEnv.constData()); } else { if (aVarName == QStringLiteral("QT_DATA_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); } else if (aVarName == QStringLiteral("QT_CONFIG_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); } else if (aVarName == QStringLiteral("QT_CACHE_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); } } } aValue.replace(nDollarPos, nEndPos - nDollarPos, env); nDollarPos += env.length(); } else { aValue.remove(nDollarPos, nEndPos - nDollarPos); } } else { // remove one of the dollar signs aValue.remove(nDollarPos, 1); nDollarPos++; } nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos); } return aValue; } KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(mode, resourceType)) { d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(SimpleConfig, resourceType)) { d_ptr->mBackend = KConfigBackend::create(file, backend); d_ptr->bDynamicBackend = false; d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(KConfigPrivate &d) : d_ptr(&d) { } KConfig::~KConfig() { Q_D(KConfig); if (d->bDirty && (d->mBackend && d->mBackend->ref.load() == 1)) { sync(); } delete d; } QStringList KConfig::groupList() const { Q_D(const KConfig); QSet groups; for (KEntryMap::ConstIterator entryMapIt(d->entryMap.constBegin()); entryMapIt != d->entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); const QByteArray group = key.mGroup; if (key.mKey.isNull() && !group.isEmpty() && group != "" && group != "$Version") { const QString groupname = QString::fromUtf8(group); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } QStringList KConfigPrivate::groupList(const QByteArray &group) const { QByteArray theGroup = group + '\x1d'; QSet groups; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && key.mGroup.startsWith(theGroup)) { const QString groupname = QString::fromUtf8(key.mGroup.mid(theGroup.length())); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } static bool isGroupOrSubGroupMatch(const QByteArray &potentialGroup, const QByteArray &group) { if (!potentialGroup.startsWith(group)) { return false; } return potentialGroup.length() == group.length() || potentialGroup[group.length()] == '\x1d'; } // List all sub groups, including subsubgroups QSet KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const { QSet groups; for (KEntryMap::const_iterator entryMapIt = entryMap.begin(); entryMapIt != entryMap.end(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && isGroupOrSubGroupMatch(key.mGroup, parentGroup)) { groups << key.mGroup; } } return groups; } bool KConfigPrivate::hasNonDeletedEntries(const QByteArray &group) const { for (KEntryMap::const_iterator it = entryMap.begin(); it != entryMap.end(); ++it) { const KEntryKey &key = it.key(); // Check for any non-deleted entry if (isGroupOrSubGroupMatch(key.mGroup, group) && !key.mKey.isNull() && !it->bDeleted) { return true; } } return false; } QStringList KConfigPrivate::keyListImpl(const QByteArray &theGroup) const { QStringList keys; const KEntryMapConstIterator theEnd = entryMap.constEnd(); KEntryMapConstIterator it = entryMap.findEntry(theGroup); if (it != theEnd) { ++it; // advance past the special group entry marker QSet tmp; for (; it != theEnd && it.key().mGroup == theGroup; ++it) { const KEntryKey &key = it.key(); if (!key.mKey.isNull() && !it->bDeleted) { tmp << QString::fromUtf8(key.mKey); } } keys = tmp.toList(); } return keys; } QStringList KConfig::keyList(const QString &aGroup) const { Q_D(const KConfig); const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); return d->keyListImpl(theGroup); } QMap KConfig::entryMap(const QString &aGroup) const { Q_D(const KConfig); QMap theMap; const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); const KEntryMapConstIterator theEnd = d->entryMap.constEnd(); KEntryMapConstIterator it = d->entryMap.findEntry(theGroup, nullptr, nullptr); if (it != theEnd) { ++it; // advance past the special group entry marker for (; it != theEnd && it.key().mGroup == theGroup; ++it) { // leave the default values and deleted entries out if (!it->bDeleted && !it.key().bDefault) { const QString key = QString::fromUtf8(it.key().mKey.constData()); // the localized entry should come first, so don't overwrite it // with the non-localized entry if (!theMap.contains(key)) { if (it->bExpand) { theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData()))); } else { theMap.insert(key, QString::fromUtf8(it->mValue.constData())); } } } } } return theMap; } bool KConfig::sync() { Q_D(KConfig); if (isImmutable() || name().isEmpty()) { // can't write to an immutable or anonymous file. return false; } if (d->bDirty && d->mBackend) { const QByteArray utf8Locale(locale().toUtf8()); // Create the containing dir, maybe it wasn't there d->mBackend->createEnclosing(); // lock the local file if (d->configState == ReadWrite && !d->lockLocal()) { qWarning() << "couldn't lock local file"; return false; } // 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) { if (e.bDirty) { if (e.bGlobal) { writeGlobals = true; } else { writeLocals = true; } if (writeGlobals && writeLocals) { break; } } } d->bDirty = false; // will revert to true if a config write fails if (d->wantGlobals() && writeGlobals) { QExplicitlySharedDataPointer tmp = KConfigBackend::create(*sGlobalFileName); if (d->configState == ReadWrite && !tmp->lock()) { qWarning() << "couldn't lock global file"; //unlock the local config if we're returning early if (d->mBackend->isLocked()) { d->mBackend->unlock(); } d->bDirty = true; return false; } if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) { d->bDirty = true; } if (tmp->isLocked()) { tmp->unlock(); } } if (writeLocals) { if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) { d->bDirty = true; } } if (d->mBackend->isLocked()) { d->mBackend->unlock(); } } return !d->bDirty; } void KConfig::markAsClean() { Q_D(KConfig); d->bDirty = false; // clear any dirty flags that entries might have set const KEntryMapIterator theEnd = d->entryMap.end(); for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) { it->bDirty = false; } } bool KConfig::isDirty() const { Q_D(const KConfig); return d->bDirty; } void KConfig::checkUpdate(const QString &id, const QString &updateFile) { const KConfigGroup cg(this, "$Version"); const QString cfg_id = updateFile + QLatin1Char(':') + id; const QStringList ids = cg.readEntry("update_info", QStringList()); if (!ids.contains(cfg_id)) { QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList() << QStringLiteral("--check") << updateFile); reparseConfiguration(); } } KConfig *KConfig::copyTo(const QString &file, KConfig *config) const { Q_D(const KConfig); if (!config) { config = new KConfig(QString(), SimpleConfig, d->resourceType); } config->d_func()->changeFileName(file); config->d_func()->entryMap = d->entryMap; config->d_func()->bFileImmutable = false; const KEntryMapIterator theEnd = config->d_func()->entryMap.end(); for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) { it->bDirty = true; } config->d_ptr->bDirty = true; return config; } QString KConfig::name() const { Q_D(const KConfig); return d->fileName; } KConfig::OpenFlags KConfig::openFlags() const { Q_D(const KConfig); return d->openFlags; } struct KConfigStaticData { QString globalMainConfigName; // Keep a copy so we can use it in global dtors, after qApp is gone QStringList appArgs; }; Q_GLOBAL_STATIC(KConfigStaticData, globalData) void KConfig::setMainConfigName(const QString &str) { globalData()->globalMainConfigName = str; } QString KConfig::mainConfigName() { KConfigStaticData* data = globalData(); if (data->appArgs.isEmpty()) data->appArgs = QCoreApplication::arguments(); // --config on the command line overrides everything else const QStringList args = data->appArgs; for (int i = 1; i < args.count(); ++i) { if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) { return args.at(i + 1); } } const QString globalName = data->globalMainConfigName; if (!globalName.isEmpty()) { return globalName; } QString appName = QCoreApplication::applicationName(); return appName + QLatin1String("rc"); } void KConfigPrivate::changeFileName(const QString &name) { fileName = name; QString file; if (name.isEmpty()) { if (wantDefaults()) { // accessing default app-specific config "appnamerc" fileName = KConfig::mainConfigName(); file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere? resourceType = QStandardPaths::GenericConfigLocation; fileName = QStringLiteral("kdeglobals"); file = *sGlobalFileName; } else { // anonymous config openFlags = KConfig::SimpleConfig; return; } } else if (QDir::isAbsolutePath(fileName)) { fileName = QFileInfo(fileName).canonicalFilePath(); if (fileName.isEmpty()) { // file doesn't exist (yet) fileName = name; } file = fileName; } else { file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } Q_ASSERT(!file.isEmpty()); bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0); if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend mBackend = KConfigBackend::create(file); } else { mBackend->setFilePath(file); } configState = mBackend->accessMode(); } void KConfig::reparseConfiguration() { Q_D(KConfig); if (d->fileName.isEmpty()) { return; } // Don't lose pending changes if (!d->isReadOnly() && d->bDirty) { sync(); } d->entryMap.clear(); d->bFileImmutable = false; { QMutexLocker locker(&s_globalFilesMutex); s_globalFiles()->clear(); } // Parse all desired files from the least to the most specific. if (d->wantGlobals()) { d->parseGlobalFiles(); } d->parseConfigFiles(); } QStringList KConfigPrivate::getGlobalFiles() const { QMutexLocker locker(&s_globalFilesMutex); if (s_globalFiles()->isEmpty()) { const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals")); const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals")); const bool useEtcKderc = !etc_kderc.isEmpty(); s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0)); Q_FOREACH (const QString &dir1, paths1) { s_globalFiles()->push_front(dir1); } Q_FOREACH (const QString &dir2, paths2) { s_globalFiles()->push_front(dir2); } if (useEtcKderc) { s_globalFiles()->push_front(etc_kderc); } } return *s_globalFiles(); } void KConfigPrivate::parseGlobalFiles() { const QStringList globalFiles = getGlobalFiles(); // qDebug() << "parsing global files" << globalFiles; // TODO: can we cache the values in etc_kderc / other global files // on a per-application basis? const QByteArray utf8Locale = locale.toUtf8(); Q_FOREACH (const QString &file, globalFiles) { KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions; if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) parseOpts |= KConfigBackend::ParseDefaults; QExplicitlySharedDataPointer backend = KConfigBackend::create(file); if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) { break; } } } void KConfigPrivate::parseConfigFiles() { // can only read the file if there is a backend and a file name if (mBackend && !fileName.isEmpty()) { bFileImmutable = false; QList files; if (wantDefaults()) { if (bSuppressGlobal) { files = getGlobalFiles(); } else { if (QDir::isAbsolutePath(fileName)) { files << fileName; } else { Q_FOREACH (const QString &f, QStandardPaths::locateAll(resourceType, fileName)) { files.prepend(f); } // allow fallback to config files bundled in resources const QString resourceFile(QStringLiteral(":/kconfig/") + fileName); if (QFile::exists(resourceFile)) { files.prepend(resourceFile); } } } } else { files << mBackend->filePath(); } if (!isSimple()) { files = extraFiles.toList() + files; } // qDebug() << "parsing local files" << files; const QByteArray utf8Locale = locale.toUtf8(); foreach (const QString &file, files) { if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) { switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) { case KConfigBackend::ParseOk: break; case KConfigBackend::ParseImmutable: bFileImmutable = true; break; case KConfigBackend::ParseOpenError: configState = KConfigBase::NoAccess; break; } } else { QExplicitlySharedDataPointer backend = KConfigBackend::create(file); bFileImmutable = (backend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions) == KConfigBackend::ParseImmutable); } if (bFileImmutable) { break; } } } } KConfig::AccessMode KConfig::accessMode() const { Q_D(const KConfig); return d->configState; } void KConfig::addConfigSources(const QStringList &files) { Q_D(KConfig); Q_FOREACH (const QString &file, files) { d->extraFiles.push(file); } if (!files.isEmpty()) { reparseConfiguration(); } } +QStringList KConfig::additionalConfigSources() const +{ + Q_D(const KConfig); + return d->extraFiles.toList(); +} + QString KConfig::locale() const { Q_D(const KConfig); return d->locale; } bool KConfigPrivate::setLocale(const QString &aLocale) { if (aLocale != locale) { locale = aLocale; return true; } return false; } bool KConfig::setLocale(const QString &locale) { Q_D(KConfig); if (d->setLocale(locale)) { reparseConfiguration(); return true; } return false; } void KConfig::setReadDefaults(bool b) { Q_D(KConfig); d->bReadDefaults = b; } bool KConfig::readDefaults() const { Q_D(const KConfig); return d->bReadDefaults; } bool KConfig::isImmutable() const { Q_D(const KConfig); return d->bFileImmutable; } bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const { Q_D(const KConfig); return isImmutable() || d->entryMap.getEntryOption(aGroup, nullptr, nullptr, KEntryMap::EntryImmutable); } #ifndef KDE_NO_DEPRECATED void KConfig::setForceGlobal(bool b) { Q_D(KConfig); d->bForceGlobal = b; } #endif #ifndef KDE_NO_DEPRECATED bool KConfig::forceGlobal() const { Q_D(const KConfig); return d->bForceGlobal; } #endif KConfigGroup KConfig::groupImpl(const QByteArray &group) { return KConfigGroup(this, group.constData()); } const KConfigGroup KConfig::groupImpl(const QByteArray &group) const { return KConfigGroup(this, group.constData()); } KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags) { KEntryMap::EntryOptions options = nullptr; if (flags & KConfig::Persistent) { options |= KEntryMap::EntryDirty; } if (flags & KConfig::Global) { options |= KEntryMap::EntryGlobal; } if (flags & KConfig::Localized) { options |= KEntryMap::EntryLocalized; } return options; } void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags) { Q_D(KConfig); KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted; const QSet groups = d->allSubGroups(aGroup); Q_FOREACH (const QByteArray &group, groups) { const QStringList keys = d->keyListImpl(group); Q_FOREACH (const QString &_key, keys) { const QByteArray &key = _key.toUtf8(); if (d->canWriteEntry(group, key.constData())) { d->entryMap.setEntry(group, key, QByteArray(), options); d->bDirty = true; } } } } bool KConfig::isConfigWritable(bool warnUser) { Q_D(KConfig); bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false); if (warnUser && !allWritable) { QString errorMsg; if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately errorMsg = d->mBackend->nonWritableErrorMessage(); } // Note: We don't ask the user if we should not ask this question again because we can't save the answer. errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator."); QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog")); if (!cmdToExec.isEmpty()) { QProcess::execute(cmdToExec, QStringList() << QStringLiteral("--title") << QCoreApplication::applicationName() << QStringLiteral("--msgbox") << errorMsg); } } d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status return allWritable; } bool KConfig::hasGroupImpl(const QByteArray &aGroup) const { Q_D(const KConfig); // No need to look for the actual group entry anymore, or for subgroups: // a group exists if it contains any non-deleted entry. return d->hasNonDeletedEntries(aGroup); } bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const { if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) { return isDefault; } return true; } void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand) { KEntryMap::EntryOptions options = convertToOptions(flags); if (bForceGlobal) { options |= KEntryMap::EntryGlobal; } if (expand) { options |= KEntryMap::EntryExpansion; } if (value.isNull()) { // deleting entry options |= KEntryMap::EntryDeleted; } bool dirtied = entryMap.setEntry(group, key, value, options); if (dirtied && (flags & KConfigBase::Persistent)) { bDirty = true; } } void KConfigPrivate::revertEntry(const QByteArray &group, const char *key) { bool dirtied = entryMap.revertEntry(group, key); if (dirtied) { bDirty = true; } } QByteArray KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } const KEntryMapConstIterator it = entryMap.findEntry(group, key, flags); if (it == entryMap.constEnd()) { return QByteArray(); } return it->mValue; } QString KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } return entryMap.getEntry(group, key, QString(), flags, expand); } QStandardPaths::StandardLocation KConfig::locationType() const { Q_D(const KConfig); return d->resourceType; } void KConfig::virtual_hook(int /*id*/, void * /*data*/) { /* nothing */ } diff --git a/src/core/kconfig.h b/src/core/kconfig.h index c6be56d..5b34123 100644 --- a/src/core/kconfig.h +++ b/src/core/kconfig.h @@ -1,403 +1,408 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 2001 Waldo Bastian Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer This library 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 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCONFIG_H #define KCONFIG_H #include "kconfigbase.h" #include #include #include #include #include #include class KConfigGroup; class KEntryMap; class KConfigPrivate; /** * \class KConfig kconfig.h * * \brief The central class of the KDE configuration data system. * * Quickstart: * * Get the default application config object via KSharedConfig::openConfig(). * * Load a specific configuration file: * \code * KConfig config( "/etc/kderc", KConfig::SimpleConfig ); * \endcode * * Load the configuration of a specific component: * \code * KConfig config( "pluginrc" ); * \endcode * * In general it is recommended to use KSharedConfig instead of * creating multiple instances of KConfig to avoid the overhead of * separate objects or concerns about synchronizing writes to disk * even if the configuration object is updated from multiple code paths. * KSharedConfig provides a set of open methods as counterparts for the * KConfig constructors. * * \sa KSharedConfig, KConfigGroup, the techbase HOWTO on KConfig. */ class KCONFIGCORE_EXPORT KConfig : public KConfigBase { public: /** * Determines how the system-wide and user's global settings will affect * the reading of the configuration. * * If CascadeConfig is selected, system-wide configuration sources are used * to provide defaults for the settings accessed through this object, or * possibly to override those settings in certain cases. * * IncludeGlobals does the same, but with the global settings sources. * * Note that the main configuration source overrides the cascaded sources, * which override those provided to addConfigSources(), which override the * global sources. The exception is that if a key or group is marked as * being immutable, it will not be overridden. * * Note that all values other than IncludeGlobals and CascadeConfig are * convenience definitions for the basic mode. * Do @em not combine them with anything. */ enum OpenFlag { IncludeGlobals = 0x01, ///< Blend kdeglobals into the config object. CascadeConfig = 0x02, ///< Cascade to system-wide config files. SimpleConfig = 0x00, ///< Just a single config file. NoCascade = IncludeGlobals, ///< Include user's globals, but omit system settings. NoGlobals = CascadeConfig, ///< Cascade to system settings, but omit user's globals. FullConfig = IncludeGlobals | CascadeConfig ///< Fully-fledged config, including globals and cascading to system settings }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Creates a KConfig object to manipulate a configuration file for the * current application. * * If an absolute path is specified for @p file, that file will be used * as the store for the configuration settings. If a non-absolute path * is provided, the file will be looked for in the standard directory * specified by type. If no path is provided, a default * configuration file will be used based on the name of the main * application component. * * @p mode determines whether the user or global settings will be allowed * to influence the values returned by this object. See OpenFlags for * more details. * * @note You probably want to use KSharedConfig::openConfig instead. * * @param file the name of the file. If an empty string is passed in * and SimpleConfig is passed in for the OpenFlags, then an in-memory * KConfig object is created which will not write out to file nor which * requires any file in the filesystem at all. * @param mode how global settings should affect the configuration * options exposed by this KConfig object * @param type The standard directory to look for the configuration * file in * * @sa KSharedConfig::openConfig(const QString&, OpenFlags, QStandardPaths::StandardLocation) */ explicit KConfig(const QString &file = QString(), OpenFlags mode = FullConfig, QStandardPaths::StandardLocation type = QStandardPaths::GenericConfigLocation); /** * @internal * * Creates a KConfig object using the specified backend. If the backend can not * be found or loaded, then the standard configuration parser is used as a fallback. * * @param file the file to be parsed * @param backend the backend to load * @param type where to look for the file if an absolute path is not provided * * @since 4.1 */ KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation type = QStandardPaths::GenericConfigLocation); virtual ~KConfig(); /** * Returns the standard location enum passed to the constructor. * Used by KSharedConfig. * @since 5.0 */ QStandardPaths::StandardLocation locationType() const; /** * Returns the filename used to store the configuration. */ QString name() const; /** * @return the flags this object was opened with * @since 5.3 */ OpenFlags openFlags() const; /// @reimp bool sync() override; /// Returns true if sync has any changes to write out. /// @since 4.12 bool isDirty() const; /// @reimp void markAsClean() override; /// @{ configuration object state /// @reimp AccessMode accessMode() const override; /** * Whether the configuration can be written to. * * If @p warnUser is true and the configuration cannot be * written to (ie: this method returns @c false), a warning * message box will be shown to the user telling them to * contact their system administrator to get the problem fixed. * * The most likely cause for this method returning @c false * is that the user does not have write permission for the * configuration file. * * @param warnUser whether to show a warning message to the user * if the configuration cannot be written to * * @returns true if the configuration can be written to, false * if the configuration cannot be written to */ bool isConfigWritable(bool warnUser); /// @} /** * Copies all entries from this config object to a new config * object that will save itself to @p file. * * The configuration will not actually be saved to @p file * until the returned object is destroyed, or sync() is called * on it. * * Do not forget to delete the returned KConfig object if * @p config was 0. * * @param file the new config object will save itself to * @param config if not 0, copy to the given KConfig object rather * than creating a new one * * @return @p config if it was set, otherwise a new KConfig object */ KConfig *copyTo(const QString &file, KConfig *config = nullptr) const; /** * Ensures that the configuration file contains a certain update. * * If the configuration file does not contain the update @p id * as contained in @p updateFile, kconf_update is run to update * the configuration file. * * If you install config update files with critical fixes * you may wish to use this method to verify that a critical * update has indeed been performed to catch the case where * a user restores an old config file from backup that has * not been updated yet. * * @param id the update to check * @param updateFile the file containing the update */ void checkUpdate(const QString &id, const QString &updateFile); /** * Updates the state of this object to match the persistent storage. */ void reparseConfiguration(); /// @{ extra config files /** * Adds the list of configuration sources to the merge stack. * * Currently only files are accepted as configuration sources. * * The first entry in @p sources is treated as the most general and will * be overridden by the second entry. The settings in the final entry * in @p sources will override all the other sources provided in the list. * * The settings in @p sources will also be overridden by the sources * provided by any previous calls to addConfigSources(). * * The settings in the global configuration sources will be overridden by * the sources provided to this method (@see IncludeGlobals). * All the sources provided to any call to this method will be overridden * by any files that cascade from the source provided to the constructor * (@see CascadeConfig), which will in turn be * overridden by the source provided to the constructor. * * Note that only the most specific file, ie: the file provided to the * constructor, will be written to by this object. * * The state is automatically updated by this method, so there is no need to call * reparseConfiguration(). * * @param sources A list of extra config sources. */ void addConfigSources(const QStringList &sources); + /** + * Returns a list of the additional configuration sources used in this object + */ + QStringList additionalConfigSources() const; + /// @} /// @{ locales /** * Returns the current locale. */ QString locale() const; /** * Sets the locale to @p aLocale. * * The global locale is used by default. * * @note If set to the empty string, @b no locale will be matched. This effectively disables * reading translated entries. * * @return @c true if locale was changed, @c false if the call had no * effect (eg: @p aLocale was already the current locale for this * object) */ bool setLocale(const QString &aLocale); /// @} /// @{ defaults /** * When set, all readEntry calls return the system-wide (default) values * instead of the user's settings. * * This is off by default. * * @param b whether to read the system-wide defaults instead of the * user's settings */ void setReadDefaults(bool b); /** * @returns @c true if the system-wide defaults will be read instead of the * user's settings */ bool readDefaults() const; /// @} /// @{ immutability /// @reimp bool isImmutable() const override; /// @} /// @{ global /** * @deprecated * * Forces all following write-operations to be performed on @c kdeglobals, * independent of the @c Global flag in writeEntry(). * * @param force true to force writing to kdeglobals * @see forceGlobal */ #ifndef KDE_NO_DEPRECATED KCONFIGCORE_DEPRECATED void setForceGlobal(bool force); #endif /** * @deprecated * * Returns whether all entries are being written to @c kdeglobals. * * @return @c true if all entries are being written to @c kdeglobals * @see setForceGlobal * @deprecated */ #ifndef KDE_NO_DEPRECATED KCONFIGCORE_DEPRECATED bool forceGlobal() const; #endif /// @} /// @reimp QStringList groupList() const override; /** * Returns a map (tree) of entries in a particular group. * * The entries are all returned as strings. * * @param aGroup The group to get entries from. * * @return A map of entries in the group specified, indexed by key. * The returned map may be empty if the group is empty, or not found. * @see QMap */ QMap entryMap(const QString &aGroup = QString()) const; /** * Sets the name of the application config file. * @since 5.0 */ static void setMainConfigName(const QString &str); protected: bool hasGroupImpl(const QByteArray &group) const override; KConfigGroup groupImpl(const QByteArray &b) override; const KConfigGroup groupImpl(const QByteArray &b) const override; void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags = Normal) override; bool isGroupImmutableImpl(const QByteArray &aGroup) const override; friend class KConfigGroup; friend class KConfigGroupPrivate; friend class KSharedConfig; /** Virtual hook, used to add new "virtual" functions while maintaining * binary compatibility. Unused in this class. */ void virtual_hook(int id, void *data) override; KConfigPrivate *const d_ptr; KConfig(KConfigPrivate &d); private: friend class KConfigTest; QStringList keyList(const QString &aGroup = QString()) const; /** * @internal for KSharedConfig. Could be made public if needed, though. */ static QString mainConfigName(); Q_DISABLE_COPY(KConfig) Q_DECLARE_PRIVATE(KConfig) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KConfig::OpenFlags) #endif // KCONFIG_H