KConfig

kconfig.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
4 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
5 SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kconfig.h"
11#include "kconfig_p.h"
12
13#include "config-kconfig.h"
14#include "dbussanitizer_p.h"
15#include "kconfig_core_log_settings.h"
16
17#include <fcntl.h>
18
19#include "kconfiggroup.h"
20
21#include <QBasicMutex>
22#include <QByteArray>
23#include <QCache>
24#include <QCoreApplication>
25#include <QDir>
26#include <QFile>
27#include <QLocale>
28#include <QMutexLocker>
29#include <QProcess>
30#include <QSet>
31#include <QThreadStorage>
32#include <QTimeZone>
33
34#include <algorithm>
35#include <iterator>
36#include <set>
37
38#if KCONFIG_USE_DBUS
39#include <QDBusConnection>
40#include <QDBusMessage>
41#include <QDBusMetaType>
42#endif
43
44bool KConfigPrivate::mappingsRegistered = false;
45
46// For caching purposes
47static bool s_wasTestModeEnabled = false;
48
49Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
50static QBasicMutex s_globalFilesMutex;
51Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals")))
52
53using ParseCacheKey = std::pair<QStringList, QString>;
54struct ParseCacheValue {
55 KEntryMap entries;
56 QDateTime parseTime;
57};
59Q_GLOBAL_STATIC(ParseCache, sGlobalParse)
60
61#ifndef Q_OS_WIN
62static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive;
63#else
64static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive;
65#endif
66
67KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType)
68 : openFlags(flags)
69 , resourceType(resourceType)
70 , mBackend(new KConfigIniBackend)
71 , bDirty(false)
72 , bReadDefaults(false)
73 , bFileImmutable(false)
74 , bForceGlobal(false)
75 , bSuppressGlobal(false)
76 , configState(KConfigBase::NoAccess)
77{
78 const bool isTestMode = QStandardPaths::isTestModeEnabled();
79 // If sGlobalFileName was initialised and testMode has been toggled,
80 // sGlobalFileName may need to be updated to point to the correct kdeglobals file
81 if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) {
82 s_wasTestModeEnabled = isTestMode;
84 }
85
86 static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1);
87 if (use_etc_kderc.loadRelaxed() < 0) {
88 use_etc_kderc.storeRelaxed(!qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests
89 }
90 if (use_etc_kderc.loadRelaxed()) {
91 etc_kderc =
92#ifdef Q_OS_WIN
93 QFile::decodeName(qgetenv("WINDIR") + "/kde5rc");
94#else
95 QStringLiteral("/etc/kde5rc");
96#endif
97 if (!QFileInfo(etc_kderc).isReadable()) {
98 use_etc_kderc.storeRelaxed(false);
99 etc_kderc.clear();
100 }
101 }
102
103 // if (!mappingsRegistered) {
104 // KEntryMap tmp;
105 // if (!etc_kderc.isEmpty()) {
106 // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(etc_kderc, QLatin1String("INI"));
107 // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults);
108 // }
109 // const QString kde5rc(QDir::home().filePath(".kde5rc"));
110 // if (KStandardDirs::checkAccess(kde5rc, R_OK)) {
111 // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(kde5rc, QLatin1String("INI"));
112 // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions());
113 // }
114 // KConfigBackend::registerMappings(tmp);
115 // mappingsRegistered = true;
116 // }
117
118 setLocale(QLocale().name());
119}
120
121bool KConfigPrivate::lockLocal()
122{
123 return mBackend->lock();
124}
125
126static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QString &group)
127{
128 const QString &entryGroup = entryMapIt->first.mGroup;
129 Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
130 return entryGroup.size() == group.size() || entryGroup[group.size()] == QLatin1Char('\x1d');
131}
132
133void KConfigPrivate::copyGroup(const QString &source, const QString &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
134{
135 KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
136 const bool sameName = (destination == source);
137
138 // we keep this bool outside the for loop so that if
139 // the group is empty, we don't end up marking the other config
140 // as dirty erroneously
141 bool dirtied = false;
142
143 entryMap.forEachEntryWhoseGroupStartsWith(source, [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
144 // don't copy groups that start with the same prefix, but are not sub-groups
145 if (!isGroupOrSubGroupMatch(entryMapIt, source)) {
146 return;
147 }
148
149 KEntryKey newKey = entryMapIt->first;
150
151 if (flags & KConfigBase::Localized) {
152 newKey.bLocal = true;
153 }
154
155 if (!sameName) {
156 newKey.mGroup.replace(0, source.size(), destination);
157 }
158
159 KEntry entry = entryMapIt->second;
160 dirtied = entry.bDirty = flags & KConfigBase::Persistent;
161
162 if (flags & KConfigBase::Global) {
163 entry.bGlobal = true;
164 }
165
166 if (flags & KConfigBase::Notify) {
167 entry.bNotify = true;
168 }
169
170 otherMap[newKey] = entry;
171 });
172
173 if (dirtied) {
174 otherGroup->config()->d_ptr->bDirty = true;
175 }
176}
177
178QString KConfigPrivate::expandString(const QString &value)
179{
180 QString aValue = value;
181
182 // check for environment variables and make necessary translations
183 int nDollarPos = aValue.indexOf(QLatin1Char('$'));
184 while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
185 // there is at least one $
186 if (aValue.at(nDollarPos + 1) != QLatin1Char('$')) {
187 int nEndPos = nDollarPos + 1;
188 // the next character is not $
189 QStringView aVarName;
190 if (aValue.at(nEndPos) == QLatin1Char('{')) {
191 while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
192 ++nEndPos;
193 }
194 ++nEndPos;
195 aVarName = QStringView(aValue).mid(nDollarPos + 2, nEndPos - nDollarPos - 3);
196 } else {
197 while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
198 ++nEndPos;
199 }
200 aVarName = QStringView(aValue).mid(nDollarPos + 1, nEndPos - nDollarPos - 1);
201 }
202 QString env;
203 if (!aVarName.isEmpty()) {
204#ifdef Q_OS_WIN
205 if (aVarName == QLatin1String("HOME")) {
206 env = QDir::homePath();
207 } else
208#endif
209 {
210 QByteArray pEnv = qgetenv(aVarName.toLatin1().constData());
211 if (!pEnv.isEmpty()) {
212 env = QString::fromLocal8Bit(pEnv.constData());
213 } else {
214 if (aVarName == QLatin1String("QT_DATA_HOME")) {
216 } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
218 } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
220 }
221 }
222 }
223 aValue.replace(nDollarPos, nEndPos - nDollarPos, env);
224 nDollarPos += env.length();
225 } else {
226 aValue.remove(nDollarPos, nEndPos - nDollarPos);
227 }
228 } else {
229 // remove one of the dollar signs
230 aValue.remove(nDollarPos, 1);
231 ++nDollarPos;
232 }
233 nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos);
234 }
235
236 return aValue;
237}
238
240 : d_ptr(new KConfigPrivate(mode, resourceType))
241{
242 d_ptr->changeFileName(file); // set the local file name
243
244 // read initial information off disk
246}
247
248#if KCONFIGCORE_BUILD_DEPRECATED_SINCE(6, 3)
249KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
250 : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
251{
252 Q_UNUSED(backend);
253 d_ptr->changeFileName(file); // set the local file name
254
255 // read initial information off disk
257}
258#endif
259
260KConfig::KConfig(KConfigPrivate &d)
261 : d_ptr(&d)
262{
263}
264
265KConfig::~KConfig()
266{
267 Q_D(KConfig);
268 if (d->bDirty && d->mBackend->ref.loadRelaxed() == 1) {
269 sync();
270 }
271 delete d;
272}
273
274static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
275{
276 return !entryMapIt->first.mKey.isNull() && !entryMapIt->second.bDeleted;
277}
278// is a key without default values and not deleted
279static bool isSetKey(KEntryMapConstIterator entryMapIt)
280{
281 return !entryMapIt->first.bDefault && !entryMapIt->second.bDeleted;
282}
283
284static int findFirstGroupEndPos(const QString &groupFullName, int from = 0)
285{
286 const auto index = groupFullName.indexOf(QLatin1Char('\x1d'), from);
287 return index == -1 ? groupFullName.size() : index;
288}
289
290static QStringList stringListFromStringViewCollection(const QSet<QStringView> &source)
291{
293 list.reserve(source.size());
294 std::transform(source.cbegin(), source.cend(), std::back_inserter(list), [](QStringView view) {
295 return view.toString();
296 });
297 return list;
298}
299
301{
302 Q_D(const KConfig);
303 QSet<QStringView> groups;
304
305 for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
306 const QString &group = entryMapIt->first.mGroup;
307 if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != QStringLiteral("<default>") && group != QStringLiteral("$Version")) {
308 groups.insert(QStringView(group).left(findFirstGroupEndPos(group)));
309 }
310 }
311
312 return stringListFromStringViewCollection(groups);
313}
314
315QStringList KConfigPrivate::groupList(const QString &groupName) const
316{
317 const QString theGroup = groupName + QLatin1Char('\x1d');
318 QSet<QStringView> groups;
319
320 entryMap.forEachEntryWhoseGroupStartsWith(theGroup, [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
321 if (isNonDeletedKey(entryMapIt)) {
322 const QString &entryGroup = entryMapIt->first.mGroup;
323 const auto subgroupStartPos = theGroup.size();
324 const auto subgroupEndPos = findFirstGroupEndPos(entryGroup, subgroupStartPos);
325 groups.insert(QStringView(entryGroup).mid(subgroupStartPos, subgroupEndPos - subgroupStartPos));
326 }
327 });
328
329 return stringListFromStringViewCollection(groups);
330}
331
332/// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
333QSet<QString> KConfigPrivate::allSubGroups(const QString &parentGroup) const
334{
335 QSet<QString> groups;
336
337 entryMap.forEachEntryWhoseGroupStartsWith(parentGroup, [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
338 const KEntryKey &key = entryMapIt->first;
339 if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, parentGroup)) {
340 groups << key.mGroup;
341 }
342 });
343
344 return groups;
345}
346
347bool KConfigPrivate::hasNonDeletedEntries(const QString &group) const
348{
349 return entryMap.anyEntryWhoseGroupStartsWith(group, [&group](KEntryMapConstIterator entryMapIt) {
350 return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
351 });
352}
353
354QList<QByteArray> KConfigPrivate::keyListImpl(const QString &theGroup) const
355{
356 std::set<QByteArray> tmp; // unique set, sorted for unittests
357
358 entryMap.forEachEntryOfGroup(theGroup, [&tmp](KEntryMapConstIterator it) {
359 if (isNonDeletedKey(it)) {
360 tmp.insert(it->first.mKey);
361 }
362 });
363
364 return QList<QByteArray>(tmp.begin(), tmp.end());
365}
366
367QStringList KConfigPrivate::usedKeyList(const QString &theGroup) const
368{
369 std::set<QString> tmp; // unique set, sorting as side-effect
370
371 entryMap.forEachEntryOfGroup(theGroup, [&tmp](KEntryMapConstIterator it) {
372 // leave the default values and deleted entries out, same as KConfig::entryMap()
373 if (isSetKey(it)) {
374 const QString key = QString::fromUtf8(it->first.mKey);
375 tmp.insert(key);
376 }
377 });
378
379 return QStringList(tmp.begin(), tmp.end());
380}
381
383{
384 Q_D(const KConfig);
386 const QString theGroup = aGroup.isEmpty() ? QStringLiteral("<default>") : aGroup;
387
388 d->entryMap.forEachEntryOfGroup(theGroup, [&theMap](KEntryMapConstIterator it) {
389 // leave the default values and deleted entries out
390 if (isSetKey(it)) {
391 const QString key = QString::fromUtf8(it->first.mKey.constData());
392 // the localized entry should come first, so don't overwrite it
393 // with the non-localized entry
394 if (!theMap.contains(key)) {
395 if (it->second.bExpand) {
396 theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->second.mValue.constData())));
397 } else {
398 theMap.insert(key, QString::fromUtf8(it->second.mValue.constData()));
399 }
400 }
401 }
402 });
403
404 return theMap;
405}
406
408{
409 Q_D(KConfig);
410
411 if (isImmutable() || name().isEmpty()) {
412 // can't write to an immutable or anonymous file.
413 return false;
414 }
415
416 QHash<QString, QByteArrayList> notifyGroupsLocal;
417 QHash<QString, QByteArrayList> notifyGroupsGlobal;
418
419 if (d->bDirty) {
420 const QByteArray utf8Locale(locale().toUtf8());
421
422 // Create the containing dir, maybe it wasn't there
423 d->mBackend->createEnclosing();
424
425 // lock the local file
426 if (d->configState == ReadWrite && !d->lockLocal()) {
427 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
428 return false;
429 }
430
431 // Rewrite global/local config only if there is a dirty entry in it.
432 bool writeGlobals = false;
433 bool writeLocals = false;
434
435 for (const auto &[key, e] : d->entryMap) {
436 if (e.bDirty) {
437 if (e.bGlobal) {
438 writeGlobals = true;
439 if (e.bNotify) {
440 notifyGroupsGlobal[key.mGroup] << key.mKey;
441 }
442 } else {
443 writeLocals = true;
444 if (e.bNotify) {
445 notifyGroupsLocal[key.mGroup] << key.mKey;
446 }
447 }
448 }
449 }
450
451 d->bDirty = false; // will revert to true if a config write fails
452
453 if (d->wantGlobals() && writeGlobals) {
454 QExplicitlySharedDataPointer<KConfigIniBackend> tmp(new KConfigIniBackend());
455 tmp->setFilePath(*sGlobalFileName);
456 if (d->configState == ReadWrite && !tmp->lock()) {
457 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file";
458
459 // unlock the local config if we're returning early
460 if (d->mBackend->isLocked()) {
461 d->mBackend->unlock();
462 }
463
464 d->bDirty = true;
465 return false;
466 }
467 if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigIniBackend::WriteGlobal)) {
468 d->bDirty = true;
469 }
470 if (tmp->isLocked()) {
471 tmp->unlock();
472 }
473 }
474
475 if (writeLocals) {
476 if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigIniBackend::WriteOptions())) {
477 d->bDirty = true;
478 }
479 }
480 if (d->mBackend->isLocked()) {
481 d->mBackend->unlock();
482 }
483 }
484
485 // Notifying absolute paths is not supported and also makes no sense.
486 const bool isAbsolutePath = name().at(0) == QLatin1Char('/');
487 if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
488 d->notifyClients(notifyGroupsLocal, kconfigDBusSanitizePath(QLatin1Char('/') + name()));
489 }
490 if (!notifyGroupsGlobal.isEmpty()) {
491 d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
492 }
493
494 return !d->bDirty;
495}
496
497void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
498{
499#if KCONFIG_USE_DBUS
500 qDBusRegisterMetaType<QByteArrayList>();
501
502 qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
503
504 QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
505 message.setArguments({QVariant::fromValue(changes)});
507#else
508 Q_UNUSED(changes)
509 Q_UNUSED(path)
510#endif
511}
512
514{
515 Q_D(KConfig);
516 d->bDirty = false;
517
518 // clear any dirty flags that entries might have set
519 for (auto &[_, entry] : d->entryMap) {
520 entry.bDirty = false;
521 entry.bNotify = false;
522 }
523}
524
526{
527 Q_D(const KConfig);
528 return d->bDirty;
529}
530
531void KConfig::checkUpdate(const QString &id, const QString &updateFile)
532{
533 const KConfigGroup cg(this, QStringLiteral("$Version"));
534 const QString cfg_id = updateFile + QLatin1Char(':') + id;
535 const QStringList ids = cg.readEntry("update_info", QStringList());
536 if (!ids.contains(cfg_id)) {
537 QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList{QStringLiteral("--check"), updateFile});
539 }
540}
541
542KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
543{
544 Q_D(const KConfig);
545 if (!config) {
546 config = new KConfig(QString(), SimpleConfig, d->resourceType);
547 }
548 config->d_func()->changeFileName(file);
549 config->d_func()->entryMap = d->entryMap;
550 config->d_func()->bFileImmutable = false;
551
552 for (auto &[_, entry] : config->d_func()->entryMap) {
553 entry.bDirty = true;
554 }
555 config->d_ptr->bDirty = true;
556
557 return config;
558}
559
561{
562 Q_D(const KConfig);
563 return d->fileName;
564}
565
567{
568 Q_D(const KConfig);
569 return d->openFlags;
570}
571
572struct KConfigStaticData {
573 QString globalMainConfigName;
574 // Keep a copy so we can use it in global dtors, after qApp is gone
575 QStringList appArgs;
576};
577Q_GLOBAL_STATIC(KConfigStaticData, globalData)
578static QBasicMutex s_globalDataMutex;
579
581{
582 QMutexLocker locker(&s_globalDataMutex);
583 globalData()->globalMainConfigName = str;
584}
585
587{
588 QMutexLocker locker(&s_globalDataMutex);
589 KConfigStaticData *data = globalData();
590 if (data->appArgs.isEmpty()) {
591 data->appArgs = QCoreApplication::arguments();
592 }
593
594 // --config on the command line overrides everything else
595 const QStringList args = data->appArgs;
596 for (int i = 1; i < args.count(); ++i) {
597 if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
598 return args.at(i + 1);
599 }
600 }
601 const QString globalName = data->globalMainConfigName;
602 if (!globalName.isEmpty()) {
603 return globalName;
604 }
605
607 return appName + QLatin1String("rc");
608}
609
610void KConfigPrivate::changeFileName(const QString &name)
611{
612 fileName = name;
613
614 QString file;
615 if (name.isEmpty()) {
616 if (wantDefaults()) { // accessing default app-specific config "appnamerc"
617 fileName = KConfig::mainConfigName();
618 file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
619 } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
621 fileName = QStringLiteral("kdeglobals");
622 file = *sGlobalFileName;
623 } else {
624 // anonymous config
625 openFlags = KConfig::SimpleConfig;
626 return;
627 }
628 } else if (QDir::isAbsolutePath(fileName)) {
629 fileName = QFileInfo(fileName).canonicalFilePath();
630 if (fileName.isEmpty()) { // file doesn't exist (yet)
631 fileName = name;
632 }
633 file = fileName;
634 } else {
635 file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
636 }
637
638 Q_ASSERT(!file.isEmpty());
639
640 bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0);
641
642 mBackend->setFilePath(file);
643
644 configState = mBackend->accessMode();
645}
646
648{
649 Q_D(KConfig);
650 if (d->fileName.isEmpty()) {
651 return;
652 }
653
654 // Don't lose pending changes
655 if (!d->isReadOnly() && d->bDirty) {
656 sync();
657 }
658
659 d->entryMap.clear();
660
661 d->bFileImmutable = false;
662
663 {
664 QMutexLocker locker(&s_globalFilesMutex);
665 s_globalFiles()->clear();
666 }
667
668 // Parse all desired files from the least to the most specific.
669 if (d->wantGlobals()) {
670 d->parseGlobalFiles();
671 }
672
673 d->parseConfigFiles();
674}
675
676QStringList KConfigPrivate::getGlobalFiles() const
677{
678 QMutexLocker locker(&s_globalFilesMutex);
679 if (s_globalFiles()->isEmpty()) {
680 const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
681 const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
682
683 const bool useEtcKderc = !etc_kderc.isEmpty();
684 s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
685
686 for (const QString &dir1 : paths1) {
687 s_globalFiles()->push_front(dir1);
688 }
689 for (const QString &dir2 : paths2) {
690 s_globalFiles()->push_front(dir2);
691 }
692
693 if (useEtcKderc) {
694 s_globalFiles()->push_front(etc_kderc);
695 }
696 }
697
698 return *s_globalFiles();
699}
700
701void KConfigPrivate::parseGlobalFiles()
702{
703 const QStringList globalFiles = getGlobalFiles();
704 // qDebug() << "parsing global files" << globalFiles;
705
706 Q_ASSERT(entryMap.empty());
707 const ParseCacheKey key = {globalFiles, locale};
708 auto data = sGlobalParse->localData().object(key);
709 QDateTime newest;
710 for (const auto &file : globalFiles) {
711 const auto fileDate = QFileInfo(file).lastModified(QTimeZone::UTC);
712 if (fileDate > newest) {
713 newest = fileDate;
714 }
715 }
716 if (data) {
717 if (data->parseTime < newest) {
718 data = nullptr;
719 } else {
720 entryMap = data->entries;
721 return;
722 }
723 }
724
725 const QByteArray utf8Locale = locale.toUtf8();
726 for (const QString &file : globalFiles) {
727 KConfigIniBackend::ParseOptions parseOpts = KConfigIniBackend::ParseGlobal | KConfigIniBackend::ParseExpansions;
728
729 if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
730 parseOpts |= KConfigIniBackend::ParseDefaults;
731 }
732
733 QExplicitlySharedDataPointer<KConfigIniBackend> backend(new KConfigIniBackend);
734 backend->setFilePath(file);
735 if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable) {
736 break;
737 }
738 }
739 sGlobalParse->localData().insert(key, new ParseCacheValue({entryMap, newest}));
740}
741
742void KConfigPrivate::parseConfigFiles()
743{
744 // can only read the file if there is a backend and a file name
745 if (!fileName.isEmpty()) {
746 bFileImmutable = false;
747
748 QList<QString> files;
749 if (wantDefaults()) {
750 if (bSuppressGlobal) {
751 files = getGlobalFiles();
752 } else {
753 if (QDir::isAbsolutePath(fileName)) {
754 const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
755 if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
756 files << canonicalFile;
757 }
758 } else {
759 const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
760 for (const QString &f : localFilesPath) {
761 files.prepend(QFileInfo(f).canonicalFilePath());
762 }
763
764 // allow fallback to config files bundled in resources
765 const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
766 if (QFile::exists(resourceFile)) {
767 files.prepend(resourceFile);
768 }
769 }
770 }
771 } else {
772 files << mBackend->filePath();
773 }
774 if (!isSimple()) {
775 files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
776 }
777
778 // qDebug() << "parsing local files" << files;
779
780 const QByteArray utf8Locale = locale.toUtf8();
781 for (const QString &file : std::as_const(files)) {
782 if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) {
783 switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigIniBackend::ParseExpansions)) {
784 case KConfigIniBackend::ParseOk:
785 break;
786 case KConfigIniBackend::ParseImmutable:
787 bFileImmutable = true;
788 break;
789 case KConfigIniBackend::ParseOpenError:
790 configState = KConfigBase::NoAccess;
791 break;
792 }
793 } else {
794 QExplicitlySharedDataPointer<KConfigIniBackend> backend(new KConfigIniBackend);
795 backend->setFilePath(file);
796 constexpr auto parseOpts = KConfigIniBackend::ParseDefaults | KConfigIniBackend::ParseExpansions;
797 bFileImmutable = backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable;
798 }
799
800 if (bFileImmutable) {
801 break;
802 }
803 }
804 }
805}
806
808{
809 Q_D(const KConfig);
810 return d->configState;
811}
812
814{
815 Q_D(KConfig);
816 for (const QString &file : files) {
817 d->extraFiles.push(file);
818 }
819
820 if (!files.isEmpty()) {
822 }
823}
824
826{
827 Q_D(const KConfig);
828 return d->extraFiles.toList();
829}
830
832{
833 Q_D(const KConfig);
834 return d->locale;
835}
836
837bool KConfigPrivate::setLocale(const QString &aLocale)
838{
839 if (aLocale != locale) {
840 locale = aLocale;
841 return true;
842 }
843 return false;
844}
845
846bool KConfig::setLocale(const QString &locale)
847{
848 Q_D(KConfig);
849 if (d->setLocale(locale)) {
851 return true;
852 }
853 return false;
854}
855
857{
858 Q_D(KConfig);
859 d->bReadDefaults = b;
860}
861
863{
864 Q_D(const KConfig);
865 return d->bReadDefaults;
866}
867
869{
870 Q_D(const KConfig);
871 return d->bFileImmutable;
872}
873
874bool KConfig::isGroupImmutableImpl(const QString &aGroup) const
875{
876 Q_D(const KConfig);
877 return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
878}
879
881{
882 return KConfigGroup(this, group);
883}
884
885const KConfigGroup KConfig::groupImpl(const QString &group) const
886{
887 return KConfigGroup(this, group);
888}
889
890KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
891{
892 KEntryMap::EntryOptions options = {};
893
894 if (flags & KConfig::Persistent) {
895 options |= KEntryMap::EntryDirty;
896 }
897 if (flags & KConfig::Global) {
898 options |= KEntryMap::EntryGlobal;
899 }
900 if (flags & KConfig::Localized) {
901 options |= KEntryMap::EntryLocalized;
902 }
903 if (flags.testFlag(KConfig::Notify)) {
904 options |= KEntryMap::EntryNotify;
905 }
906 return options;
907}
908
910{
911 Q_D(KConfig);
912 KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
913
914 const QSet<QString> groups = d->allSubGroups(aGroup);
915 for (const QString &group : groups) {
916 const QList<QByteArray> keys = d->keyListImpl(group);
917 for (const QByteArray &key : keys) {
918 if (d->canWriteEntry(group, key)) {
919 d->entryMap.setEntry(group, key, QByteArray(), options);
920 d->bDirty = true;
921 }
922 }
923 }
924}
925
926bool KConfig::isConfigWritable(bool warnUser)
927{
928 Q_D(KConfig);
929 bool allWritable = d->mBackend->isWritable();
930
931 if (warnUser && !allWritable) {
932 QString errorMsg;
933 errorMsg = d->mBackend->nonWritableErrorMessage();
934
935 // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
936 errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
937 QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
938 if (!cmdToExec.isEmpty()) {
939 QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
940 }
941 }
942
943 d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
944
945 return allWritable;
946}
947
948bool KConfig::hasGroupImpl(const QString &aGroup) const
949{
950 Q_D(const KConfig);
951
952 // No need to look for the actual group entry anymore, or for subgroups:
953 // a group exists if it contains any non-deleted entry.
954
955 return d->hasNonDeletedEntries(aGroup);
956}
957
958bool KConfigPrivate::canWriteEntry(const QString &group, QAnyStringView key, bool isDefault) const
959{
960 if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
961 return isDefault;
962 }
963 return true;
964}
965
966void KConfigPrivate::putData(const QString &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
967{
968 KEntryMap::EntryOptions options = convertToOptions(flags);
969
970 if (bForceGlobal) {
971 options |= KEntryMap::EntryGlobal;
972 }
973 if (expand) {
974 options |= KEntryMap::EntryExpansion;
975 }
976
977 if (value.isNull()) { // deleting entry
978 options |= KEntryMap::EntryDeleted;
979 }
980
981 bool dirtied = entryMap.setEntry(group, key, value, options);
982 if (dirtied && (flags & KConfigBase::Persistent)) {
983 bDirty = true;
984 }
985}
986
987void KConfigPrivate::revertEntry(const QString &group, QAnyStringView key, KConfigBase::WriteConfigFlags flags)
988{
989 KEntryMap::EntryOptions options = convertToOptions(flags);
990
991 bool dirtied = entryMap.revertEntry(group, key, options);
992 if (dirtied) {
993 bDirty = true;
994 }
995}
996
997QByteArray KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
998{
999 return lookupInternalEntry(group, key, flags).mValue;
1000}
1001
1002KEntry KConfigPrivate::lookupInternalEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1003{
1004 if (bReadDefaults) {
1005 flags |= KEntryMap::SearchDefaults;
1006 }
1007 const auto it = entryMap.constFindEntry(group, key, flags);
1008 if (it == entryMap.cend()) {
1009 return {};
1010 }
1011 return it->second;
1012}
1013
1014QString KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags, bool *expand) const
1015{
1016 if (bReadDefaults) {
1017 flags |= KEntryMap::SearchDefaults;
1018 }
1019 return entryMap.getEntry(group, key, QString(), flags, expand);
1020}
1021
1023{
1024 Q_D(const KConfig);
1025 return d->resourceType;
1026}
1027
1028void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1029{
1030 /* nothing */
1031}
Interface to interact with configuration.
Definition kconfigbase.h:31
@ Notify
Notify remote KConfigWatchers of changes (requires DBus support) Implied persistent.
Definition kconfigbase.h:51
@ Persistent
Save this entry when saving the config object.
Definition kconfigbase.h:38
@ Global
Save the entry to the global KDE config file instead of the application specific config file.
Definition kconfigbase.h:42
@ Localized
Add the locale tag to the key when writing it.
Definition kconfigbase.h:47
AccessMode
Possible return values for accessMode().
KConfigGroup group(const QString &group)
Returns an object for the named subgroup.
A class for one specific group in a KConfig object.
T readEntry(const QString &key, const T &aDefault) const
Reads the value of an entry specified by pKey in the current group.
KConfig * config()
Return the config object that this group belongs to.
The central class of the KDE configuration data system.
Definition kconfig.h:56
KConfig(const QString &file=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Creates a KConfig object to manipulate a configuration file for the current application.
Definition kconfig.cpp:239
AccessMode accessMode() const override
Definition kconfig.cpp:807
void markAsClean() override
Definition kconfig.cpp:513
QStringList additionalConfigSources() const
Returns a list of the additional configuration sources used in this object.
Definition kconfig.cpp:825
QString locale() const
locales
Definition kconfig.cpp:831
void reparseConfiguration()
Updates the state of this object to match the persistent storage.
Definition kconfig.cpp:647
QMap< QString, QString > entryMap(const QString &aGroup=QString()) const
Returns a map (tree) of entries in a particular group.
Definition kconfig.cpp:382
void setReadDefaults(bool b)
defaults
Definition kconfig.cpp:856
bool sync() override
Definition kconfig.cpp:407
QString name() const
Returns the filename used to store the configuration.
Definition kconfig.cpp:560
bool hasGroupImpl(const QString &groupName) const override
Definition kconfig.cpp:948
void virtual_hook(int id, void *data) override
Virtual hook, used to add new "virtual" functions while maintaining binary compatibility.
Definition kconfig.cpp:1028
static QString mainConfigName()
Get the name of application config file.
Definition kconfig.cpp:586
bool readDefaults() const
Definition kconfig.cpp:862
bool isImmutable() const override
Definition kconfig.cpp:868
void checkUpdate(const QString &id, const QString &updateFile)
Ensures that the configuration file contains a certain update.
Definition kconfig.cpp:531
QStringList groupList() const override
Definition kconfig.cpp:300
KConfig * copyTo(const QString &file, KConfig *config=nullptr) const
Copies all entries from this config object to a new config object that will save itself to file.
Definition kconfig.cpp:542
static void setMainConfigName(const QString &str)
Sets the name of the application config file.
Definition kconfig.cpp:580
bool isGroupImmutableImpl(const QString &groupName) const override
Definition kconfig.cpp:874
KConfigGroup groupImpl(const QString &groupName) override
Definition kconfig.cpp:880
QStandardPaths::StandardLocation locationType() const
Returns the standard location enum passed to the constructor.
Definition kconfig.cpp:1022
OpenFlags openFlags() const
Definition kconfig.cpp:566
void addConfigSources(const QStringList &sources)
extra config files
Definition kconfig.cpp:813
bool isConfigWritable(bool warnUser)
Whether the configuration can be written to.
Definition kconfig.cpp:926
@ SimpleConfig
Just a single config file.
Definition kconfig.h:85
bool isDirty() const
Returns true if sync has any changes to write out.
Definition kconfig.cpp:525
void deleteGroupImpl(const QString &groupName, WriteConfigFlags flags=Normal) override
Definition kconfig.cpp:909
bool setLocale(const QString &aLocale)
Sets the locale to aLocale.
Definition kconfig.cpp:846
QString name(GameStandardAction id)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const char * constData() const const
QByteArray & insert(qsizetype i, QByteArrayView data)
bool isEmpty() const const
bool isNull() const const
QStringList arguments()
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
QString homePath()
bool isAbsolutePath(const QString &path)
QString decodeName(const QByteArray &localFileName)
bool exists() const const
QString canonicalFilePath() const const
QDateTime lastModified() const const
bool testFlag(Enum flag) const const
bool isEmpty() const const
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void prepend(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
int execute(const QString &program, const QStringList &arguments)
const_iterator cbegin() const const
const_iterator cend() const const
iterator insert(const T &value)
qsizetype size() const const
QString findExecutable(const QString &executableName, const QStringList &paths)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QString writableLocation(StandardLocation type)
const QChar at(qsizetype position) const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString first(qsizetype n) const const
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringView mid(qsizetype start, qsizetype length) const const
bool isEmpty() const const
QByteArray toLatin1() const const
CaseSensitivity
QVariant fromValue(T &&value)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.