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.
8 * Represents each volume, such as "drive", "download directory", each "USB
9 * flush storage", or "mounted zip archive" etc.
11 * @param {VolumeManagerCommon.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).
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} extensionId Id of the extension providing this volume. Empty
36 this.volumeType_ = volumeType;
37 this.volumeId_ = volumeId;
38 this.fileSystem_ = fileSystem;
40 this.displayRoot_ = null;
41 this.fakeEntries_ = {};
42 this.displayRoot_ = null;
43 this.displayRootPromise_ = null;
45 if (volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
46 // TODO(mtomasz): Convert fake entries to DirectoryProvider.
47 this.fakeEntries_[VolumeManagerCommon.RootType.DRIVE_OFFLINE] = {
49 rootType: VolumeManagerCommon.RootType.DRIVE_OFFLINE,
50 toURL: function() { return 'fake-entry://drive_offline'; }
52 this.fakeEntries_[VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME] = {
54 rootType: VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME,
55 toURL: function() { return 'fake-entry://drive_shared_with_me'; }
57 this.fakeEntries_[VolumeManagerCommon.RootType.DRIVE_RECENT] = {
59 rootType: VolumeManagerCommon.RootType.DRIVE_RECENT,
60 toURL: function() { return 'fake-entry://drive_recent'; }
64 // Note: This represents if the mounting of the volume is successfully done
65 // or not. (If error is empty string, the mount is successfully done).
66 // TODO(hidehiko): Rename to make this more understandable.
68 this.deviceType_ = deviceType;
69 this.isReadOnly_ = isReadOnly;
70 this.profile_ = Object.freeze(profile);
71 this.extensionId_ = extensionId;
76 VolumeInfo.prototype = {
78 * @return {VolumeManagerCommon.VolumeType} Volume type.
81 return this.volumeType_;
84 * @return {string} Volume ID.
87 return this.volumeId_;
90 * @return {DOMFileSystem} File system object.
93 return this.fileSystem_;
96 * @return {DirectoryEntry} Display root path. It is null before finishing to
100 return this.displayRoot_;
103 * @return {Object.<string, Object>} Fake entries.
106 return this.fakeEntries_;
109 * @return {string} Error identifier.
115 * @return {string} Device type identifier.
118 return this.deviceType_;
121 * @return {boolean} Whether read only or not.
124 return this.isReadOnly_;
127 * @return {!{displayName:string, isCurrentProfile:boolean}} Profile data.
130 return this.profile_;
133 * @return {string} Label for the volume.
139 * @return {string} Id of an extennsion providing this volume.
142 return this.extensionId_;
147 * Starts resolving the display root and obtains it. It may take long time for
148 * Drive. Once resolved, it is cached.
150 * @param {function(DirectoryEntry)} onSuccess Success callback with the
151 * display root directory as an argument.
152 * @param {function(FileError)} onFailure Failure callback.
155 VolumeInfo.prototype.resolveDisplayRoot = function(onSuccess, onFailure) {
156 if (!this.displayRootPromise_) {
157 // TODO(mtomasz): Do not add VolumeInfo which failed to resolve root, and
158 // remove this if logic. Call onSuccess() always, instead.
159 if (this.volumeType !== VolumeManagerCommon.VolumeType.DRIVE) {
160 if (this.fileSystem_)
161 this.displayRootPromise_ = Promise.resolve(this.fileSystem_.root);
163 this.displayRootPromise_ = Promise.reject(this.error);
165 // For Drive, we need to resolve.
166 var displayRootURL = this.fileSystem_.root.toURL() + '/root';
167 this.displayRootPromise_ = new Promise(
168 webkitResolveLocalFileSystemURL.bind(null, displayRootURL));
171 // Store the obtained displayRoot.
172 this.displayRootPromise_.then(function(displayRoot) {
173 this.displayRoot_ = displayRoot;
177 this.displayRootPromise_.then(onSuccess, onFailure);
178 return this.displayRootPromise_;
182 * Utilities for volume manager implementation.
184 var volumeManagerUtil = {};
187 * Throws an Error when the given error is not in
188 * VolumeManagerCommon.VolumeError.
190 * @param {VolumeManagerCommon.VolumeError} error Status string usually received
193 volumeManagerUtil.validateError = function(error) {
194 for (var key in VolumeManagerCommon.VolumeError) {
195 if (error === VolumeManagerCommon.VolumeError[key])
199 throw new Error('Invalid mount error: ' + error);
203 * Builds the VolumeInfo data from VolumeMetadata.
204 * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume.
205 * @param {function(VolumeInfo)} callback Called on completion.
207 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) {
209 switch (volumeMetadata.volumeType) {
210 case VolumeManagerCommon.VolumeType.DOWNLOADS:
211 localizedLabel = str('DOWNLOADS_DIRECTORY_LABEL');
213 case VolumeManagerCommon.VolumeType.DRIVE:
214 localizedLabel = str('DRIVE_DIRECTORY_LABEL');
217 // TODO(mtomasz): Calculate volumeLabel for all types of volumes in the
219 localizedLabel = volumeMetadata.volumeLabel ||
220 volumeMetadata.volumeId.split(':', 2)[1];
224 chrome.fileBrowserPrivate.requestFileSystem(
225 volumeMetadata.volumeId,
226 function(fileSystem) {
227 // TODO(mtomasz): chrome.runtime.lastError should have error reason.
229 console.error('File system not found: ' + volumeMetadata.volumeId);
230 callback(new VolumeInfo(
231 volumeMetadata.volumeType,
232 volumeMetadata.volumeId,
233 null, // File system is not found.
234 volumeMetadata.mountCondition,
235 volumeMetadata.deviceType,
236 volumeMetadata.isReadOnly,
237 volumeMetadata.profile,
239 volumeMetadata.extensionId));
242 if (volumeMetadata.volumeType ==
243 VolumeManagerCommon.VolumeType.DRIVE) {
244 // After file system is mounted, we "read" drive grand root
245 // entry at first. This triggers full feed fetch on background.
246 // Note: we don't need to handle errors here, because even if
247 // it fails, accessing to some path later will just become
248 // a fast-fetch and it re-triggers full-feed fetch.
249 fileSystem.root.createReader().readEntries(
250 function() { /* do nothing */ },
253 'Triggering full feed fetch is failed: ' + error.name);
256 callback(new VolumeInfo(
257 volumeMetadata.volumeType,
258 volumeMetadata.volumeId,
260 volumeMetadata.mountCondition,
261 volumeMetadata.deviceType,
262 volumeMetadata.isReadOnly,
263 volumeMetadata.profile,
265 volumeMetadata.extensionId));
270 * The order of the volume list based on root type.
271 * @type {Array.<VolumeManagerCommon.VolumeType>}
275 volumeManagerUtil.volumeListOrder_ = [
276 VolumeManagerCommon.VolumeType.DRIVE,
277 VolumeManagerCommon.VolumeType.DOWNLOADS,
278 VolumeManagerCommon.VolumeType.ARCHIVE,
279 VolumeManagerCommon.VolumeType.REMOVABLE,
280 VolumeManagerCommon.VolumeType.MTP,
281 VolumeManagerCommon.VolumeType.PROVIDED,
282 VolumeManagerCommon.VolumeType.CLOUD_DEVICE
286 * Orders two volumes by volumeType and volumeId.
288 * The volumes at first are compared by volume type in the order of
289 * volumeListOrder_. Then they are compared by volume ID.
291 * @param {VolumeInfo} volumeInfo1 Volume info to be compared.
292 * @param {VolumeInfo} volumeInfo2 Volume info to be compared.
293 * @return {number} Returns -1 if volume1 < volume2, returns 1 if volume2 >
294 * volume1, returns 0 if volume1 === volume2.
297 volumeManagerUtil.compareVolumeInfo_ = function(volumeInfo1, volumeInfo2) {
299 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo1.volumeType);
301 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo2.volumeType);
302 if (typeIndex1 !== typeIndex2)
303 return typeIndex1 < typeIndex2 ? -1 : 1;
304 if (volumeInfo1.volumeId !== volumeInfo2.volumeId)
305 return volumeInfo1.volumeId < volumeInfo2.volumeId ? -1 : 1;
310 * The container of the VolumeInfo for each mounted volume.
313 function VolumeInfoList() {
314 var field = 'volumeType,volumeId';
317 * Holds VolumeInfo instances.
318 * @type {cr.ui.ArrayDataModel}
321 this.model_ = new cr.ui.ArrayDataModel([]);
322 this.model_.setCompareFunction(field, volumeManagerUtil.compareVolumeInfo_);
323 this.model_.sort(field, 'asc');
328 VolumeInfoList.prototype = {
329 get length() { return this.model_.length; }
333 * Adds the event listener to listen the change of volume info.
334 * @param {string} type The name of the event.
335 * @param {function(Event)} handler The handler for the event.
337 VolumeInfoList.prototype.addEventListener = function(type, handler) {
338 this.model_.addEventListener(type, handler);
342 * Removes the event listener.
343 * @param {string} type The name of the event.
344 * @param {function(Event)} handler The handler to be removed.
346 VolumeInfoList.prototype.removeEventListener = function(type, handler) {
347 this.model_.removeEventListener(type, handler);
351 * Adds the volumeInfo to the appropriate position. If there already exists,
353 * @param {VolumeInfo} volumeInfo The information of the new volume.
355 VolumeInfoList.prototype.add = function(volumeInfo) {
356 var index = this.findIndex(volumeInfo.volumeId);
358 this.model_.splice(index, 1, volumeInfo);
360 this.model_.push(volumeInfo);
364 * Removes the VolumeInfo having the given ID.
365 * @param {string} volumeId ID of the volume.
367 VolumeInfoList.prototype.remove = function(volumeId) {
368 var index = this.findIndex(volumeId);
370 this.model_.splice(index, 1);
374 * Obtains an index from the volume ID.
375 * @param {string} volumeId Volume ID.
376 * @return {number} Index of the volume.
378 VolumeInfoList.prototype.findIndex = function(volumeId) {
379 for (var i = 0; i < this.model_.length; i++) {
380 if (this.model_.item(i).volumeId === volumeId)
387 * Searches the information of the volume that contains the passed entry.
388 * @param {Entry|Object} entry Entry on the volume to be found.
389 * @return {VolumeInfo} The volume's information, or null if not found.
391 VolumeInfoList.prototype.findByEntry = function(entry) {
392 for (var i = 0; i < this.length; i++) {
393 var volumeInfo = this.item(i);
394 if (volumeInfo.fileSystem &&
395 util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) {
398 // Additionally, check fake entries.
399 for (var key in volumeInfo.fakeEntries_) {
400 var fakeEntry = volumeInfo.fakeEntries_[key];
401 if (util.isSameEntry(fakeEntry, entry))
409 * @param {number} index The index of the volume in the list.
410 * @return {VolumeInfo} The VolumeInfo instance.
412 VolumeInfoList.prototype.item = function(index) {
413 return this.model_.item(index);
417 * VolumeManager is responsible for tracking list of mounted volumes.
420 * @extends {cr.EventTarget}
422 function VolumeManager() {
424 * The list of archives requested to mount. We will show contents once
425 * archive is mounted, but only for mounts from within this filebrowser tab.
426 * @type {Object.<string, Object>}
432 * The list of VolumeInfo instances for each mounted volume.
433 * @type {VolumeInfoList}
435 this.volumeInfoList = new VolumeInfoList();
438 * Queue for mounting.
439 * @type {AsyncUtil.Queue}
442 this.mountQueue_ = new AsyncUtil.Queue();
444 // The status should be merged into VolumeManager.
445 // TODO(hidehiko): Remove them after the migration.
446 this.driveConnectionState_ = {
447 type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
448 reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE
451 chrome.fileBrowserPrivate.onDriveConnectionStatusChanged.addListener(
452 this.onDriveConnectionStatusChanged_.bind(this));
453 this.onDriveConnectionStatusChanged_();
457 * Invoked when the drive connection status is changed.
460 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() {
461 chrome.fileBrowserPrivate.getDriveConnectionState(function(state) {
462 this.driveConnectionState_ = state;
463 cr.dispatchSimpleEvent(this, 'drive-connection-changed');
468 * Returns the drive connection state.
469 * @return {VolumeManagerCommon.DriveConnectionType} Connection type.
471 VolumeManager.prototype.getDriveConnectionState = function() {
472 return this.driveConnectionState_;
476 * VolumeManager extends cr.EventTarget.
478 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
481 * Time in milliseconds that we wait a response for. If no response on
482 * mount/unmount received the request supposed failed.
484 VolumeManager.TIMEOUT = 15 * 60 * 1000;
487 * Queue to run getInstance sequentially.
488 * @type {AsyncUtil.Queue}
491 VolumeManager.getInstanceQueue_ = new AsyncUtil.Queue();
494 * The singleton instance of VolumeManager. Initialized by the first invocation
496 * @type {VolumeManager}
499 VolumeManager.instance_ = null;
504 VolumeManager.instancePromise_ = null;
507 * Returns the VolumeManager instance asynchronously. If it is not created or
508 * under initialization, it will waits for the finish of the initialization.
509 * @param {function(VolumeManager)} callback Called with the VolumeManager
510 * instance. TODO(hirono): Remove the callback and use Promise instead.
511 * @return {Promise} Promise to be fulfilled with the volume manager.
513 VolumeManager.getInstance = function(callback) {
514 if (!VolumeManager.instancePromise_) {
515 VolumeManager.instance_ = new VolumeManager();
516 VolumeManager.instancePromise_ = new Promise(function(fulfill) {
517 VolumeManager.instance_.initialize_(function() {
518 return fulfill(VolumeManager.instance_);
523 VolumeManager.instancePromise_.then(callback);
524 return VolumeManager.instancePromise_;
528 * Initializes mount points.
529 * @param {function()} callback Called upon the completion of the
533 VolumeManager.prototype.initialize_ = function(callback) {
534 chrome.fileBrowserPrivate.getVolumeMetadataList(function(volumeMetadataList) {
535 // We must subscribe to the mount completed event in the callback of
536 // getVolumeMetadataList. crbug.com/330061.
537 // But volumes reported by onMountCompleted events must be added after the
538 // volumes in the volumeMetadataList are mounted. crbug.com/135477.
539 this.mountQueue_.run(function(inCallback) {
540 // Create VolumeInfo for each volume.
541 var group = new AsyncUtil.Group();
542 for (var i = 0; i < volumeMetadataList.length; i++) {
543 group.add(function(volumeMetadata, continueCallback) {
544 volumeManagerUtil.createVolumeInfo(
546 function(volumeInfo) {
547 this.volumeInfoList.add(volumeInfo);
548 if (volumeMetadata.volumeType ===
549 VolumeManagerCommon.VolumeType.DRIVE)
550 this.onDriveConnectionStatusChanged_();
553 }.bind(this, volumeMetadataList[i]));
555 group.run(function() {
556 // Call the callback of the initialize function.
558 // Call the callback of AsyncQueue. Maybe it invokes callbacks
559 // registered by mountCompleted events.
564 chrome.fileBrowserPrivate.onMountCompleted.addListener(
565 this.onMountCompleted_.bind(this));
570 * Event handler called when some volume was mounted or unmounted.
571 * @param {MountCompletedEvent} event Received event.
574 VolumeManager.prototype.onMountCompleted_ = function(event) {
575 this.mountQueue_.run(function(callback) {
576 switch (event.eventType) {
578 var requestKey = this.makeRequestKey_(
580 event.volumeMetadata.sourcePath);
582 if (event.status === 'success' ||
584 VolumeManagerCommon.VolumeError.UNKNOWN_FILESYSTEM ||
586 VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
587 volumeManagerUtil.createVolumeInfo(
588 event.volumeMetadata,
589 function(volumeInfo) {
590 this.volumeInfoList.add(volumeInfo);
591 this.finishRequest_(requestKey, event.status, volumeInfo);
593 if (volumeInfo.volumeType ===
594 VolumeManagerCommon.VolumeType.DRIVE) {
595 // Update the network connection status, because until the
596 // drive is initialized, the status is set to not ready.
597 // TODO(mtomasz): The connection status should be migrated
598 // into VolumeMetadata.
599 this.onDriveConnectionStatusChanged_();
604 console.warn('Failed to mount a volume: ' + event.status);
605 this.finishRequest_(requestKey, event.status);
611 var volumeId = event.volumeMetadata.volumeId;
612 var status = event.status;
613 if (status === VolumeManagerCommon.VolumeError.PATH_UNMOUNTED) {
614 console.warn('Volume already unmounted: ', volumeId);
617 var requestKey = this.makeRequestKey_('unmount', volumeId);
618 var requested = requestKey in this.requests_;
619 var volumeInfoIndex =
620 this.volumeInfoList.findIndex(volumeId);
621 var volumeInfo = volumeInfoIndex !== -1 ?
622 this.volumeInfoList.item(volumeInfoIndex) : null;
623 if (event.status === 'success' && !requested && volumeInfo) {
624 console.warn('Mounted volume without a request: ' + volumeId);
625 var e = new Event('externally-unmounted');
626 e.volumeInfo = volumeInfo;
627 this.dispatchEvent(e);
630 this.finishRequest_(requestKey, status);
631 if (event.status === 'success')
632 this.volumeInfoList.remove(event.volumeMetadata.volumeId);
640 * Creates string to match mount events with requests.
641 * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
643 * @param {string} argument Argument describing the request, eg. source file
644 * path of the archive to be mounted, or a volumeId for unmounting.
645 * @return {string} Key for |this.requests_|.
648 VolumeManager.prototype.makeRequestKey_ = function(requestType, argument) {
649 return requestType + ':' + argument;
653 * @param {string} fileUrl File url to the archive file.
654 * @param {function(VolumeInfo)} successCallback Success callback.
655 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Error
658 VolumeManager.prototype.mountArchive = function(
659 fileUrl, successCallback, errorCallback) {
660 chrome.fileBrowserPrivate.addMount(fileUrl, function(sourcePath) {
662 'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath);
663 var requestKey = this.makeRequestKey_('mount', sourcePath);
664 this.startRequest_(requestKey, successCallback, errorCallback);
670 * @param {!VolumeInfo} volumeInfo Volume to be unmounted.
671 * @param {function()} successCallback Success callback.
672 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Error
675 VolumeManager.prototype.unmount = function(volumeInfo,
678 chrome.fileBrowserPrivate.removeMount(volumeInfo.volumeId);
679 var requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId);
680 this.startRequest_(requestKey, successCallback, errorCallback);
684 * Obtains a volume info containing the passed entry.
685 * @param {Entry|Object} entry Entry on the volume to be returned. Can be fake.
686 * @return {VolumeInfo} The VolumeInfo instance or null if not found.
688 VolumeManager.prototype.getVolumeInfo = function(entry) {
689 return this.volumeInfoList.findByEntry(entry);
693 * Obtains volume information of the current profile.
695 * @param {VolumeManagerCommon.VolumeType} volumeType Volume type.
696 * @return {VolumeInfo} Volume info.
698 VolumeManager.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
699 for (var i = 0; i < this.volumeInfoList.length; i++) {
700 var volumeInfo = this.volumeInfoList.item(i);
701 if (volumeInfo.profile.isCurrentProfile &&
702 volumeInfo.volumeType === volumeType)
709 * Obtains location information from an entry.
711 * @param {Entry|Object} entry File or directory entry. It can be a fake entry.
712 * @return {EntryLocation} Location information.
714 VolumeManager.prototype.getLocationInfo = function(entry) {
715 var volumeInfo = this.volumeInfoList.findByEntry(entry);
719 if (util.isFakeEntry(entry)) {
720 return new EntryLocation(
723 true /* the entry points a root directory. */,
724 true /* fake entries are read only. */);
730 if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
731 // For Drive, the roots are /root and /other, instead of /. Root URLs
732 // contain trailing slashes.
733 if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) {
734 rootType = VolumeManagerCommon.RootType.DRIVE;
735 isReadOnly = volumeInfo.isReadOnly;
736 isRootEntry = entry.fullPath === '/root';
737 } else if (entry.fullPath == '/other' ||
738 entry.fullPath.indexOf('/other/') === 0) {
739 rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
741 isRootEntry = entry.fullPath === '/other';
743 // Accessing Drive files outside of /drive/root and /drive/other is not
744 // allowed, but can happen. Therefore returning null.
748 switch (volumeInfo.volumeType) {
749 case VolumeManagerCommon.VolumeType.DOWNLOADS:
750 rootType = VolumeManagerCommon.RootType.DOWNLOADS;
752 case VolumeManagerCommon.VolumeType.REMOVABLE:
753 rootType = VolumeManagerCommon.RootType.REMOVABLE;
755 case VolumeManagerCommon.VolumeType.ARCHIVE:
756 rootType = VolumeManagerCommon.RootType.ARCHIVE;
758 case VolumeManagerCommon.VolumeType.CLOUD_DEVICE:
759 rootType = VolumeManagerCommon.RootType.CLOUD_DEVICE;
761 case VolumeManagerCommon.VolumeType.MTP:
762 rootType = VolumeManagerCommon.RootType.MTP;
764 case VolumeManagerCommon.VolumeType.PROVIDED:
765 rootType = VolumeManagerCommon.RootType.PROVIDED;
768 // Programming error, throw an exception.
769 throw new Error('Invalid volume type: ' + volumeInfo.volumeType);
771 isReadOnly = volumeInfo.isReadOnly;
772 isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root);
775 return new EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly);
779 * @param {string} key Key produced by |makeRequestKey_|.
780 * @param {function(VolumeInfo)} successCallback To be called when the request
781 * finishes successfully.
782 * @param {function(VolumeManagerCommon.VolumeError)} errorCallback To be called
783 * when the request fails.
786 VolumeManager.prototype.startRequest_ = function(key,
787 successCallback, errorCallback) {
788 if (key in this.requests_) {
789 var request = this.requests_[key];
790 request.successCallbacks.push(successCallback);
791 request.errorCallbacks.push(errorCallback);
793 this.requests_[key] = {
794 successCallbacks: [successCallback],
795 errorCallbacks: [errorCallback],
797 timeout: setTimeout(this.onTimeout_.bind(this, key),
798 VolumeManager.TIMEOUT)
804 * Called if no response received in |TIMEOUT|.
805 * @param {string} key Key produced by |makeRequestKey_|.
808 VolumeManager.prototype.onTimeout_ = function(key) {
809 this.invokeRequestCallbacks_(this.requests_[key],
810 VolumeManagerCommon.VolumeError.TIMEOUT);
811 delete this.requests_[key];
815 * @param {string} key Key produced by |makeRequestKey_|.
816 * @param {VolumeManagerCommon.VolumeError|'success'} status Status received
818 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
821 VolumeManager.prototype.finishRequest_ = function(key, status, opt_volumeInfo) {
822 var request = this.requests_[key];
826 clearTimeout(request.timeout);
827 this.invokeRequestCallbacks_(request, status, opt_volumeInfo);
828 delete this.requests_[key];
832 * @param {Object} request Structure created in |startRequest_|.
833 * @param {VolumeManagerCommon.VolumeError|string} status If status ===
834 * 'success' success callbacks are called.
835 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
838 VolumeManager.prototype.invokeRequestCallbacks_ = function(
839 request, status, opt_volumeInfo) {
840 var callEach = function(callbacks, self, args) {
841 for (var i = 0; i < callbacks.length; i++) {
842 callbacks[i].apply(self, args);
845 if (status === 'success') {
846 callEach(request.successCallbacks, this, [opt_volumeInfo]);
848 volumeManagerUtil.validateError(status);
849 callEach(request.errorCallbacks, this, [status]);
854 * Location information which shows where the path points in FileManager's
857 * @param {!VolumeInfo} volumeInfo Volume information.
858 * @param {VolumeManagerCommon.RootType} rootType Root type.
859 * @param {boolean} isRootEntry Whether the entry is root entry or not.
860 * @param {boolean} isReadOnly Whether the entry is read only or not.
863 function EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly) {
865 * Volume information.
866 * @type {!VolumeInfo}
868 this.volumeInfo = volumeInfo;
872 * @type {VolumeManagerCommon.RootType}
874 this.rootType = rootType;
877 * Whether the entry is root entry or not.
880 this.isRootEntry = isRootEntry;
883 * Whether the location obtained from the fake entry correspond to special
887 this.isSpecialSearchRoot =
888 this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
889 this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
890 this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT;
893 * Whether the location is under Google Drive or a special search root which
894 * represents a special search from Google Drive.
898 this.rootType === VolumeManagerCommon.RootType.DRIVE ||
899 this.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER ||
900 this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
901 this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
902 this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE;
905 * Whether the given path can be a target path of folder shortcut.
908 this.isEligibleForFolderShortcut =
909 !this.isSpecialSearchRoot &&
914 * Whether the entry is read only or not.
917 this.isReadOnly = isReadOnly;