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