OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium 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 * Represents each volume, such as "drive", "download directory", each "USB | |
9 * flush storage", or "mounted zip archive" etc. | |
10 * | |
11 * @param {util.VolumeType} volumeType The type of the volume. | |
12 * @param {string} volumeId ID of the volume. | |
13 * @param {DOMFileSystem} fileSystem The file system object for this volume. | |
14 * @param {string} error The error if an error is found. | |
15 * @param {string} deviceType The type of device ('usb'|'sd'|'optical'|'mobile' | |
16 * |'unknown') (as defined in chromeos/disks/disk_mount_manager.cc). | |
17 * Can be null. | |
18 * @param {boolean} isReadOnly True if the volume is read only. | |
19 * @param {!{displayName:string, isCurrentProfile:boolean}} profile Profile | |
20 * information. | |
21 * @param {string} label Label of the volume. | |
22 * @constructor | |
23 */ | |
24 function VolumeInfo( | |
25 volumeType, | |
26 volumeId, | |
27 fileSystem, | |
28 error, | |
29 deviceType, | |
30 isReadOnly, | |
31 profile, | |
32 label) { | |
33 this.volumeType_ = volumeType; | |
34 this.volumeId_ = volumeId; | |
35 this.fileSystem_ = fileSystem; | |
36 this.label_ = label; | |
37 this.displayRoot_ = null; | |
38 this.fakeEntries_ = {}; | |
39 this.displayRoot_ = null; | |
40 this.displayRootPromise_ = null; | |
41 | |
42 if (volumeType === util.VolumeType.DRIVE) { | |
43 // TODO(mtomasz): Convert fake entries to DirectoryProvider. | |
44 this.fakeEntries_[RootType.DRIVE_OFFLINE] = { | |
45 isDirectory: true, | |
46 rootType: RootType.DRIVE_OFFLINE, | |
47 toURL: function() { return 'fake-entry://drive_offline' } | |
48 }; | |
49 this.fakeEntries_[RootType.DRIVE_SHARED_WITH_ME] = { | |
50 isDirectory: true, | |
51 rootType: RootType.DRIVE_SHARED_WITH_ME, | |
52 toURL: function() { return 'fake-entry://drive_shared_with_me'; } | |
53 }; | |
54 this.fakeEntries_[RootType.DRIVE_RECENT] = { | |
55 isDirectory: true, | |
56 rootType: RootType.DRIVE_RECENT, | |
57 toURL: function() { return 'fake-entry://drive_recent'; } | |
58 }; | |
59 } | |
60 | |
61 // Note: This represents if the mounting of the volume is successfully done | |
62 // or not. (If error is empty string, the mount is successfully done). | |
63 // TODO(hidehiko): Rename to make this more understandable. | |
64 this.error_ = error; | |
65 this.deviceType_ = deviceType; | |
66 this.isReadOnly_ = isReadOnly; | |
67 this.profile_ = Object.freeze(profile); | |
68 | |
69 Object.seal(this); | |
70 } | |
71 | |
72 VolumeInfo.prototype = { | |
73 /** | |
74 * @return {util.VolumeType} Volume type. | |
75 */ | |
76 get volumeType() { | |
77 return this.volumeType_; | |
78 }, | |
79 /** | |
80 * @return {string} Volume ID. | |
81 */ | |
82 get volumeId() { | |
83 return this.volumeId_; | |
84 }, | |
85 /** | |
86 * @return {DOMFileSystem} File system object. | |
87 */ | |
88 get fileSystem() { | |
89 return this.fileSystem_; | |
90 }, | |
91 /** | |
92 * @return {DirectoryEntry} Display root path. It is null before finishing to | |
93 * resolve the entry. | |
94 */ | |
95 get displayRoot() { | |
96 return this.displayRoot_; | |
97 }, | |
98 /** | |
99 * @return {Object.<string, Object>} Fake entries. | |
100 */ | |
101 get fakeEntries() { | |
102 return this.fakeEntries_; | |
103 }, | |
104 /** | |
105 * @return {string} Error identifier. | |
106 */ | |
107 get error() { | |
108 return this.error_; | |
109 }, | |
110 /** | |
111 * @return {string} Device type identifier. | |
112 */ | |
113 get deviceType() { | |
114 return this.deviceType_; | |
115 }, | |
116 /** | |
117 * @return {boolean} Whether read only or not. | |
118 */ | |
119 get isReadOnly() { | |
120 return this.isReadOnly_; | |
121 }, | |
122 /** | |
123 * @return {!{displayName:string, isCurrentProfile:boolean}} Profile data. | |
124 */ | |
125 get profile() { | |
126 return this.profile_; | |
127 }, | |
128 /** | |
129 * @return {string} Label for the volume. | |
130 */ | |
131 get label() { | |
132 return this.label_; | |
133 } | |
134 }; | |
135 | |
136 /** | |
137 * Starts resolving the display root and obtains it. It may take long time for | |
138 * Drive. Once resolved, it is cached. | |
139 * | |
140 * @param {function(DirectoryEntry)} onSuccess Success callback with the | |
141 * display root directory as an argument. | |
142 * @param {function(FileError)} onFailure Failure callback. | |
143 */ | |
144 VolumeInfo.prototype.resolveDisplayRoot = function(onSuccess, onFailure) { | |
145 if (!this.displayRootPromise_) { | |
146 // TODO(mtomasz): Do not add VolumeInfo which failed to resolve root, and | |
147 // remove this if logic. Call onSuccess() always, instead. | |
148 if (this.volumeType !== util.VolumeType.DRIVE) { | |
149 if (this.fileSystem_) | |
150 this.displayRootPromise_ = Promise.resolve(this.fileSystem_.root); | |
151 else | |
152 this.displayRootPromise_ = Promise.reject(this.error); | |
153 } else { | |
154 // For Drive, we need to resolve. | |
155 var displayRootURL = this.fileSystem_.root.toURL() + '/root'; | |
156 this.displayRootPromise_ = new Promise( | |
157 webkitResolveLocalFileSystemURL.bind(null, displayRootURL)); | |
158 } | |
159 | |
160 // Store the obtained displayRoot. | |
161 this.displayRootPromise_.then(function(displayRoot) { | |
162 this.displayRoot_ = displayRoot; | |
163 }.bind(this)); | |
164 } | |
165 this.displayRootPromise_.then(onSuccess, onFailure); | |
166 }; | |
167 | |
168 /** | |
169 * Utilities for volume manager implementation. | |
170 */ | |
171 var volumeManagerUtil = {}; | |
172 | |
173 /** | |
174 * Throws an Error when the given error is not in util.VolumeError. | |
175 * @param {util.VolumeError} error Status string usually received from APIs. | |
176 */ | |
177 volumeManagerUtil.validateError = function(error) { | |
178 for (var key in util.VolumeError) { | |
179 if (error === util.VolumeError[key]) | |
180 return; | |
181 } | |
182 | |
183 throw new Error('Invalid mount error: ' + error); | |
184 }; | |
185 | |
186 /** | |
187 * Builds the VolumeInfo data from VolumeMetadata. | |
188 * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume. | |
189 * @param {function(VolumeInfo)} callback Called on completion. | |
190 */ | |
191 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) { | |
192 var localizedLabel; | |
193 switch (volumeMetadata.volumeType) { | |
194 case util.VolumeType.DOWNLOADS: | |
195 localizedLabel = str('DOWNLOADS_DIRECTORY_LABEL'); | |
196 break; | |
197 case util.VolumeType.DRIVE: | |
198 localizedLabel = str('DRIVE_DIRECTORY_LABEL'); | |
199 break; | |
200 default: | |
201 localizedLabel = volumeMetadata.volumeId.split(':', 2)[1]; | |
202 break; | |
203 } | |
204 | |
205 chrome.fileBrowserPrivate.requestFileSystem( | |
206 volumeMetadata.volumeId, | |
207 function(fileSystem) { | |
208 // TODO(mtomasz): chrome.runtime.lastError should have error reason. | |
209 if (!fileSystem) { | |
210 console.error('File system not found: ' + volumeMetadata.volumeId); | |
211 callback(new VolumeInfo( | |
212 volumeMetadata.volumeType, | |
213 volumeMetadata.volumeId, | |
214 null, // File system is not found. | |
215 volumeMetadata.mountCondition, | |
216 volumeMetadata.deviceType, | |
217 volumeMetadata.isReadOnly, | |
218 volumeMetadata.profile, | |
219 localizedLabel)); | |
220 return; | |
221 } | |
222 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) { | |
223 // After file system is mounted, we "read" drive grand root | |
224 // entry at first. This triggers full feed fetch on background. | |
225 // Note: we don't need to handle errors here, because even if | |
226 // it fails, accessing to some path later will just become | |
227 // a fast-fetch and it re-triggers full-feed fetch. | |
228 fileSystem.root.createReader().readEntries( | |
229 function() { /* do nothing */ }, | |
230 function(error) { | |
231 console.error( | |
232 'Triggering full feed fetch is failed: ' + error.name); | |
233 }); | |
234 } | |
235 callback(new VolumeInfo( | |
236 volumeMetadata.volumeType, | |
237 volumeMetadata.volumeId, | |
238 fileSystem, | |
239 volumeMetadata.mountCondition, | |
240 volumeMetadata.deviceType, | |
241 volumeMetadata.isReadOnly, | |
242 volumeMetadata.profile, | |
243 localizedLabel)); | |
244 }); | |
245 }; | |
246 | |
247 /** | |
248 * The order of the volume list based on root type. | |
249 * @type {Array.<util.VolumeType>} | |
250 * @const | |
251 * @private | |
252 */ | |
253 volumeManagerUtil.volumeListOrder_ = [ | |
254 util.VolumeType.DRIVE, | |
255 util.VolumeType.DOWNLOADS, | |
256 util.VolumeType.ARCHIVE, | |
257 util.VolumeType.REMOVABLE, | |
258 util.VolumeType.MTP, | |
259 util.VolumeType.CLOUD_DEVICE | |
260 ]; | |
261 | |
262 /** | |
263 * Orders two volumes by volumeType and volumeId. | |
264 * | |
265 * The volumes at first are compared by volume type in the order of | |
266 * volumeListOrder_. Then they are compared by volume ID. | |
267 * | |
268 * @param {VolumeInfo} volumeInfo1 Volume info to be compared. | |
269 * @param {VolumeInfo} volumeInfo2 Volume info to be compared. | |
270 * @return {number} Returns -1 if volume1 < volume2, returns 1 if volume2 > | |
271 * volume1, returns 0 if volume1 === volume2. | |
272 * @private | |
273 */ | |
274 volumeManagerUtil.compareVolumeInfo_ = function(volumeInfo1, volumeInfo2) { | |
275 var typeIndex1 = | |
276 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo1.volumeType); | |
277 var typeIndex2 = | |
278 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo2.volumeType); | |
279 if (typeIndex1 !== typeIndex2) | |
280 return typeIndex1 < typeIndex2 ? -1 : 1; | |
281 if (volumeInfo1.volumeId !== volumeInfo2.volumeId) | |
282 return volumeInfo1.volumeId < volumeInfo2.volumeId ? -1 : 1; | |
283 return 0; | |
284 }; | |
285 | |
286 /** | |
287 * The container of the VolumeInfo for each mounted volume. | |
288 * @constructor | |
289 */ | |
290 function VolumeInfoList() { | |
291 var field = 'volumeType,volumeId'; | |
292 | |
293 /** | |
294 * Holds VolumeInfo instances. | |
295 * @type {cr.ui.ArrayDataModel} | |
296 * @private | |
297 */ | |
298 this.model_ = new cr.ui.ArrayDataModel([]); | |
299 this.model_.setCompareFunction(field, volumeManagerUtil.compareVolumeInfo_); | |
300 this.model_.sort(field, 'asc'); | |
301 | |
302 Object.freeze(this); | |
303 } | |
304 | |
305 VolumeInfoList.prototype = { | |
306 get length() { return this.model_.length; } | |
307 }; | |
308 | |
309 /** | |
310 * Adds the event listener to listen the change of volume info. | |
311 * @param {string} type The name of the event. | |
312 * @param {function(Event)} handler The handler for the event. | |
313 */ | |
314 VolumeInfoList.prototype.addEventListener = function(type, handler) { | |
315 this.model_.addEventListener(type, handler); | |
316 }; | |
317 | |
318 /** | |
319 * Removes the event listener. | |
320 * @param {string} type The name of the event. | |
321 * @param {function(Event)} handler The handler to be removed. | |
322 */ | |
323 VolumeInfoList.prototype.removeEventListener = function(type, handler) { | |
324 this.model_.removeEventListener(type, handler); | |
325 }; | |
326 | |
327 /** | |
328 * Adds the volumeInfo to the appropriate position. If there already exists, | |
329 * just replaces it. | |
330 * @param {VolumeInfo} volumeInfo The information of the new volume. | |
331 */ | |
332 VolumeInfoList.prototype.add = function(volumeInfo) { | |
333 var index = this.findIndex(volumeInfo.volumeId); | |
334 if (index !== -1) | |
335 this.model_.splice(index, 1, volumeInfo); | |
336 else | |
337 this.model_.push(volumeInfo); | |
338 }; | |
339 | |
340 /** | |
341 * Removes the VolumeInfo having the given ID. | |
342 * @param {string} volumeId ID of the volume. | |
343 */ | |
344 VolumeInfoList.prototype.remove = function(volumeId) { | |
345 var index = this.findIndex(volumeId); | |
346 if (index !== -1) | |
347 this.model_.splice(index, 1); | |
348 }; | |
349 | |
350 /** | |
351 * Obtains an index from the volume ID. | |
352 * @param {string} volumeId Volume ID. | |
353 * @return {number} Index of the volume. | |
354 */ | |
355 VolumeInfoList.prototype.findIndex = function(volumeId) { | |
356 for (var i = 0; i < this.model_.length; i++) { | |
357 if (this.model_.item(i).volumeId === volumeId) | |
358 return i; | |
359 } | |
360 return -1; | |
361 }; | |
362 | |
363 /** | |
364 * Searches the information of the volume that contains the passed entry. | |
365 * @param {Entry|Object} entry Entry on the volume to be found. | |
366 * @return {VolumeInfo} The volume's information, or null if not found. | |
367 */ | |
368 VolumeInfoList.prototype.findByEntry = function(entry) { | |
369 for (var i = 0; i < this.length; i++) { | |
370 var volumeInfo = this.item(i); | |
371 if (volumeInfo.fileSystem && | |
372 util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) { | |
373 return volumeInfo; | |
374 } | |
375 // Additionally, check fake entries. | |
376 for (var key in volumeInfo.fakeEntries_) { | |
377 var fakeEntry = volumeInfo.fakeEntries_[key]; | |
378 if (util.isSameEntry(fakeEntry, entry)) | |
379 return volumeInfo; | |
380 } | |
381 } | |
382 return null; | |
383 }; | |
384 | |
385 /** | |
386 * @param {number} index The index of the volume in the list. | |
387 * @return {VolumeInfo} The VolumeInfo instance. | |
388 */ | |
389 VolumeInfoList.prototype.item = function(index) { | |
390 return this.model_.item(index); | |
391 }; | |
392 | |
393 /** | |
394 * VolumeManager is responsible for tracking list of mounted volumes. | |
395 * | |
396 * @constructor | |
397 * @extends {cr.EventTarget} | |
398 */ | |
399 function VolumeManager() { | |
400 /** | |
401 * The list of archives requested to mount. We will show contents once | |
402 * archive is mounted, but only for mounts from within this filebrowser tab. | |
403 * @type {Object.<string, Object>} | |
404 * @private | |
405 */ | |
406 this.requests_ = {}; | |
407 | |
408 /** | |
409 * The list of VolumeInfo instances for each mounted volume. | |
410 * @type {VolumeInfoList} | |
411 */ | |
412 this.volumeInfoList = new VolumeInfoList(); | |
413 | |
414 /** | |
415 * Queue for mounting. | |
416 * @type {AsyncUtil.Queue} | |
417 * @private | |
418 */ | |
419 this.mountQueue_ = new AsyncUtil.Queue(); | |
420 | |
421 // The status should be merged into VolumeManager. | |
422 // TODO(hidehiko): Remove them after the migration. | |
423 this.driveConnectionState_ = { | |
424 type: util.DriveConnectionType.OFFLINE, | |
425 reason: util.DriveConnectionReason.NO_SERVICE | |
426 }; | |
427 | |
428 chrome.fileBrowserPrivate.onDriveConnectionStatusChanged.addListener( | |
429 this.onDriveConnectionStatusChanged_.bind(this)); | |
430 this.onDriveConnectionStatusChanged_(); | |
431 } | |
432 | |
433 /** | |
434 * Invoked when the drive connection status is changed. | |
435 * @private_ | |
436 */ | |
437 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() { | |
438 chrome.fileBrowserPrivate.getDriveConnectionState(function(state) { | |
439 this.driveConnectionState_ = state; | |
440 cr.dispatchSimpleEvent(this, 'drive-connection-changed'); | |
441 }.bind(this)); | |
442 }; | |
443 | |
444 /** | |
445 * Returns the drive connection state. | |
446 * @return {util.DriveConnectionType} Connection type. | |
447 */ | |
448 VolumeManager.prototype.getDriveConnectionState = function() { | |
449 return this.driveConnectionState_; | |
450 }; | |
451 | |
452 /** | |
453 * VolumeManager extends cr.EventTarget. | |
454 */ | |
455 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype; | |
456 | |
457 /** | |
458 * Time in milliseconds that we wait a response for. If no response on | |
459 * mount/unmount received the request supposed failed. | |
460 */ | |
461 VolumeManager.TIMEOUT = 15 * 60 * 1000; | |
462 | |
463 /** | |
464 * Queue to run getInstance sequentially. | |
465 * @type {AsyncUtil.Queue} | |
466 * @private | |
467 */ | |
468 VolumeManager.getInstanceQueue_ = new AsyncUtil.Queue(); | |
469 | |
470 /** | |
471 * The singleton instance of VolumeManager. Initialized by the first invocation | |
472 * of getInstance(). | |
473 * @type {VolumeManager} | |
474 * @private | |
475 */ | |
476 VolumeManager.instance_ = null; | |
477 | |
478 /** | |
479 * Returns the VolumeManager instance asynchronously. If it is not created or | |
480 * under initialization, it will waits for the finish of the initialization. | |
481 * @param {function(VolumeManager)} callback Called with the VolumeManager | |
482 * instance. | |
483 */ | |
484 VolumeManager.getInstance = function(callback) { | |
485 VolumeManager.getInstanceQueue_.run(function(continueCallback) { | |
486 if (VolumeManager.instance_) { | |
487 callback(VolumeManager.instance_); | |
488 continueCallback(); | |
489 return; | |
490 } | |
491 | |
492 VolumeManager.instance_ = new VolumeManager(); | |
493 VolumeManager.instance_.initialize_(function() { | |
494 callback(VolumeManager.instance_); | |
495 continueCallback(); | |
496 }); | |
497 }); | |
498 }; | |
499 | |
500 /** | |
501 * Initializes mount points. | |
502 * @param {function()} callback Called upon the completion of the | |
503 * initialization. | |
504 * @private | |
505 */ | |
506 VolumeManager.prototype.initialize_ = function(callback) { | |
507 chrome.fileBrowserPrivate.getVolumeMetadataList(function(volumeMetadataList) { | |
508 // We must subscribe to the mount completed event in the callback of | |
509 // getVolumeMetadataList. crbug.com/330061. | |
510 // But volumes reported by onMountCompleted events must be added after the | |
511 // volumes in the volumeMetadataList are mounted. crbug.com/135477. | |
512 this.mountQueue_.run(function(inCallback) { | |
513 // Create VolumeInfo for each volume. | |
514 var group = new AsyncUtil.Group(); | |
515 for (var i = 0; i < volumeMetadataList.length; i++) { | |
516 group.add(function(volumeMetadata, continueCallback) { | |
517 volumeManagerUtil.createVolumeInfo( | |
518 volumeMetadata, | |
519 function(volumeInfo) { | |
520 this.volumeInfoList.add(volumeInfo); | |
521 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) | |
522 this.onDriveConnectionStatusChanged_(); | |
523 continueCallback(); | |
524 }.bind(this)); | |
525 }.bind(this, volumeMetadataList[i])); | |
526 } | |
527 group.run(function() { | |
528 // Call the callback of the initialize function. | |
529 callback(); | |
530 // Call the callback of AsyncQueue. Maybe it invokes callbacks | |
531 // registered by mountCompleted events. | |
532 inCallback(); | |
533 }); | |
534 }.bind(this)); | |
535 | |
536 chrome.fileBrowserPrivate.onMountCompleted.addListener( | |
537 this.onMountCompleted_.bind(this)); | |
538 }.bind(this)); | |
539 }; | |
540 | |
541 /** | |
542 * Event handler called when some volume was mounted or unmounted. | |
543 * @param {MountCompletedEvent} event Received event. | |
544 * @private | |
545 */ | |
546 VolumeManager.prototype.onMountCompleted_ = function(event) { | |
547 this.mountQueue_.run(function(callback) { | |
548 switch (event.eventType) { | |
549 case 'mount': | |
550 var requestKey = this.makeRequestKey_( | |
551 'mount', | |
552 event.volumeMetadata.sourcePath); | |
553 | |
554 var error = event.status === 'success' ? '' : event.status; | |
555 if (!error || event.status === 'error_unknown_filesystem') { | |
556 volumeManagerUtil.createVolumeInfo( | |
557 event.volumeMetadata, | |
558 function(volumeInfo) { | |
559 this.volumeInfoList.add(volumeInfo); | |
560 this.finishRequest_(requestKey, event.status, volumeInfo); | |
561 | |
562 if (volumeInfo.volumeType === util.VolumeType.DRIVE) { | |
563 // Update the network connection status, because until the | |
564 // drive is initialized, the status is set to not ready. | |
565 // TODO(mtomasz): The connection status should be migrated | |
566 // into VolumeMetadata. | |
567 this.onDriveConnectionStatusChanged_(); | |
568 } | |
569 callback(); | |
570 }.bind(this)); | |
571 } else { | |
572 console.warn('Failed to mount a volume: ' + event.status); | |
573 this.finishRequest_(requestKey, event.status); | |
574 callback(); | |
575 } | |
576 break; | |
577 | |
578 case 'unmount': | |
579 var volumeId = event.volumeMetadata.volumeId; | |
580 var status = event.status; | |
581 if (status === util.VolumeError.PATH_UNMOUNTED) { | |
582 console.warn('Volume already unmounted: ', volumeId); | |
583 status = 'success'; | |
584 } | |
585 var requestKey = this.makeRequestKey_('unmount', volumeId); | |
586 var requested = requestKey in this.requests_; | |
587 var volumeInfoIndex = | |
588 this.volumeInfoList.findIndex(volumeId); | |
589 var volumeInfo = volumeInfoIndex !== -1 ? | |
590 this.volumeInfoList.item(volumeInfoIndex) : null; | |
591 if (event.status === 'success' && !requested && volumeInfo) { | |
592 console.warn('Mounted volume without a request: ' + volumeId); | |
593 var e = new Event('externally-unmounted'); | |
594 e.volumeInfo = volumeInfo; | |
595 this.dispatchEvent(e); | |
596 } | |
597 | |
598 this.finishRequest_(requestKey, status); | |
599 if (event.status === 'success') | |
600 this.volumeInfoList.remove(event.volumeMetadata.volumeId); | |
601 callback(); | |
602 break; | |
603 } | |
604 }.bind(this)); | |
605 }; | |
606 | |
607 /** | |
608 * Creates string to match mount events with requests. | |
609 * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by | |
610 * enum. | |
611 * @param {string} argument Argument describing the request, eg. source file | |
612 * path of the archive to be mounted, or a volumeId for unmounting. | |
613 * @return {string} Key for |this.requests_|. | |
614 * @private | |
615 */ | |
616 VolumeManager.prototype.makeRequestKey_ = function(requestType, argument) { | |
617 return requestType + ':' + argument; | |
618 }; | |
619 | |
620 /** | |
621 * @param {string} fileUrl File url to the archive file. | |
622 * @param {function(VolumeInfo)} successCallback Success callback. | |
623 * @param {function(util.VolumeError)} errorCallback Error callback. | |
624 */ | |
625 VolumeManager.prototype.mountArchive = function( | |
626 fileUrl, successCallback, errorCallback) { | |
627 chrome.fileBrowserPrivate.addMount(fileUrl, function(sourcePath) { | |
628 console.info( | |
629 'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath); | |
630 var requestKey = this.makeRequestKey_('mount', sourcePath); | |
631 this.startRequest_(requestKey, successCallback, errorCallback); | |
632 }.bind(this)); | |
633 }; | |
634 | |
635 /** | |
636 * Unmounts volume. | |
637 * @param {!VolumeInfo} volumeInfo Volume to be unmounted. | |
638 * @param {function()} successCallback Success callback. | |
639 * @param {function(util.VolumeError)} errorCallback Error callback. | |
640 */ | |
641 VolumeManager.prototype.unmount = function(volumeInfo, | |
642 successCallback, | |
643 errorCallback) { | |
644 chrome.fileBrowserPrivate.removeMount(volumeInfo.volumeId); | |
645 var requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId); | |
646 this.startRequest_(requestKey, successCallback, errorCallback); | |
647 }; | |
648 | |
649 /** | |
650 * Obtains a volume info containing the passed entry. | |
651 * @param {Entry|Object} entry Entry on the volume to be returned. Can be fake. | |
652 * @return {VolumeInfo} The VolumeInfo instance or null if not found. | |
653 */ | |
654 VolumeManager.prototype.getVolumeInfo = function(entry) { | |
655 return this.volumeInfoList.findByEntry(entry); | |
656 }; | |
657 | |
658 /** | |
659 * Obtains volume information of the current profile. | |
660 * | |
661 * @param {util.VolumeType} volumeType Volume type. | |
662 * @return {VolumeInfo} Volume info. | |
663 */ | |
664 VolumeManager.prototype.getCurrentProfileVolumeInfo = function(volumeType) { | |
665 for (var i = 0; i < this.volumeInfoList.length; i++) { | |
666 var volumeInfo = this.volumeInfoList.item(i); | |
667 if (volumeInfo.profile.isCurrentProfile && | |
668 volumeInfo.volumeType === volumeType) | |
669 return volumeInfo; | |
670 } | |
671 return null; | |
672 }; | |
673 | |
674 /** | |
675 * Obtains location information from an entry. | |
676 * | |
677 * @param {Entry|Object} entry File or directory entry. It can be a fake entry. | |
678 * @return {EntryLocation} Location information. | |
679 */ | |
680 VolumeManager.prototype.getLocationInfo = function(entry) { | |
681 var volumeInfo = this.volumeInfoList.findByEntry(entry); | |
682 if (!volumeInfo) | |
683 return null; | |
684 | |
685 if (util.isFakeEntry(entry)) { | |
686 return new EntryLocation( | |
687 volumeInfo, | |
688 entry.rootType, | |
689 true /* the entry points a root directory. */, | |
690 true /* fake entries are read only. */); | |
691 } | |
692 | |
693 var rootType; | |
694 var isReadOnly; | |
695 var isRootEntry; | |
696 if (volumeInfo.volumeType === util.VolumeType.DRIVE) { | |
697 // For Drive, the roots are /root and /other, instead of /. Root URLs | |
698 // contain trailing slashes. | |
699 if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) { | |
700 rootType = RootType.DRIVE; | |
701 isReadOnly = volumeInfo.isReadOnly; | |
702 isRootEntry = entry.fullPath === '/root'; | |
703 } else if (entry.fullPath == '/other' || | |
704 entry.fullPath.indexOf('/other/') === 0) { | |
705 rootType = RootType.DRIVE_OTHER; | |
706 isReadOnly = true; | |
707 isRootEntry = entry.fullPath === '/other'; | |
708 } else { | |
709 // Accessing Drive files outside of /drive/root and /drive/other is not | |
710 // allowed, but can happen. Therefore returning null. | |
711 return null; | |
712 } | |
713 } else { | |
714 switch (volumeInfo.volumeType) { | |
715 case util.VolumeType.DOWNLOADS: | |
716 rootType = RootType.DOWNLOADS; | |
717 break; | |
718 case util.VolumeType.REMOVABLE: | |
719 rootType = RootType.REMOVABLE; | |
720 break; | |
721 case util.VolumeType.ARCHIVE: | |
722 rootType = RootType.ARCHIVE; | |
723 break; | |
724 case util.VolumeType.CLOUD_DEVICE: | |
725 rootType = RootType.CLOUD_DEVICE; | |
726 break; | |
727 case util.VolumeType.MTP: | |
728 rootType = RootType.MTP; | |
729 break; | |
730 default: | |
731 // Programming error, throw an exception. | |
732 throw new Error('Invalid volume type: ' + volumeInfo.volumeType); | |
733 } | |
734 isReadOnly = volumeInfo.isReadOnly; | |
735 isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root); | |
736 } | |
737 | |
738 return new EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly); | |
739 }; | |
740 | |
741 /** | |
742 * @param {string} key Key produced by |makeRequestKey_|. | |
743 * @param {function(VolumeInfo)} successCallback To be called when request | |
744 * finishes successfully. | |
745 * @param {function(util.VolumeError)} errorCallback To be called when | |
746 * request fails. | |
747 * @private | |
748 */ | |
749 VolumeManager.prototype.startRequest_ = function(key, | |
750 successCallback, errorCallback) { | |
751 if (key in this.requests_) { | |
752 var request = this.requests_[key]; | |
753 request.successCallbacks.push(successCallback); | |
754 request.errorCallbacks.push(errorCallback); | |
755 } else { | |
756 this.requests_[key] = { | |
757 successCallbacks: [successCallback], | |
758 errorCallbacks: [errorCallback], | |
759 | |
760 timeout: setTimeout(this.onTimeout_.bind(this, key), | |
761 VolumeManager.TIMEOUT) | |
762 }; | |
763 } | |
764 }; | |
765 | |
766 /** | |
767 * Called if no response received in |TIMEOUT|. | |
768 * @param {string} key Key produced by |makeRequestKey_|. | |
769 * @private | |
770 */ | |
771 VolumeManager.prototype.onTimeout_ = function(key) { | |
772 this.invokeRequestCallbacks_(this.requests_[key], | |
773 util.VolumeError.TIMEOUT); | |
774 delete this.requests_[key]; | |
775 }; | |
776 | |
777 /** | |
778 * @param {string} key Key produced by |makeRequestKey_|. | |
779 * @param {util.VolumeError|'success'} status Status received from the API. | |
780 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume. | |
781 * @private | |
782 */ | |
783 VolumeManager.prototype.finishRequest_ = function(key, status, opt_volumeInfo) { | |
784 var request = this.requests_[key]; | |
785 if (!request) | |
786 return; | |
787 | |
788 clearTimeout(request.timeout); | |
789 this.invokeRequestCallbacks_(request, status, opt_volumeInfo); | |
790 delete this.requests_[key]; | |
791 }; | |
792 | |
793 /** | |
794 * @param {Object} request Structure created in |startRequest_|. | |
795 * @param {util.VolumeError|string} status If status === 'success' | |
796 * success callbacks are called. | |
797 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume. | |
798 * @private | |
799 */ | |
800 VolumeManager.prototype.invokeRequestCallbacks_ = function( | |
801 request, status, opt_volumeInfo) { | |
802 var callEach = function(callbacks, self, args) { | |
803 for (var i = 0; i < callbacks.length; i++) { | |
804 callbacks[i].apply(self, args); | |
805 } | |
806 }; | |
807 if (status === 'success') { | |
808 callEach(request.successCallbacks, this, [opt_volumeInfo]); | |
809 } else { | |
810 volumeManagerUtil.validateError(status); | |
811 callEach(request.errorCallbacks, this, [status]); | |
812 } | |
813 }; | |
814 | |
815 /** | |
816 * Location information which shows where the path points in FileManager's | |
817 * file system. | |
818 * | |
819 * @param {!VolumeInfo} volumeInfo Volume information. | |
820 * @param {RootType} rootType Root type. | |
821 * @param {boolean} isRootEntry Whether the entry is root entry or not. | |
822 * @param {boolean} isReadOnly Whether the entry is read only or not. | |
823 * @constructor | |
824 */ | |
825 function EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly) { | |
826 /** | |
827 * Volume information. | |
828 * @type {!VolumeInfo} | |
829 */ | |
830 this.volumeInfo = volumeInfo; | |
831 | |
832 /** | |
833 * Root type. | |
834 * @type {RootType} | |
835 */ | |
836 this.rootType = rootType; | |
837 | |
838 /** | |
839 * Whether the entry is root entry or not. | |
840 * @type {boolean} | |
841 */ | |
842 this.isRootEntry = isRootEntry; | |
843 | |
844 /** | |
845 * Whether the location obtained from the fake entry correspond to special | |
846 * searches. | |
847 * @type {boolean} | |
848 */ | |
849 this.isSpecialSearchRoot = | |
850 this.rootType === RootType.DRIVE_OFFLINE || | |
851 this.rootType === RootType.DRIVE_SHARED_WITH_ME || | |
852 this.rootType === RootType.DRIVE_RECENT; | |
853 | |
854 /** | |
855 * Whether the location is under Google Drive or a special search root which | |
856 * represents a special search from Google Drive. | |
857 * @type {boolean} | |
858 */ | |
859 this.isDriveBased = | |
860 this.rootType === RootType.DRIVE || | |
861 this.rootType === RootType.DRIVE_OTHER || | |
862 this.rootType === RootType.DRIVE_SHARED_WITH_ME || | |
863 this.rootType === RootType.DRIVE_RECENT || | |
864 this.rootType === RootType.DRIVE_OFFLINE; | |
865 | |
866 /** | |
867 * Whether the given path can be a target path of folder shortcut. | |
868 * @type {boolean} | |
869 */ | |
870 this.isEligibleForFolderShortcut = | |
871 !this.isSpecialSearchRoot && | |
872 !this.isRootEntry && | |
873 this.isDriveBased; | |
874 | |
875 /** | |
876 * Whether the entry is read only or not. | |
877 * @type {boolean} | |
878 */ | |
879 this.isReadOnly = isReadOnly; | |
880 | |
881 Object.freeze(this); | |
882 } | |
OLD | NEW |