KConfig

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

KDE's Doxygen guidelines are available online.