diff options
Diffstat (limited to 'src/corelib/io/qsettings.cpp')
-rw-r--r-- | src/corelib/io/qsettings.cpp | 209 |
1 files changed, 127 insertions, 82 deletions
diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp index cf5a0c66c3..6934ca4404 100644 --- a/src/corelib/io/qsettings.cpp +++ b/src/corelib/io/qsettings.cpp @@ -44,7 +44,7 @@ # include <shlobj.h> #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) #define Q_XDG_PLATFORM #endif @@ -66,6 +66,7 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +using namespace QtMiscUtils; struct QConfFileCustomFormat { @@ -128,12 +129,12 @@ bool QConfFile::isWritable() const { QFileInfo fileInfo(name); -#ifndef QT_NO_TEMPORARYFILE +#if QT_CONFIG(temporaryfile) if (fileInfo.exists()) { #endif QFile file(name); return file.open(QFile::ReadWrite); -#ifndef QT_NO_TEMPORARYFILE +#if QT_CONFIG(temporaryfile) } else { // Create the directories to the file. QDir dir(fileInfo.absolutePath()); @@ -211,9 +212,7 @@ namespace { } QChar *write(QChar *out, QLatin1StringView v) { - for (char ch : v) - *out++ = QLatin1Char(ch); - return out; + return QLatin1::convertToUnicode(out, v); } QChar *write(QChar *out, QStringView v) { @@ -271,7 +270,7 @@ QString QSettingsPrivate::normalizedKey(QAnyStringView key) // see also qsettings_win.cpp and qsettings_mac.cpp -#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_WASM) +#if !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) && !defined(Q_OS_WASM) QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application) { @@ -343,7 +342,7 @@ void QSettingsPrivate::requestUpdate() QStringList QSettingsPrivate::variantListToStringList(const QVariantList &l) { QStringList result; - result.reserve(l.count()); + result.reserve(l.size()); for (auto v : l) result.append(variantToString(v)); return result; @@ -358,7 +357,7 @@ QVariant QSettingsPrivate::stringListToVariantList(const QStringList &l) if (str.startsWith(u'@')) { if (str.size() < 2 || str.at(1) != u'@') { QVariantList variantList; - variantList.reserve(l.count()); + variantList.reserve(l.size()); for (const auto &s : l) variantList.append(stringToVariant(s)); return variantList; @@ -515,8 +514,7 @@ void QSettingsPrivate::iniEscapedKey(const QString &key, QByteArray &result) if (ch == '/') { result += '\\'; - } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') - || ch == '_' || ch == '-' || ch == '.') { + } else if (isAsciiLetterOrNumber(ch) || ch == '_' || ch == '-' || ch == '.') { result += (char)ch; } else if (ch <= 0xFF) { result += '%'; @@ -560,7 +558,7 @@ bool QSettingsPrivate::iniUnescapedKey(QByteArrayView key, QString &result) } int numDigits = 2; - int firstDigitPos = i + 1; + qsizetype firstDigitPos = i + 1; ch = decoded.at(i + 1).unicode(); if (ch == 'U') { @@ -608,10 +606,7 @@ void QSettingsPrivate::iniEscapedString(const QString &str, QByteArray &result) if (ch == ';' || ch == ',' || ch == '=') needsQuotes = true; - if (escapeNextIfDigit - && ((ch >= '0' && ch <= '9') - || (ch >= 'a' && ch <= 'f') - || (ch >= 'A' && ch <= 'F'))) { + if (escapeNextIfDigit && isHexDigit(ch)) { result += "\\x" + QByteArray::number(ch, 16); continue; } @@ -754,10 +749,10 @@ StNormal: goto end; ch = str.at(i); - if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) + if (isHexDigit(ch)) goto StHexEscape; - } else if (ch >= '0' && ch <= '7') { - escapeVal = ch - '0'; + } else if (const int o = fromOct(ch); o != -1) { + escapeVal = o; goto StOctEscape; } else if (ch == '\n' || ch == '\r') { if (i < str.size()) { @@ -819,11 +814,9 @@ StHexEscape: } ch = str.at(i); - if (ch >= 'a') - ch -= 'a' - 'A'; - if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) { + if (const int h = fromHex(ch); h != -1) { escapeVal <<= 4; - escapeVal += QtMiscUtils::fromHex(ch); + escapeVal += h; ++i; goto StHexEscape; } else { @@ -838,9 +831,9 @@ StOctEscape: } ch = str.at(i); - if (ch >= '0' && ch <= '7') { + if (const int o = fromOct(ch); o != -1) { escapeVal <<= 3; - escapeVal += ch - '0'; + escapeVal += o; ++i; goto StOctEscape; } else { @@ -885,16 +878,26 @@ QStringList QSettingsPrivate::splitArgs(const QString &s, qsizetype idx) void QConfFileSettingsPrivate::initFormat() { +#if defined(Q_OS_WASM) + extension = (format == QSettings::NativeFormat || format == QSettings::WebIndexedDBFormat) + ? ".conf"_L1 + : ".ini"_L1; +#else extension = (format == QSettings::NativeFormat) ? ".conf"_L1 : ".ini"_L1; +#endif readFunc = nullptr; writeFunc = nullptr; -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) caseSensitivity = (format == QSettings::NativeFormat) ? Qt::CaseSensitive : IniCaseSensitivity; #else caseSensitivity = IniCaseSensitivity; #endif +#if defined Q_OS_WASM + if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat) { +#else if (format > QSettings::IniFormat) { +#endif const auto locker = qt_scoped_lock(settingsGlobalMutex); const CustomFormatVector *customFormatVector = customFormatVectorFunc(); @@ -912,7 +915,11 @@ void QConfFileSettingsPrivate::initFormat() void QConfFileSettingsPrivate::initAccess() { if (!confFiles.isEmpty()) { +#if defined Q_OS_WASM + if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat) { +#else if (format > QSettings::IniFormat) { +#endif if (!readFunc) setStatus(QSettings::AccessError); } @@ -950,26 +957,43 @@ static inline int pathHashKey(QSettings::Format format, QSettings::Scope scope) } #ifndef Q_OS_WIN -static QString make_user_path() +static constexpr QChar sep = u'/'; + +#if !defined(QSETTINGS_USE_QSTANDARDPATHS) || defined(Q_OS_ANDROID) +static QString make_user_path_without_qstandard_paths() { - static constexpr QChar sep = u'/'; -#ifndef QSETTINGS_USE_QSTANDARDPATHS - // Non XDG platforms (OS X, iOS, Android...) have used this code path erroneously - // for some time now. Moving away from that would require migrating existing settings. QByteArray env = qgetenv("XDG_CONFIG_HOME"); if (env.isEmpty()) { return QDir::homePath() + "/.config/"_L1; } else if (env.startsWith('/')) { return QFile::decodeName(env) + sep; - } else { - return QDir::homePath() + sep + QFile::decodeName(env) + sep; } + + return QDir::homePath() + sep + QFile::decodeName(env) + sep; +} +#endif // !QSETTINGS_USE_QSTANDARDPATHS || Q_OS_ANDROID + +static QString make_user_path() +{ +#ifndef QSETTINGS_USE_QSTANDARDPATHS + // Non XDG platforms (OS X, iOS, Android...) have used this code path erroneously + // for some time now. Moving away from that would require migrating existing settings. + // The migration has already been done for Android. + return make_user_path_without_qstandard_paths(); #else - // When using a proper XDG platform, use QStandardPaths rather than the above hand-written code; - // it makes the use of test mode from unit tests possible. + +#ifdef Q_OS_ANDROID + // If an old settings path exists, use it instead of creating a new one + QString ret = make_user_path_without_qstandard_paths(); + if (QFile(ret).exists()) + return ret; +#endif // Q_OS_ANDROID + + // When using a proper XDG platform or Android platform, use QStandardPaths rather than the + // above hand-written code. It makes the use of test mode from unit tests possible. // Ideally all platforms should use this, but see above for the migration issue. return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + sep; -#endif +#endif // !QSETTINGS_USE_QSTANDARDPATHS } #endif // !Q_OS_WIN @@ -1005,7 +1029,7 @@ static std::unique_lock<QBasicMutex> initDefaultPaths(std::unique_lock<QBasicMut const QString userPath = make_user_path(); pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), Path(userPath, false)); pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), Path(systemPath, false)); -#ifndef Q_OS_MAC +#ifndef Q_OS_DARWIN pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::UserScope), Path(userPath, false)); pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::SystemScope), Path(systemPath, false)); #endif @@ -1081,16 +1105,16 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format, QStringList paths; if (!application.isEmpty()) { paths.reserve(dirs.size() * 2); - for (const auto &dir : qAsConst(dirs)) + for (const auto &dir : std::as_const(dirs)) paths.append(dir + u'/' + appFile); } else { paths.reserve(dirs.size()); } - for (const auto &dir : qAsConst(dirs)) + for (const auto &dir : std::as_const(dirs)) paths.append(dir + u'/' + orgFile); // Note: No check for existence of files is done intentionally. - for (const auto &path : qAsConst(paths)) + for (const auto &path : std::as_const(paths)) confFiles.append(QConfFile::fromName(path, false)); } else #endif // Q_XDG_PLATFORM && !QT_NO_STANDARDPATHS @@ -1100,9 +1124,7 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format, confFiles.append(QConfFile::fromName(systemPath.path + orgFile, false)); } -#ifndef Q_OS_WASM // wasm needs to delay access until after file sync initAccess(); -#endif } QConfFileSettingsPrivate::QConfFileSettingsPrivate(const QString &fileName, @@ -1123,7 +1145,7 @@ QConfFileSettingsPrivate::~QConfFileSettingsPrivate() ConfFileHash *usedHash = usedHashFunc(); ConfFileCache *unusedCache = unusedCacheFunc(); - for (auto conf_file : qAsConst(confFiles)) { + for (auto conf_file : std::as_const(confFiles)) { if (!conf_file->ref.deref()) { if (conf_file->size == 0) { delete conf_file; @@ -1197,7 +1219,7 @@ std::optional<QVariant> QConfFileSettingsPrivate::get(const QString &key) const ParsedSettingsMap::const_iterator j; bool found = false; - for (auto confFile : qAsConst(confFiles)) { + for (auto confFile : std::as_const(confFiles)) { const auto locker = qt_scoped_lock(confFile->mutex); if (!confFile->addedKeys.isEmpty()) { @@ -1226,7 +1248,7 @@ QStringList QConfFileSettingsPrivate::children(const QString &prefix, ChildSpec QSettingsKey thePrefix(prefix, caseSensitivity); qsizetype startPos = prefix.size(); - for (auto confFile : qAsConst(confFiles)) { + for (auto confFile : std::as_const(confFiles)) { const auto locker = qt_scoped_lock(confFile->mutex); if (thePrefix.isEmpty()) @@ -1234,17 +1256,17 @@ QStringList QConfFileSettingsPrivate::children(const QString &prefix, ChildSpec else ensureSectionParsed(confFile, thePrefix); - auto j = const_cast<const ParsedSettingsMap *>( - &confFile->originalKeys)->lowerBound( thePrefix); - while (j != confFile->originalKeys.constEnd() && j.key().startsWith(thePrefix)) { - if (!confFile->removedKeys.contains(j.key())) - processChild(QStringView{j.key().originalCaseKey()}.sliced(startPos), spec, result); - ++j; + const auto &originalKeys = confFile->originalKeys; + auto i = originalKeys.lowerBound(thePrefix); + while (i != originalKeys.end() && i.key().startsWith(thePrefix)) { + if (!confFile->removedKeys.contains(i.key())) + processChild(QStringView{i.key().originalCaseKey()}.sliced(startPos), spec, result); + ++i; } - j = const_cast<const ParsedSettingsMap *>( - &confFile->addedKeys)->lowerBound(thePrefix); - while (j != confFile->addedKeys.constEnd() && j.key().startsWith(thePrefix)) { + const auto &addedKeys = confFile->addedKeys; + auto j = addedKeys.lowerBound(thePrefix); + while (j != addedKeys.end() && j.key().startsWith(thePrefix)) { processChild(QStringView{j.key().originalCaseKey()}.sliced(startPos), spec, result); ++j; } @@ -1277,7 +1299,7 @@ void QConfFileSettingsPrivate::sync() // people probably won't be checking the status a whole lot, so in case of // error we just try to go on and make the best of it - for (auto confFile : qAsConst(confFiles)) { + for (auto confFile : std::as_const(confFiles)) { const auto locker = qt_scoped_lock(confFile->mutex); syncConfFile(confFile); } @@ -1299,7 +1321,11 @@ QString QConfFileSettingsPrivate::fileName() const bool QConfFileSettingsPrivate::isWritable() const { +#if defined(Q_OS_WASM) + if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat && !writeFunc) +#else if (format > QSettings::IniFormat && !writeFunc) +#endif return false; if (confFiles.isEmpty()) @@ -1312,13 +1338,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) { bool readOnly = confFile->addedKeys.isEmpty() && confFile->removedKeys.isEmpty(); + QFileInfo fileInfo(confFile->name); /* We can often optimize the read-only case, if the file on disk hasn't changed. */ if (readOnly && confFile->size > 0) { - QFileInfo fileInfo(confFile->name); - if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified()) + if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified(QTimeZone::UTC)) return; } @@ -1334,8 +1360,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) // On android and if it is a content URL put the lock file in a // writable location to prevent permissions issues and invalid paths. if (confFile->name.startsWith("content:"_L1)) - lockFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - + QFileInfo(lockFileName).fileName(); + lockFileName = make_user_path() + QFileInfo(lockFileName).fileName(); # endif /* Use a lockfile in order to protect us against other QSettings instances @@ -1355,13 +1380,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) We hold the lock. Let's reread the file if it has changed since last time we read it. */ - QFileInfo fileInfo(confFile->name); + fileInfo.refresh(); bool mustReadFile = true; bool createFile = !fileInfo.exists(); if (!readOnly) mustReadFile = (confFile->size != fileInfo.size() - || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified())); + || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified(QTimeZone::UTC))); if (mustReadFile) { confFile->unparsedIniSections.clear(); @@ -1379,7 +1404,14 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) */ if (file.isReadable() && file.size() != 0) { bool ok = false; -#ifdef Q_OS_MAC + +#ifdef Q_OS_WASM + if (format == QSettings::WebIndexedDBFormat) { + QByteArray data = file.readAll(); + ok = readIniFile(data, &confFile->unparsedIniSections); + } else +#endif +#ifdef Q_OS_DARWIN if (format == QSettings::NativeFormat) { QByteArray data = file.readAll(); ok = readPlistFile(data, &confFile->originalKeys); @@ -1407,7 +1439,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) } confFile->size = fileInfo.size(); - confFile->timeStamp = fileInfo.lastModified(); + confFile->timeStamp = fileInfo.lastModified(QTimeZone::UTC); } /* @@ -1435,7 +1467,12 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) return; } -#ifdef Q_OS_MAC +#ifdef Q_OS_WASM + if (format == QSettings::WebIndexedDBFormat) { + ok = writeIniFile(sf, mergedKeys); + } else +#endif +#ifdef Q_OS_DARWIN if (format == QSettings::NativeFormat) { ok = writePlistFile(sf, mergedKeys); } else @@ -1464,9 +1501,9 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) confFile->addedKeys.clear(); confFile->removedKeys.clear(); - QFileInfo fileInfo(confFile->name); + fileInfo.refresh(); confFile->size = fileInfo.size(); - confFile->timeStamp = fileInfo.lastModified(); + confFile->timeStamp = fileInfo.lastModified(QTimeZone::UTC); // If we have created the file, apply the file perms if (createFile) { @@ -1481,6 +1518,8 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) } } +namespace SettingsImpl { + enum { Space = 0x1, Special = 0x2 }; static const char charTraits[256] = @@ -1507,11 +1546,16 @@ static const char charTraits[256] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +} // namespace SettingsImpl + +using SettingsImpl::charTraits; + bool QConfFileSettingsPrivate::readIniLine(QByteArrayView data, qsizetype &dataPos, qsizetype &lineStart, qsizetype &lineLen, qsizetype &equalsPos) { - qsizetype dataLen = data.length(); + using namespace SettingsImpl; + qsizetype dataLen = data.size(); bool inQuotes = false; equalsPos = -1; @@ -1638,7 +1682,7 @@ bool QConfFileSettingsPrivate::readIniFile(QByteArrayView data, ++position; } - Q_ASSERT(lineStart == data.length()); + Q_ASSERT(lineStart == data.size()); FLUSH_CURRENT_SECTION(); return ok; @@ -2084,10 +2128,6 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, as QString. The numeric value can be recovered using \l QString::toInt(), \l QString::toDouble() and related functions. - The \l{tools/settingseditor}{Settings Editor} example lets you - experiment with different settings location and with fallbacks - turned on or off. - \section1 Restoring the State of a GUI Application QSettings is often used to store the state of a GUI @@ -2113,9 +2153,6 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, \codeline \snippet settings/settings.cpp 21 - See the \l{mainwindows/application}{Application} example for a - self-contained example that uses QSettings. - \section1 Accessing Settings from Multiple Threads or Processes Simultaneously QSettings is \l{reentrant}. This means that you can use @@ -2157,8 +2194,8 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, following files are used by default: \list 1 - \li \c{$HOME/.config/MySoft/Star Runner.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.conf}) - \li \c{$HOME/.config/MySoft.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.conf}) + \li \c{$HOME/.config/MySoft/Star Runner.conf} + \li \c{$HOME/.config/MySoft.conf} \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.conf} \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.conf} \endlist @@ -2195,8 +2232,8 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, used on Unix, \macos, and iOS: \list 1 - \li \c{$HOME/.config/MySoft/Star Runner.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.ini}) - \li \c{$HOME/.config/MySoft.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.ini}) + \li \c{$HOME/.config/MySoft/Star Runner.ini} + \li \c{$HOME/.config/MySoft.ini} \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.ini} \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.ini} \endlist @@ -2331,7 +2368,7 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, \endlist - \sa QVariant, QSessionManager, {Settings Editor Example}, {Qt Widgets - Application Example} + \sa QVariant, QSessionManager */ /*! \enum QSettings::Status @@ -2369,6 +2406,16 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, lose the distinction between numeric data and the strings used to encode them, so values written as numbers shall be read back as QString. + \value WebLocalStorageFormat + WASM only: Store the settings in window.localStorage for the current + origin. If cookies are not allowed, this falls back to the INI format. + This provides up to 5MiB storage per origin, but access to it is + synchronous and JSPI is not required. + \value WebIndexedDBFormat + WASM only: Store the settings in an Indexed DB for the current + origin. If cookies are not allowed, this falls back to the INI format. + This requires JSPI, but provides more storage than + WebLocalStorageFormat. \value InvalidFormat Special value returned by registerFormat(). \omitvalue CustomFormat1 @@ -3367,8 +3414,6 @@ QSettings::Format QSettings::defaultFormat() \row \li SystemScope \li \c FOLDERID_ProgramData \row \li{1,2} Unix \li{1,2} NativeFormat, IniFormat \li UserScope \li \c $HOME/.config \row \li SystemScope \li \c /etc/xdg - \row \li{1,2} Qt for Embedded Linux \li{1,2} NativeFormat, IniFormat \li UserScope \li \c $HOME/Settings - \row \li SystemScope \li \c /etc/xdg \row \li{1,2} \macos and iOS \li{1,2} IniFormat \li UserScope \li \c $HOME/.config \row \li SystemScope \li \c /etc/xdg \endtable |