wasm: add platform qsettings

Since the backend is async, the settings will not be ready to read/write
instantly as on other platforms, but only be ready after the
filesystem has been synced to the sandbox. This takes at least 250 to
500 ms. The QSettings status() or isWritable() can be used to discern when the
settings are ready for use.

This also fixes a crash in threaded wasm

Task-number: QTBUG-70002
Fixes: QTBUG-63923
Fixes: QTBUG-79650
Change-Id: If24c6ada1b91b2a565ed6733da74972c3027f622
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Lorn Potter 2018-10-09 10:14:43 +10:00
parent f2cf5f5417
commit a3f62d7ead
6 changed files with 277 additions and 38 deletions

View File

@ -136,6 +136,7 @@ qtConfig(settings) {
} else: darwin:!nacl {
SOURCES += io/qsettings_mac.cpp
}
wasm : SOURCES += io/qsettings_wasm.cpp
}
win32 {

View File

@ -76,10 +76,6 @@
# include <ioLib.h>
#endif
#ifdef Q_OS_WASM
#include <emscripten.h>
#endif
#include <algorithm>
#include <stdlib.h>
@ -295,7 +291,7 @@ after_loop:
// see also qsettings_win.cpp, qsettings_winrt.cpp and qsettings_mac.cpp
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_WASM)
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
const QString &organization, const QString &application)
{
@ -1185,7 +1181,9 @@ 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,
@ -1548,13 +1546,6 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
perms |= QFile::ReadGroup | QFile::ReadOther;
QFile(confFile->name).setPermissions(perms);
}
#ifdef Q_OS_WASM
EM_ASM(
// Sync sandbox filesystem to persistent database filesystem. See QTBUG-70002
FS.syncfs(false, function(err) {
});
);
#endif
} else {
setStatus(QSettings::AccessError);
}

View File

@ -57,6 +57,10 @@
#include "QtCore/qiodevice.h"
#include "QtCore/qstack.h"
#include "QtCore/qstringlist.h"
#include <QtCore/qvariant.h>
#include "qsettings.h"
#ifndef QT_NO_QOBJECT
#include "private/qobject_p.h"
#endif
@ -253,6 +257,10 @@ protected:
mutable QSettings::Status status;
};
#ifdef Q_OS_WASM
class QWasmSettingsPrivate;
#endif
class QConfFileSettingsPrivate : public QSettingsPrivate
{
public:
@ -281,7 +289,7 @@ public:
private:
void initFormat();
void initAccess();
virtual void initAccess();
void syncConfFile(QConfFile *confFile);
bool writeIniFile(QIODevice &device, const ParsedSettingsMap &map);
#ifdef Q_OS_MAC
@ -297,6 +305,9 @@ private:
QString extension;
Qt::CaseSensitivity caseSensitivity;
int nextPosition;
#ifdef Q_OS_WASM
friend class QWasmSettingsPrivate;
#endif
};
QT_END_NAMESPACE

View File

