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#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
712 const auto fileDate = QFileInfo(file).lastModified(QTimeZone::UTC);
713#else
714 const auto fileDate = QFileInfo(file).lastModified();
715#endif
716 if (fileDate > newest) {
717 newest = fileDate;
718 }
719 }
720 if (data) {
721 if (data->parseTime < newest) {
722 data = nullptr;
723 } else {
724 entryMap = data->entries;
725 return;
726 }
727 }
728
729 const QByteArray utf8Locale = locale.toUtf8();
730 for (const QString &file : globalFiles) {
731 KConfigIniBackend::ParseOptions parseOpts = KConfigIniBackend::ParseGlobal | KConfigIniBackend::ParseExpansions;
732
733 if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
734 parseOpts |= KConfigIniBackend::ParseDefaults;
735 }
736
737 QExplicitlySharedDataPointer<KConfigIniBackend> backend(new KConfigIniBackend);
738 backend->setFilePath(file);
739 if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable) {
740 break;
741 }
742 }
743 sGlobalParse->localData().insert(key, new ParseCacheValue({entryMap, newest}));
744}
745
746void KConfigPrivate::parseConfigFiles()
747{
748 // can only read the file if there is a backend and a file name
749 if (!fileName.isEmpty()) {
750 bFileImmutable = false;
751
752 QList<QString> files;
753 if (wantDefaults()) {
754 if (bSuppressGlobal) {
755 files = getGlobalFiles();
756 } else {
757 if (QDir::isAbsolutePath(fileName)) {
758 const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
759 if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
760 files << canonicalFile;
761 }
762 } else {
763 const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
764 for (const QString &f : localFilesPath) {
765 files.prepend(QFileInfo(f).canonicalFilePath());
766 }
767
768 // allow fallback to config files bundled in resources
769 const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
770 if (QFile::exists(resourceFile)) {
771 files.prepend(resourceFile);
772 }
773 }
774 }
775 } else {
776 files << mBackend->filePath();
777 }
778 if (!isSimple()) {
779 files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
780 }
781
782 // qDebug() << "parsing local files" << files;
783
784 const QByteArray utf8Locale = locale.toUtf8();
785 for (const QString &file : std::as_const(files)) {
786 if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) {
787 switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigIniBackend::ParseExpansions)) {
788 case KConfigIniBackend::ParseOk:
789 break;
790 case KConfigIniBackend::ParseImmutable:
791 bFileImmutable = true;
792 break;
793 case KConfigIniBackend::ParseOpenError:
794 configState = KConfigBase::NoAccess;
795 break;
796 }
797 } else {
798 QExplicitlySharedDataPointer<KConfigIniBackend> backend(new KConfigIniBackend);
799 backend->setFilePath(file);
800 constexpr auto parseOpts = KConfigIniBackend::ParseDefaults | KConfigIniBackend::ParseExpansions;
801 bFileImmutable = backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable;
802 }
803
804 if (bFileImmutable) {
805 break;
806 }
807 }
808 }
809}
810
812{
813 Q_D(const KConfig);
814 return d->configState;
815}
816
818{
819 Q_D(KConfig);
820 for (const QString &file : files) {
821 d->extraFiles.push(file);
822 }
823
824 if (!files.isEmpty()) {
826 }
827}
828
830{
831 Q_D(const KConfig);
832 return d->extraFiles.toList();
833}
834
836{
837 Q_D(const KConfig);
838 return d->locale;
839}
840
841bool KConfigPrivate::setLocale(const QString &aLocale)
842{
843 if (aLocale != locale) {
844 locale = aLocale;
845 return true;
846 }
847 return false;
848}
849
850bool KConfig::setLocale(const QString &locale)
851{
852 Q_D(KConfig);
853 if (d->setLocale(locale)) {
855 return true;
856 }
857 return false;
858}
859
861{
862 Q_D(KConfig);
863 d->bReadDefaults = b;
864}
865
867{
868 Q_D(const KConfig);
869 return d->bReadDefaults;
870}
871
873{
874 Q_D(const KConfig);
875 return d->bFileImmutable;
876}
877
878bool KConfig::isGroupImmutableImpl(const QString &aGroup) const
879{
880 Q_D(const KConfig);
881 return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
882}
883
885{
886 return KConfigGroup(this, group);
887}
888
889const KConfigGroup KConfig::groupImpl(const QString &group) const
890{
891 return KConfigGroup(this, group);
892}
893
894KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
895{
896 KEntryMap::EntryOptions options = {};
897
898 if (flags & KConfig::Persistent) {
899 options |= KEntryMap::EntryDirty;
900 }
901 if (flags & KConfig::Global) {
902 options |= KEntryMap::EntryGlobal;
903 }
904 if (flags & KConfig::Localized) {
905 options |= KEntryMap::EntryLocalized;
906 }
907 if (flags.testFlag(KConfig::Notify)) {
908 options |= KEntryMap::EntryNotify;
909 }
910 return options;
911}
912
914{
915 Q_D(KConfig);
916 KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
917
918 const QSet<QString> groups = d->allSubGroups(aGroup);
919 for (const QString &group : groups) {
920 const QList<QByteArray> keys = d->keyListImpl(group);
921 for (const QByteArray &key : keys) {
922 if (d->canWriteEntry(group, key)) {
923 d->entryMap.setEntry(group, key, QByteArray(), options);
924 d->bDirty = true;
925 }
926 }
927 }
928}
929
930bool KConfig::isConfigWritable(bool warnUser)
931{
932 Q_D(KConfig);
933 bool allWritable = d->mBackend->isWritable();
934
935 if (warnUser && !allWritable) {
936 QString errorMsg;
937 errorMsg = d->mBackend->nonWritableErrorMessage();
938
939 // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
940 errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
941 QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
942 if (!cmdToExec.isEmpty()) {
943 QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
944 }
945 }
946
947 d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
948
949 return allWritable;
950}
951
952bool KConfig::hasGroupImpl(const QString &aGroup) const
953{
954 Q_D(const KConfig);
955
956 // No need to look for the actual group entry anymore, or for subgroups:
957 // a group exists if it contains any non-deleted entry.
958
959 return d->hasNonDeletedEntries(aGroup);
960}
961
962bool KConfigPrivate::canWriteEntry(const QString &group, QAnyStringView key, bool isDefault) const
963{
964 if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
965 return isDefault;
966 }
967 return true;
968}
969
970void KConfigPrivate::putData(const QString &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
971{
972 KEntryMap::EntryOptions options = convertToOptions(flags);
973
974 if (bForceGlobal) {
975 options |= KEntryMap::EntryGlobal;
976 }
977 if (expand) {
978 options |= KEntryMap::EntryExpansion;
979 }
980
981 if (value.isNull()) { // deleting entry
982 options |= KEntryMap::EntryDeleted;
983 }
984
985 bool dirtied = entryMap.setEntry(group, key, value, options);
986 if (dirtied && (flags & KConfigBase::Persistent)) {
987 bDirty = true;
988 }
989}
990
991void KConfigPrivate::revertEntry(const QString &group, QAnyStringView key, KConfigBase::WriteConfigFlags flags)
992{
993 KEntryMap::EntryOptions options = convertToOptions(flags);
994
995 bool dirtied = entryMap.revertEntry(group, key, options);
996 if (dirtied) {
997 bDirty = true;
998 }
999}
1000
1001QByteArray KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1002{
1003 return lookupInternalEntry(group, key, flags).mValue;
1004}
1005
1006KEntry KConfigPrivate::lookupInternalEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1007{
1008 if (bReadDefaults) {
1009 flags |= KEntryMap::SearchDefaults;
1010 }
1011 const auto it = entryMap.constFindEntry(group, key, flags);
1012 if (it == entryMap.cend()) {
1013 return {};
1014 }
1015 return it->second;
1016}
1017
1018QString KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags, bool *expand) const
1019{
1020 if (bReadDefaults) {
1021 flags |= KEntryMap::SearchDefaults;
1022 }
1023 return entryMap.getEntry(group, key, QString(), flags, expand);
1024}
1025
1027{
1028 Q_D(const KConfig);
1029 return d->resourceType;
1030}
1031
1032void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1033{
1034 /* nothing */
1035}
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:811
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:829
QString locale() const
locales
Definition kconfig.cpp:835
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:860
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:952
void virtual_hook(int id, void *data) override
Virtual hook, used to add new "virtual" functions while maintaining binary compatibility.
Definition kconfig.cpp:1032
static QString mainConfigName()
Get the name of application config file.
Definition kconfig.cpp:586
bool readDefaults() const
Definition kconfig.cpp:866
bool isImmutable() const override
Definition kconfig.cpp:872
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:878
KConfigGroup groupImpl(const QString &groupName) override
Definition kconfig.cpp:884
QStandardPaths::StandardLocation locationType() const
Returns the standard location enum passed to the constructor.
Definition kconfig.cpp:1026
OpenFlags openFlags() const
Definition kconfig.cpp:566
void addConfigSources(const QStringList &sources)
extra config files
Definition kconfig.cpp:817
bool isConfigWritable(bool warnUser)
Whether the configuration can be written to.
Definition kconfig.cpp:930
@ 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:913
bool setLocale(const QString &aLocale)
Sets the locale to aLocale.
Definition kconfig.cpp:850
QString name(GameStandardAction id)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const char * constData() const const
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 left(qsizetype length) 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 Fri Jul 26 2024 11:52:48 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.