Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltoolingsettings.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <QtCore/qdebug.h>
8#include <QtCore/qdir.h>
9#include <QtCore/qfileinfo.h>
10#include <QtCore/qset.h>
11#include <QtCore/qtextstream.h>
12#if QT_CONFIG(settings)
13#include <QtCore/qsettings.h>
14#endif
15#include <QtCore/qstandardpaths.h>
16
17using namespace Qt::StringLiterals;
18
19void QQmlToolingSettings::addOption(const QString &name, const QVariant &defaultValue)
20{
21 if (defaultValue.isValid()) {
22 m_values[name] = defaultValue;
23 }
24}
25
26QQmlToolingSettings::QQmlToolingSettings(const QString &toolName)
27 : m_searcher(u".%1.ini"_s.arg(toolName), u"%1.ini"_s.arg(toolName))
28{
29}
30
31QQmlToolingSettings::SearchResult QQmlToolingSettings::read(const QString &settingsFilePath)
32{
33#if QT_CONFIG(settings)
34 Q_ASSERT(QFileInfo::exists(settingsFilePath));
35
36 if (m_currentSettingsPath == settingsFilePath)
37 return { SearchResult::ResultType::Found, settingsFilePath };
38
39 QSettings settings(settingsFilePath, QSettings::IniFormat);
40 for (const QString &key : settings.allKeys())
41 m_values[key] = settings.value(key).toString();
42
43 m_currentSettingsPath = settingsFilePath;
44 return { SearchResult::ResultType::Found, settingsFilePath };
45#else
46 Q_UNUSED(settingsFilePath);
47 return SearchResult();
48#endif
49}
50
51bool QQmlToolingSettings::writeDefaults() const
52{
53#if QT_CONFIG(settings)
54 const QString path = QFileInfo(m_searcher.localSettingsFile()).absoluteFilePath();
55
56 QSettings settings(path, QSettings::IniFormat);
57 for (auto it = m_values.constBegin(); it != m_values.constEnd(); ++it) {
58 settings.setValue(it.key(), it.value().isNull() ? QString() : it.value());
59 }
60
61 settings.sync();
62
63 if (settings.status() != QSettings::NoError) {
64 qWarning() << "Failed to write default settings to" << path
65 << "Error:" << settings.status();
66 return false;
67 }
68
69 qInfo() << "Wrote default settings to" << path;
70 return true;
71#else
72 return false;
73#endif
74}
75
76QQmlToolingSettings::SearchResult
77QQmlToolingSettings::Searcher::searchCurrentDirInCache(const QString &dirPath)
78{
79 const auto it = m_seenDirectories.constFind(dirPath);
80 return it != m_seenDirectories.constEnd()
81 ? SearchResult{ SearchResult::ResultType::Found, *it }
82 : SearchResult{ SearchResult::ResultType::NotFound, {} };
83}
84
85static QString findIniFile(const QString &local, const QString &global)
86{
87 // If we reach here, we didn't find the settings file in the current directory or any parent
88 // directories. Now we will try to locate the settings file in the standard locations. First try
89 // to locate settings file with the standard name.
90 const QString iniFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, local);
91 if (!iniFile.isEmpty())
92 return iniFile;
93
94 // If not found, try alternate name format
95 return QStandardPaths::locate(QStandardPaths::GenericConfigLocation, global);
96}
97
98QQmlToolingSettings::SearchResult
99QQmlToolingSettings::Searcher::searchDefaultLocation(const QSet<QString> *visitedDirs)
100{
101 QString iniFile = findIniFile(m_localSettingsFile, m_globalSettingsFile);
102
103 // Update the seen directories cache unconditionally with the current result
104 for (const QString &dir : *visitedDirs)
105 m_seenDirectories[dir] = iniFile;
106
107 const SearchResult::ResultType found = iniFile.isEmpty()
108 ? SearchResult::ResultType::NotFound
109 : SearchResult::ResultType::Found;
110 return SearchResult { found, std::move(iniFile) };
111}
112
113QQmlToolingSettings::SearchResult
114QQmlToolingSettings::Searcher::searchDirectoryHierarchy(
115 QSet<QString> *visitedDirs, const QString &path)
116{
117 const QFileInfo fileInfo(path);
118 QDir dir(fileInfo.isDir() ? path : fileInfo.dir());
119
120 while (dir.exists() && dir.isReadable()) {
121 const QString dirPath = dir.absolutePath();
122
123 // Check if we have already seen this directory
124 // If we have, we can use the cached INI file path
125 // to avoid unnecessary file system operations
126 // This is useful for large directory trees where the settings file might be in a parent
127 // directory
128 if (const SearchResult result = searchCurrentDirInCache(dirPath); result.isValid())
129 return result;
130
131 visitedDirs->insert(dirPath);
132
133 // Check if the settings file exists in the current directory
134 // If it does, read it and update the seen directories cache
135 if (QString ini = dir.absoluteFilePath(m_localSettingsFile); QFileInfo::exists(ini)) {
136 for (const QString &visitedDir : std::as_const(*visitedDirs))
137 m_seenDirectories[visitedDir] = ini;
138
139 return { SearchResult::ResultType::Found, std::move(ini) };
140 }
141
142 if (!dir.cdUp())
143 break;
144 }
145
146 return SearchResult();
147}
148
149QQmlToolingSettings::SearchResult QQmlToolingSettings::Searcher::search(const QString &path)
150{
151 QSet<QString> visitedDirs;
152
153 // Try to find settings in directory hierarchy
154 if (const SearchResult result = searchDirectoryHierarchy(&visitedDirs, path); result.isValid())
155 return result;
156
157 // If we didn't find the settings file in the current directory or any parent directories,
158 // try to locate the settings file in the standard locations
159 if (const SearchResult result = searchDefaultLocation(&visitedDirs); result.isValid())
160 return result;
161
162 return SearchResult();
163}
164
165QQmlToolingSettings::SearchResult QQmlToolingSettings::search(
166 const QString &path, const SearchOptions &options)
167{
168 const auto maybeReport = qScopeGuard([&]() {
169 if (options.verbose)
170 reportConfigForFiles({ path });
171 });
172
173 // If a specific settings file is provided, read it directly
174 if (!options.settingsFileName.isEmpty()) {
175 QFileInfo fileInfo(options.settingsFileName);
176 return fileInfo.exists() ? read(fileInfo.absoluteFilePath()) : SearchResult();
177 }
178
179 if (const SearchResult result = m_searcher.search(path); result.isValid())
180 return read(result.iniFilePath);
181
182 return SearchResult();
183}
184
185QVariant QQmlToolingSettings::value(const QString &name) const
186{
187 return m_values.value(name);
188}
189
190bool QQmlToolingSettings::isSet(const QString &name) const
191{
192 if (!m_values.contains(name))
193 return false;
194
195 QVariant variant = m_values[name];
196
197 // Unset is encoded as an empty string
198 return !(variant.canConvert(QMetaType(QMetaType::QString)) && variant.toString().isEmpty());
199}
200
201bool QQmlToolingSettings::reportConfigForFiles(const QStringList &files)
202{
203 constexpr int maxAllowedFileLength = 255;
204 constexpr int minAllowedFileLength = 40;
205 bool headerPrinted = false;
206 auto lengthForFile = [maxAllowedFileLength](const QString &file) {
207 return std::min(int(file.length()), maxAllowedFileLength);
208 };
209
210 int maxFileLength =
211 std::accumulate(files.begin(), files.end(), 0, [&](int acc, const QString &file) {
212 return std::max(acc, lengthForFile(file));
213 });
214
215 if (maxFileLength < minAllowedFileLength)
216 maxFileLength = minAllowedFileLength;
217
218 for (const auto &file : files) {
219 if (file.isEmpty()) {
220 qWarning().noquote() << "Error: Could not find file" << file;
221 return false;
222 }
223
224 QString displayFile = file;
225 if (displayFile.length() > maxAllowedFileLength) {
226 displayFile = u"..." + displayFile.right(maxAllowedFileLength - 3);
227 }
228
229 const auto result = search(file);
230
231 if (!headerPrinted) {
232 QString header =
233 QStringLiteral("%1 | %2").arg("File", -maxFileLength).arg("Settings File");
234 qWarning().noquote() << header;
235 qWarning().noquote() << QString(header.length(), u'-');
236 headerPrinted = true;
237 }
238 QString line =
239 QStringLiteral("%1 | %2").arg(displayFile, -maxFileLength).arg(result.iniFilePath);
240 qWarning().noquote() << line;
241 }
242
243 return true;
244}
static QString findIniFile(const QString &local, const QString &global)