KConfig

ksharedconfig.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
4 SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
5 SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "ksharedconfig.h"
11#include "kconfig_core_log_settings.h"
12#include "kconfig_p.h"
13#include "kconfiggroup.h"
14#include <QCoreApplication>
15#include <QThread>
16#include <QThreadStorage>
17
18using namespace Qt::StringLiterals;
19
20void _k_globalMainConfigSync();
21
23
24class GlobalSharedConfig
25{
26public:
27 GlobalSharedConfig()
28 : wasTestModeEnabled(false)
29 {
30 if (!qApp || QThread::currentThread() == qApp->thread()) {
31 // We want to force the sync() before the QCoreApplication
32 // instance is gone. Otherwise we trigger a QLockFile::lock()
33 // after QCoreApplication is gone, calling qAppName() for a non
34 // existent app...
35 qAddPostRoutine(&_k_globalMainConfigSync);
36 }
37 // In other threads, QThreadStorage takes care of deleting the GlobalSharedConfigList when
38 // the thread exits.
39 }
40
41 SharedConfigList configList;
42 // in addition to the list, we need to hold the main config,
43 // so that it's not created and destroyed all the time.
44 KSharedConfigPtr mainConfig;
45 bool wasTestModeEnabled;
46};
47
49template<typename T>
50T *perThreadGlobalStatic()
51{
52 if (!s_storage.hasLocalData()) {
53 s_storage.setLocalData(new T);
54 }
55 return s_storage.localData();
56}
57
58// Q_GLOBAL_STATIC(GlobalSharedConfigList, globalSharedConfigList), but per thread:
59static GlobalSharedConfig *globalSharedConfig()
60{
61 return perThreadGlobalStatic<GlobalSharedConfig>();
62}
63
64namespace
65{
66[[nodiscard]] QString migrateStateRc(const QString &fileName)
67{
68 // Migrate from an old legacy path to new spec compliant ~/.local/state/
69 // https://gitlab.freedesktop.org/xdg/xdg-specs/-/blob/master/basedir/basedir-spec.xml
70 // TODO KF7: refactor openStateConfig so it always opens from XDG_STATE_HOME instead of the legacy when on an XDG platform
71
72#if !defined(Q_OS_WINDOWS) && !defined(Q_OS_ANDROID) && !defined(Q_OS_MACOS)
73 if (QFileInfo(fileName).isAbsolute()) {
74 return fileName;
75 }
76
77 static auto xdgStateHome = qEnvironmentVariable("XDG_STATE_HOME", QDir::homePath() + "/.local/state"_L1);
78 if (fileName.startsWith(xdgStateHome)) [[unlikely]] {
79 return fileName;
80 }
81
82 QString newPath = xdgStateHome + "/"_L1 + fileName; // intentionally not const so it can be move returned
84 if (oldPath.isEmpty()) { // nothing to migrate
85 return newPath;
86 }
87 if (QFile::exists(oldPath) && QFile::exists(newPath)) {
88 qCDebug(KCONFIG_CORE_LOG) << "Old staterc and new staterc found. Not migrating! Using new path" << newPath;
89 return newPath;
90 }
91
92 if (QFile::exists(newPath)) { // already migrated
93 return newPath;
94 }
95
96 // Migrate legacy files.
97 // On failure we return the new path because we want higher level technology to surface the new path for read/write errors.
98 if (!QDir().exists(xdgStateHome)) {
99 if (!QDir().mkpath(xdgStateHome)) {
100 qCWarning(KCONFIG_CORE_LOG) << "Failed to make state directory" << xdgStateHome;
101 return newPath;
102 }
103 }
104 qCInfo(KCONFIG_CORE_LOG) << "Migrating old staterc" << oldPath << "->" << newPath;
105 if (!QFile::rename(oldPath, newPath)) {
106 qCWarning(KCONFIG_CORE_LOG) << "Failed to migrate" << oldPath << "->" << newPath;
107 return newPath;
108 }
109
110 return newPath;
111#else
112 return fileName;
113#endif
114}
115} // namespace
116
117void _k_globalMainConfigSync()
118{
119 if (KSharedConfigPtr mainConfig = globalSharedConfig()->mainConfig) {
120 mainConfig->sync();
121 }
122}
123
125{
126 QString fileName(_fileName);
127 GlobalSharedConfig *global = globalSharedConfig();
128 if (fileName.isEmpty() && !flags.testFlag(KConfig::SimpleConfig)) {
129 // Determine the config file name that KConfig will make up (see KConfigPrivate::changeFileName)
130 fileName = KConfig::mainConfigName();
131 }
132
133 if (!global->wasTestModeEnabled && QStandardPaths::isTestModeEnabled()) {
134 global->wasTestModeEnabled = true;
135 global->configList.clear();
136 global->mainConfig = nullptr;
137 }
138
139 for (auto *cfg : std::as_const(global->configList)) {
140 if (cfg->name() == fileName && cfg->d_ptr->openFlags == flags && cfg->locationType() == resType) {
141 return KSharedConfigPtr(cfg);
142 }
143 }
144
145 KSharedConfigPtr ptr(new KSharedConfig(fileName, flags, resType));
146
147 if (_fileName.isEmpty() && flags == FullConfig && resType == QStandardPaths::GenericConfigLocation) {
148 global->mainConfig = ptr;
149
150 const bool isMainThread = !qApp || QThread::currentThread() == qApp->thread();
151 static bool userWarned = false;
152 if (isMainThread && !userWarned) {
153 userWarned = true;
154 const bool isReadOnly = qEnvironmentVariableIsEmpty("KDE_HOME_READONLY");
155 if (isReadOnly && QCoreApplication::applicationName() != QLatin1String("kdialog")) {
156 if (ptr->group(QStringLiteral("General")).readEntry(QStringLiteral("warn_unwritable_config"), true)) {
157 ptr->isConfigWritable(true);
158 }
159 }
160 }
161 }
162
163 return ptr;
164}
165
167{
168 QString fileName(_fileName);
169
170 if (fileName.isEmpty()) {
171 fileName = QCoreApplication::applicationName() + QLatin1String("staterc");
172 }
173
174 return openConfig(migrateStateRc(fileName),
176 QStandardPaths::AppDataLocation /* only used on !XDG platform, on XDG we resolve an absolute path (unless there are problems) */);
177}
178
179KSharedConfig::KSharedConfig(const QString &fileName, OpenFlags flags, QStandardPaths::StandardLocation resType)
180 : KConfig(fileName, flags, resType)
181{
182 globalSharedConfig()->configList.append(this);
183}
184
185KSharedConfig::~KSharedConfig()
186{
187 if (s_storage.hasLocalData()) {
188 globalSharedConfig()->configList.removeAll(this);
189 }
190}
191
192KConfigGroup KSharedConfig::groupImpl(const QString &groupName)
193{
194 KSharedConfigPtr ptr(this);
195 return KConfigGroup(ptr, groupName);
196}
197
198const KConfigGroup KSharedConfig::groupImpl(const QString &groupName) const
199{
200 const KSharedConfigPtr ptr(const_cast<KSharedConfig *>(this));
201 return KConfigGroup(ptr, groupName);
202}
A class for one specific group in a KConfig object.
The central class of the KDE configuration data system.
Definition kconfig.h:56
static QString mainConfigName()
Get the name of application config file.
Definition kconfig.cpp:586
@ SimpleConfig
Just a single config file.
Definition kconfig.h:85
@ FullConfig
Fully-fledged config, including globals and cascading to system settings.
Definition kconfig.h:88
KConfig variant using shared memory.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Creates a KSharedConfig object to manipulate a configuration file.
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
Creates a KSharedConfig object to manipulate a configuration file suitable for storing state informat...
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
QString homePath()
bool exists() const const
bool rename(const QString &newName)
bool testFlag(Enum flag) const const
void append(QList< T > &&value)
void clear()
qsizetype removeAll(const AT &t)
QThread * thread() const const
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QThread * currentThread()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:17:22 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.