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