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.
6 * Represents each volume, such as "drive", "download directory", each "USB
7 * flush storage", or "mounted zip archive" etc.
9 * @param {VolumeManagerCommon.VolumeType} volumeType The type of the volume.
10 * @param {string} volumeId ID of the volume.
11 * @param {FileSystem} fileSystem The file system object for this volume.
12 * @param {(string|undefined)} error The error if an error is found.
13 * @param {(string|undefined)} deviceType The type of device
14 * ('usb'|'sd'|'optical'|'mobile'|'unknown') (as defined in
15 * chromeos/disks/disk_mount_manager.cc). Can be undefined.
16 * @param {(string|undefined)} devicePath Identifier of the device that the
17 * volume belongs to. Can be undefined.
18 * @param {boolean} isReadOnly True if the volume is read only.
19 * @param {!{displayName:string, isCurrentProfile:boolean}} profile Profile
21 * @param {string} label Label of the volume.
22 * @param {(string|undefined)} extensionId Id of the extension providing this
23 * volume. Empty for native volumes.
37 this.volumeType_ = volumeType;
38 this.volumeId_ = volumeId;
39 this.fileSystem_ = fileSystem;
41 this.displayRoot_ = null;
42 this.fakeEntries_ = {};
43 this.displayRoot_ = null;
44 this.displayRootPromise_ = null;
46 if (volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
47 // TODO(mtomasz): Convert fake entries to DirectoryProvider.
48 this.fakeEntries_[VolumeManagerCommon.RootType.DRIVE_OFFLINE] = {
50 rootType: VolumeManagerCommon.RootType.DRIVE_OFFLINE,
51 toURL: function() { return 'fake-entry://drive_offline'; }
53 this.fakeEntries_[VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME] = {
55 rootType: VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME,
56 toURL: function() { return 'fake-entry://drive_shared_with_me'; }
58 this.fakeEntries_[VolumeManagerCommon.RootType.DRIVE_RECENT] = {
60 rootType: VolumeManagerCommon.RootType.DRIVE_RECENT,
61 toURL: function() { return 'fake-entry://drive_recent'; }
65 // Note: This represents if the mounting of the volume is successfully done
66 // or not. (If error is empty string, the mount is successfully done).
67 // TODO(hidehiko): Rename to make this more understandable.
69 this.deviceType_ = deviceType;
70 this.devicePath_ = devicePath;
71 this.isReadOnly_ = isReadOnly;
72 this.profile_ = Object.freeze(profile);
73 this.extensionId_ = extensionId;
78 VolumeInfo.prototype = {
80 * @return {VolumeManagerCommon.VolumeType} Volume type.
83 return this.volumeType_;
86 * @return {string} Volume ID.
89 return this.volumeId_;
92 * @return {FileSystem} File system object.
95 return this.fileSystem_;
98 * @return {DirectoryEntry} Display root path. It is null before finishing to
102 return this.displayRoot_;
105 * @return {Object.<string, Object>} Fake entries.
108 return this.fakeEntries_;
111 * @return {(string|undefined)} Error identifier.
117 * @return {(string|undefined)} Device type identifier.
120 return this.deviceType_;
123 * @return {(string|undefined)} Device identifier.
126 return this.devicePath_;
129 * @return {boolean} Whether read only or not.
132 return this.isReadOnly_;
135 * @return {!{displayName:string, isCurrentProfile:boolean}} Profile data.
138 return this.profile_;
141 * @return {string} Label for the volume.
147 * @return {(string|undefined)} Id of an extennsion providing this volume.
150 return this.extensionId_;
155 * Starts resolving the display root and obtains it. It may take long time for
156 * Drive. Once resolved, it is cached.
158 * @param {function(DirectoryEntry)=} opt_onSuccess Success callback with the
159 * display root directory as an argument.
160 * @param {function(*)=} opt_onFailure Failure callback.
163 VolumeInfo.prototype.resolveDisplayRoot = function(opt_onSuccess,
165 if (!this.displayRootPromise_) {
166 // TODO(mtomasz): Do not add VolumeInfo which failed to resolve root, and
167 // remove this if logic. Call opt_onSuccess() always, instead.
168 if (this.volumeType !== VolumeManagerCommon.VolumeType.DRIVE) {
169 if (this.fileSystem_)
170 this.displayRootPromise_ = Promise.resolve(this.fileSystem_.root);
172 this.displayRootPromise_ = Promise.reject(this.error);
174 // For Drive, we need to resolve.
175 var displayRootURL = this.fileSystem_.root.toURL() + '/root';
176 this.displayRootPromise_ = new Promise(
177 window.webkitResolveLocalFileSystemURL.bind(null, displayRootURL));
180 // Store the obtained displayRoot.
181 this.displayRootPromise_.then(function(displayRoot) {
182 this.displayRoot_ = displayRoot;
186 this.displayRootPromise_.then(opt_onSuccess, opt_onFailure);
187 return this.displayRootPromise_;
191 * Utilities for volume manager implementation.
193 var volumeManagerUtil = {};
196 * Throws an Error when the given error is not in
197 * VolumeManagerCommon.VolumeError.
199 * @param {string} error Status string usually received from APIs.
201 volumeManagerUtil.validateError = function(error) {
202 for (var key in VolumeManagerCommon.VolumeError) {
203 if (error === VolumeManagerCommon.VolumeError[key])
207 throw new Error('Invalid mount error: ' + error);
211 * Builds the VolumeInfo data from VolumeMetadata.
212 * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume.
213 * @param {function(VolumeInfo)} callback Called on completion.
215 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) {
217 switch (volumeMetadata.volumeType) {
218 case VolumeManagerCommon.VolumeType.DOWNLOADS:
219 localizedLabel = str('DOWNLOADS_DIRECTORY_LABEL');
221 case VolumeManagerCommon.VolumeType.DRIVE:
222 localizedLabel = str('DRIVE_DIRECTORY_LABEL');
225 // TODO(mtomasz): Calculate volumeLabel for all types of volumes in the
227 localizedLabel = volumeMetadata.volumeLabel ||
228 volumeMetadata.volumeId.split(':', 2)[1];
232 chrome.fileManagerPrivate.requestFileSystem(
233 volumeMetadata.volumeId,
234 function(fileSystem) {
235 // TODO(mtomasz): chrome.runtime.lastError should have error reason.
237 console.error('File system not found: ' + volumeMetadata.volumeId);
238 callback(new VolumeInfo(
239 /** @type {VolumeManagerCommon.VolumeType} */
240 (volumeMetadata.volumeType),
241 volumeMetadata.volumeId,
242 null, // File system is not found.
243 volumeMetadata.mountCondition,
244 volumeMetadata.deviceType,
245 volumeMetadata.devicePath,
246 volumeMetadata.isReadOnly,
247 volumeMetadata.profile,
249 volumeMetadata.extensionId));
252 if (volumeMetadata.volumeType ==
253 VolumeManagerCommon.VolumeType.DRIVE) {
254 // After file system is mounted, we "read" drive grand root
255 // entry at first. This triggers full feed fetch on background.
256 // Note: we don't need to handle errors here, because even if
257 // it fails, accessing to some path later will just become
258 // a fast-fetch and it re-triggers full-feed fetch.
259 fileSystem.root.createReader().readEntries(
260 function() { /* do nothing */ },
263 'Triggering full feed fetch is failed: ' + error.name);
266 callback(new VolumeInfo(
267 /** @type {VolumeManagerCommon.VolumeType} */
268 (volumeMetadata.volumeType),
269 volumeMetadata.volumeId,
271 volumeMetadata.mountCondition,
272 volumeMetadata.deviceType,
273 volumeMetadata.devicePath,
274 volumeMetadata.isReadOnly,
275 volumeMetadata.profile,
277 volumeMetadata.extensionId));
282 * The order of the volume list based on root type.
283 * @type {Array.<VolumeManagerCommon.VolumeType>}
287 volumeManagerUtil.volumeListOrder_ = [
288 VolumeManagerCommon.VolumeType.DRIVE,
289 VolumeManagerCommon.VolumeType.DOWNLOADS,
290 VolumeManagerCommon.VolumeType.ARCHIVE,
291 VolumeManagerCommon.VolumeType.REMOVABLE,
292 VolumeManagerCommon.VolumeType.MTP,
293 VolumeManagerCommon.VolumeType.PROVIDED,
294 VolumeManagerCommon.VolumeType.CLOUD_DEVICE
298 * Orders two volumes by volumeType and volumeId.
300 * The volumes at first are compared by volume type in the order of
301 * volumeListOrder_. Then they are compared by volume ID.
303 * @param {!VolumeInfo} volumeInfo1 Volume info to be compared.
304 * @param {!VolumeInfo} volumeInfo2 Volume info to be compared.
305 * @return {number} Returns -1 if volume1 < volume2, returns 1 if volume2 >
306 * volume1, returns 0 if volume1 === volume2.
309 volumeManagerUtil.compareVolumeInfo_ = function(volumeInfo1, volumeInfo2) {
311 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo1.volumeType);
313 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo2.volumeType);
314 if (typeIndex1 !== typeIndex2)
315 return typeIndex1 < typeIndex2 ? -1 : 1;
316 if (volumeInfo1.volumeId !== volumeInfo2.volumeId)
317 return volumeInfo1.volumeId < volumeInfo2.volumeId ? -1 : 1;
322 * The container of the VolumeInfo for each mounted volume.
325 function VolumeInfoList() {
326 var field = 'volumeType,volumeId';
329 * Holds VolumeInfo instances.
330 * @type {cr.ui.ArrayDataModel}
333 this.model_ = new cr.ui.ArrayDataModel([]);
334 this.model_.setCompareFunction(field,
335 /** @type {function(*, *): number} */
336 (volumeManagerUtil.compareVolumeInfo_));
337 this.model_.sort(field, 'asc');
342 VolumeInfoList.prototype = {
343 get length() { return this.model_.length; }
347 * Adds the event listener to listen the change of volume info.
348 * @param {string} type The name of the event.
349 * @param {function(Event)} handler The handler for the event.
351 VolumeInfoList.prototype.addEventListener = function(type, handler) {
352 this.model_.addEventListener(type, handler);
356 * Removes the event listener.
357 * @param {string} type The name of the event.
358 * @param {function(Event)} handler The handler to be removed.
360 VolumeInfoList.prototype.removeEventListener = function(type, handler) {
361 this.model_.removeEventListener(type, handler);
365 * Adds the volumeInfo to the appropriate position. If there already exists,
367 * @param {VolumeInfo} volumeInfo The information of the new volume.
369 VolumeInfoList.prototype.add = function(volumeInfo) {
370 var index = this.findIndex(volumeInfo.volumeId);
372 this.model_.splice(index, 1, volumeInfo);
374 this.model_.push(volumeInfo);
378 * Removes the VolumeInfo having the given ID.
379 * @param {string} volumeId ID of the volume.
381 VolumeInfoList.prototype.remove = function(volumeId) {
382 var index = this.findIndex(volumeId);
384 this.model_.splice(index, 1);
388 * Obtains an index from the volume ID.
389 * @param {string} volumeId Volume ID.
390 * @return {number} Index of the volume.
392 VolumeInfoList.prototype.findIndex = function(volumeId) {
393 for (var i = 0; i < this.model_.length; i++) {
394 if (this.model_.item(i).volumeId === volumeId)
401 * Searches the information of the volume that contains the passed entry.
402 * @param {Entry|Object} entry Entry on the volume to be found.
403 * @return {VolumeInfo} The volume's information, or null if not found.
405 VolumeInfoList.prototype.findByEntry = function(entry) {
406 for (var i = 0; i < this.length; i++) {
407 var volumeInfo = this.item(i);
408 if (volumeInfo.fileSystem &&
409 util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) {
412 // Additionally, check fake entries.
413 for (var key in volumeInfo.fakeEntries_) {
414 var fakeEntry = volumeInfo.fakeEntries_[key];
415 if (util.isSameEntry(fakeEntry, entry))
423 * @param {number} index The index of the volume in the list.
424 * @return {!VolumeInfo} The VolumeInfo instance.
426 VolumeInfoList.prototype.item = function(index) {
427 return /** @type {!VolumeInfo} */ (this.model_.item(index));
431 * VolumeManager is responsible for tracking list of mounted volumes.
434 * @extends {cr.EventTarget}
436 function VolumeManager() {
438 * The list of archives requested to mount. We will show contents once
439 * archive is mounted, but only for mounts from within this filebrowser tab.
440 * @type {Object.<string, Object>}
446 * The list of VolumeInfo instances for each mounted volume.
447 * @type {VolumeInfoList}
449 this.volumeInfoList = new VolumeInfoList();
452 * Queue for mounting.
453 * @type {AsyncUtil.Queue}
456 this.mountQueue_ = new AsyncUtil.Queue();
458 // The status should be merged into VolumeManager.
459 // TODO(hidehiko): Remove them after the migration.
461 * Connection state of the Drive.
462 * @type {VolumeManagerCommon.DriveConnectionState}
465 this.driveConnectionState_ = {
466 type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
467 reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE,
468 hasCellularNetworkAccess: false
471 chrome.fileManagerPrivate.onDriveConnectionStatusChanged.addListener(
472 this.onDriveConnectionStatusChanged_.bind(this));
473 this.onDriveConnectionStatusChanged_();
477 * Invoked when the drive connection status is changed.
480 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() {
481 chrome.fileManagerPrivate.getDriveConnectionState(function(state) {
482 this.driveConnectionState_ = state;
483 cr.dispatchSimpleEvent(this, 'drive-connection-changed');
488 * Returns the drive connection state.
489 * @return {VolumeManagerCommon.DriveConnectionState} Connection state.
491 VolumeManager.prototype.getDriveConnectionState = function() {
492 return this.driveConnectionState_;
496 * VolumeManager extends cr.EventTarget.
498 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
501 * Time in milliseconds that we wait a response for. If no response on
502 * mount/unmount received the request supposed failed.
504 VolumeManager.TIMEOUT = 15 * 60 * 1000;
507 * The singleton instance of VolumeManager. Initialized by the first invocation
509 * @type {VolumeManager}
512 VolumeManager.instance_ = null;
515 * Returns instance of VolumeManager for debug purpose.
516 * This method returns VolumeManager.instance_ which may not be initialized.
518 * @return {VolumeManager} Volume manager.
520 VolumeManager.getInstanceForDebug = function() {
521 return VolumeManager.instance_;
528 VolumeManager.instancePromise_ = null;
531 * Returns the VolumeManager instance asynchronously. If it is not created or
532 * under initialization, it will waits for the finish of the initialization.
533 * @param {function(VolumeManager)=} opt_callback Called with the VolumeManager
534 * instance. TODO(hirono): Remove the callback and use Promise instead.
535 * @return {Promise} Promise to be fulfilled with the volume manager.
537 VolumeManager.getInstance = function(opt_callback) {
538 if (!VolumeManager.instancePromise_) {
539 VolumeManager.instance_ = new VolumeManager();
540 VolumeManager.instancePromise_ = new Promise(function(fulfill) {
541 VolumeManager.instance_.initialize_(function() {
542 return fulfill(VolumeManager.instance_);
547 VolumeManager.instancePromise_.then(opt_callback);
548 return VolumeManager.instancePromise_;
552 * Revokes the singleton instance for testing.
554 VolumeManager.revokeInstanceForTesting = function() {
555 VolumeManager.instancePromise_ = null;
556 VolumeManager.instance_ = null;
560 * Initializes mount points.
561 * @param {function()} callback Called upon the completion of the
565 VolumeManager.prototype.initialize_ = function(callback) {
566 chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
567 // We must subscribe to the mount completed event in the callback of
568 // getVolumeMetadataList. crbug.com/330061.
569 // But volumes reported by onMountCompleted events must be added after the
570 // volumes in the volumeMetadataList are mounted. crbug.com/135477.
571 this.mountQueue_.run(function(inCallback) {
572 // Create VolumeInfo for each volume.
573 var group = new AsyncUtil.Group();
574 for (var i = 0; i < volumeMetadataList.length; i++) {
575 group.add(function(volumeMetadata, continueCallback) {
576 volumeManagerUtil.createVolumeInfo(
578 function(volumeInfo) {
579 this.volumeInfoList.add(volumeInfo);
580 if (volumeMetadata.volumeType ===
581 VolumeManagerCommon.VolumeType.DRIVE)
582 this.onDriveConnectionStatusChanged_();
585 }.bind(this, volumeMetadataList[i]));
587 group.run(function() {
588 // Call the callback of the initialize function.
590 // Call the callback of AsyncQueue. Maybe it invokes callbacks
591 // registered by mountCompleted events.
596 chrome.fileManagerPrivate.onMountCompleted.addListener(
597 this.onMountCompleted_.bind(this));
602 * Event handler called when some volume was mounted or unmounted.
603 * @param {MountCompletedEvent} event Received event.
606 VolumeManager.prototype.onMountCompleted_ = function(event) {
607 this.mountQueue_.run(function(callback) {
608 switch (event.eventType) {
610 var requestKey = this.makeRequestKey_(
612 event.volumeMetadata.sourcePath);
614 if (event.status === 'success' ||
616 VolumeManagerCommon.VolumeError.UNKNOWN_FILESYSTEM ||
618 VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
619 volumeManagerUtil.createVolumeInfo(
620 event.volumeMetadata,
621 function(volumeInfo) {
622 this.volumeInfoList.add(volumeInfo);
623 this.finishRequest_(requestKey, event.status, volumeInfo);
625 if (volumeInfo.volumeType ===
626 VolumeManagerCommon.VolumeType.DRIVE) {
627 // Update the network connection status, because until the
628 // drive is initialized, the status is set to not ready.
629 // TODO(mtomasz): The connection status should be migrated
630 // into VolumeMetadata.
631 this.onDriveConnectionStatusChanged_();
636 console.warn('Failed to mount a volume: ' + event.status);
637 this.finishRequest_(requestKey, event.status);
643 var volumeId = event.volumeMetadata.volumeId;
644 var status = event.status;
645 if (status === VolumeManagerCommon.VolumeError.PATH_UNMOUNTED) {
646 console.warn('Volume already unmounted: ', volumeId);
649 var requestKey = this.makeRequestKey_('unmount', volumeId);
650 var requested = requestKey in this.requests_;
651 var volumeInfoIndex =
652 this.volumeInfoList.findIndex(volumeId);
653 var volumeInfo = volumeInfoIndex !== -1 ?
654 this.volumeInfoList.item(volumeInfoIndex) : null;
655 if (event.status === 'success' && !requested && volumeInfo) {
656 console.warn('Mounted volume without a request: ' + volumeId);
657 var e = new Event('externally-unmounted');
658 e.volumeInfo = volumeInfo;
659 this.dispatchEvent(e);
662 this.finishRequest_(requestKey, status);
663 if (event.status === 'success')
664 this.volumeInfoList.remove(event.volumeMetadata.volumeId);
672 * Creates string to match mount events with requests.
673 * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
675 * @param {string} argument Argument describing the request, eg. source file
676 * path of the archive to be mounted, or a volumeId for unmounting.
677 * @return {string} Key for |this.requests_|.
680 VolumeManager.prototype.makeRequestKey_ = function(requestType, argument) {
681 return requestType + ':' + argument;
685 * @param {string} fileUrl File url to the archive file.
686 * @param {function(VolumeInfo)} successCallback Success callback.
687 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Error
690 VolumeManager.prototype.mountArchive = function(
691 fileUrl, successCallback, errorCallback) {
692 chrome.fileManagerPrivate.addMount(fileUrl, function(sourcePath) {
694 'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath);
695 var requestKey = this.makeRequestKey_('mount', sourcePath);
696 this.startRequest_(requestKey, successCallback, errorCallback);
702 * @param {!VolumeInfo} volumeInfo Volume to be unmounted.
703 * @param {function()} successCallback Success callback.
704 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Error
707 VolumeManager.prototype.unmount = function(volumeInfo,
710 chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
711 var requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId);
712 this.startRequest_(requestKey, successCallback, errorCallback);
716 * Obtains a volume info containing the passed entry.
717 * @param {Entry|Object} entry Entry on the volume to be returned. Can be fake.
718 * @return {VolumeInfo} The VolumeInfo instance or null if not found.
720 VolumeManager.prototype.getVolumeInfo = function(entry) {
721 return this.volumeInfoList.findByEntry(entry);
725 * Obtains volume information of the current profile.
727 * @param {VolumeManagerCommon.VolumeType} volumeType Volume type.
728 * @return {VolumeInfo} Volume info.
730 VolumeManager.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
731 for (var i = 0; i < this.volumeInfoList.length; i++) {
732 var volumeInfo = this.volumeInfoList.item(i);
733 if (volumeInfo.profile.isCurrentProfile &&
734 volumeInfo.volumeType === volumeType)
741 * Obtains location information from an entry.
743 * @param {Entry|Object} entry File or directory entry. It can be a fake entry.
744 * @return {EntryLocation} Location information.
746 VolumeManager.prototype.getLocationInfo = function(entry) {
747 var volumeInfo = this.volumeInfoList.findByEntry(entry);
751 if (util.isFakeEntry(entry)) {
752 return new EntryLocation(
755 true /* the entry points a root directory. */,
756 true /* fake entries are read only. */);
762 if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
763 // For Drive, the roots are /root and /other, instead of /. Root URLs
764 // contain trailing slashes.
765 if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) {
766 rootType = VolumeManagerCommon.RootType.DRIVE;
767 isReadOnly = volumeInfo.isReadOnly;
768 isRootEntry = entry.fullPath === '/root';
769 } else if (entry.fullPath == '/other' ||
770 entry.fullPath.indexOf('/other/') === 0) {
771 rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
773 isRootEntry = entry.fullPath === '/other';
775 // Accessing Drive files outside of /drive/root and /drive/other is not
776 // allowed, but can happen. Therefore returning null.
780 switch (volumeInfo.volumeType) {
781 case VolumeManagerCommon.VolumeType.DOWNLOADS:
782 rootType = VolumeManagerCommon.RootType.DOWNLOADS;
784 case VolumeManagerCommon.VolumeType.REMOVABLE:
785 rootType = VolumeManagerCommon.RootType.REMOVABLE;
787 case VolumeManagerCommon.VolumeType.ARCHIVE:
788 rootType = VolumeManagerCommon.RootType.ARCHIVE;
790 case VolumeManagerCommon.VolumeType.CLOUD_DEVICE:
791 rootType = VolumeManagerCommon.RootType.CLOUD_DEVICE;
793 case VolumeManagerCommon.VolumeType.MTP:
794 rootType = VolumeManagerCommon.RootType.MTP;
796 case VolumeManagerCommon.VolumeType.PROVIDED:
797 rootType = VolumeManagerCommon.RootType.PROVIDED;
800 // Programming error, throw an exception.
801 throw new Error('Invalid volume type: ' + volumeInfo.volumeType);
803 isReadOnly = volumeInfo.isReadOnly;
804 isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root);
807 return new EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly);
811 * @param {string} key Key produced by |makeRequestKey_|.
812 * @param {function(VolumeInfo)} successCallback To be called when the request
813 * finishes successfully.
814 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback To be called
815 * when the request fails.
818 VolumeManager.prototype.startRequest_ = function(key,
819 successCallback, errorCallback) {
820 if (key in this.requests_) {
821 var request = this.requests_[key];
822 request.successCallbacks.push(successCallback);
823 request.errorCallbacks.push(errorCallback);
825 this.requests_[key] = {
826 successCallbacks: [successCallback],
827 errorCallbacks: [errorCallback],
829 timeout: setTimeout(this.onTimeout_.bind(this, key),
830 VolumeManager.TIMEOUT)
836 * Called if no response received in |TIMEOUT|.
837 * @param {string} key Key produced by |makeRequestKey_|.
840 VolumeManager.prototype.onTimeout_ = function(key) {
841 this.invokeRequestCallbacks_(this.requests_[key],
842 VolumeManagerCommon.VolumeError.TIMEOUT);
843 delete this.requests_[key];
847 * @param {string} key Key produced by |makeRequestKey_|.
848 * @param {VolumeManagerCommon.VolumeError|string} status Status received
850 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
853 VolumeManager.prototype.finishRequest_ = function(key, status, opt_volumeInfo) {
854 var request = this.requests_[key];
858 clearTimeout(request.timeout);
859 this.invokeRequestCallbacks_(request, status, opt_volumeInfo);
860 delete this.requests_[key];
864 * @param {Object} request Structure created in |startRequest_|.
865 * @param {VolumeManagerCommon.VolumeError|string} status If status ===
866 * 'success' success callbacks are called.
867 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
870 VolumeManager.prototype.invokeRequestCallbacks_ = function(
871 request, status, opt_volumeInfo) {
872 var callEach = function(callbacks, self, args) {
873 for (var i = 0; i < callbacks.length; i++) {
874 callbacks[i].apply(self, args);
877 if (status === 'success') {
878 callEach(request.successCallbacks, this, [opt_volumeInfo]);
880 volumeManagerUtil.validateError(status);
881 callEach(request.errorCallbacks, this, [status]);
886 * Returns current state of VolumeManager.
887 * @return {string} Current state of VolumeManager.
889 VolumeManager.prototype.toString = function() {
890 return 'VolumeManager\n' +
892 ' ' + this.mountQueue_.toString().replace(/\n/g, '\n ');
896 * Location information which shows where the path points in FileManager's
899 * @param {!VolumeInfo} volumeInfo Volume information.
900 * @param {VolumeManagerCommon.RootType} rootType Root type.
901 * @param {boolean} isRootEntry Whether the entry is root entry or not.
902 * @param {boolean} isReadOnly Whether the entry is read only or not.
905 function EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly) {
907 * Volume information.
908 * @type {!VolumeInfo}
910 this.volumeInfo = volumeInfo;
914 * @type {VolumeManagerCommon.RootType}
916 this.rootType = rootType;
919 * Whether the entry is root entry or not.
922 this.isRootEntry = isRootEntry;
925 * Whether the location obtained from the fake entry correspond to special
929 this.isSpecialSearchRoot =
930 this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
931 this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
932 this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT;
935 * Whether the location is under Google Drive or a special search root which
936 * represents a special search from Google Drive.
940 this.rootType === VolumeManagerCommon.RootType.DRIVE ||
941 this.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER ||
942 this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
943 this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
944 this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE;
947 * Whether the given path can be a target path of folder shortcut.
950 this.isEligibleForFolderShortcut =
951 !this.isSpecialSearchRoot &&
956 * Whether the entry is read only or not.
959 this.isReadOnly = isReadOnly;