@ -0,0 +1,259 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qsettings.h"
#ifndef QT_NO_SETTINGS
#include "qsettings_p.h"
#ifndef QT_NO_QOBJECT
#include "qcoreapplication.h"
#include <QFile>
#endif // QT_NO_QOBJECT
#include <QDebug>
#include <QFileInfo>
#include <QDir>
#include <emscripten.h>
QT_BEGIN_NAMESPACE
static bool isReadReady = false;
class QWasmSettingsPrivate : public QConfFileSettingsPrivate
{
public:
QWasmSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application);
~QWasmSettingsPrivate();
bool get(const QString &key, QVariant *value) 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:
QString databaseName;
QString id;
};
static void QWasmSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
{
QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData);
QFile file(wasm->fileName());
QFileInfo fileInfo(wasm->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();
wasm->setReady();
}
}
static void QWasmSettingsPrivate_onError(void *userData)
{
QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData);
if (wasm)
wasm->setStatus(QSettings::AccessError);
}
static void QWasmSettingsPrivate_onStore(void *userData)
{
QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData);
if (wasm)
wasm->setStatus(QSettings::NoError);
}
static void QWasmSettingsPrivate_onCheck(void *userData, int exists)
{
QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData);
if (wasm) {
if (exists)
wasm->loadLocal(wasm->fileName().toLocal8Bit());
else
wasm->setReady();
}
}
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format,
QSettings::Scope scope,
const QString &organization,
const QString &application)
{
Q_UNUSED(format)
if (organization == QLatin1String("Qt"))
{
QString organizationDomain = QCoreApplication::organizationDomain();
QString applicationName = QCoreApplication::applicationName();
QSettingsPrivate *newSettings;
newSettings = new QWasmSettingsPrivate(scope, organizationDomain, applicationName);
newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(organization)));
if (!application.isEmpty())
newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(application)));
return newSettings;
}
return new QWasmSettingsPrivate(scope, organization, application);
}
QWasmSettingsPrivate::QWasmSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application)
: QConfFileSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
{
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),
QWasmSettingsPrivate_onCheck,
QWasmSettingsPrivate_onError);
}
QWasmSettingsPrivate::~QWasmSettingsPrivate()
{
}
void QWasmSettingsPrivate::initAccess()
{
if (isReadReady)
QConfFileSettingsPrivate::initAccess();
}
bool QWasmSettingsPrivate::get(const QString &key, QVariant *value) const
{
if (isReadReady)
return QConfFileSettingsPrivate::get(key, value);
return false;
}
QStringList QWasmSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
{
return QConfFileSettingsPrivate::children(prefix, spec);
}
void QWasmSettingsPrivate::clear()
{
QConfFileSettingsPrivate::clear();
emscripten_idb_async_delete("/home/web_user",
fileName().toLocal8Bit(),
reinterpret_cast<void*>(this),
QWasmSettingsPrivate_onStore,
QWasmSettingsPrivate_onError);
}
void QWasmSettingsPrivate::sync()
{
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),
QWasmSettingsPrivate_onStore,
QWasmSettingsPrivate_onError);
}
}
void QWasmSettingsPrivate::flush()
{
sync();
}
bool QWasmSettingsPrivate::isWritable() const
{
return isReadReady && QConfFileSettingsPrivate::isWritable();
}
void QWasmSettingsPrivate::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),
QWasmSettingsPrivate_onStore,
QWasmSettingsPrivate_onError);
setReady();
}
}
void QWasmSettingsPrivate::loadLocal(const QByteArray &filename)
{
emscripten_idb_async_load("/home/web_user",
filename.data(),
reinterpret_cast<void*>(this),
QWasmSettingsPrivate_onLoad,
QWasmSettingsPrivate_onError);
}
void QWasmSettingsPrivate::setReady()
{
isReadReady = true;
setStatus(QSettings::NoError);
QConfFileSettingsPrivate::initAccess();
}
QT_END_NAMESPACE
#endif // QT_NO_SETTINGS

View File

@ -121,7 +121,6 @@
#endif
#ifdef Q_OS_WASM
#include <emscripten.h>
#include <emscripten/val.h>
#endif
@ -480,13 +479,6 @@ QCoreApplicationPrivate::QCoreApplicationPrivate(int &aargc, char **aargv, uint
QCoreApplicationPrivate::~QCoreApplicationPrivate()
{
#ifdef Q_OS_WASM
EM_ASM(
// unmount persistent directory as IDBFS
// see also QTBUG-70002
FS.unmount('/home/web_user');
);
#endif
#ifndef QT_NO_QOBJECT
cleanupThreadData();
#endif
@ -780,17 +772,8 @@ void QCoreApplicationPrivate::init()
Q_ASSERT_X(!QCoreApplication::self, "QCoreApplication", "there should be only one application object");
QCoreApplication::self = q;
#ifdef Q_OS_WASM
EM_ASM(
// mount and sync persistent filesystem to sandbox
FS.mount(IDBFS, {}, '/home/web_user');
FS.syncfs(true, function(err) {
if (err)
Module.print(err);
});
);
#if QT_CONFIG(thread)
#ifdef Q_OS_WASM
QThreadPrivate::idealThreadCount = emscripten::val::global("navigator")["hardwareConcurrency"].as<int>();
#endif
#endif

View File

@ -1696,13 +1696,7 @@ QGuiApplicationPrivate::~QGuiApplicationPrivate()
qt_gl_set_global_share_context(0);
}
#endif
#ifdef Q_OS_WASM
EM_ASM(
// unmount persistent directory as IDBFS
// see QTBUG-70002
FS.unmount('/home/web_user');
);
#endif
platform_integration->destroy();
delete platform_theme;