Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qsettings_wasm.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsettings.h"
5#ifndef QT_NO_SETTINGS
6
7#include "qsettings_p.h"
8#ifndef QT_NO_QOBJECT
9#include "qcoreapplication.h"
10#include <QFile>
11#endif // QT_NO_QOBJECT
12#include <QDebug>
13
14#include <QFileInfo>
15#include <QDir>
16#include <QList>
17
18#include <emscripten.h>
19#include <emscripten/val.h>
20
22
23using emscripten::val;
24using namespace Qt::StringLiterals;
25
26//
27// Native settings implementation for WebAssembly using window.localStorage
28// as the storage backend. localStorage is a key-value store with a synchronous
29// API and a 5MB storage limit.
30//
32{
33public:
35 const QString &application);
36
37 void remove(const QString &key) final;
38 void set(const QString &key, const QVariant &value) final;
39 std::optional<QVariant> get(const QString &key) const final;
40 QStringList children(const QString &prefix, ChildSpec spec) const final;
41 void clear() final;
42 void sync() final;
43 void flush() final;
44 bool isWritable() const final;
45 QString fileName() const final;
46
47private:
48 QString prependStoragePrefix(const QString &key) const;
49 QStringView removeStoragePrefix(QStringView key) const;
50 val m_localStorage = val::global("window")["localStorage"];
51 QString m_keyPrefix;
52};
53
55 const QString &organization,
56 const QString &application)
57 : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
58{
59 // The key prefix contians "qt" to separate Qt keys from other keys on localStorage, a
60 // version tag to allow for making changes to the key format in the future, the org
61 // and app names.
62 //
63 // User code could could create separate settings object with different org and app names,
64 // and would expect them to have separate settings. Also, different webassembly instanaces
65 // on the page could write to the same window.localStorage. Add the org and app name
66 // to the key prefix to differentiate, even if that leads to keys with redundant sectons
67 // for the common case of a single org and app name.
68 const QLatin1String separator("-");
69 const QLatin1String doubleSeparator("--");
70 const QString escapedOrganization = QString(organization).replace(separator, doubleSeparator);
71 const QString escapedApplication = QString(application).replace(separator, doubleSeparator);
72 const QLatin1String prefix("qt-v0-");
73 m_keyPrefix.reserve(prefix.length() + escapedOrganization.length() +
74 escapedApplication.length() + separator.length() * 2);
75 m_keyPrefix = prefix + escapedOrganization + separator + escapedApplication + separator;
76}
77
79{
80 const std::string keyString = prependStoragePrefix(key).toStdString();
81 m_localStorage.call<val>("removeItem", keyString);
82}
83
85{
86 const std::string keyString = prependStoragePrefix(key).toStdString();
87 const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
88 m_localStorage.call<void>("setItem", keyString, valueString);
89}
90
91std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
92{
93 const std::string keyString = prependStoragePrefix(key).toStdString();
94 const emscripten::val value = m_localStorage.call<val>("getItem", keyString);
95 if (value.isNull())
96 return std::nullopt;
97 const QString valueString = QString::fromStdString(value.as<std::string>());
98 return QSettingsPrivate::stringToVariant(valueString);
99}
100
102{
103 // Loop through all keys on window.localStorage, return Qt keys belonging to
104 // this application, with the correct prefix, and according to ChildSpec.
106 const int length = m_localStorage["length"].as<int>();
107 for (int i = 0; i < length; ++i) {
108 const QString keyString =
109 QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
110
111 const QStringView key = removeStoragePrefix(QStringView(keyString));
112 if (key.isEmpty())
113 continue;
114 if (!key.startsWith(prefix))
115 continue;
116
117 QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
118 }
119
120 return children;
121}
122
124{
125 // Get all Qt keys from window.localStorage
126 const int length = m_localStorage["length"].as<int>();
127 std::vector<std::string> keys;
128 keys.reserve(length);
129 for (int i = 0; i < length; ++i) {
130 std::string key = (m_localStorage.call<val>("key", i).as<std::string>());
131 keys.push_back(std::move(key));
132 }
133
134 // Remove all Qt keys. Note that localStorage does not guarantee a stable
135 // iteration order when the storage is mutated, which is why removal is done
136 // in a second step after getting all keys.
137 for (std::string key: keys) {
138 if (removeStoragePrefix(QString::fromStdString(key)).isEmpty() == false)
139 m_localStorage.call<val>("removeItem", key);
140 }
141}
142
144
146
148{
149 return true;
150}
151
153{
154 return QString();
155}
156
157QString QWasmLocalStorageSettingsPrivate::prependStoragePrefix(const QString &key) const
158{
159 return m_keyPrefix + key;
160}
161
162QStringView QWasmLocalStorageSettingsPrivate::removeStoragePrefix(QStringView key) const
163{
164 // Return the key slice after m_keyPrefix, or an empty string view if no match
165 if (!key.startsWith(m_keyPrefix))
166 return QStringView();
167 return key.sliced(m_keyPrefix.length());
168}
169
170//
171// Native settings implementation for WebAssembly using the indexed database as
172// the storage backend
173//
175{
176public:
178 const QString &application);
180 static QWasmIDBSettingsPrivate *get(void *userData);
181
182 std::optional<QVariant> get(const QString &key) const override;
183 QStringList children(const QString &prefix, ChildSpec spec) const override;
184 void clear() override;
185 void sync() override;
186 void flush() override;
187 bool isWritable() const override;
188
189 void syncToLocal(const char *data, int size);
190 void loadLocal(const QByteArray &filename);
191 void setReady();
192 void initAccess() override;
193
194private:
195 QString databaseName;
196 QString id;
197 static QList<QWasmIDBSettingsPrivate *> liveSettings;
198};
199
200QList<QWasmIDBSettingsPrivate *> QWasmIDBSettingsPrivate::liveSettings;
201static bool isReadReady = false;
202
203static void QWasmIDBSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
204{
206 if (!settings)
207 return;
208
210 QFileInfo fileInfo(settings->fileName());
211 QDir dir(fileInfo.path());
212 if (!dir.exists())
213 dir.mkpath(fileInfo.path());
214
216 file.write(reinterpret_cast<char *>(dataPtr), size);
217 file.close();
218 settings->setReady();
219 }
220}
221
222static void QWasmIDBSettingsPrivate_onError(void *userData)
223{
226}
227
228static void QWasmIDBSettingsPrivate_onStore(void *userData)
229{
231 settings->setStatus(QSettings::NoError);
232}
233
234static void QWasmIDBSettingsPrivate_onCheck(void *userData, int exists)
235{
237 if (exists)
238 settings->loadLocal(settings->fileName().toLocal8Bit());
239 else
240 settings->setReady();
241 }
242}
243
245 const QString &organization,
246 const QString &application)
247 : QConfFileSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
248{
249 liveSettings.push_back(this);
250
251 setStatus(QSettings::AccessError); // access error until sandbox gets loaded
252 databaseName = organization;
253 id = application;
254
255 emscripten_idb_async_exists("/home/web_user",
257 reinterpret_cast<void*>(this),
260}
261
263{
264 liveSettings.removeAll(this);
265}
266
268{
269 if (QWasmIDBSettingsPrivate::liveSettings.contains(userData))
270 return reinterpret_cast<QWasmIDBSettingsPrivate *>(userData);
271 return nullptr;
272}
273
275{
276 if (isReadReady)
278}
279
280std::optional<QVariant> QWasmIDBSettingsPrivate::get(const QString &key) const
281{
282 if (isReadReady)
284
285 return std::nullopt;
286}
287
289{
290 return QConfFileSettingsPrivate::children(prefix, spec);
291}
292
294{
296 emscripten_idb_async_delete("/home/web_user",
298 reinterpret_cast<void*>(this),
301}
302
304{
306
309 QByteArray dataPointer = file.readAll();
310
311 emscripten_idb_async_store("/home/web_user",
313 reinterpret_cast<void *>(dataPointer.data()),
314 dataPointer.length(),
315 reinterpret_cast<void*>(this),
318 }
319}
320
322{
323 sync();
324}
325
327{
329}
330
332{
334
336 file.write(data, size + 1);
338
339 emscripten_idb_async_store("/home/web_user",
341 reinterpret_cast<void *>(data.data()),
342 data.length(),
343 reinterpret_cast<void*>(this),
346 setReady();
347 }
348}
349
351{
352 emscripten_idb_async_load("/home/web_user",
353 filename.data(),
354 reinterpret_cast<void*>(this),
357}
358
360{
361 isReadReady = true;
364}
365
367 const QString &organization, const QString &application)
368{
369 const auto WebLocalStorageFormat = QSettings::IniFormat + 1;
370 const auto WebIdbFormat = QSettings::IniFormat + 2;
371
372 // Make WebLocalStorageFormat the default native format
374 format = QSettings::Format(WebLocalStorageFormat);
375
376 // Check if cookies are enabled (required for using persistent storage)
377 const bool cookiesEnabled = val::global("navigator")["cookieEnabled"].as<bool>();
378 constexpr QLatin1StringView cookiesWarningMessage
379 ("QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
380 if (format == WebLocalStorageFormat && !cookiesEnabled) {
381 qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
383 } else if (format == WebIdbFormat && !cookiesEnabled) {
384 qWarning() << cookiesWarningMessage.arg("WebIdbFormat");
386 }
387
388 // Create settings backend according to selected format
389 if (format == WebLocalStorageFormat) {
390 return new QWasmLocalStorageSettingsPrivate(scope, organization, application);
391 } else if (format == WebIdbFormat) {
392 return new QWasmIDBSettingsPrivate(scope, organization, application);
393 } else if (format == QSettings::IniFormat) {
394 return new QConfFileSettingsPrivate(format, scope, organization, application);
395 }
396
397 qWarning() << "Unsupported settings format" << format;
398 return nullptr;
399}
400
402#endif // QT_NO_SETTINGS
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype length() const noexcept
Same as size().
Definition qbytearray.h:479
virtual void initAccess()
bool isWritable() const override
QString fileName() const override
std::optional< QVariant > get(const QString &key) const override
\inmodule QtCore
Definition qdir.h:19
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QString path() const
Returns the file's path.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
constexpr qsizetype length() const noexcept
Definition qlist.h:74
void push_back(parameter_type t)
Definition qlist.h:672
qsizetype removeAll(const AT &t)
Definition qlist.h:575
QObjectList children
Definition qobject.h:62
static QSettingsPrivate * create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application)
void setStatus(QSettings::Status status) const
static QVariant stringToVariant(const QString &s)
static QString variantToString(const QVariant &v)
QSettings::Format format
QSettings::Scope scope
static void processChild(QStringView key, ChildSpec spec, QStringList &result)
\inmodule QtCore
Definition qsettings.h:30
Format
This enum type specifies the storage format used by QSettings.
Definition qsettings.h:48
@ NativeFormat
Definition qsettings.h:49
Scope
This enum specifies whether settings are user-specific or shared by all users of the same system.
Definition qsettings.h:85
QString fileName() const
Returns the path where settings written using this QSettings object are stored.
@ AccessError
Definition qsettings.h:41
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:76
constexpr bool isEmpty() const noexcept
Returns whether this string view is empty - that is, whether {size() == 0}.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3794
void reserve(qsizetype size)
Ensures the string has space for at least size characters.
Definition qstring.h:1173
static QString fromStdString(const std::string &s)
Definition qstring.h:1322
std::string toStdString() const
Returns a std::string object with the data contained in this QString.
Definition qstring.h:1319
QByteArray toLocal8Bit() const &
Definition qstring.h:567
qsizetype length() const
Returns the number of characters in this string.
Definition qstring.h:187
\inmodule QtCore
Definition qvariant.h:64
bool isWritable() const override
void loadLocal(const QByteArray &filename)
void syncToLocal(const char *data, int size)
static QWasmIDBSettingsPrivate * get(void *userData)
QWasmIDBSettingsPrivate(QSettings::Scope scope, const QString &organization, const QString &application)
std::optional< QVariant > get(const QString &key) const final
QWasmLocalStorageSettingsPrivate(QSettings::Scope scope, const QString &organization, const QString &application)
void remove(const QString &key) final
void set(const QString &key, const QVariant &value) final
Combined button and popup list for selecting options.
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:162
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
GLuint GLfloat * val
static QString keyString(int sym, QChar::Category category)
static void QWasmIDBSettingsPrivate_onError(void *userData)
static void QWasmIDBSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
static void QWasmIDBSettingsPrivate_onCheck(void *userData, int exists)
static bool isReadReady
static void QWasmIDBSettingsPrivate_onStore(void *userData)
static char * toLocal8Bit(char *out, QStringView in, QStringConverter::State *state)
QFuture< QSet< QChar > > set
[10]
QFile file
[0]
QSettings settings("MySoft", "Star Runner")
[0]
QStringList keys
QString dir
[11]