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 
380 QStringList KConfig::keyList(const QString &aGroup) const
381 {
382  Q_D(const KConfig);
383  const QByteArray theGroup(aGroup.isEmpty() ? "<default>" : aGroup.toUtf8());
384  return d->keyListImpl(theGroup);
385 }
386 
388 {
389  Q_D(const KConfig);
390  QMap<QString, QString> theMap;
391  const QByteArray theGroup(aGroup.isEmpty() ? "<default>" : aGroup.toUtf8());
392 
393  const auto theEnd = d->entryMap.constEnd();
394  auto it = d->entryMap.constFindEntry(theGroup, {}, {});
395  if (it != theEnd) {
396  ++it; // advance past the special group entry marker
397 
398  for (; it != theEnd && it.key().mGroup == theGroup; ++it) {
399  // leave the default values and deleted entries out
400  if (!it->bDeleted && !it.key().bDefault) {
401  const QString key = QString::fromUtf8(it.key().mKey.constData());
402  // the localized entry should come first, so don't overwrite it
403  // with the non-localized entry
404  if (!theMap.contains(key)) {
405  if (it->bExpand) {
406  theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData())));
407  } else {
408  theMap.insert(key, QString::fromUtf8(it->mValue.constData()));
409  }
410  }
411  }
412  }
413  }
414 
415  return theMap;
416 }
417 
419 {
420  Q_D(KConfig);
421 
422  if (isImmutable() || name().isEmpty()) {
423  // can't write to an immutable or anonymous file.
424  return false;
425  }
426 
427  QHash<QString, QByteArrayList> notifyGroupsLocal;
428  QHash<QString, QByteArrayList> notifyGroupsGlobal;
429 
430  if (d->bDirty && d->mBackend) {
431  const QByteArray utf8Locale(locale().toUtf8());
432 
433  // Create the containing dir, maybe it wasn't there
434  d->mBackend->createEnclosing();
435 
436  // lock the local file
437  if (d->configState == ReadWrite && !d->lockLocal()) {
438  qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
439  return false;
440  }
441 
442  // Rewrite global/local config only if there is a dirty entry in it.
443  bool writeGlobals = false;
444  bool writeLocals = false;
445 
446  for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) {
447  auto e = it.value();
448  if (e.bDirty) {
449  if (e.bGlobal) {
450  writeGlobals = true;
451  if (e.bNotify) {
452  notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
453  }
454  } else {
455  writeLocals = true;
456  if (e.bNotify) {
457  notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
458  }
459  }
460  }
461  }
462 
463  d->bDirty = false; // will revert to true if a config write fails
464 
465  if (d->wantGlobals() && writeGlobals) {
466  QExplicitlySharedDataPointer<KConfigBackend> tmp = KConfigBackend::create(*sGlobalFileName);
467  if (d->configState == ReadWrite && !tmp->lock()) {
468  qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file";
469 
470  // unlock the local config if we're returning early
471  if (d->mBackend->isLocked()) {
472  d->mBackend->unlock();
473  }
474 
475  d->bDirty = true;
476  return false;
477  }
478  if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) {
479  d->bDirty = true;
480  }
481  if (tmp->isLocked()) {
482  tmp->unlock();
483  }
484  }
485 
486  if (writeLocals) {
487  if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) {
488  d->bDirty = true;
489  }
490  }
491  if (d->mBackend->isLocked()) {
492  d->mBackend->unlock();
493  }
494  }
495 
496  if (!notifyGroupsLocal.isEmpty()) {
497  d->notifyClients(notifyGroupsLocal, QLatin1Char('/') + name());
498  }
499  if (!notifyGroupsGlobal.isEmpty()) {
500  d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
501  }
502 
503  return !d->bDirty;
504 }
505 
506 void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
507 {
508 #if KCONFIG_USE_DBUS
509  qDBusRegisterMetaType<QByteArrayList>();
510 
511  qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
512 
513  QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
514  message.setArguments({QVariant::fromValue(changes)});
516 #else
517  Q_UNUSED(changes)
518  Q_UNUSED(path)
519 #endif
520 }
521 
523 {
524  Q_D(KConfig);
525  d->bDirty = false;
526 
527  // clear any dirty flags that entries might have set
528  const KEntryMapIterator theEnd = d->entryMap.end();
529  for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) {
530  it->bDirty = false;
531  it->bNotify = false;
532  }
533 }
534 
535 bool KConfig::isDirty() const
536 {
537  Q_D(const KConfig);
538  return d->bDirty;
539 }
540 
541 void KConfig::checkUpdate(const QString &id, const QString &updateFile)
542 {
543  const KConfigGroup cg(this, "$Version");
544  const QString cfg_id = updateFile + QLatin1Char(':') + id;
545  const QStringList ids = cg.readEntry("update_info", QStringList());
546  if (!ids.contains(cfg_id)) {
547  QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList{QStringLiteral("--check"), updateFile});
549  }
550 }
551 
552 KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
553 {
554  Q_D(const KConfig);
555  if (!config) {
556  config = new KConfig(QString(), SimpleConfig, d->resourceType);
557  }
558  config->d_func()->changeFileName(file);
559  config->d_func()->entryMap = d->entryMap;
560  config->d_func()->bFileImmutable = false;
561 
562  const KEntryMapIterator theEnd = config->d_func()->entryMap.end();
563  for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) {
564  it->bDirty = true;
565  }
566  config->d_ptr->bDirty = true;
567 
568  return config;
569 }
570 
572 {
573  Q_D(const KConfig);
574  return d->fileName;
575 }
576 
578 {
579  Q_D(const KConfig);
580  return d->openFlags;
581 }
582 
583 struct KConfigStaticData {
584  QString globalMainConfigName;
585  // Keep a copy so we can use it in global dtors, after qApp is gone
586  QStringList appArgs;
587 };
588 Q_GLOBAL_STATIC(KConfigStaticData, globalData)
589 
590 void KConfig::setMainConfigName(const QString &str)
591 {
592  globalData()->globalMainConfigName = str;
593 }
594 
596 {
597  KConfigStaticData *data = globalData();
598  if (data->appArgs.isEmpty()) {
599  data->appArgs = QCoreApplication::arguments();
600  }
601 
602  // --config on the command line overrides everything else
603  const QStringList args = data->appArgs;
604  for (int i = 1; i < args.count(); ++i) {
605  if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
606  return args.at(i + 1);
607  }
608  }
609  const QString globalName = data->globalMainConfigName;
610  if (!globalName.isEmpty()) {
611  return globalName;
612  }
613 
615  return appName + QLatin1String("rc");
616 }
617 
618 void KConfigPrivate::changeFileName(const QString &name)
619 {
620  fileName = name;
621 
622  QString file;
623  if (name.isEmpty()) {
624  if (wantDefaults()) { // accessing default app-specific config "appnamerc"
625  fileName = KConfig::mainConfigName();
626  file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
627  } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
629  fileName = QStringLiteral("kdeglobals");
630  file = *sGlobalFileName;
631  } else {
632  // anonymous config
633  openFlags = KConfig::SimpleConfig;
634  return;
635  }
636  } else if (QDir::isAbsolutePath(fileName)) {
637  fileName = QFileInfo(fileName).canonicalFilePath();
638  if (fileName.isEmpty()) { // file doesn't exist (yet)
639  fileName = name;
640  }
641  file = fileName;
642  } else {
643  file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
644  }
645 
646  Q_ASSERT(!file.isEmpty());
647 
648  bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0);
649 
650  if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend
651  mBackend = KConfigBackend::create(file);
652  } else {
653  mBackend->setFilePath(file);
654  }
655 
656  configState = mBackend->accessMode();
657 }
658 
660 {
661  Q_D(KConfig);
662  if (d->fileName.isEmpty()) {
663  return;
664  }
665 
666  // Don't lose pending changes
667  if (!d->isReadOnly() && d->bDirty) {
668  sync();
669  }
670 
671  d->entryMap.clear();
672 
673  d->bFileImmutable = false;
674 
675  {
676  QMutexLocker locker(&s_globalFilesMutex);
677  s_globalFiles()->clear();
678  }
679 
680  // Parse all desired files from the least to the most specific.
681  if (d->wantGlobals()) {
682  d->parseGlobalFiles();
683  }
684 
685  d->parseConfigFiles();
686 }
687 
688 QStringList KConfigPrivate::getGlobalFiles() const
689 {
690  QMutexLocker locker(&s_globalFilesMutex);
691  if (s_globalFiles()->isEmpty()) {
692  const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
693  const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
694 
695  const bool useEtcKderc = !etc_kderc.isEmpty();
696  s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
697 
698  for (const QString &dir1 : paths1) {
699  s_globalFiles()->push_front(dir1);
700  }
701  for (const QString &dir2 : paths2) {
702  s_globalFiles()->push_front(dir2);
703  }
704 
705  if (useEtcKderc) {
706  s_globalFiles()->push_front(etc_kderc);
707  }
708  }
709 
710  return *s_globalFiles();
711 }
712 
713 void KConfigPrivate::parseGlobalFiles()
714 {
715  const QStringList globalFiles = getGlobalFiles();
716  // qDebug() << "parsing global files" << globalFiles;
717 
718  Q_ASSERT(entryMap.isEmpty());
719  const ParseCacheKey key = {globalFiles, locale};
720  auto data = sGlobalParse->localData().object(key);
721  QDateTime newest;
722  for (const auto &file : globalFiles) {
723  const auto fileDate = QFileInfo(file).lastModified();
724  if (fileDate > newest) {
725  newest = fileDate;
726  }
727  }
728  if (data) {
729  if (data->parseTime < newest) {
730  data = nullptr;
731  } else {
732  entryMap = data->entries;
733  return;
734  }
735  }
736 
737  const QByteArray utf8Locale = locale.toUtf8();
738  for (const QString &file : globalFiles) {
739  KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions;
740 
741  if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
742  parseOpts |= KConfigBackend::ParseDefaults;
743  }
744 
745  QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(file);
746  if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) {
747  break;
748  }
749  }
750  sGlobalParse->localData().insert(key, new ParseCacheValue({entryMap, newest}));
751 }
752 
753 void KConfigPrivate::parseConfigFiles()
754 {
755  // can only read the file if there is a backend and a file name
756  if (mBackend && !fileName.isEmpty()) {
757  bFileImmutable = false;
758 
759  QList<QString> files;
760  if (wantDefaults()) {
761  if (bSuppressGlobal) {
762  files = getGlobalFiles();
763  } else {
764  if (QDir::isAbsolutePath(fileName)) {
765  const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
766  if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
767  files << canonicalFile;
768  }
769  } else {
770  const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
771  for (const QString &f : localFilesPath) {
772  files.prepend(QFileInfo(f).canonicalFilePath());
773  }
774 
775  // allow fallback to config files bundled in resources
776  const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
777  if (QFile::exists(resourceFile)) {
778  files.prepend(resourceFile);
779  }
780  }
781  }
782  } else {
783  files << mBackend->filePath();
784  }
785  if (!isSimple()) {
786  files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
787  }
788 
789  // qDebug() << "parsing local files" << files;
790 
791  const QByteArray utf8Locale = locale.toUtf8();
792  for (const QString &file : std::as_const(files)) {
793  if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) {
794  switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) {
795  case KConfigBackend::ParseOk:
796  break;
797  case KConfigBackend::ParseImmutable:
798  bFileImmutable = true;
799  break;
800  case KConfigBackend::ParseOpenError:
801  configState = KConfigBase::NoAccess;
802  break;
803  }
804  } else {
805  QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(file);
806  constexpr auto parseOpts = KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions;
807  bFileImmutable = backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable;
808  }
809 
810  if (bFileImmutable) {
811  break;
812  }
813  }
814  }
815 }
816 
818 {
819  Q_D(const KConfig);
820  return d->configState;
821 }
822 
824 {
825  Q_D(KConfig);
826  for (const QString &file : files) {
827  d->extraFiles.push(file);
828  }
829 
830  if (!files.isEmpty()) {
832  }
833 }
834 
836 {
837  Q_D(const KConfig);
838  return d->extraFiles.toList();
839 }
840 
842 {
843  Q_D(const KConfig);
844  return d->locale;
845 }
846 
847 bool KConfigPrivate::setLocale(const QString &aLocale)
848 {
849  if (aLocale != locale) {
850  locale = aLocale;
851  return true;
852  }
853  return false;
854 }
855 
856 bool KConfig::setLocale(const QString &locale)
857 {
858  Q_D(KConfig);
859  if (d->setLocale(locale)) {
861  return true;
862  }
863  return false;
864 }
865 
867 {
868  Q_D(KConfig);
869  d->bReadDefaults = b;
870 }
871 
873 {
874  Q_D(const KConfig);
875  return d->bReadDefaults;
876 }
877 
879 {
880  Q_D(const KConfig);
881  return d->bFileImmutable;
882 }
883 
884 bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const
885 {
886  Q_D(const KConfig);
887  return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
888 }
889 
890 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0)
892 {
893  Q_D(KConfig);
894  d->bForceGlobal = b;
895 }
896 #endif
897 
898 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0)
900 {
901  Q_D(const KConfig);
902  return d->bForceGlobal;
903 }
904 #endif
905 
907 {
908  return KConfigGroup(this, group.constData());
909 }
910 
911 const KConfigGroup KConfig::groupImpl(const QByteArray &group) const
912 {
913  return KConfigGroup(this, group.constData());
914 }
915 
916 KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
917 {
918  KEntryMap::EntryOptions options = {};
919 
920  if (flags & KConfig::Persistent) {
921  options |= KEntryMap::EntryDirty;
922  }
923  if (flags & KConfig::Global) {
924  options |= KEntryMap::EntryGlobal;
925  }
926  if (flags & KConfig::Localized) {
927  options |= KEntryMap::EntryLocalized;
928  }
929  if (flags.testFlag(KConfig::Notify)) {
930  options |= KEntryMap::EntryNotify;
931  }
932  return options;
933 }
934 
936 {
937  Q_D(KConfig);
938  KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
939 
940  const QSet<QByteArray> groups = d->allSubGroups(aGroup);
941  for (const QByteArray &group : groups) {
942  const QStringList keys = d->keyListImpl(group);
943  for (const QString &_key : keys) {
944  const QByteArray &key = _key.toUtf8();
945  if (d->canWriteEntry(group, key.constData())) {
946  d->entryMap.setEntry(group, key, QByteArray(), options);
947  d->bDirty = true;
948  }
949  }
950  }
951 }
952 
953 bool KConfig::isConfigWritable(bool warnUser)
954 {
955  Q_D(KConfig);
956  bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false);
957 
958  if (warnUser && !allWritable) {
959  QString errorMsg;
960  if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately
961  errorMsg = d->mBackend->nonWritableErrorMessage();
962  }
963 
964  // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
965  errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
966  QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
967  if (!cmdToExec.isEmpty()) {
968  QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
969  }
970  }
971 
972  d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
973 
974  return allWritable;
975 }
976 
977 bool KConfig::hasGroupImpl(const QByteArray &aGroup) const
978 {
979  Q_D(const KConfig);
980 
981  // No need to look for the actual group entry anymore, or for subgroups:
982  // a group exists if it contains any non-deleted entry.
983 
984  return d->hasNonDeletedEntries(aGroup);
985 }
986 
987 bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const
988 {
989  if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
990  return isDefault;
991  }
992  return true;
993 }
994 
995 void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
996 {
997  KEntryMap::EntryOptions options = convertToOptions(flags);
998 
999  if (bForceGlobal) {
1000  options |= KEntryMap::EntryGlobal;
1001  }
1002  if (expand) {
1003  options |= KEntryMap::EntryExpansion;
1004  }
1005 
1006  if (value.isNull()) { // deleting entry
1007  options |= KEntryMap::EntryDeleted;
1008  }
1009 
1010  bool dirtied = entryMap.setEntry(group, key, value, options);
1011  if (dirtied && (flags & KConfigBase::Persistent)) {
1012  bDirty = true;
1013  }
1014 }
1015 
1016 void KConfigPrivate::revertEntry(const QByteArray &group, const char *key, KConfigBase::WriteConfigFlags flags)
1017 {
1018  KEntryMap::EntryOptions options = convertToOptions(flags);
1019 
1020  bool dirtied = entryMap.revertEntry(group, key, options);
1021  if (dirtied) {
1022  bDirty = true;
1023  }
1024 }
1025 
1026 QByteArray KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const
1027 {
1028  return lookupInternalEntry(group, key, flags).mValue;
1029 }
1030 
1031 KEntry KConfigPrivate::lookupInternalEntry(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const
1032 {
1033  if (bReadDefaults) {
1034  flags |= KEntryMap::SearchDefaults;
1035  }
1036  const auto it = entryMap.constFindEntry(group, key, flags);
1037  if (it == entryMap.constEnd()) {
1038  return {};
1039  }
1040  return it.value();
1041 }
1042 
1043 QString KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const
1044 {
1045  if (bReadDefaults) {
1046  flags |= KEntryMap::SearchDefaults;
1047  }
1048  return entryMap.getEntry(group, key, QString(), flags, expand);
1049 }
1050 
1052 {
1053  Q_D(const KConfig);
1054  return d->resourceType;
1055 }
1056 
1057 void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1058 {
1059  /* nothing */
1060 }
bool isNull() const const
AccessMode accessMode() const override
Definition: kconfig.cpp:817
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:1057
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:899
CaseSensitivity
QVariant fromValue(const T &value)
void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags=Normal) override
Definition: kconfig.cpp:935
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:823
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:571
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:552
The central class of the KDE configuration data system.
Definition: kconfig.h:56
KConfigGroup groupImpl(const QByteArray &b) override
Definition: kconfig.cpp:906
void markAsClean() override
Definition: kconfig.cpp:522
@ 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:835
QString writableLocation(QStandardPaths::StandardLocation type)
void reparseConfiguration()
Updates the state of this object to match the persistent storage.
Definition: kconfig.cpp:659
QString homePath()
bool readDefaults() const
Definition: kconfig.cpp:872
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
bool sync() override
Definition: kconfig.cpp:418
bool setLocale(const QString &aLocale)
Sets the locale to aLocale.
Definition: kconfig.cpp:856
bool exists() const const
bool isDirty() const
Returns true if sync has any changes to write out.
Definition: kconfig.cpp:535
@ 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:866
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:387
QStringList groupList() const override
Definition: kconfig.cpp:305
bool isEmpty() const const
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:953
QStandardPaths::StandardLocation locationType() const
Returns the standard location enum passed to the constructor.
Definition: kconfig.cpp:1051
bool isEmpty() const const
void setForceGlobal(bool force)
global
Definition: kconfig.cpp:891
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 char * name(StandardAction id)
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:878
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:977
int compare(const QString &other, Qt::CaseSensitivity cs) const const
static QString mainConfigName()
Get the name of application config file.
Definition: kconfig.cpp:595
bool isGroupImmutableImpl(const QByteArray &aGroup) const override
Definition: kconfig.cpp:884
QString locale() const
locales
Definition: kconfig.cpp:841
QDateTime lastModified() const const
OpenFlags openFlags() const
Definition: kconfig.cpp:577
void checkUpdate(const QString &id, const QString &updateFile)
Ensures that the configuration file contains a certain update.
Definition: kconfig.cpp:541
@ 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-2022 The KDE developers.
Generated on Fri May 27 2022 04:09:35 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.