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 * Converts a c/c++ time_t variable to Date. | |
9 * @param {number} timestamp A c/c++ time_t variable. | |
10 * @return {!Date} | |
11 */ | |
12 function DateFromTimeT(timestamp) { | |
13 return new Date(1000 * timestamp); | |
14 } | |
15 | |
16 /** | |
17 * Corrects metadata entries fields in order for them to be sent to Files.app. | |
18 * This function runs recursively for every entry in a directory. | |
19 * @param {!Object<string, !EntryMetadata>} entryMetadata The metadata to | |
20 * correct. | |
21 */ | |
22 function correctMetadata(entryMetadata) { | |
23 entryMetadata.index = parseInt(entryMetadata.index, 10); | |
24 entryMetadata.size = parseInt(entryMetadata.size, 10); | |
25 entryMetadata.modificationTime = | |
26 DateFromTimeT(entryMetadata.modificationTime); | |
27 if (entryMetadata.isDirectory) { | |
28 console.assert(entryMetadata.entries, | |
29 'The field "entries" is mandatory for dictionaries.'); | |
30 for (var entry in entryMetadata.entries) { | |
31 correctMetadata(entryMetadata.entries[entry]); | |
32 } | |
33 } | |
34 } | |
35 | |
36 /** | |
37 * Defines a volume object that contains information about archives' contents | |
38 * and performs operations on these contents. | |
39 * @constructor | |
40 * @param {!unpacker.Decompressor} decompressor The decompressor used to obtain | |
41 * data from archives. | |
42 * @param {!Entry} entry The entry corresponding to the volume's archive. | |
43 */ | |
44 unpacker.Volume = function(decompressor, entry) { | |
45 /** | |
46 * Used for restoring the opened file entry after resuming the event page. | |
47 * @type {!Entry} | |
48 */ | |
49 this.entry = entry; | |
50 | |
51 /** | |
52 * @type {!unpacker.Decompressor} | |
53 */ | |
54 this.decompressor = decompressor; | |
55 | |
56 /** | |
57 * The volume's metadata. The key is the full path to the file on this volume. | |
58 * For more details see | |
59 * https://developer.chrome.com/apps/fileSystemProvider#type-EntryMetadata | |
60 * @type {?Object<string, !EntryMetadata>} | |
61 */ | |
62 this.metadata = null; | |
63 | |
64 /** | |
65 * A map with currently opened files. The key is a requestId value from the | |
66 * openFileRequested event and the value is the open file options. | |
67 * @type {!Object<!unpacker.types.RequestId, | |
68 * !unpacker.types.OpenFileRequestedOptions>} | |
69 */ | |
70 this.openedFiles = {}; | |
71 | |
72 /** | |
73 * Default encoding set for this archive. If empty, then not known. | |
74 * @type {string} | |
75 */ | |
76 this.encoding = | |
77 unpacker.Volume.ENCODING_TABLE[chrome.i18n.getUILanguage()] || ''; | |
78 | |
79 /** | |
80 * The default read metadata request id. -1 is ok as the request ids used by | |
81 * flleSystemProvider are greater than 0. | |
82 * @type {number} | |
83 */ | |
84 this.DEFAULT_READ_METADATA_REQUEST_ID = -1; | |
85 }; | |
86 | |
87 /** | |
88 * The default read metadata request id. -1 is ok as the request ids used by | |
89 * flleSystemProvider are greater than 0. | |
90 * @const {number} | |
91 */ | |
92 unpacker.Volume.DEFAULT_READ_METADATA_REQUEST_ID = -1; | |
93 | |
94 /** | |
95 * Map from language codes to default charset encodings. | |
96 * @const {!Object<string, string>} | |
97 */ | |
98 unpacker.Volume.ENCODING_TABLE = { | |
99 ar: 'CP1256', | |
100 bg: 'CP1251', | |
101 ca: 'CP1252', | |
102 cs: 'CP1250', | |
103 da: 'CP1252', | |
104 de: 'CP1252', | |
105 el: 'CP1253', | |
106 en: 'CP1250', | |
107 en_GB: 'CP1250', | |
108 es: 'CP1252', | |
109 es_419: 'CP1252', | |
110 et: 'CP1257', | |
111 fa: 'CP1256', | |
112 fi: 'CP1252', | |
113 fil: 'CP1252', | |
114 fr: 'CP1252', | |
115 he: 'CP1255', | |
116 hi: 'UTF-8', // Another one may be better. | |
117 hr: 'CP1250', | |
118 hu: 'CP1250', | |
119 id: 'CP1252', | |
120 it: 'CP1252', | |
121 ja: 'CP932', // Alternatively SHIFT-JIS. | |
122 ko: 'CP949', // Alternatively EUC-KR. | |
123 lt: 'CP1257', | |
124 lv: 'CP1257', | |
125 ms: 'CP1252', | |
126 nl: 'CP1252', | |
127 no: 'CP1252', | |
128 pl: 'CP1250', | |
129 pt_BR: 'CP1252', | |
130 pt_PT: 'CP1252', | |
131 ro: 'CP1250', | |
132 ru: 'CP1251', | |
133 sk: 'CP1250', | |
134 sl: 'CP1250', | |
135 sr: 'CP1251', | |
136 sv: 'CP1252', | |
137 th: 'CP874', // Confirm! | |
138 tr: 'CP1254', | |
139 uk: 'CP1251', | |
140 vi: 'CP1258', | |
141 zh_CN: 'CP936', | |
142 zh_TW: 'CP950' | |
143 }; | |
144 | |
145 /** | |
146 * @return {boolean} True if volume is ready to be used. | |
147 */ | |
148 unpacker.Volume.prototype.isReady = function() { | |
149 return !!this.metadata; | |
150 }; | |
151 | |
152 /** | |
153 * @return {boolean} True if volume is in use. | |
154 */ | |
155 unpacker.Volume.prototype.inUse = function() { | |
156 return this.decompressor.hasRequestsInProgress() || | |
157 Object.keys(this.openedFiles).length > 0; | |
158 }; | |
159 | |
160 /** | |
161 * Initializes the volume by reading its metadata. | |
162 * @param {function()} onSuccess Callback to execute on success. | |
163 * @param {function(!ProviderError)} onError Callback to execute on error. | |
164 */ | |
165 unpacker.Volume.prototype.initialize = function(onSuccess, onError) { | |
166 var requestId = unpacker.Volume.DEFAULT_READ_METADATA_REQUEST_ID; | |
167 this.decompressor.readMetadata(requestId, this.encoding, function(metadata) { | |
168 // Make a deep copy of metadata. | |
169 this.metadata = /** @type {!Object<string, !EntryMetadata>} */ (JSON.parse( | |
170 JSON.stringify(metadata))); | |
171 correctMetadata(this.metadata); | |
172 | |
173 onSuccess(); | |
174 }.bind(this), onError); | |
175 }; | |
176 | |
177 /** | |
178 * Obtains the metadata for a single entry in the archive. Assumes metadata is | |
179 * loaded. | |
180 * @param {!unpacker.types.GetMetadataRequestedOptions} options Options for | |
181 * getting the metadata of an entry. | |
182 * @param {function(!EntryMetadata)} onSuccess Callback to execute on success. | |
183 * @param {function(!ProviderError)} onError Callback to execute on error. | |
184 */ | |
185 unpacker.Volume.prototype.onGetMetadataRequested = function(options, onSuccess, | |
186 onError) { | |
187 console.assert(this.isReady(), 'Metadata must be loaded.'); | |
188 var entryMetadata = this.getEntryMetadata_(options.entryPath); | |
189 if (!entryMetadata) | |
190 onError('NOT_FOUND'); | |
191 else | |
192 onSuccess(entryMetadata); | |
193 }; | |
194 | |
195 /** | |
196 * Reads a directory contents from metadata. Assumes metadata is loaded. | |
197 * @param {!unpacker.types.ReadDirectoryRequestedOptions} options Options | |
198 * for reading the contents of a directory. | |
199 * @param {function(!Array<!EntryMetadata>, boolean)} onSuccess Callback to | |
200 * execute on success. | |
201 * @param {function(!ProviderError)} onError Callback to execute on error. | |
202 */ | |
203 unpacker.Volume.prototype.onReadDirectoryRequested = function( | |
204 options, onSuccess, onError) { | |
205 console.assert(this.isReady(), 'Metadata must be loaded.'); | |
206 var directoryMetadata = this.getEntryMetadata_(options.directoryPath); | |
207 if (!directoryMetadata) { | |
208 onError('NOT_FOUND'); | |
209 return; | |
210 } | |
211 if (!directoryMetadata.isDirectory) { | |
212 onError('NOT_A_DIRECTORY'); | |
213 return; | |
214 } | |
215 | |
216 // Convert dictionary entries to an array. | |
217 var entries = []; | |
218 for (var entry in directoryMetadata.entries) { | |
219 entries.push(directoryMetadata.entries[entry]); | |
220 } | |
221 | |
222 onSuccess(entries, false /* Last call. */); | |
223 }; | |
224 | |
225 /** | |
226 * Opens a file for read or write. | |
227 * @param {!unpacker.types.OpenFileRequestedOptions} options Options for | |
228 * opening a file. | |
229 * @param {function()} onSuccess Callback to execute on success. | |
230 * @param {function(!ProviderError)} onError Callback to execute on error. | |
231 */ | |
232 unpacker.Volume.prototype.onOpenFileRequested = function(options, onSuccess, | |
233 onError) { | |
234 console.assert(this.isReady(), 'Metadata must be loaded.'); | |
235 if (options.mode != 'READ') { | |
236 onError('INVALID_OPERATION'); | |
237 return; | |
238 } | |
239 | |
240 var metadata = this.getEntryMetadata_(options.filePath); | |
241 if (!metadata) { | |
242 onError('NOT_FOUND'); | |
243 return; | |
244 } | |
245 | |
246 this.openedFiles[options.requestId] = options; | |
247 | |
248 this.decompressor.openFile( | |
249 options.requestId, metadata.index, this.encoding, function() { | |
250 onSuccess(); | |
251 }.bind(this), function(error) { | |
252 delete this.openedFiles[options.requestId]; | |
253 onError('FAILED'); | |
254 }.bind(this)); | |
255 }; | |
256 | |
257 /** | |
258 * Closes a file identified by options.openRequestId. | |
259 * @param {!unpacker.types.CloseFileRequestedOptions} options Options for | |
260 * closing a file. | |
261 * @param {function()} onSuccess Callback to execute on success. | |
262 * @param {function(!ProviderError)} onError Callback to execute on error. | |
263 */ | |
264 unpacker.Volume.prototype.onCloseFileRequested = function(options, onSuccess, | |
265 onError) { | |
266 console.assert(this.isReady(), 'Metadata must be loaded.'); | |
267 var openRequestId = options.openRequestId; | |
268 var openOptions = this.openedFiles[openRequestId]; | |
269 if (!openOptions) { | |
270 onError('INVALID_OPERATION'); | |
271 return; | |
272 } | |
273 | |
274 this.decompressor.closeFile(options.requestId, openRequestId, function() { | |
275 delete this.openedFiles[openRequestId]; | |
276 onSuccess(); | |
277 }.bind(this), onError); | |
278 }; | |
279 | |
280 /** | |
281 * Reads the contents of a file identified by options.openRequestId. | |
282 * @param {!unpacker.types.ReadFileRequestedOptions} options Options for | |
283 * reading a file's contents. | |
284 * @param {function(!ArrayBuffer, boolean)} onSuccess Callback to execute on | |
285 * success. | |
286 * @param {function(!ProviderError)} onError Callback to execute on error. | |
287 */ | |
288 unpacker.Volume.prototype.onReadFileRequested = function(options, onSuccess, | |
289 onError) { | |
290 console.assert(this.isReady(), 'Metadata must be loaded.'); | |
291 var openOptions = this.openedFiles[options.openRequestId]; | |
292 if (!openOptions) { | |
293 onError('INVALID_OPERATION'); | |
294 return; | |
295 } | |
296 | |
297 var offset = options.offset; | |
298 var length = options.length; | |
299 // Offset and length should be validated by the API. | |
300 console.assert(offset >= 0, 'Offset should be >= 0.'); | |
301 console.assert(length >= 0, 'Length should be >= 0.'); | |
302 | |
303 var fileSize = this.getEntryMetadata_(openOptions.filePath).size; | |
304 if (offset >= fileSize || length == 0) { // No more data. | |
305 onSuccess(new ArrayBuffer(0), false /* Last call. */); | |
306 return; | |
307 } | |
308 length = Math.min(length, fileSize - offset); | |
309 | |
310 this.decompressor.readFile(options.requestId, options.openRequestId, | |
311 offset, length, onSuccess, onError); | |
312 }; | |
313 | |
314 /** | |
315 * Gets the metadata for an entry based on its path. | |
316 * @param {string} entryPath The full path to the entry. | |
317 * @return {?Object} The correspondent metadata. | |
318 * @private | |
319 */ | |
320 unpacker.Volume.prototype.getEntryMetadata_ = function(entryPath) { | |
321 var pathArray = entryPath.split('/'); | |
322 | |
323 // Remove empty strings resulted after split. As paths start with '/' we will | |
324 // have an empty string at the beginning of pathArray and possible an | |
325 // empty string at the end for directories (e.g. /path/to/dir/). The code | |
326 // assumes entryPath cannot have consecutive '/'. | |
327 pathArray.splice(0, 1); | |
328 | |
329 if (pathArray.length > 0) { // In case of 0 this is root directory. | |
330 var lastIndex = pathArray.length - 1; | |
331 if (pathArray[lastIndex] == '') | |
332 pathArray.splice(lastIndex); | |
333 } | |
334 | |
335 // Get the actual metadata by iterating through every directory metadata | |
336 // on the path to the entry. | |
337 var entryMetadata = this.metadata; | |
338 for (var i = 0, limit = pathArray.length; i < limit; i++) { | |
339 if (!entryMetadata || | |
340 !entryMetadata.isDirectory && i != limit - 1 /* Parent directory. */) | |
341 return null; | |
342 entryMetadata = entryMetadata.entries[pathArray[i]]; | |
343 } | |
344 | |
345 return entryMetadata; | |
346 }; | |
OLD | NEW |