| Index: ui/file_manager/file_manager/background/js/volume_manager_impl.js
|
| diff --git a/ui/file_manager/file_manager/background/js/volume_manager_impl.js b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..84f578aa3d0de6afb5ae97dfbaed9a54569de794
|
| --- /dev/null
|
| +++ b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
|
| @@ -0,0 +1,420 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * VolumeManager is responsible for tracking list of mounted volumes.
|
| + *
|
| + * @constructor
|
| + * @implements {VolumeManager}
|
| + * @extends {cr.EventTarget}
|
| + */
|
| +function VolumeManagerImpl() {
|
| + /** @override */
|
| + this.volumeInfoList = new VolumeInfoListImpl();
|
| +
|
| + /**
|
| + * The list of archives requested to mount. We will show contents once
|
| + * archive is mounted, but only for mounts from within this filebrowser tab.
|
| + * @type {Object<Object>}
|
| + * @private
|
| + */
|
| + this.requests_ = {};
|
| +
|
| + /**
|
| + * Queue for mounting.
|
| + * @type {AsyncUtil.Queue}
|
| + * @private
|
| + */
|
| + this.mountQueue_ = new AsyncUtil.Queue();
|
| +
|
| + // The status should be merged into VolumeManager.
|
| + // TODO(hidehiko): Remove them after the migration.
|
| + /**
|
| + * Connection state of the Drive.
|
| + * @type {VolumeManagerCommon.DriveConnectionState}
|
| + * @private
|
| + */
|
| + this.driveConnectionState_ = {
|
| + type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
|
| + reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE,
|
| + hasCellularNetworkAccess: false
|
| + };
|
| +
|
| + chrome.fileManagerPrivate.onDriveConnectionStatusChanged.addListener(
|
| + this.onDriveConnectionStatusChanged_.bind(this));
|
| + this.onDriveConnectionStatusChanged_();
|
| +}
|
| +
|
| +/**
|
| + * Invoked when the drive connection status is changed.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.onDriveConnectionStatusChanged_ = function() {
|
| + chrome.fileManagerPrivate.getDriveConnectionState(function(state) {
|
| + this.driveConnectionState_ = state;
|
| + cr.dispatchSimpleEvent(this, 'drive-connection-changed');
|
| + }.bind(this));
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.getDriveConnectionState = function() {
|
| + return this.driveConnectionState_;
|
| +};
|
| +
|
| +/**
|
| + * VolumeManager extends cr.EventTarget.
|
| + */
|
| +VolumeManagerImpl.prototype.__proto__ = cr.EventTarget.prototype;
|
| +
|
| +/**
|
| + * Adds new volume info from the given volumeMetadata. If the corresponding
|
| + * volume info has already been added, the volumeMetadata is ignored.
|
| + * @param {!VolumeMetadata} volumeMetadata
|
| + * @return {!Promise<!VolumeInfo>}
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.addVolumeMetadata_ = function(volumeMetadata) {
|
| + return volumeManagerUtil.createVolumeInfo(volumeMetadata).then(
|
| + /**
|
| + * @param {!VolumeInfo} volumeInfo
|
| + * @return {!VolumeInfo}
|
| + */
|
| + function(volumeInfo) {
|
| + // We don't show Downloads and Drive on volume list if they have mount
|
| + // error, since users can do nothing in this situation.
|
| + // We show Removable and Provided volumes regardless of mount error so
|
| + // that users can unmount or format the volume.
|
| + // TODO(fukino): Once Files.app get ready, show erroneous Drive volume
|
| + // so that users can see auth warning banner on the volume.
|
| + // crbug.com/517772.
|
| + var shouldShow = true;
|
| + switch (volumeInfo.volumeType) {
|
| + case VolumeManagerCommon.VolumeType.DOWNLOADS:
|
| + case VolumeManagerCommon.VolumeType.DRIVE:
|
| + shouldShow = !!volumeInfo.fileSystem;
|
| + break;
|
| + }
|
| + if (shouldShow &&
|
| + this.volumeInfoList.findIndex(volumeInfo.volumeId) === -1) {
|
| + this.volumeInfoList.add(volumeInfo);
|
| +
|
| + // Update the network connection status, because until the drive is
|
| + // initialized, the status is set to not ready.
|
| + // TODO(mtomasz): The connection status should be migrated into
|
| + // VolumeMetadata.
|
| + if (volumeMetadata.volumeType ===
|
| + VolumeManagerCommon.VolumeType.DRIVE) {
|
| + this.onDriveConnectionStatusChanged_();
|
| + }
|
| + }
|
| + return volumeInfo;
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * Initializes mount points.
|
| + * @param {function()} callback Called upon the completion of the
|
| + * initialization.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.initialize_ = function(callback) {
|
| + chrome.fileManagerPrivate.onMountCompleted.addListener(
|
| + this.onMountCompleted_.bind(this));
|
| + console.debug('Requesting volume list.');
|
| + chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
|
| + console.debug('Volume list fetched with: ' + volumeMetadataList.length +
|
| + ' items.');
|
| + // We must subscribe to the mount completed event in the callback of
|
| + // getVolumeMetadataList. crbug.com/330061.
|
| + // But volumes reported by onMountCompleted events must be added after the
|
| + // volumes in the volumeMetadataList are mounted. crbug.com/135477.
|
| + this.mountQueue_.run(function(inCallback) {
|
| + // Create VolumeInfo for each volume.
|
| + Promise.all(
|
| + volumeMetadataList.map(function(volumeMetadata) {
|
| + console.debug(
|
| + 'Initializing volume: ' + volumeMetadata.volumeId);
|
| + return this.addVolumeMetadata_(volumeMetadata).then(
|
| + function(volumeInfo) {
|
| + console.debug('Initialized volume: ' + volumeInfo.volumeId);
|
| + });
|
| + }.bind(this)))
|
| + .then(function() {
|
| + console.debug('Initialized all volumes.');
|
| + // Call the callback of the initialize function.
|
| + callback();
|
| + // Call the callback of AsyncQueue. Maybe it invokes callbacks
|
| + // registered by mountCompleted events.
|
| + inCallback();
|
| + });
|
| + }.bind(this));
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * Event handler called when some volume was mounted or unmounted.
|
| + * @param {MountCompletedEvent} event Received event.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.onMountCompleted_ = function(event) {
|
| + this.mountQueue_.run(function(callback) {
|
| + switch (event.eventType) {
|
| + case 'mount':
|
| + var requestKey = this.makeRequestKey_(
|
| + 'mount',
|
| + event.volumeMetadata.sourcePath || '');
|
| +
|
| + if (event.status === 'success' ||
|
| + event.status ===
|
| + VolumeManagerCommon.VolumeError.UNKNOWN_FILESYSTEM ||
|
| + event.status ===
|
| + VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
|
| + this.addVolumeMetadata_(event.volumeMetadata).then(
|
| + function(volumeInfo) {
|
| + this.finishRequest_(requestKey, event.status, volumeInfo);
|
| + callback();
|
| + }.bind(this));
|
| + } else {
|
| + console.warn('Failed to mount a volume: ' + event.status);
|
| + this.finishRequest_(requestKey, event.status);
|
| + callback();
|
| + }
|
| + break;
|
| +
|
| + case 'unmount':
|
| + var volumeId = event.volumeMetadata.volumeId;
|
| + var status = event.status;
|
| + if (status === VolumeManagerCommon.VolumeError.PATH_UNMOUNTED) {
|
| + console.warn('Volume already unmounted: ', volumeId);
|
| + status = 'success';
|
| + }
|
| + var requestKey = this.makeRequestKey_('unmount', volumeId);
|
| + var requested = requestKey in this.requests_;
|
| + var volumeInfoIndex =
|
| + this.volumeInfoList.findIndex(volumeId);
|
| + var volumeInfo = volumeInfoIndex !== -1 ?
|
| + this.volumeInfoList.item(volumeInfoIndex) : null;
|
| + if (event.status === 'success' && !requested && volumeInfo) {
|
| + console.warn('Mounted volume without a request: ' + volumeId);
|
| + var e = new Event('externally-unmounted');
|
| + e.volumeInfo = volumeInfo;
|
| + this.dispatchEvent(e);
|
| + }
|
| +
|
| + this.finishRequest_(requestKey, status);
|
| + if (event.status === 'success')
|
| + this.volumeInfoList.remove(event.volumeMetadata.volumeId);
|
| + callback();
|
| + break;
|
| + }
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * Creates string to match mount events with requests.
|
| + * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
|
| + * enum.
|
| + * @param {string} argument Argument describing the request, eg. source file
|
| + * path of the archive to be mounted, or a volumeId for unmounting.
|
| + * @return {string} Key for |this.requests_|.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.makeRequestKey_ = function(requestType, argument) {
|
| + return requestType + ':' + argument;
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.mountArchive = function(
|
| + fileUrl, successCallback, errorCallback) {
|
| + chrome.fileManagerPrivate.addMount(fileUrl, function(sourcePath) {
|
| + console.info(
|
| + 'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath);
|
| + var requestKey = this.makeRequestKey_('mount', sourcePath);
|
| + this.startRequest_(requestKey, successCallback, errorCallback);
|
| + }.bind(this));
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.unmount = function(volumeInfo,
|
| + successCallback,
|
| + errorCallback) {
|
| + chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
|
| + var requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId);
|
| + this.startRequest_(requestKey, successCallback, errorCallback);
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.configure = function(volumeInfo) {
|
| + return new Promise(function(fulfill, reject) {
|
| + chrome.fileManagerPrivate.configureVolume(
|
| + volumeInfo.volumeId,
|
| + function() {
|
| + if (chrome.runtime.lastError)
|
| + reject(chrome.runtime.lastError.message);
|
| + else
|
| + fulfill();
|
| + });
|
| + });
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.getVolumeInfo = function(entry) {
|
| + return this.volumeInfoList.findByEntry(entry);
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
|
| + for (var i = 0; i < this.volumeInfoList.length; i++) {
|
| + var volumeInfo = this.volumeInfoList.item(i);
|
| + if (volumeInfo.profile.isCurrentProfile &&
|
| + volumeInfo.volumeType === volumeType)
|
| + return volumeInfo;
|
| + }
|
| + return null;
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.getLocationInfo = function(entry) {
|
| + var volumeInfo = this.volumeInfoList.findByEntry(entry);
|
| + if (!volumeInfo)
|
| + return null;
|
| +
|
| + if (util.isFakeEntry(entry)) {
|
| + return new EntryLocationImpl(
|
| + volumeInfo,
|
| + entry.rootType,
|
| + true /* the entry points a root directory. */,
|
| + true /* fake entries are read only. */);
|
| + }
|
| +
|
| + var rootType;
|
| + var isReadOnly;
|
| + var isRootEntry;
|
| + if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
|
| + // For Drive, the roots are /root and /other, instead of /. Root URLs
|
| + // contain trailing slashes.
|
| + if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) {
|
| + rootType = VolumeManagerCommon.RootType.DRIVE;
|
| + isReadOnly = volumeInfo.isReadOnly;
|
| + isRootEntry = entry.fullPath === '/root';
|
| + } else if (entry.fullPath == '/other' ||
|
| + entry.fullPath.indexOf('/other/') === 0) {
|
| + rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
|
| + isReadOnly = true;
|
| + isRootEntry = entry.fullPath === '/other';
|
| + } else {
|
| + // Accessing Drive files outside of /drive/root and /drive/other is not
|
| + // allowed, but can happen. Therefore returning null.
|
| + return null;
|
| + }
|
| + } else {
|
| + switch (volumeInfo.volumeType) {
|
| + case VolumeManagerCommon.VolumeType.DOWNLOADS:
|
| + rootType = VolumeManagerCommon.RootType.DOWNLOADS;
|
| + break;
|
| + case VolumeManagerCommon.VolumeType.REMOVABLE:
|
| + rootType = VolumeManagerCommon.RootType.REMOVABLE;
|
| + break;
|
| + case VolumeManagerCommon.VolumeType.ARCHIVE:
|
| + rootType = VolumeManagerCommon.RootType.ARCHIVE;
|
| + break;
|
| + case VolumeManagerCommon.VolumeType.MTP:
|
| + rootType = VolumeManagerCommon.RootType.MTP;
|
| + break;
|
| + case VolumeManagerCommon.VolumeType.PROVIDED:
|
| + rootType = VolumeManagerCommon.RootType.PROVIDED;
|
| + break;
|
| + default:
|
| + // Programming error, throw an exception.
|
| + throw new Error('Invalid volume type: ' + volumeInfo.volumeType);
|
| + }
|
| + isReadOnly = volumeInfo.isReadOnly;
|
| + isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root);
|
| + }
|
| +
|
| + return new EntryLocationImpl(volumeInfo, rootType, isRootEntry, isReadOnly);
|
| +};
|
| +
|
| +/**
|
| + * @param {string} key Key produced by |makeRequestKey_|.
|
| + * @param {function(VolumeInfo)} successCallback To be called when the request
|
| + * finishes successfully.
|
| + * @param {function(VolumeManagerCommon.VolumeError)} errorCallback To be called
|
| + * when the request fails.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.startRequest_ = function(key,
|
| + successCallback, errorCallback) {
|
| + if (key in this.requests_) {
|
| + var request = this.requests_[key];
|
| + request.successCallbacks.push(successCallback);
|
| + request.errorCallbacks.push(errorCallback);
|
| + } else {
|
| + this.requests_[key] = {
|
| + successCallbacks: [successCallback],
|
| + errorCallbacks: [errorCallback],
|
| +
|
| + timeout: setTimeout(this.onTimeout_.bind(this, key),
|
| + volumeManagerUtil.TIMEOUT)
|
| + };
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Called if no response received in |TIMEOUT|.
|
| + * @param {string} key Key produced by |makeRequestKey_|.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.onTimeout_ = function(key) {
|
| + this.invokeRequestCallbacks_(this.requests_[key],
|
| + VolumeManagerCommon.VolumeError.TIMEOUT);
|
| + delete this.requests_[key];
|
| +};
|
| +
|
| +/**
|
| + * @param {string} key Key produced by |makeRequestKey_|.
|
| + * @param {VolumeManagerCommon.VolumeError|string} status Status received
|
| + * from the API.
|
| + * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.finishRequest_ =
|
| + function(key, status, opt_volumeInfo) {
|
| + var request = this.requests_[key];
|
| + if (!request)
|
| + return;
|
| +
|
| + clearTimeout(request.timeout);
|
| + this.invokeRequestCallbacks_(request, status, opt_volumeInfo);
|
| + delete this.requests_[key];
|
| +};
|
| +
|
| +/**
|
| + * @param {Object} request Structure created in |startRequest_|.
|
| + * @param {VolumeManagerCommon.VolumeError|string} status If status ===
|
| + * 'success' success callbacks are called.
|
| + * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
|
| + * @private
|
| + */
|
| +VolumeManagerImpl.prototype.invokeRequestCallbacks_ = function(
|
| + request, status, opt_volumeInfo) {
|
| + var callEach = function(callbacks, self, args) {
|
| + for (var i = 0; i < callbacks.length; i++) {
|
| + callbacks[i].apply(self, args);
|
| + }
|
| + };
|
| + if (status === 'success') {
|
| + callEach(request.successCallbacks, this, [opt_volumeInfo]);
|
| + } else {
|
| + volumeManagerUtil.validateError(status);
|
| + callEach(request.errorCallbacks, this, [status]);
|
| + }
|
| +};
|
| +
|
| +/** @override */
|
| +VolumeManagerImpl.prototype.toString = function() {
|
| + return 'VolumeManager\n' +
|
| + '- MountQueue_:\n' +
|
| + ' ' + this.mountQueue_.toString().replace(/\n/g, '\n ');
|
| +};
|
|
|