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 (QFile::exists(oldPath) && QFile::exists(newPath)) {
85 qCDebug(KCONFIG_CORE_LOG) << "Old staterc and new staterc found. Not migrating! Using new path" << newPath;
86 return newPath;
87 }
88
89 if (QFile::exists(newPath)) { // already migrated
90 return newPath;
91 }
92
93 // Migrate legacy files.
94 // On failure we return the new path because we want higher level technology to surface the new path for read/write errors.
95 if (!QDir().exists(xdgStateHome)) {
96 if (!QDir().mkpath(xdgStateHome)) {
97 qCWarning(KCONFIG_CORE_LOG) << "Failed to make state directory" << xdgStateHome;
98 return newPath;
99 }
100 }
101 qCInfo(KCONFIG_CORE_LOG) << "Migrating old staterc" << oldPath << "->" << newPath;
102 if (!QFile::rename(oldPath, newPath)) {
103 qCWarning(KCONFIG_CORE_LOG) << "Failed to migrate" << oldPath << "->" << newPath;
104 return newPath;
105 }
106
107 return newPath;
108#else
109 return fileName;
110#endif
111}
112} // namespace
113
114void _k_globalMainConfigSync()
115{
116 if (KSharedConfigPtr mainConfig = globalSharedConfig()->mainConfig) {
117 mainConfig->sync();
118 }
119}
120
122{
123 QString fileName(_fileName);
124 GlobalSharedConfig *global = globalSharedConfig();
125 if (fileName.isEmpty() && !flags.testFlag(KConfig::SimpleConfig)) {
126 // Determine the config file name that KConfig will make up (see KConfigPrivate::changeFileName)
127 fileName = KConfig::mainConfigName();
128 }
129
130 if (!global->wasTestModeEnabled && QStandardPaths::isTestModeEnabled()) {
131 global->wasTestModeEnabled = true;
132 global->configList.clear();
133 global->mainConfig = nullptr;
134 }
135
136 for (auto *cfg : std::as_const(global->configList)) {
137 if (cfg->name() == fileName && cfg->d_ptr->openFlags == flags && cfg->locationType() == resType) {
138 return KSharedConfigPtr(cfg);
139 }
140 }
141
142 KSharedConfigPtr ptr(new KSharedConfig(fileName, flags, resType));
143
144 if (_fileName.isEmpty() && flags == FullConfig && resType == QStandardPaths::GenericConfigLocation) {
145 global->mainConfig = ptr;
146
147 const bool isMainThread = !qApp || QThread::currentThread() == qApp->thread();
148 static bool userWarned = false;
149 if (isMainThread && !userWarned) {
150 userWarned = true;
151 const bool isReadOnly = qEnvironmentVariableIsEmpty("KDE_HOME_READONLY");
152 if (isReadOnly && QCoreApplication::applicationName() != QLatin1String("kdialog")) {
153 if (ptr->group(QStringLiteral("General")).readEntry(QStringLiteral("warn_unwritable_config"), true)) {
154 ptr->isConfigWritable(true);
155 }
156 }
157 }
158 }
159
160 return ptr;
161}
162
164{
165 QString fileName(_fileName);
166
167 if (fileName.isEmpty()) {
168 fileName = QCoreApplication::applicationName() + QLatin1String("staterc");
169 }
170
171 return openConfig(migrateStateRc(fileName),
173 QStandardPaths::AppDataLocation /* only used on !XDG platform, on XDG we resolve an absolute path (unless there are problems) */);
174}
175
176KSharedConfig::KSharedConfig(const QString &fileName, OpenFlags flags, QStandardPaths::StandardLocation resType)
177 : KConfig(fileName, flags, resType)
178{
179 globalSharedConfig()->configList.append(this);
180}
181
182KSharedConfig::~KSharedConfig()
183{
184 if (s_storage.hasLocalData()) {
185 globalSharedConfig()->configList.removeAll(this);
186 }
187}
188
189KConfigGroup KSharedConfig::groupImpl(const QString &groupName)
190{
191 KSharedConfigPtr ptr(this);
192 return KConfigGroup(ptr, groupName);
193}
194
195const KConfigGroup KSharedConfig::groupImpl(const QString &groupName) const
196{
197 const KSharedConfigPtr ptr(const_cast<KSharedConfig *>(this));
198 return KConfigGroup(ptr, groupName);
199}
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 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.