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
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,
51 qstdweb::PromiseCallbacks callbacks)
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,
86 qstdweb::PromiseCallbacks callbacks)
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{
100 Q_ASSERT(canShowSave());
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) {
182 auto files = qstdweb::FileList(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) {
268 saveDataToFileInChunks(result, data);
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 QByteArray data(content, size);
281 FileDialog::showSave(fileNameHint, {
282 .thenFunc = [=](emscripten::val result) {
283 saveDataToFileInChunks(result, data);
284 },
285 });
286}
287
288} // namespace QWasmLocalFileAccess
289
290QT_END_NAMESPACE
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 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 downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)