summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qsettings_wasm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qsettings_wasm.cpp')
-rw-r--r--src/corelib/io/qsettings_wasm.cpp453
1 files changed, 227 insertions, 226 deletions
diff --git a/src/corelib/io/qsettings_wasm.cpp b/src/corelib/io/qsettings_wasm.cpp
index 15ab688abe..7d80ff82d3 100644
--- a/src/corelib/io/qsettings_wasm.cpp
+++ b/src/corelib/io/qsettings_wasm.cpp
@@ -10,19 +10,33 @@
#include <QFile>
#endif // QT_NO_QOBJECT
#include <QDebug>
+#include <QtCore/private/qstdweb_p.h>
#include <QFileInfo>
#include <QDir>
#include <QList>
+#include <QSet>
#include <emscripten.h>
-#include <emscripten/val.h>
+# include <emscripten/proxying.h>
+# include <emscripten/threading.h>
+# include <emscripten/val.h>
QT_BEGIN_NAMESPACE
using emscripten::val;
using namespace Qt::StringLiterals;
+namespace {
+QStringView keyNameFromPrefixedStorageName(QStringView prefix, QStringView prefixedStorageName)
+{
+ // Return the key slice after m_keyPrefix, or an empty string view if no match
+ if (!prefixedStorageName.startsWith(prefix))
+ return QStringView();
+ return prefixedStorageName.sliced(prefix.length());
+}
+} // namespace
+
//
// Native settings implementation for WebAssembly using window.localStorage
// as the storage backend. localStorage is a key-value store with a synchronous
@@ -33,6 +47,7 @@ class QWasmLocalStorageSettingsPrivate final : public QSettingsPrivate
public:
QWasmLocalStorageSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application);
+ ~QWasmLocalStorageSettingsPrivate() final = default;
void remove(const QString &key) final;
void set(const QString &key, const QVariant &value) final;
@@ -45,10 +60,7 @@ public:
QString fileName() const final;
private:
- QString prependStoragePrefix(const QString &key) const;
- QStringView removeStoragePrefix(QStringView key) const;
- val m_localStorage = val::global("window")["localStorage"];
- QString m_keyPrefix;
+ QStringList m_keyPrefixes;
};
QWasmLocalStorageSettingsPrivate::QWasmLocalStorageSettingsPrivate(QSettings::Scope scope,
@@ -56,88 +68,147 @@ QWasmLocalStorageSettingsPrivate::QWasmLocalStorageSettingsPrivate(QSettings::Sc
const QString &application)
: QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
{
+ if (organization.isEmpty()) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
+
// The key prefix contians "qt" to separate Qt keys from other keys on localStorage, a
// version tag to allow for making changes to the key format in the future, the org
// and app names.
//
// User code could could create separate settings object with different org and app names,
- // and would expect them to have separate settings. Also, different webassembly instanaces
+ // and would expect them to have separate settings. Also, different webassembly instances
// on the page could write to the same window.localStorage. Add the org and app name
- // to the key prefix to differentiate, even if that leads to keys with redundant sectons
+ // to the key prefix to differentiate, even if that leads to keys with redundant sections
// for the common case of a single org and app name.
+ //
+ // Also, the common Qt mechanism for user/system scope and all-application settings are
+ // implemented, using different prefixes.
+ const QString allAppsSetting = QStringLiteral("all-apps");
+ const QString systemSetting = QStringLiteral("sys-tem");
+
const QLatin1String separator("-");
const QLatin1String doubleSeparator("--");
const QString escapedOrganization = QString(organization).replace(separator, doubleSeparator);
const QString escapedApplication = QString(application).replace(separator, doubleSeparator);
- const QLatin1String prefix("qt-v0-");
- m_keyPrefix.reserve(prefix.length() + escapedOrganization.length() +
- escapedApplication.length() + separator.length() * 2);
- m_keyPrefix = prefix + escapedOrganization + separator + escapedApplication + separator;
+ const QString prefix = "qt-v0-" + escapedOrganization + separator;
+ if (scope == QSettings::Scope::UserScope) {
+ if (!escapedApplication.isEmpty())
+ m_keyPrefixes.push_back(prefix + escapedApplication + separator);
+ m_keyPrefixes.push_back(prefix + allAppsSetting + separator);
+ }
+ if (!escapedApplication.isEmpty()) {
+ m_keyPrefixes.push_back(prefix + escapedApplication + separator + systemSetting
+ + separator);
+ }
+ m_keyPrefixes.push_back(prefix + allAppsSetting + separator + systemSetting + separator);
}
void QWasmLocalStorageSettingsPrivate::remove(const QString &key)
{
- const std::string keyString = prependStoragePrefix(key).toStdString();
- m_localStorage.call<val>("removeItem", keyString);
+ const std::string removed = QString(m_keyPrefixes.first() + key).toStdString();
+
+ qstdweb::runTaskOnMainThread<void>([this, &removed, &key]() {
+ std::vector<std::string> children = { removed };
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ const QString storedKeyWithPrefix = QString::fromStdString(
+ val::global("window")["localStorage"].call<val>("key", i).as<std::string>());
+
+ const QStringView storedKey = keyNameFromPrefixedStorageName(
+ m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
+ if (storedKey.isEmpty() || !storedKey.startsWith(key))
+ continue;
+
+ children.push_back(storedKeyWithPrefix.toStdString());
+ }
+
+ for (const auto &child : children)
+ val::global("window")["localStorage"].call<val>("removeItem", child);
+ });
}
void QWasmLocalStorageSettingsPrivate::set(const QString &key, const QVariant &value)
{
- const std::string keyString = prependStoragePrefix(key).toStdString();
- const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
- m_localStorage.call<void>("setItem", keyString, valueString);
+ qstdweb::runTaskOnMainThread<void>([this, &key, &value]() {
+ const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
+ const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
+ val::global("window")["localStorage"].call<void>("setItem", keyString, valueString);
+ });
}
std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
{
- const std::string keyString = prependStoragePrefix(key).toStdString();
- const emscripten::val value = m_localStorage.call<val>("getItem", keyString);
- if (value.isNull())
- return std::nullopt;
- const QString valueString = QString::fromStdString(value.as<std::string>());
- return QSettingsPrivate::stringToVariant(valueString);
+ return qstdweb::runTaskOnMainThread<std::optional<QVariant>>(
+ [this, &key]() -> std::optional<QVariant> {
+ for (const auto &prefix : m_keyPrefixes) {
+ const std::string keyString = QString(prefix + key).toStdString();
+ const emscripten::val value =
+ val::global("window")["localStorage"].call<val>("getItem", keyString);
+ if (!value.isNull()) {
+ return QSettingsPrivate::stringToVariant(
+ QString::fromStdString(value.as<std::string>()));
+ }
+ if (!fallbacks) {
+ return std::nullopt;
+ }
+ }
+ return std::nullopt;
+ });
}
QStringList QWasmLocalStorageSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
{
- // Loop through all keys on window.localStorage, return Qt keys belonging to
- // this application, with the correct prefix, and according to ChildSpec.
- QStringList children;
- const int length = m_localStorage["length"].as<int>();
- for (int i = 0; i < length; ++i) {
- const QString keyString =
- QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
-
- const QStringView key = removeStoragePrefix(QStringView(keyString));
- if (key.isEmpty())
- continue;
- if (!key.startsWith(prefix))
- continue;
-
- QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
- }
-
- return children;
+ return qstdweb::runTaskOnMainThread<QStringList>([this, &prefix, &spec]() -> QStringList {
+ QSet<QString> nodes;
+ // Loop through all keys on window.localStorage, return Qt keys belonging to
+ // this application, with the correct prefix, and according to ChildSpec.
+ QStringList children;
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ for (const auto &storagePrefix : m_keyPrefixes) {
+ const QString keyString =
+ QString::fromStdString(val::global("window")["localStorage"]
+ .call<val>("key", i)
+ .as<std::string>());
+
+ const QStringView key =
+ keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
+ if (!key.isEmpty() && key.startsWith(prefix)) {
+ QStringList children;
+ QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
+ if (!children.isEmpty())
+ nodes.insert(children.first());
+ }
+ if (!fallbacks)
+ break;
+ }
+ }
+
+ return QStringList(nodes.begin(), nodes.end());
+ });
}
void QWasmLocalStorageSettingsPrivate::clear()
{
- // Get all Qt keys from window.localStorage
- const int length = m_localStorage["length"].as<int>();
- std::vector<std::string> keys;
- keys.reserve(length);
- for (int i = 0; i < length; ++i) {
- std::string key = (m_localStorage.call<val>("key", i).as<std::string>());
- keys.push_back(std::move(key));
- }
-
- // Remove all Qt keys. Note that localStorage does not guarantee a stable
- // iteration order when the storage is mutated, which is why removal is done
- // in a second step after getting all keys.
- for (std::string key: keys) {
- if (removeStoragePrefix(QString::fromStdString(key)).isEmpty() == false)
- m_localStorage.call<val>("removeItem", key);
- }
+ qstdweb::runTaskOnMainThread<void>([this]() {
+ // Get all Qt keys from window.localStorage
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ QStringList keys;
+ keys.reserve(length);
+ for (int i = 0; i < length; ++i)
+ keys.append(QString::fromStdString(
+ (val::global("window")["localStorage"].call<val>("key", i).as<std::string>())));
+
+ // Remove all Qt keys. Note that localStorage does not guarantee a stable
+ // iteration order when the storage is mutated, which is why removal is done
+ // in a second step after getting all keys.
+ for (const QString &key : keys) {
+ if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
+ val::global("window")["localStorage"].call<val>("removeItem", key.toStdString());
+ }
+ });
}
void QWasmLocalStorageSettingsPrivate::sync() { }
@@ -154,19 +225,6 @@ QString QWasmLocalStorageSettingsPrivate::fileName() const
return QString();
}
-QString QWasmLocalStorageSettingsPrivate::prependStoragePrefix(const QString &key) const
-{
- return m_keyPrefix + key;
-}
-
-QStringView QWasmLocalStorageSettingsPrivate::removeStoragePrefix(QStringView key) const
-{
- // Return the key slice after m_keyPrefix, or an empty string view if no match
- if (!key.startsWith(m_keyPrefix))
- return QStringView();
- return key.sliced(m_keyPrefix.length());
-}
-
//
// Native settings implementation for WebAssembly using the indexed database as
// the storage backend
@@ -177,225 +235,168 @@ public:
QWasmIDBSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application);
~QWasmIDBSettingsPrivate();
- static QWasmIDBSettingsPrivate *get(void *userData);
- std::optional<QVariant> get(const QString &key) const override;
- QStringList children(const QString &prefix, ChildSpec spec) const override;
void clear() override;
void sync() override;
- void flush() override;
- bool isWritable() const override;
-
- void syncToLocal(const char *data, int size);
- void loadLocal(const QByteArray &filename);
- void setReady();
- void initAccess() override;
private:
+ bool writeSettingsToTemporaryFile(const QString &fileName, void *dataPtr, int size);
+ void loadIndexedDBFiles();
+
+
QString databaseName;
QString id;
- static QList<QWasmIDBSettingsPrivate *> liveSettings;
};
-QList<QWasmIDBSettingsPrivate *> QWasmIDBSettingsPrivate::liveSettings;
-static bool isReadReady = false;
-
-static void QWasmIDBSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
-{
- QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData);
- if (!settings)
- return;
-
- QFile file(settings->fileName());
- QFileInfo fileInfo(settings->fileName());
- QDir dir(fileInfo.path());
- if (!dir.exists())
- dir.mkpath(fileInfo.path());
-
- if (file.open(QFile::WriteOnly)) {
- file.write(reinterpret_cast<char *>(dataPtr), size);
- file.close();
- settings->setReady();
- }
-}
-
-static void QWasmIDBSettingsPrivate_onError(void *userData)
-{
- if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData))
- settings->setStatus(QSettings::AccessError);
-}
-
-static void QWasmIDBSettingsPrivate_onStore(void *userData)
-{
- if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData))
- settings->setStatus(QSettings::NoError);
-}
-
-static void QWasmIDBSettingsPrivate_onCheck(void *userData, int exists)
-{
- if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData)) {
- if (exists)
- settings->loadLocal(settings->fileName().toLocal8Bit());
- else
- settings->setReady();
- }
-}
+constexpr char DbName[] = "/home/web_user";
QWasmIDBSettingsPrivate::QWasmIDBSettingsPrivate(QSettings::Scope scope,
const QString &organization,
const QString &application)
- : QConfFileSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
+ : QConfFileSettingsPrivate(QSettings::WebIndexedDBFormat, scope, organization, application)
{
- liveSettings.push_back(this);
+ Q_ASSERT_X(qstdweb::haveJspi(), Q_FUNC_INFO, "QWasmIDBSettingsPrivate needs JSPI to work");
+
+ if (organization.isEmpty()) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
- setStatus(QSettings::AccessError); // access error until sandbox gets loaded
databaseName = organization;
id = application;
- emscripten_idb_async_exists("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onCheck,
- QWasmIDBSettingsPrivate_onError);
-}
-
-QWasmIDBSettingsPrivate::~QWasmIDBSettingsPrivate()
-{
- liveSettings.removeAll(this);
-}
+ loadIndexedDBFiles();
-QWasmIDBSettingsPrivate *QWasmIDBSettingsPrivate::get(void *userData)
-{
- if (QWasmIDBSettingsPrivate::liveSettings.contains(userData))
- return reinterpret_cast<QWasmIDBSettingsPrivate *>(userData);
- return nullptr;
+ QConfFileSettingsPrivate::initAccess();
}
-void QWasmIDBSettingsPrivate::initAccess()
-{
- if (isReadReady)
- QConfFileSettingsPrivate::initAccess();
-}
+QWasmIDBSettingsPrivate::~QWasmIDBSettingsPrivate() = default;
-std::optional<QVariant> QWasmIDBSettingsPrivate::get(const QString &key) const
+bool QWasmIDBSettingsPrivate::writeSettingsToTemporaryFile(const QString &fileName, void *dataPtr,
+ int size)
{
- if (isReadReady)
- return QConfFileSettingsPrivate::get(key);
+ QFile file(fileName);
+ QFileInfo fileInfo(fileName);
+ QDir dir(fileInfo.path());
+ if (!dir.exists())
+ dir.mkpath(fileInfo.path());
- return std::nullopt;
-}
+ if (!file.open(QFile::WriteOnly))
+ return false;
-QStringList QWasmIDBSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
-{
- return QConfFileSettingsPrivate::children(prefix, spec);
+ return size == file.write(reinterpret_cast<char *>(dataPtr), size);
}
void QWasmIDBSettingsPrivate::clear()
{
QConfFileSettingsPrivate::clear();
- emscripten_idb_async_delete("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onStore,
- QWasmIDBSettingsPrivate_onError);
+
+ int error = 0;
+ emscripten_idb_delete(DbName, fileName().toLocal8Bit(), &error);
+ setStatus(!!error ? QSettings::AccessError : QSettings::NoError);
}
void QWasmIDBSettingsPrivate::sync()
{
+ // Reload the files, in case there were any changes in IndexedDB, and flush them to disk.
+ // Thanks to this, QConfFileSettingsPrivate::sync will handle key merging correctly.
+ loadIndexedDBFiles();
+
QConfFileSettingsPrivate::sync();
QFile file(fileName());
if (file.open(QFile::ReadOnly)) {
QByteArray dataPointer = file.readAll();
- emscripten_idb_async_store("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void *>(dataPointer.data()),
- dataPointer.length(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onStore,
- QWasmIDBSettingsPrivate_onError);
+ int error = 0;
+ emscripten_idb_store(DbName, fileName().toLocal8Bit(),
+ reinterpret_cast<void *>(dataPointer.data()), dataPointer.length(),
+ &error);
+ setStatus(!!error ? QSettings::AccessError : QSettings::NoError);
}
}
-void QWasmIDBSettingsPrivate::flush()
-{
- sync();
-}
-
-bool QWasmIDBSettingsPrivate::isWritable() const
+void QWasmIDBSettingsPrivate::loadIndexedDBFiles()
{
- return isReadReady && QConfFileSettingsPrivate::isWritable();
-}
-
-void QWasmIDBSettingsPrivate::syncToLocal(const char *data, int size)
-{
- QFile file(fileName());
-
- if (file.open(QFile::WriteOnly)) {
- file.write(data, size + 1);
- QByteArray data = file.readAll();
-
- emscripten_idb_async_store("/home/web_user",
- fileName().toLocal8Bit(),
- reinterpret_cast<void *>(data.data()),
- data.length(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onStore,
- QWasmIDBSettingsPrivate_onError);
- setReady();
+ for (const auto *confFile : getConfFiles()) {
+ int exists = 0;
+ int error = 0;
+ emscripten_idb_exists(DbName, confFile->name.toLocal8Bit(), &exists, &error);
+ if (error) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
+ if (exists) {
+ void *contents;
+ int size;
+ emscripten_idb_load(DbName, confFile->name.toLocal8Bit(), &contents, &size, &error);
+ if (error || !writeSettingsToTemporaryFile(confFile->name, contents, size)) {
+ setStatus(QSettings::AccessError);
+ return;
+ }
+ }
}
}
-void QWasmIDBSettingsPrivate::loadLocal(const QByteArray &filename)
-{
- emscripten_idb_async_load("/home/web_user",
- filename.data(),
- reinterpret_cast<void*>(this),
- QWasmIDBSettingsPrivate_onLoad,
- QWasmIDBSettingsPrivate_onError);
-}
-
-void QWasmIDBSettingsPrivate::setReady()
-{
- isReadReady = true;
- setStatus(QSettings::NoError);
- QConfFileSettingsPrivate::initAccess();
-}
-
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
const QString &organization, const QString &application)
{
- const auto WebLocalStorageFormat = QSettings::IniFormat + 1;
- const auto WebIdbFormat = QSettings::IniFormat + 2;
-
// Make WebLocalStorageFormat the default native format
if (format == QSettings::NativeFormat)
- format = QSettings::Format(WebLocalStorageFormat);
+ format = QSettings::WebLocalStorageFormat;
// Check if cookies are enabled (required for using persistent storage)
- const bool cookiesEnabled = val::global("navigator")["cookieEnabled"].as<bool>();
- constexpr QLatin1StringView cookiesWarningMessage
- ("QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
- if (format == WebLocalStorageFormat && !cookiesEnabled) {
- qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
- format = QSettings::IniFormat;
- } else if (format == WebIdbFormat && !cookiesEnabled) {
- qWarning() << cookiesWarningMessage.arg("WebIdbFormat");
+
+ const bool cookiesEnabled = qstdweb::runTaskOnMainThread<bool>(
+ []() { return val::global("navigator")["cookieEnabled"].as<bool>(); });
+
+ constexpr QLatin1StringView cookiesWarningMessage(
+ "QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
+ if (!cookiesEnabled) {
+ if (format == QSettings::WebLocalStorageFormat) {
+ qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
+ format = QSettings::IniFormat;
+ } else if (format == QSettings::WebIndexedDBFormat) {
+ qWarning() << cookiesWarningMessage.arg("WebIndexedDBFormat");
+ format = QSettings::IniFormat;
+ }
+ }
+ if (format == QSettings::WebIndexedDBFormat && !qstdweb::haveJspi()) {
+ qWarning() << "QSettings::WebIndexedDBFormat requires JSPI, falling back to IniFormat with "
+ "temporary file";
format = QSettings::IniFormat;
}
// Create settings backend according to selected format
- if (format == WebLocalStorageFormat) {
+ switch (format) {
+ case QSettings::Format::WebLocalStorageFormat:
return new QWasmLocalStorageSettingsPrivate(scope, organization, application);
- } else if (format == WebIdbFormat) {
+ case QSettings::Format::WebIndexedDBFormat:
return new QWasmIDBSettingsPrivate(scope, organization, application);
- } else if (format == QSettings::IniFormat) {
+ case QSettings::Format::IniFormat:
+ case QSettings::Format::CustomFormat1:
+ case QSettings::Format::CustomFormat2:
+ case QSettings::Format::CustomFormat3:
+ case QSettings::Format::CustomFormat4:
+ case QSettings::Format::CustomFormat5:
+ case QSettings::Format::CustomFormat6:
+ case QSettings::Format::CustomFormat7:
+ case QSettings::Format::CustomFormat8:
+ case QSettings::Format::CustomFormat9:
+ case QSettings::Format::CustomFormat10:
+ case QSettings::Format::CustomFormat11:
+ case QSettings::Format::CustomFormat12:
+ case QSettings::Format::CustomFormat13:
+ case QSettings::Format::CustomFormat14:
+ case QSettings::Format::CustomFormat15:
+ case QSettings::Format::CustomFormat16:
return new QConfFileSettingsPrivate(format, scope, organization, application);
+ case QSettings::Format::InvalidFormat:
+ return nullptr;
+ case QSettings::Format::NativeFormat:
+ Q_UNREACHABLE();
+ break;
}
-
- qWarning() << "Unsupported settings format" << format;
- return nullptr;
}
QT_END_NAMESPACE