OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium OS Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 /** | |
8 * A class that takes care of communication between NaCl and archive volume. | |
9 * Its job is to handle communication with the naclModule. | |
10 * @constructor | |
11 * @param {!Object} naclModule The nacl module with which the decompressor | |
12 * communicates. | |
13 * @param {!unpacker.types.FileSystemId} fileSystemId The file system id of for | |
14 * the archive volume to decompress. | |
15 * @param {!Blob} blob The correspondent file blob for fileSystemId. | |
16 * @param {!unpacker.PassphraseManager} passphraseManager Passphrase manager. | |
17 */ | |
18 unpacker.Decompressor = function(naclModule, fileSystemId, blob, | |
19 passphraseManager) { | |
20 /** | |
21 * @private {!Object} | |
22 * @const | |
23 */ | |
24 this.naclModule_ = naclModule; | |
25 | |
26 /** | |
27 * @private {!unpacker.types.FileSystemId} | |
28 * @const | |
29 */ | |
30 this.fileSystemId_ = fileSystemId; | |
31 | |
32 /** | |
33 * @private {!Blob} | |
34 * @const | |
35 */ | |
36 this.blob_ = blob; | |
37 | |
38 /** | |
39 * @public {!unpacker.PassphraseManager} | |
40 * @const | |
41 */ | |
42 this.passphraseManager = passphraseManager; | |
43 | |
44 /** | |
45 * Requests in progress. No need to save them onSuspend for now as metadata | |
46 * reads are restarted from start. | |
47 * @public {!Object<!unpacker.types.RequestId, !Object>} | |
48 * @const | |
49 */ | |
50 this.requestsInProgress = {}; | |
51 }; | |
52 | |
53 /** | |
54 * @return {boolean} True if there is any request in progress. | |
55 */ | |
56 unpacker.Decompressor.prototype.hasRequestsInProgress = function() { | |
57 return Object.keys(this.requestsInProgress).length > 0; | |
58 }; | |
59 | |
60 /** | |
61 * Sends a request to NaCl and mark it as a request in progress. onSuccess and | |
62 * onError are the callbacks used when receiving an answer from NaCl. | |
63 * @param {!unpacker.types.RequestId} requestId The operation request id, which | |
64 * should be unique per every volume. | |
65 * @param {function(...)} onSuccess Callback to execute on success. | |
66 * @param {function(!ProviderError)} onError Callback to execute on error. | |
67 * @param {!Object} naclRequest A request that must be sent to NaCl using | |
68 * postMessage. | |
69 * @private | |
70 */ | |
71 unpacker.Decompressor.prototype.addRequest_ = function(requestId, onSuccess, | |
72 onError, naclRequest) { | |
73 console.assert(!this.requestsInProgress[requestId], | |
74 'There is already a request with the id ' + requestId + '.'); | |
75 | |
76 this.requestsInProgress[requestId] = { | |
77 onSuccess: onSuccess, | |
78 onError: onError | |
79 }; | |
80 | |
81 this.naclModule_.postMessage(naclRequest); | |
82 }; | |
83 | |
84 /** | |
85 * Creates a request for reading metadata. | |
86 * @param {!unpacker.types.RequestId} requestId | |
87 * @param {string} encoding Default encoding for the archive's headers. | |
88 * @param {function(!Object<string, !Object>)} onSuccess Callback to execute | |
89 * once the metadata is obtained from NaCl. It has one parameter, which is | |
90 * the metadata itself. The metadata has as key the full path to an entry | |
91 * and as value information about the entry. | |
92 * @param {function(!ProviderError)} onError Callback to execute on error. | |
93 */ | |
94 unpacker.Decompressor.prototype.readMetadata = function(requestId, encoding, | |
95 onSuccess, onError) { | |
96 this.addRequest_( | |
97 requestId, onSuccess, onError, | |
98 unpacker.request.createReadMetadataRequest(this.fileSystemId_, requestId, | |
99 encoding, this.blob_.size)); | |
100 }; | |
101 | |
102 /** | |
103 * Sends an open file request to NaCl. | |
104 * @param {!unpacker.types.RequestId} requestId | |
105 * @param {number} index Index of the file in the header list. | |
106 * @param {string} encoding Default encoding for the archive's headers. | |
107 * @param {function()} onSuccess Callback to execute on successful open. | |
108 * @param {function(!ProviderError)} onError Callback to execute on error. | |
109 */ | |
110 unpacker.Decompressor.prototype.openFile = function(requestId, index, encoding, | |
111 onSuccess, onError) { | |
112 this.addRequest_( | |
113 requestId, onSuccess, onError, | |
114 unpacker.request.createOpenFileRequest(this.fileSystemId_, requestId, | |
115 index, encoding, this.blob_.size)); | |
116 }; | |
117 | |
118 /** | |
119 * Sends a close file request to NaCl. | |
120 * @param {!unpacker.types.RequestId} requestId | |
121 * @param {!unpacker.types.RequestId} openRequestId The request id of the | |
122 * corresponding open file operation for the file to close. | |
123 * @param {function()} onSuccess Callback to execute on successful open. | |
124 * @param {function(!ProviderError)} onError Callback to execute on error. | |
125 */ | |
126 unpacker.Decompressor.prototype.closeFile = function(requestId, openRequestId, | |
127 onSuccess, onError) { | |
128 this.addRequest_(requestId, onSuccess, onError, | |
129 unpacker.request.createCloseFileRequest( | |
130 this.fileSystemId_, requestId, openRequestId)); | |
131 }; | |
132 | |
133 /** | |
134 * Sends a read file request to NaCl. | |
135 * @param {!unpacker.types.RequestId} requestId | |
136 * @param {!unpacker.types.RequestId} openRequestId The request id of the | |
137 * corresponding open file operation for the file to read. | |
138 * @param {number} offset The offset from where read operation should start. | |
139 * @param {number} length The number of bytes to read. | |
140 * @param {function(!ArrayBuffer, boolean)} onSuccess Callback to execute on | |
141 * success. | |
142 * @param {function(!ProviderError)} onError Callback to execute on error. | |
143 */ | |
144 unpacker.Decompressor.prototype.readFile = function( | |
145 requestId, openRequestId, offset, length, onSuccess, onError) { | |
146 this.addRequest_( | |
147 requestId, onSuccess, onError, | |
148 unpacker.request.createReadFileRequest(this.fileSystemId_, requestId, | |
149 openRequestId, offset, length)); | |
150 }; | |
151 | |
152 /** | |
153 * Processes messages from NaCl module. | |
154 * @param {!Object} data The data contained in the message from NaCl. Its | |
155 * types depend on the operation of the request. | |
156 * @param {!unpacker.request.Operation} operation An operation from request.js. | |
157 * @param {number} requestId The request id, which should be unique per every | |
158 * volume. | |
159 */ | |
160 unpacker.Decompressor.prototype.processMessage = function(data, operation, | |
161 requestId) { | |
162 // Create a request reference for asynchronous calls as sometimes we delete | |
163 // some requestsInProgress from this.requestsInProgress. | |
164 var requestInProgress = this.requestsInProgress[requestId]; | |
165 console.assert(requestInProgress, 'No request with id <' + requestId + | |
166 '> for: ' + this.fileSystemId_ + '.'); | |
167 | |
168 switch (operation) { | |
169 case unpacker.request.Operation.READ_METADATA_DONE: | |
170 var metadata = data[unpacker.request.Key.METADATA]; | |
171 console.assert(metadata, 'No metadata.'); | |
172 requestInProgress.onSuccess(metadata); | |
173 break; | |
174 | |
175 case unpacker.request.Operation.READ_CHUNK: | |
176 this.readChunk_(data, requestId); | |
177 // this.requestsInProgress_[requestId] should be valid as long as NaCL | |
178 // can still make READ_CHUNK requests. | |
179 return; | |
180 | |
181 case unpacker.request.Operation.READ_PASSPHRASE: | |
182 this.readPassphrase_(data, requestId); | |
183 // this.requestsInProgress_[requestId] should be valid as long as NaCL | |
184 // can still make READ_PASSPHRASE requests. | |
185 return; | |
186 | |
187 case unpacker.request.Operation.OPEN_FILE_DONE: | |
188 requestInProgress.onSuccess(); | |
189 // this.requestsInProgress_[requestId] should be valid until closing the | |
190 // file so NaCL can make READ_CHUNK requests. | |
191 return; | |
192 | |
193 case unpacker.request.Operation.CLOSE_FILE_DONE: | |
194 var openRequestId = data[unpacker.request.Key.OPEN_REQUEST_ID]; | |
195 console.assert(openRequestId, 'No open request id.'); | |
196 | |
197 openRequestId = Number(openRequestId); // Received as string. | |
198 delete this.requestsInProgress[openRequestId]; | |
199 requestInProgress.onSuccess(); | |
200 break; | |
201 | |
202 case unpacker.request.Operation.READ_FILE_DONE: | |
203 var buffer = data[unpacker.request.Key.READ_FILE_DATA]; | |
204 console.assert(buffer, 'No buffer for read file operation.'); | |
205 var hasMoreData = data[unpacker.request.Key.HAS_MORE_DATA]; | |
206 console.assert(buffer !== undefined, | |
207 'No HAS_MORE_DATA boolean value for file operation.'); | |
208 | |
209 requestInProgress.onSuccess(buffer, hasMoreData /* Last call. */); | |
210 if (hasMoreData) | |
211 return; // Do not delete requestInProgress. | |
212 break; | |
213 | |
214 case unpacker.request.Operation.FILE_SYSTEM_ERROR: | |
215 console.error('File system error for <' + this.fileSystemId_ + '>: ' + | |
216 data[unpacker.request.Key.ERROR]); // The error contains | |
217 // the '.' at the end. | |
218 requestInProgress.onError('FAILED'); | |
219 break; | |
220 | |
221 case unpacker.request.Operation.CONSOLE_LOG: | |
222 case unpacker.request.Operation.CONSOLE_DEBUG: | |
223 var src_file = data[unpacker.request.Key.SRC_FILE]; | |
224 var src_line = data[unpacker.request.Key.SRC_LINE]; | |
225 var src_func = data[unpacker.request.Key.SRC_FUNC]; | |
226 var msg = data[unpacker.request.Key.MESSAGE]; | |
227 var log = operation == unpacker.request.Operation.CONSOLE_LOG ? | |
228 console.log : console.debug; | |
229 log(src_file + ':' + src_func + ':' + src_line + ': ' + msg); | |
230 break; | |
231 | |
232 default: | |
233 console.error('Invalid NaCl operation: ' + operation + '.'); | |
234 requestInProgress.onError('FAILED'); | |
235 } | |
236 delete this.requestsInProgress[requestId]; | |
237 }; | |
238 | |
239 /** | |
240 * Reads a chunk of data from this.blob_ for READ_CHUNK operation. | |
241 * @param {!Object} data The data received from the NaCl module. | |
242 * @param {number} requestId The request id, which should be unique per every | |
243 * volume. | |
244 * @private | |
245 */ | |
246 unpacker.Decompressor.prototype.readChunk_ = function(data, requestId) { | |
247 // Offset and length are received as strings. See request.js. | |
248 var offset_str = data[unpacker.request.Key.OFFSET]; | |
249 var length_str = data[unpacker.request.Key.LENGTH]; | |
250 | |
251 // Explicit check if offset is undefined as it can be 0. | |
252 console.assert(offset_str !== undefined && !isNaN(offset_str) && | |
253 Number(offset_str) >= 0 && | |
254 Number(offset_str) < this.blob_.size, | |
255 'Invalid offset.'); | |
256 console.assert(length_str && !isNaN(length_str) && Number(length_str) > 0, | |
257 'Invalid length.'); | |
258 | |
259 var offset = Number(offset_str); | |
260 var length = Math.min(this.blob_.size - offset, Number(length_str)); | |
261 | |
262 // Read a chunk from offset to offset + length. | |
263 var blob = this.blob_.slice(offset, offset + length); | |
264 var fileReader = new FileReader(); | |
265 | |
266 fileReader.onload = function(event) { | |
267 this.naclModule_.postMessage(unpacker.request.createReadChunkDoneResponse( | |
268 this.fileSystemId_, requestId, event.target.result, offset)); | |
269 }.bind(this); | |
270 | |
271 fileReader.onerror = function(event) { | |
272 console.error('Failed to read a chunk of data from the archive.'); | |
273 this.naclModule_.postMessage(unpacker.request.createReadChunkErrorResponse( | |
274 this.fileSystemId_, requestId)); | |
275 // Reading from the source file failed. Assume that the file is gone and | |
276 // unmount the archive. | |
277 // TODO(523195): Show a notification that the source file is gone. | |
278 unpacker.app.unmountVolume(this.fileSystemId_, true); | |
279 }.bind(this); | |
280 | |
281 fileReader.readAsArrayBuffer(blob); | |
282 }; | |
283 | |
284 /** | |
285 * Reads a passphrase from user input for READ_PASSPHRASE operation. | |
286 * @param {!Object} data The data received from the NaCl module. | |
287 * @param {number} requestId The request id, which should be unique per every | |
288 * volume. | |
289 * @private | |
290 */ | |
291 unpacker.Decompressor.prototype.readPassphrase_ = function(data, requestId) { | |
292 this.passphraseManager.getPassphrase() | |
293 .then(function(passphrase) { | |
294 this.naclModule_.postMessage( | |
295 unpacker.request.createReadPassphraseDoneResponse( | |
296 this.fileSystemId_, requestId, passphrase)); | |
297 }.bind(this)) | |
298 .catch(function(error) { | |
299 console.error(error.stack || error); | |
300 this.naclModule_.postMessage( | |
301 unpacker.request.createReadPassphraseErrorResponse( | |
302 this.fileSystemId_, requestId)); | |
303 // TODO(mtomasz): Instead of unmounting just let the current operation | |
304 // fail and ask for password for another files. This is however | |
305 // impossible for now due to a bug in libarchive. | |
306 unpacker.app.unmountVolume(this.fileSystemId_, true); | |
307 }.bind(this)); | |
308 }; | |
OLD | NEW |