From 790cf25f9d811f2cda7e252b4f1844e8fce27e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Fri, 18 Jan 2019 14:21:50 +0100 Subject: wasm: add local file access private API Access to the local file system is restricted by the Web sandbox, and a separate API an implementation is needed to facilitate this for Qt applications. This adds a private asynchronous callback-based C++ API for opening a file dialog and reading file content. The implementation uses a file input html element to show a file dialog, and then the uses the native File and FileReader APIs to read the selected file(s). Change-Id: I4e28baa032d7c3cd63241465f0ae55efd219a05b Reviewed-by: Lorn Potter --- src/gui/gui.pro | 1 + src/gui/platform/platform.pri | 1 + src/gui/platform/wasm/qwasmlocalfileaccess.cpp | 169 +++++++++++++++++++++++++ src/gui/platform/wasm/qwasmlocalfileaccess_p.h | 78 ++++++++++++ src/gui/platform/wasm/wasm.pri | 3 + 5 files changed, 252 insertions(+) create mode 100644 src/gui/platform/platform.pri create mode 100644 src/gui/platform/wasm/qwasmlocalfileaccess.cpp create mode 100644 src/gui/platform/wasm/qwasmlocalfileaccess_p.h create mode 100644 src/gui/platform/wasm/wasm.pri (limited to 'src/gui') diff --git a/src/gui/gui.pro b/src/gui/gui.pro index 06c9cd3939..edf8124081 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -48,6 +48,7 @@ include(opengl/opengl.pri) qtConfig(animation): include(animation/animation.pri) include(itemmodels/itemmodels.pri) include(vulkan/vulkan.pri) +include(platform/platform.pri) QMAKE_LIBS += $$QMAKE_LIBS_GUI diff --git a/src/gui/platform/platform.pri b/src/gui/platform/platform.pri new file mode 100644 index 0000000000..1fe2db81b0 --- /dev/null +++ b/src/gui/platform/platform.pri @@ -0,0 +1 @@ +wasm:include(wasm/wasm.pri) diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp new file mode 100644 index 0000000000..83f9415c69 --- /dev/null +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui 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 "qwasmlocalfileaccess_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QWasmLocalFileAccess { + +void streamFile(const qstdweb::File &file, uint32_t offset, uint32_t length, char *buffer, const std::function &completed) +{ + // Read file in chunks in order to avoid holding two copies in memory at the same time + const uint32_t chunkSize = 256 * 1024; + const uint32_t end = offset + length; + // assert end < file.size + auto fileReader = std::make_shared(); + + auto chunkCompleted = std::make_shared>(); + *chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable { + + // Copy current chunk from JS memory to Wasm memory + qstdweb::ArrayBuffer result = fileReader->result(); + qstdweb::Uint8Array(result).copyTo(chunkBuffer); + + // Read next chunk if not at buffer end + uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end); + uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end); + if (nextChunkBegin == end) { + completed(); + chunkCompleted.reset(); + return; + } + char *nextChunkBuffer = chunkBuffer + result.byteLength(); + fileReader->onLoad([=]() { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); }); + qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd); + fileReader->readAsArrayBuffer(blob); + }; + + // Read first chunk. First iteration is a dummy iteration with no available data. + (*chunkCompleted)(offset, buffer); +} + +void streamFile(const qstdweb::File &file, char *buffer, const std::function &completed) +{ + streamFile(file, 0, file.size(), buffer, completed); +} + +void readFiles(const qstdweb::FileList &fileList, + const std::function &acceptFile, + const std::function &fileDataReady) +{ + auto readFile = std::make_shared>(); + + *readFile = [=](int fileIndex) mutable { + // Stop when all files have been processed + if (fileIndex >= fileList.length()) { + readFile.reset(); + return; + } + + const qstdweb::File file = fileList[fileIndex]; + + // Ask caller if the file should be accepted + char *buffer = acceptFile(file.size(), file.name()); + if (buffer == nullptr) { + (*readFile)(fileIndex + 1); + return; + } + + // Read file data into caller-provided buffer + streamFile(file, buffer, [=]() { + fileDataReady(); + (*readFile)(fileIndex + 1); + }); + }; + + (*readFile)(0); +} + +typedef std::function OpenFileDialogCallback; +void openFileDialog(const std::string &accept, FileSelectMode fileSelectMode, + const OpenFileDialogCallback &filesSelected) +{ + // Create file input html element which will display a native file dialog + // and call back to our onchange handler once the user has selected + // one or more files. + emscripten::val document = emscripten::val::global("document"); + emscripten::val input = document.call("createElement", std::string("input")); + input.set("type", "file"); + input.set("style", "display:none"); + input.set("accept", emscripten::val(accept)); + input.set("multiple", emscripten::val(fileSelectMode == MultipleFiles)); + + // Note: there is no event in case the user cancels the file dialog. + static std::unique_ptr changeEvent; + auto callback = [=]() { filesSelected(qstdweb::FileList(input["files"])); }; + changeEvent.reset(new qstdweb::EventCallback(input, "change", callback)); + + // Activate file input + emscripten::val body = document["body"]; + body.call("appendChild", input); + input.call("click"); + body.call("removeChild", input); +} + +void openFiles(const std::string &accept, FileSelectMode fileSelectMode, + const std::function &fileDialogClosed, + const std::function &acceptFile, + const std::function &fileDataReady) +{ + openFileDialog(accept, fileSelectMode, [=](const qstdweb::FileList &files) { + fileDialogClosed(files.length()); + readFiles(files, acceptFile, fileDataReady); + }); +} + +void openFile(const std::string &accept, + const std::function &fileDialogClosed, + const std::function &acceptFile, + const std::function &fileDataReady) +{ + auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); }; + openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady); +} + +} // namespace QWasmLocalFileAccess + +QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h new file mode 100644 index 0000000000..794db8d9b2 --- /dev/null +++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui 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$ +** +****************************************************************************/ + +#ifndef QWASMLOCALFILEACCESS_P_H +#define QWASMLOCALFILEACCESS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QWasmLocalFileAccess { + +enum FileSelectMode { SingleFile, MultipleFiles }; + +void openFiles(const std::string &accept, FileSelectMode fileSelectMode, + const std::function &fileDialogClosed, + const std::function &acceptFile, + const std::function &fileDataReady); + +void openFile(const std::string &accept, + const std::function &fileDialogClosed, + const std::function &acceptFile, + const std::function &fileDataReady); + +} // namespace QWasmLocalFileAccess + +QT_END_NAMESPACE + +#endif // QWASMLOCALFILEACCESS_P_H diff --git a/src/gui/platform/wasm/wasm.pri b/src/gui/platform/wasm/wasm.pri new file mode 100644 index 0000000000..1b5d7eadb5 --- /dev/null +++ b/src/gui/platform/wasm/wasm.pri @@ -0,0 +1,3 @@ +INCLUDEDIR += $$PWD +HEADERS += $$PWD/qwasmlocalfileaccess_p.h +SOURCES += $$PWD/qwasmlocalfileaccess.cpp -- cgit v1.2.3