wasm: add public API for local file access

The web sandbox restricts access to the local file system,
which means Qt needs new public API to provide such
access to applications.

This adds a new static, asynchornous getOpenFileContent()
function to QFileDialog, which will show a file dialog,
read the file contents once a file has been selected, and
finally call the user-provided callback with the selected
file name and file data.

  auto fileReady = [](const QString &fileName, const QByteArray &fileContents) {
    // Process file
  }
  QFileDialog::getOpenFileContent("*.*", fileReady);

This API can be expanded in at least two ways in the
future: allow reading multiple files, and allow file
streaming by returning a QOIDevice.

Change-Id: I011b92fbcf21f0696604e66b7f9eb265c1a07aaa
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
Morten Johan Sørvig 2019-01-18 15:02:32 +01:00
parent 790cf25f9d
commit 928cab5ff1
3 changed files with 97 additions and 0 deletions

View File

@ -73,6 +73,9 @@
#elif defined(Q_OS_WIN)
# include <QtCore/qt_windows.h>
#endif
#if defined(Q_OS_WASM)
#include <private/qwasmlocalfileaccess_p.h>
#endif
QT_BEGIN_NAMESPACE
@ -2347,6 +2350,85 @@ QList<QUrl> QFileDialog::getOpenFileUrls(QWidget *parent,
return QList<QUrl>();
}
/*!
This is a convenience static function that will return the content of a file
selected by the user.
This function is used to access local files on Qt for WebAssembly, where the web
sandbox places restrictions on how such access may happen. Its implementation will
make the browser display a native file dialog, where the user makes the file selection.
It can also be used on other platforms, where it will fall back to using QFileDialog.
The function is asynchronous and returns immediately. The \a fileOpenCompleted
callback will be called when a file has been selected and its contents has been
read into memory.
\snippet code/src_gui_dialogs_qfiledialog.cpp 14
\since 5.13
*/
void QFileDialog::getOpenFileContent(const QString &nameFilter, const std::function<void(const QString &, const QByteArray &)> &fileOpenCompleted)
{
#ifdef Q_OS_WASM
auto openFileImpl = std::make_shared<std::function<void(void)>>();
QString fileName;
QByteArray fileContent;
*openFileImpl = [=]() mutable {
auto fileDialogClosed = [&](bool fileSelected) {
if (!fileSelected) {
fileOpenCompleted(fileName, fileContent);
openFileImpl.reset();
}
};
auto acceptFile = [&](uint64_t size, const std::string name) -> char * {
const uint64_t twoGB = 1ULL << 31; // QByteArray limit
if (size > twoGB)
return nullptr;
fileName = QString::fromStdString(name);
fileContent.resize(size);
return fileContent.data();
};
auto fileContentReady = [&]() mutable {
fileOpenCompleted(fileName, fileContent);
openFileImpl.reset();
};
auto qtFilterStringToWebAcceptString = [](const QString &qtString) {
// The Qt and Web name filter string formats are similar, but
// not identical.
return qtString.toStdString(); // ### TODO
};
QWasmLocalFileAccess::openFile(qtFilterStringToWebAcceptString(nameFilter), fileDialogClosed, acceptFile, fileContentReady);
};
(*openFileImpl)();
#else
QFileDialog *dialog = new QFileDialog();
dialog->selectNameFilter(nameFilter);
auto fileSelected = [=](const QString &fileName) {
QByteArray fileContent;
if (!fileName.isNull()) {
QFile selectedFile(fileName);
selectedFile.open(QIODevice::ReadOnly);
fileContent = selectedFile.readAll();
}
fileOpenCompleted(fileName, fileContent);
};
auto dialogClosed = [=](int code) {
Q_UNUSED(code);
delete dialog;
};
connect(dialog, &QFileDialog::fileSelected, fileSelected);
connect(dialog, &QFileDialog::finished, dialogClosed);
dialog->show();
#endif
}
/*!
This is a convenience static function that will return a file name selected
by the user. The file does not have to exist.

View File

@ -46,6 +46,8 @@
#include <QtCore/qurl.h>
#include <QtWidgets/qdialog.h>
#include <functional>
QT_REQUIRE_CONFIG(filedialog);
QT_BEGIN_NAMESPACE
@ -273,6 +275,8 @@ public:
Options options = Options(),
const QStringList &supportedSchemes = QStringList());
static void getOpenFileContent(const QString &nameFilter,
const std::function<void(const QString &, const QByteArray &)> &fileContentsReady);
protected:
QFileDialog(const QFileDialogArgs &args);

View File

@ -144,3 +144,14 @@ dialog.exec();
//! [14]
"Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)"
//! [14]
//! [14]
auto fileOpenCompleted = [](const QSting &fileName, const QByteArray &fileContent) {
if (fileName.isEmpty()) {
// No file was selected
} else {
// Use fileName and fileContent
}
}
QFileDialog::getOpenFileContent("Images (*.png *.xpm *.jpg)", fileContentReady);
//! [14]