1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qwasmlocalfileaccess_p.h"
41 #include <private/qstdweb_p.h>
42 #include <emscripten.h>
43 #include <emscripten/bind.h>
44 #include <emscripten/html5.h>
45 #include <emscripten/val.h>
46
47 QT_BEGIN_NAMESPACE
48
49 namespace QWasmLocalFileAccess {
50
streamFile(const qstdweb::File & file,uint32_t offset,uint32_t length,char * buffer,const std::function<void ()> & completed)51 void streamFile(const qstdweb::File &file, uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed)
52 {
53 // Read file in chunks in order to avoid holding two copies in memory at the same time
54 const uint32_t chunkSize = 256 * 1024;
55 const uint32_t end = offset + length;
56 // assert end < file.size
57 auto fileReader = std::make_shared<qstdweb::FileReader>();
58
59 auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>();
60 *chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable {
61
62 // Copy current chunk from JS memory to Wasm memory
63 qstdweb::ArrayBuffer result = fileReader->result();
64 qstdweb::Uint8Array(result).copyTo(chunkBuffer);
65
66 // Read next chunk if not at buffer end
67 uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
68 uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
69 if (nextChunkBegin == end) {
70 completed();
71 chunkCompleted.reset();
72 return;
73 }
74 char *nextChunkBuffer = chunkBuffer + result.byteLength();
75 fileReader->onLoad([=]() { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); });
76 qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd);
77 fileReader->readAsArrayBuffer(blob);
78 };
79
80 // Read first chunk. First iteration is a dummy iteration with no available data.
81 (*chunkCompleted)(offset, buffer);
82 }
83
streamFile(const qstdweb::File & file,char * buffer,const std::function<void ()> & completed)84 void streamFile(const qstdweb::File &file, char *buffer, const std::function<void ()> &completed)
85 {
86 streamFile(file, 0, file.size(), buffer, completed);
87 }
88
readFiles(const qstdweb::FileList & fileList,const std::function<char * (uint64_t size,const std::string name)> & acceptFile,const std::function<void ()> & fileDataReady)89 void readFiles(const qstdweb::FileList &fileList,
90 const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
91 const std::function<void ()> &fileDataReady)
92 {
93 auto readFile = std::make_shared<std::function<void(int)>>();
94
95 *readFile = [=](int fileIndex) mutable {
96 // Stop when all files have been processed
97 if (fileIndex >= fileList.length()) {
98 readFile.reset();
99 return;
100 }
101
102 const qstdweb::File file = fileList[fileIndex];
103
104 // Ask caller if the file should be accepted
105 char *buffer = acceptFile(file.size(), file.name());
106 if (buffer == nullptr) {
107 (*readFile)(fileIndex + 1);
108 return;
109 }
110
111 // Read file data into caller-provided buffer
112 streamFile(file, buffer, [=]() {
113 fileDataReady();
114 (*readFile)(fileIndex + 1);
115 });
116 };
117
118 (*readFile)(0);
119 }
120
121 typedef std::function<void (const qstdweb::FileList &fileList)> OpenFileDialogCallback;
openFileDialog(const std::string & accept,FileSelectMode fileSelectMode,const OpenFileDialogCallback & filesSelected)122 void openFileDialog(const std::string &accept, FileSelectMode fileSelectMode,
123 const OpenFileDialogCallback &filesSelected)
124 {
125 // Create file input html element which will display a native file dialog
126 // and call back to our onchange handler once the user has selected
127 // one or more files.
128 emscripten::val document = emscripten::val::global("document");
129 emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
130 input.set("type", "file");
131 input.set("style", "display:none");
132 input.set("accept", emscripten::val(accept));
133 input.set("multiple", emscripten::val(fileSelectMode == MultipleFiles));
134
135 // Note: there is no event in case the user cancels the file dialog.
136 static std::unique_ptr<qstdweb::EventCallback> changeEvent;
137 auto callback = [=]() { filesSelected(qstdweb::FileList(input["files"])); };
138 changeEvent.reset(new qstdweb::EventCallback(input, "change", callback));
139
140 // Activate file input
141 emscripten::val body = document["body"];
142 body.call<void>("appendChild", input);
143 input.call<void>("click");
144 body.call<void>("removeChild", input);
145 }
146
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)147 void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
148 const std::function<void (int fileCount)> &fileDialogClosed,
149 const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
150 const std::function<void()> &fileDataReady)
151 {
152 openFileDialog(accept, fileSelectMode, [=](const qstdweb::FileList &files) {
153 fileDialogClosed(files.length());
154 readFiles(files, acceptFile, fileDataReady);
155 });
156 }
157
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)158 void openFile(const std::string &accept,
159 const std::function<void (bool fileSelected)> &fileDialogClosed,
160 const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
161 const std::function<void()> &fileDataReady)
162 {
163 auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
164 openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
165 }
166
saveFile(const char * content,size_t size,const std::string & fileNameHint)167 void saveFile(const char *content, size_t size, const std::string &fileNameHint)
168 {
169 // Save a file by creating programatically clicking a download
170 // link to an object url to a Blob containing the file content.
171 // File content is copied once, so that the passed in content
172 // buffer can be released as soon as this function returns - we
173 // don't know for how long the browser will retain the TypedArray
174 // view used to create the Blob.
175
176 emscripten::val document = emscripten::val::global("document");
177 emscripten::val window = emscripten::val::global("window");
178
179 emscripten::val fileContentView = emscripten::val(emscripten::typed_memory_view(size, content));
180 emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(size);
181 emscripten::val fileContentCopyView = emscripten::val::global("Uint8Array").new_(fileContentCopy);
182 fileContentCopyView.call<void>("set", fileContentView);
183
184 emscripten::val contentArray = emscripten::val::array();
185 contentArray.call<void>("push", fileContentCopyView);
186 emscripten::val type = emscripten::val::object();
187 type.set("type","application/octet-stream");
188 emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
189
190 emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob);
191 emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a"));
192 contentLink.set("href", contentUrl);
193 contentLink.set("download", fileNameHint);
194 contentLink.set("style", "display:none");
195
196 emscripten::val body = document["body"];
197 body.call<void>("appendChild", contentLink);
198 contentLink.call<void>("click");
199 body.call<void>("removeChild", contentLink);
200
201 window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl);
202 }
203
204 } // namespace QWasmLocalFileAccess
205
206 QT_END_NAMESPACE
207