You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1033 lines
32 KiB
1033 lines
32 KiB
/* |
|
This file is part of the KDE libraries |
|
SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com> |
|
SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org> |
|
SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org> |
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later |
|
*/ |
|
|
|
#include "kconfig.h" |
|
#include "kconfig_p.h" |
|
|
|
#include "config-kconfig.h" |
|
#include "kconfig_core_log_settings.h" |
|
|
|
#include <cstdlib> |
|
#include <fcntl.h> |
|
|
|
#include "kconfigbackend_p.h" |
|
#include "kconfiggroup.h" |
|
|
|
#include <QCoreApplication> |
|
#include <QProcess> |
|
#include <QStandardPaths> |
|
#include <QByteArray> |
|
#include <QFile> |
|
#include <QLocale> |
|
#include <QDir> |
|
#include <QProcess> |
|
#include <QSet> |
|
#include <QBasicMutex> |
|
#include <QMutexLocker> |
|
|
|
#if KCONFIG_USE_DBUS |
|
#include <QDBusMessage> |
|
#include <QDBusConnection> |
|
#include <QDBusMetaType> |
|
#endif |
|
|
|
bool KConfigPrivate::mappingsRegistered = false; |
|
|
|
// For caching purposes |
|
static bool s_wasTestModeEnabled = 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) |
|
{ |
|
const bool isTestMode = QStandardPaths::isTestModeEnabled(); |
|
// If sGlobalFileName was initialised and testMode has been toggled, |
|
// sGlobalFileName may need to be updated to point to the correct kdeglobals file |
|
if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) { |
|
s_wasTestModeEnabled = isTestMode; |
|
*sGlobalFileName = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals"); |
|
} |
|
|
|
static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1); |
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) |
|
if (use_etc_kderc.load() < 0) { |
|
use_etc_kderc.store( !qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests |
|
} |
|
if (use_etc_kderc.load()) { |
|
#else |
|
if (use_etc_kderc.loadRelaxed() < 0) { |
|
use_etc_kderc.storeRelaxed( !qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests |
|
} |
|
if (use_etc_kderc.loadRelaxed()) { |
|
#endif |
|
etc_kderc = |
|
#ifdef Q_OS_WIN |
|
QFile::decodeName(qgetenv("WINDIR") + "/kde5rc"); |
|
#else |
|
QStringLiteral("/etc/kde5rc"); |
|
#endif |
|
if (!QFileInfo(etc_kderc).isReadable()) { |
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) |
|
use_etc_kderc.store(false); |
|
#else |
|
use_etc_kderc.storeRelaxed(false); |
|
#endif |
|
etc_kderc.clear(); |
|
} |
|
} |
|
|
|
// if (!mappingsRegistered) { |
|
// KEntryMap tmp; |
|
// if (!etc_kderc.isEmpty()) { |
|
// QExplicitlySharedDataPointer<KConfigBackend> 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<KConfigBackend> 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 $ |
|
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 == QLatin1String("QT_DATA_HOME")) { |
|
env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); |
|
} else if (aVarName == QLatin1String("QT_CONFIG_HOME")) { |
|
env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); |
|
} else if (aVarName == QLatin1String("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 QT_VERSION < QT_VERSION_CHECK(5, 14, 0) |
|
if (d->bDirty && (d->mBackend && d->mBackend->ref.load() == 1)) { |
|
#else |
|
if (d->bDirty && (d->mBackend && d->mBackend->ref.loadRelaxed() == 1)) { |
|
#endif |
|
sync(); |
|
} |
|
delete d; |
|
} |
|
|
|
QStringList KConfig::groupList() const |
|
{ |
|
Q_D(const KConfig); |
|
QSet<QString> 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 != "<default>" && group != "$Version") { |
|
const QString groupname = QString::fromUtf8(group); |
|
groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); |
|
} |
|
} |
|
|
|
return groups.values(); |
|
} |
|
|
|
QStringList KConfigPrivate::groupList(const QByteArray &group) const |
|
{ |
|
QByteArray theGroup = group + '\x1d'; |
|
QSet<QString> 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.values(); |
|
} |
|
|
|
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<QByteArray> KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const |
|
{ |
|
QSet<QByteArray> 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<QString> 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.values(); |
|
} |
|
|
|
return keys; |
|
} |
|
|
|
QStringList KConfig::keyList(const QString &aGroup) const |
|
{ |
|
Q_D(const KConfig); |
|
const QByteArray theGroup(aGroup.isEmpty() ? "<default>" : aGroup.toUtf8()); |
|
return d->keyListImpl(theGroup); |
|
} |
|
|
|
QMap<QString, QString> KConfig::entryMap(const QString &aGroup) const |
|
{ |
|
Q_D(const KConfig); |
|
QMap<QString, QString> theMap; |
|
const QByteArray theGroup(aGroup.isEmpty() ? "<default>" : aGroup.toUtf8()); |
|
|
|
const KEntryMapConstIterator theEnd = d->entryMap.constEnd(); |
|
KEntryMapConstIterator it = d->entryMap.findEntry(theGroup, {}, {}); |
|
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; |
|
} |
|
|
|
QHash<QString, QByteArrayList> notifyGroupsLocal; |
|
QHash<QString, QByteArrayList> notifyGroupsGlobal; |
|
|
|
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()) { |
|
qCWarning(KCONFIG_CORE_LOG) << "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; |
|
|
|
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 (e.bNotify) { |
|
notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; |
|
} |
|
} |
|
} |
|
} |
|
|
|
d->bDirty = false; // will revert to true if a config write fails |
|
|
|
if (d->wantGlobals() && writeGlobals) { |
|
QExplicitlySharedDataPointer<KConfigBackend> tmp = KConfigBackend::create(*sGlobalFileName); |
|
if (d->configState == ReadWrite && !tmp->lock()) { |
|
qCWarning(KCONFIG_CORE_LOG) << "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(); |
|
} |
|
} |
|
|
|
if (!notifyGroupsLocal.isEmpty()) { |
|
d->notifyClients(notifyGroupsLocal, QLatin1Char('/') + 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); |
|
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; |
|
it->bNotify = 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)); |
|
|
|
for (const QString &dir1 : paths1) { |
|
s_globalFiles()->push_front(dir1); |
|
} |
|
for (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(); |
|
for (const QString &file : globalFiles) { |
|
KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions; |
|
|
|
if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) |
|
parseOpts |= KConfigBackend::ParseDefaults; |
|
|
|
QExplicitlySharedDataPointer<KConfigBackend> 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<QString> files; |
|
if (wantDefaults()) { |
|
if (bSuppressGlobal) { |
|
files = getGlobalFiles(); |
|
} else { |
|
if (QDir::isAbsolutePath(fileName)) { |
|
const QString canonicalFile = QFileInfo(fileName).canonicalFilePath(); |
|
if (!canonicalFile.isEmpty()) { // empty if it doesn't exist |
|
files << canonicalFile; |
|
} |
|
} else { |
|
const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName); |
|
for (const QString &f : localFilesPath) { |
|
files.prepend(QFileInfo(f).canonicalFilePath()); |
|
} |
|
|
|
// 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(); |
|
for (const QString &file : qAsConst(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<KConfigBackend> 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); |
|
for (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, {},{}, KEntryMap::EntryImmutable); |
|
} |
|
|
|
#if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0) |
|
void KConfig::setForceGlobal(bool b) |
|
{ |
|
Q_D(KConfig); |
|
d->bForceGlobal = b; |
|
} |
|
#endif |
|
|
|
#if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0) |
|
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 = {}; |
|
|
|
if (flags & KConfig::Persistent) { |
|
options |= KEntryMap::EntryDirty; |
|
} |
|
if (flags & KConfig::Global) { |
|
options |= KEntryMap::EntryGlobal; |
|
} |
|
if (flags & KConfig::Localized) { |
|
options |= KEntryMap::EntryLocalized; |
|
} |
|
if (flags.testFlag(KConfig::Notify)) { |
|
options |= KEntryMap::EntryNotify; |
|
} |
|
return options; |
|
} |
|
|
|
void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags) |
|
{ |
|
Q_D(KConfig); |
|
KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted; |
|
|
|
const QSet<QByteArray> groups = d->allSubGroups(aGroup); |
|
for (const QByteArray &group : groups) { |
|
const QStringList keys = d->keyListImpl(group); |
|
for (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, KConfigBase::WriteConfigFlags flags) |
|
{ |
|
KEntryMap::EntryOptions options = convertToOptions(flags); |
|
|
|
bool dirtied = entryMap.revertEntry(group, key, options); |
|
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 */ |
|
}
|
|
|