Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant reason:default
4
7#include <private/qstdweb_p.h>
8#include <emscripten.h>
9#include <emscripten/bind.h>
10#include <emscripten/html5.h>
11#include <emscripten/val.h>
12
13#include <QtCore/qregularexpression.h>
14
16
18namespace FileDialog {
19namespace {
20bool hasLocalFilesApi()
21{
22 return !qstdweb::window()["showOpenFilePicker"].isUndefined();
23}
24
25void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelectMode,
26 qstdweb::PromiseCallbacks onFilesSelected)
27{
28 // Create file input html element which will display a native file dialog
29 // and call back to our onchange handler once the user has selected
30 // one or more files.
31 emscripten::val document = emscripten::val::global("document");
32 emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
33 input.set("type", "file");
34 input.set("style", "display:none");
35 input.set("accept", LocalFileApi::makeFileInputAccept(accept));
36 Q_UNUSED(accept);
37 input.set("multiple", emscripten::val(fileSelectMode == FileSelectMode::MultipleFiles));
38
39 static std::unique_ptr<qstdweb::EventCallback> changeEvent;
40 static std::unique_ptr<qstdweb::EventCallback> cancelEvent;
41 auto callback = [=](emscripten::val) { onFilesSelected.thenFunc(input["files"]); };
42 auto cancelCallback = [=](emscripten::val) { onFilesSelected.catchFunc(emscripten::val::undefined()); };
43 changeEvent = std::make_unique<qstdweb::EventCallback>(input, "change", callback);
44 cancelEvent = std::make_unique<qstdweb::EventCallback>(input, "cancel", cancelCallback);
45
46 // Activate file input
47 emscripten::val body = document["body"];
48 body.call<void>("appendChild", input);
49 input.call<void>("click");
50 body.call<void>("removeChild", input);
51}
52
53void showOpenViaLocalFileApi(const QStringList &accept, FileSelectMode fileSelectMode,
54 qstdweb::PromiseCallbacks callbacks)
55{
56 using namespace qstdweb;
57
58 auto options = LocalFileApi::makeOpenFileOptions(accept, fileSelectMode == FileSelectMode::MultipleFiles);
59
60 Promise::make(
61 window(), QStringLiteral("showOpenFilePicker"),
62 {
63 .thenFunc = [=](emscripten::val fileHandles) mutable {
64 std::vector<emscripten::val> filePromises;
65 filePromises.reserve(fileHandles["length"].as<int>());
66 for (int i = 0; i < fileHandles["length"].as<int>(); ++i)
67 filePromises.push_back(fileHandles[i].call<emscripten::val>("getFile"));
68 Promise::all(std::move(filePromises), callbacks);
69 },
70 .catchFunc = callbacks.catchFunc,
71 .finallyFunc = callbacks.finallyFunc,
72 }, std::move(options));
73}
74
75void showSaveViaLocalFileApi(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
76{
77 using namespace qstdweb;
78 using namespace emscripten;
79
80 auto options = LocalFileApi::makeSaveFileOptions(QStringList(), fileNameHint);
81
82 Promise::make(
83 window(), QStringLiteral("showSaveFilePicker"),
84 std::move(callbacks), std::move(options));
85}
86} // namespace
87
88void showOpen(const QStringList &accept, FileSelectMode fileSelectMode,
89 qstdweb::PromiseCallbacks callbacks)
90{
91 hasLocalFilesApi() ?
92 showOpenViaLocalFileApi(accept, fileSelectMode, std::move(callbacks)) :
93 showOpenViaHTMLPolyfill(accept, fileSelectMode, std::move(callbacks));
94}
95
97{
98 return hasLocalFilesApi();
99}
100
101void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
102{
103 Q_ASSERT(canShowSave());
104 showSaveViaLocalFileApi(fileNameHint, std::move(callbacks));
105}
106} // namespace FileDialog
107
108namespace {
109void readFiles(const qstdweb::FileList &fileList,
110 const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
111 const std::function<void ()> &fileDataReady)
112{
113 auto readFile = std::make_shared<std::function<void(int)>>();
114
115 *readFile = [=](int fileIndex) mutable {
116 // Stop when all files have been processed
117 if (fileIndex >= fileList.length()) {
118 readFile.reset();
119 return;
120 }
121
122 const qstdweb::File file = qstdweb::File(fileList[fileIndex]);
123
124 // Ask caller if the file should be accepted
125 char *buffer = acceptFile(file.size(), file.name());
126 if (buffer == nullptr) {
127 (*readFile)(fileIndex + 1);
128 return;
129 }
130
131 // Read file data into caller-provided buffer
132 file.stream(buffer, [readFile = readFile.get(), fileIndex, fileDataReady]() {
133 fileDataReady();
134 (*readFile)(fileIndex + 1);
135 });
136 };
137
138 (*readFile)(0);
139}
140
141QStringList makeFilterList(const std::string &qtAcceptList)
142{
143 // copy of qt_make_filter_list() from qfiledialog.cpp
144 auto filter = QString::fromStdString(qtAcceptList);
145 if (filter.isEmpty())
146 return QStringList();
147 QString sep(";;");
148 if (!filter.contains(sep) && filter.contains(u'\n'))
149 sep = u'\n';
150
151 return filter.split(sep);
152}
153}
154
155void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)
156{
157 // Save a file by creating programmatically clicking a download
158 // link to an object url to a Blob containing a copy of the file
159 // content. The copy is made so that the passed in content buffer
160 // can be released as soon as this function returns.
161 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content, size);
162 emscripten::val document = emscripten::val::global("document");
163 emscripten::val window = qstdweb::window();
164 emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob.val());
165 emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a"));
166 contentLink.set("href", contentUrl);
167 contentLink.set("download", fileNameHint);
168 contentLink.set("style", "display:none");
169
170 emscripten::val body = document["body"];
171 body.call<void>("appendChild", contentLink);
172 contentLink.call<void>("click");
173 body.call<void>("removeChild", contentLink);
174
175 window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl);
176}
177
178void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
179 const std::function<void (int fileCount)> &fileDialogClosed,
180 const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
181 const std::function<void()> &fileDataReady)
182{
183 FileDialog::showOpen(makeFilterList(accept), fileSelectMode, {
184 .thenFunc = [=](emscripten::val result) {
185 auto files = qstdweb::FileList(result);
186 fileDialogClosed(files.length());
187 readFiles(files, acceptFile, fileDataReady);
188 },
189 .catchFunc = [=](emscripten::val) {
190 fileDialogClosed(0);
191 }
192 });
193}
194
195void openFile(const std::string &accept,
196 const std::function<void (bool fileSelected)> &fileDialogClosed,
197 const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
198 const std::function<void()> &fileDataReady)
199{
200 auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
201 openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
202}
203
204void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data)
205{
206 using namespace emscripten;
207 using namespace qstdweb;
208
209 Promise::make(fileHandle, QStringLiteral("createWritable"), {
210 .thenFunc = [=](val writable) {
211 struct State {
212 size_t written;
213 std::function<void(val result)> continuation;
214 };
215
216 static constexpr size_t desiredChunkSize = 1024u;
217#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
218 qstdweb::Uint8Array chunkArray(desiredChunkSize);
219#endif
220
221 auto state = std::make_shared<State>();
222 state->written = 0u;
223 state->continuation = [=](val) mutable {
224 const size_t remaining = data.size() - state->written;
225 if (remaining == 0) {
226 Promise::make(writable, QStringLiteral("close"), { .thenFunc = [=](val) {} });
227 state.reset();
228 return;
229 }
230
231 const auto currentChunkSize = std::min(remaining, desiredChunkSize);
232
233#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
234 // If shared memory is used, WebAssembly.Memory is instantiated with the 'shared'
235 // option on. Passing a typed_memory_view to SharedArrayBuffer to
236 // FileSystemWritableFileStream.write is disallowed by security policies, so we
237 // need to make a copy of the data to a chunk array buffer.
238 Promise::make(
239 writable, QStringLiteral("write"),
240 {
241 .thenFunc = state->continuation,
242 },
243 chunkArray.copyFrom(data.constData() + state->written, currentChunkSize)
244 .val()
245 .call<emscripten::val>("subarray", emscripten::val(0),
246 emscripten::val(currentChunkSize)));
247#else
248 Promise::make(writable, QStringLiteral("write"),
249 {
250 .thenFunc = state->continuation,
251 },
252 val(typed_memory_view(currentChunkSize, data.constData() + state->written)));
253#endif
254 state->written += currentChunkSize;
255 };
256
257 state->continuation(val::undefined());
258 },
259 });
260}
261
262void saveFile(const QByteArray &data, const std::string &fileNameHint)
263{
265 downloadDataAsFile(data.constData(), data.size(), fileNameHint);
266 return;
267 }
268
269 FileDialog::showSave(fileNameHint, {
270 .thenFunc = [=](emscripten::val result) {
271 saveDataToFileInChunks(result, data);
272 },
273 });
274}
275
276void saveFile(const char *content, size_t size, const std::string &fileNameHint)
277{
279 downloadDataAsFile(content, size, fileNameHint);
280 return;
281 }
282
283 QByteArray data(content, size);
284 FileDialog::showSave(fileNameHint, {
285 .thenFunc = [=](emscripten::val result) {
286 saveDataToFileInChunks(result, data);
287 },
288 });
289}
290
291void showOpenFileDialog(const std::string &accept,
292 const std::function<void (bool accepted, std::vector<qstdweb::File> files)> fileDialogClosed)
293{
294 FileDialog::showOpen(makeFilterList(accept), FileSelectMode::MultipleFiles, {
295 .thenFunc = [=](emscripten::val result) {
296 if (result.isUndefined() || result.isNull()) {
297 fileDialogClosed(false, std::vector<qstdweb::File>());
298 } else {
299 std::vector<qstdweb::File> files;
300 int length = result["length"].as<int>();
301 files.reserve(length);
302 for (int i = 0; i < length; ++i) {
303 emscripten::val fileVal = result[i];
304 if (!fileVal.isUndefined() && !fileVal.isNull()) {
305 files.push_back(qstdweb::File(fileVal));
306 }
307 }
308 fileDialogClosed(true, files);
309 }
310 },
311 .catchFunc = [=](emscripten::val) {
312 fileDialogClosed(false, std::vector<qstdweb::File>());
313 }
314 });
315}
316
317void showSaveFileDialog(const std::string &fileNameHint, const std::function<void (bool accepted, qstdweb::FileSystemFileHandle fileHandle)> fileDialogClosed)
318{
319 FileDialog::showSave(fileNameHint, {
320 .thenFunc = [=](emscripten::val result) {
321 fileDialogClosed(true, qstdweb::FileSystemFileHandle(result));
322 },
323 .catchFunc = [=](emscripten::val) {
324 fileDialogClosed(false, qstdweb::FileSystemFileHandle());
325 }
326 });
327}
328
329} // namespace QWasmLocalFileAccess
330
331QT_END_NAMESPACE
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 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 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)
void showSaveFileDialog(const std::string &fileNameHint, const std::function< void(bool accepted, qstdweb::FileSystemFileHandle fileHandle)> fileDialogClosed)
void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data)
void saveFile(const char *content, size_t size, const std::string &fileNameHint)
void saveFile(const QByteArray &data, const std::string &fileNameHint)
void showOpenFileDialog(const std::string &accept, const std::function< void(bool accepted, std::vector< qstdweb::File > files)> fileDialogClosed)
void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)