Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwasmlocalfileaccess.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
5#include "qlocalfileapi_p.h"
6#include <private/qstdweb_p.h>
7#include <emscripten.h>
8#include <emscripten/bind.h>
9#include <emscripten/html5.h>
10#include <emscripten/val.h>
11
12#include <QtCore/qregularexpression.h>
13
15
17namespace FileDialog {
18namespace {
19bool hasLocalFilesApi()
20{
21 return !qstdweb::window()["showOpenFilePicker"].isUndefined();
22}
23
24void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelectMode,
25 qstdweb::PromiseCallbacks onFilesSelected)
26{
27 // Create file input html element which will display a native file dialog
28 // and call back to our onchange handler once the user has selected
29 // one or more files.
30 emscripten::val document = emscripten::val::global("document");
31 emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
32 input.set("type", "file");
33 input.set("style", "display:none");
34 input.set("accept", LocalFileApi::makeFileInputAccept(accept));
35 Q_UNUSED(accept);
36 input.set("multiple", emscripten::val(fileSelectMode == FileSelectMode::MultipleFiles));
37
38 // Note: there is no event in case the user cancels the file dialog.
39 static std::unique_ptr<qstdweb::EventCallback> changeEvent;
40 auto callback = [=](emscripten::val) { onFilesSelected.thenFunc(input["files"]); };
41 changeEvent = std::make_unique<qstdweb::EventCallback>(input, "change", callback);
42
43 // Activate file input
44 emscripten::val body = document["body"];
45 body.call<void>("appendChild", input);
46 input.call<void>("click");
47 body.call<void>("removeChild", input);
48}
49
50void showOpenViaLocalFileApi(const QStringList &accept, FileSelectMode fileSelectMode,
52{
53 using namespace qstdweb;
54
55 auto options = LocalFileApi::makeOpenFileOptions(accept, fileSelectMode == FileSelectMode::MultipleFiles);
56
57 Promise::make(
58 window(), QStringLiteral("showOpenFilePicker"),
59 {
60 .thenFunc = [=](emscripten::val fileHandles) mutable {
61 std::vector<emscripten::val> filePromises;
62 filePromises.reserve(fileHandles["length"].as<int>());
63 for (int i = 0; i < fileHandles["length"].as<int>(); ++i)
64 filePromises.push_back(fileHandles[i].call<emscripten::val>("getFile"));
65 Promise::all(std::move(filePromises), callbacks);
66 },
67 .catchFunc = callbacks.catchFunc,
68 .finallyFunc = callbacks.finallyFunc,
69 }, std::move(options));
70}
71
72void showSaveViaLocalFileApi(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
73{
74 using namespace qstdweb;
75 using namespace emscripten;
76
77 auto options = LocalFileApi::makeSaveFileOptions(QStringList(), fileNameHint);
78
79 Promise::make(
80 window(), QStringLiteral("showSaveFilePicker"),
81 std::move(callbacks), std::move(options));
82}
83} // namespace
84
85void showOpen(const QStringList &accept, FileSelectMode fileSelectMode,
87{
88 hasLocalFilesApi() ?
89 showOpenViaLocalFileApi(accept, fileSelectMode, std::move(callbacks)) :
90 showOpenViaHTMLPolyfill(accept, fileSelectMode, std::move(callbacks));
91}
92
94{
95 return hasLocalFilesApi();
96}
97
98void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
99{
101 showSaveViaLocalFileApi(fileNameHint, std::move(callbacks));
102}
103} // namespace FileDialog
104
105namespace {
106void readFiles(const qstdweb::FileList &fileList,
107 const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
108 const std::function<void ()> &fileDataReady)
109{
110 auto readFile = std::make_shared<std::function<void(int)>>();
111
112 *readFile = [=](int fileIndex) mutable {
113 // Stop when all files have been processed
114 if (fileIndex >= fileList.length()) {
115 readFile.reset();
116 return;
117 }
118
119 const qstdweb::File file = qstdweb::File(fileList[fileIndex]);
120
121 // Ask caller if the file should be accepted
122 char *buffer = acceptFile(file.size(), file.name());
123 if (buffer == nullptr) {
124 (*readFile)(fileIndex + 1);
125 return;
126 }
127
128 // Read file data into caller-provided buffer
129 file.stream(buffer, [readFile = readFile.get(), fileIndex, fileDataReady]() {
130 fileDataReady();
131 (*readFile)(fileIndex + 1);
132 });
133 };
134
135 (*readFile)(0);
136}
137
138QStringList makeFilterList(const std::string &qtAcceptList)
139{
140 // copy of qt_make_filter_list() from qfiledialog.cpp
141 auto filter = QString::fromStdString(qtAcceptList);
142 if (filter.isEmpty())
143 return QStringList();
144 QString sep(";;");
145 if (!filter.contains(sep) && filter.contains(u'\n'))
146 sep = u'\n';
147
148 return filter.split(sep);
149}
150}
151
152void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)
153{
154 // Save a file by creating programmatically clicking a download
155 // link to an object url to a Blob containing a copy of the file
156 // content. The copy is made so that the passed in content buffer
157 // can be released as soon as this function returns.
158 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content, size);
159 emscripten::val document = emscripten::val::global("document");
160 emscripten::val window = qstdweb::window();
161 emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob.val());
162 emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a"));
163 contentLink.set("href", contentUrl);
164 contentLink.set("download", fileNameHint);
165 contentLink.set("style", "display:none");
166
167 emscripten::val body = document["body"];
168 body.call<void>("appendChild", contentLink);
169 contentLink.call<void>("click");
170 body.call<void>("removeChild", contentLink);
171
172 window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl);
173}
174
175void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
176 const std::function<void (int fileCount)> &fileDialogClosed,
177 const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
178 const std::function<void()> &fileDataReady)
179{
180 FileDialog::showOpen(makeFilterList(accept), fileSelectMode, {
181 .thenFunc = [=](emscripten::val result) {
183 fileDialogClosed(files.length());
184 readFiles(files, acceptFile, fileDataReady);
185 },
186 .catchFunc = [=](emscripten::val) {
187 fileDialogClosed(0);
188 }
189 });
190}
191
192void openFile(const std::string &accept,
193 const std::function<void (bool fileSelected)> &fileDialogClosed,
194 const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
195 const std::function<void()> &fileDataReady)
196{
197 auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
198 openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
199}
200
201void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data)
202{
203 using namespace emscripten;
204 using namespace qstdweb;
205
206 Promise::make(fileHandle, QStringLiteral("createWritable"), {
207 .thenFunc = [=](val writable) {
208 struct State {
209 size_t written;
210 std::function<void(val result)> continuation;
211 };
212
213 static constexpr size_t desiredChunkSize = 1024u;
214#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
215 qstdweb::Uint8Array chunkArray(desiredChunkSize);
216#endif
217
218 auto state = std::make_shared<State>();
219 state->written = 0u;
220 state->continuation = [=](val) mutable {
221 const size_t remaining = data.size() - state->written;
222 if (remaining == 0) {
223 Promise::make(writable, QStringLiteral("close"), { .thenFunc = [=](val) {} });
224 state.reset();
225 return;
226 }
227
228 const auto currentChunkSize = std::min(remaining, desiredChunkSize);
229
230#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
231 // If shared memory is used, WebAssembly.Memory is instantiated with the 'shared'
232 // option on. Passing a typed_memory_view to SharedArrayBuffer to
233 // FileSystemWritableFileStream.write is disallowed by security policies, so we
234 // need to make a copy of the data to a chunk array buffer.
235 Promise::make(
236 writable, QStringLiteral("write"),
237 {
238 .thenFunc = state->continuation,
239 },
240 chunkArray.copyFrom(data.constData() + state->written, currentChunkSize)
241 .val()
242 .call<emscripten::val>("subarray", emscripten::val(0),
243 emscripten::val(currentChunkSize)));
244#else
245 Promise::make(writable, QStringLiteral("write"),
246 {
247 .thenFunc = state->continuation,
248 },
249 val(typed_memory_view(currentChunkSize, data.constData() + state->written)));
250#endif
251 state->written += currentChunkSize;
252 };
253
254 state->continuation(val::undefined());
255 },
256 });
257}
258
259void saveFile(const QByteArray &data, const std::string &fileNameHint)
260{
262 downloadDataAsFile(data.constData(), data.size(), fileNameHint);
263 return;
264 }
265
266 FileDialog::showSave(fileNameHint, {
267 .thenFunc = [=](emscripten::val result) {
269 },
270 });
271}
272
273void saveFile(const char *content, size_t size, const std::string &fileNameHint)
274{
276 downloadDataAsFile(content, size, fileNameHint);
277 return;
278 }
279
280 FileDialog::showSave(fileNameHint, {
281 .thenFunc = [=](emscripten::val result) {
283 },
284 });
285}
286
287} // namespace QWasmLocalFileAccess
288
\inmodule QtCore
Definition qbytearray.h:57
qint64 size() const override
\reimp
Definition qfile.cpp:1179
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromStdString(const std::string &s)
Definition qstring.h:1447
static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType)
Definition qstdweb.cpp:425
int length() const
Definition qstdweb.cpp:550
else opt state
[0]
std::string makeFileInputAccept(const QStringList &filterList)
emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple)
emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string &suggestedName)
Combined button and popup list for selecting options.
void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
void showOpen(const QStringList &accept, FileSelectMode fileSelectMode, qstdweb::PromiseCallbacks callbacks)
void saveFile(const QByteArray &data, const std::string &fileNameHint)
void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data)
void openFile(const std::string &accept, const std::function< void(bool fileSelected)> &fileDialogClosed, const std::function< char *(uint64_t size, const std::string &name)> &acceptFile, const std::function< void()> &fileDataReady)
void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)
void openFiles(const std::string &accept, FileSelectMode fileSelectMode, const std::function< void(int fileCount)> &fileDialogClosed, const std::function< char *(uint64_t size, const std::string &name)> &acceptFile, const std::function< void()> &fileDataReady)
emscripten::val window()
Definition qstdweb_p.h:278
QList< QString > QStringList
Constructs a string list that contains the given string, str.
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLuint name
GLuint GLfloat * val
GLuint64EXT * result
[6]
GLenum GLenum GLenum input
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static constexpr QChar sep
PromiseCallbacks callbacks
Definition qstdweb.cpp:275
#define QStringLiteral(str)
#define Q_UNUSED(x)
QFile file
[0]
QStringList files
[8]
aWidget window() -> setWindowTitle("New Window Title")
[2]