| Index: ui/file_manager/file_manager/foreground/js/import_controller.js
|
| diff --git a/ui/file_manager/file_manager/foreground/js/import_controller.js b/ui/file_manager/file_manager/foreground/js/import_controller.js
|
| index 9531312df32df4a644a7f9beda32ca22227e1cf9..3d02d180e9795ef4f811ee1b02d082076a6cb9aa 100644
|
| --- a/ui/file_manager/file_manager/foreground/js/import_controller.js
|
| +++ b/ui/file_manager/file_manager/foreground/js/import_controller.js
|
| @@ -7,10 +7,12 @@ var importer = importer || {};
|
|
|
| /** @enum {string} */
|
| importer.ResponseId = {
|
| + EXECUTABLE: 'executable',
|
| HIDDEN: 'hidden',
|
| - SCANNING: 'scanning',
|
| + ACTIVE_IMPORT: 'active_import',
|
| + INSUFFICIENT_SPACE: 'insufficient_space',
|
| NO_MEDIA: 'no_media',
|
| - EXECUTABLE: 'executable'
|
| + SCANNING: 'scanning'
|
| };
|
|
|
| /**
|
| @@ -60,6 +62,12 @@ importer.ImportController =
|
| */
|
| this.cachedScans_ = {};
|
|
|
| + /**
|
| + * The active import task, if any.
|
| + * @private {importer.MediaImportHandler.ImportTask}
|
| + */
|
| + this.activeImportTask_ = null;
|
| +
|
| var listener = this.onScanEvent_.bind(this);
|
| this.scanner_.addObserver(listener);
|
| // Remove the observer when the foreground window is closed.
|
| @@ -87,13 +95,7 @@ importer.ImportController =
|
| */
|
| importer.ImportController.prototype.onScanEvent_ = function(event, result) {
|
| if (event === importer.ScanEvent.INVALIDATED) {
|
| - for (var key in this.cachedScans_) {
|
| - for (var url in this.cachedScans_[key]) {
|
| - if (this.cachedScans_[key][url].isInvalidated()) {
|
| - delete this.cachedScans_[key][url];
|
| - }
|
| - }
|
| - }
|
| + this.resetScanCache_();
|
| }
|
| if (event === importer.ScanEvent.FINALIZED ||
|
| event === importer.ScanEvent.INVALIDATED) {
|
| @@ -107,9 +109,34 @@ importer.ImportController.prototype.onScanEvent_ = function(event, result) {
|
| * by calling "update" on this class.
|
| */
|
| importer.ImportController.prototype.execute = function() {
|
| + console.assert(!this.activeImportTask_,
|
| + 'Cannot execute while an import task is already active.');
|
| metrics.recordEnum('CloudImport.UserAction', 'IMPORT_INITIATED');
|
| var result = this.getScanForImport_();
|
| - var importTask = this.importRunner_.importFromScanResult(result);
|
| + var importTask = this.importRunner_.importFromScanResult(
|
| + result,
|
| + importer.Destination.GOOGLE_DRIVE);
|
| +
|
| + this.activeImportTask_ = importTask;
|
| + var taskFinished = this.onImportFinished_.bind(this, importTask);
|
| + importTask.whenFinished.then(taskFinished).catch(taskFinished);
|
| + this.pushUpdate_();
|
| +};
|
| +
|
| +/**
|
| + * @param {!importer.MediaImportHandler.ImportTask} task
|
| + * @private
|
| + */
|
| +importer.ImportController.prototype.onImportFinished_ = function(task) {
|
| + this.activeImportTask_ = null;
|
| + this.resetScanCache_();
|
| + this.pushUpdate_();
|
| +};
|
| +
|
| +/** @private */
|
| +importer.ImportController.prototype.resetScanCache_ = function() {
|
| + // TODO(smckay): Actively cancel each scan.
|
| + this.cachedScans_ = {};
|
| };
|
|
|
| /**
|
| @@ -117,63 +144,88 @@ importer.ImportController.prototype.execute = function() {
|
| * @private
|
| */
|
| importer.ImportController.prototype.pushUpdate_ = function() {
|
| - this.commandWidget_.update(this.getCommandUpdate());
|
| + this.getCommandUpdate().then(
|
| + this.commandWidget_.update.bind(this.commandWidget_));
|
| };
|
|
|
| /**
|
| * Returns an update describing the state of the CommandWidget.
|
| *
|
| - * @return {!importer.CommandUpdate} response
|
| + * @return {!Promise.<!importer.CommandUpdate>} response
|
| */
|
| importer.ImportController.prototype.getCommandUpdate = function() {
|
| - // If there is no Google Drive mount, Drive may be disabled
|
| - // or the machine may be running in guest mode.
|
| - if (this.environment_.isGoogleDriveMounted()) {
|
| - var entries = this.environment_.getSelection();
|
| -
|
| - // Enabled if user has a selection and it consists entirely of files
|
| - // that:
|
| - // 1) are of a recognized media type
|
| - // 2) reside on a removable media device
|
| - // 3) in the DCIM directory
|
| - if (entries.length) {
|
| - if (entries.every(
|
| - importer.isEligibleEntry.bind(null, this.environment_))) {
|
| - return importer.ImportController.createUpdate_(
|
| - importer.ResponseId.EXECUTABLE, entries.length);
|
| - }
|
| - } else if (this.isCurrentDirectoryScannable_()) {
|
| - var scan = this.getCurrentDirectoryScan_();
|
| - if (scan.isFinal()) {
|
| - if (scan.getFileEntries().length === 0) {
|
| - return importer.ImportController.createUpdate_(
|
| - importer.ResponseId.NO_MEDIA);
|
| - } else {
|
| + return Promise.resolve().then(
|
| + function() {
|
| + if (!!this.activeImportTask_) {
|
| return importer.ImportController.createUpdate_(
|
| - importer.ResponseId.EXECUTABLE,
|
| - scan.getFileEntries().length);
|
| + importer.ResponseId.ACTIVE_IMPORT);
|
| + }
|
| +
|
| + // If there is no Google Drive mount, Drive may be disabled
|
| + // or the machine may be running in guest mode.
|
| + if (this.environment_.isGoogleDriveMounted()) {
|
| + var entries = this.environment_.getSelection();
|
| +
|
| + // Enabled if user has a selection and it consists entirely of files
|
| + // that:
|
| + // 1) are of a recognized media type
|
| + // 2) reside on a removable media device
|
| + // 3) in the DCIM directory
|
| + if (entries.length) {
|
| + if (entries.every(
|
| + importer.isEligibleEntry.bind(null, this.environment_))) {
|
| + return importer.ImportController.createUpdate_(
|
| + importer.ResponseId.EXECUTABLE, entries.length);
|
| + }
|
| + } else if (this.isCurrentDirectoryScannable_()) {
|
| + var scan = this.getCurrentDirectoryScan_();
|
| + if (scan.isFinal()) {
|
| + if (scan.getFileEntries().length === 0) {
|
| + return importer.ImportController.createUpdate_(
|
| + importer.ResponseId.NO_MEDIA);
|
| + } else {
|
| + return this.fitsInAvailableSpace_(scan).then(
|
| + /** @param {boolean} fits */
|
| + function(fits) {
|
| + return fits ?
|
| + importer.ImportController.createUpdate_(
|
| + importer.ResponseId.EXECUTABLE,
|
| + scan.getFileEntries().length) :
|
| + importer.ImportController.createUpdate_(
|
| + importer.ResponseId.INSUFFICIENT_SPACE,
|
| + scan.getTotalBytes());
|
| + });
|
| + }
|
| + } else {
|
| + return importer.ImportController.createUpdate_(
|
| + importer.ResponseId.SCANNING);
|
| + }
|
| + }
|
| }
|
| - } else {
|
| - return importer.ImportController.createUpdate_(
|
| - importer.ResponseId.SCANNING);
|
| - }
|
| - }
|
| - }
|
|
|
| - return importer.ImportController.createUpdate_(
|
| - importer.ResponseId.HIDDEN);
|
| + return importer.ImportController.createUpdate_(
|
| + importer.ResponseId.HIDDEN);
|
| + }.bind(this));
|
| };
|
|
|
| /**
|
| * @param {importer.ResponseId} responseId
|
| - * @param {number=} opt_fileCount
|
| + * @param {number=} opt_value
|
| *
|
| * @return {!importer.CommandUpdate}
|
| * @private
|
| */
|
| importer.ImportController.createUpdate_ =
|
| - function(responseId, opt_fileCount) {
|
| + function(responseId, opt_value) {
|
| switch(responseId) {
|
| + case importer.ResponseId.EXECUTABLE:
|
| + return {
|
| + id: responseId,
|
| + label: strf('CLOUD_IMPORT_BUTTON_LABEL', opt_value),
|
| + visible: true,
|
| + executable: true,
|
| + coreIcon: 'cloud-upload'
|
| + };
|
| case importer.ResponseId.HIDDEN:
|
| return {
|
| id: responseId,
|
| @@ -182,13 +234,21 @@ importer.ImportController.createUpdate_ =
|
| label: '** SHOULD NOT BE VISIBLE **',
|
| coreIcon: 'cloud-off'
|
| };
|
| - case importer.ResponseId.SCANNING:
|
| + case importer.ResponseId.ACTIVE_IMPORT:
|
| return {
|
| id: responseId,
|
| visible: true,
|
| executable: false,
|
| - label: str('CLOUD_IMPORT_SCANNING_BUTTON_LABEL'),
|
| - coreIcon: 'autorenew'
|
| + label: str('CLOUD_IMPORT_ACTIVE_IMPORT_BUTTON_LABEL'),
|
| + coreIcon: 'trending-up'
|
| + };
|
| + case importer.ResponseId.INSUFFICIENT_SPACE:
|
| + return {
|
| + id: responseId,
|
| + visible: true,
|
| + executable: false,
|
| + label: strf('CLOUD_IMPORT_INSUFFICIENT_SPACE_BUTTON_LABEL', opt_value),
|
| + coreIcon: 'report-problem'
|
| };
|
| case importer.ResponseId.NO_MEDIA:
|
| return {
|
| @@ -198,13 +258,13 @@ importer.ImportController.createUpdate_ =
|
| label: str('CLOUD_IMPORT_EMPTY_SCAN_BUTTON_LABEL'),
|
| coreIcon: 'cloud-done'
|
| };
|
| - case importer.ResponseId.EXECUTABLE:
|
| + case importer.ResponseId.SCANNING:
|
| return {
|
| id: responseId,
|
| - label: strf('CLOUD_IMPORT_BUTTON_LABEL', opt_fileCount),
|
| visible: true,
|
| - executable: true,
|
| - coreIcon: 'cloud-upload'
|
| + executable: false,
|
| + label: str('CLOUD_IMPORT_SCANNING_BUTTON_LABEL'),
|
| + coreIcon: 'autorenew'
|
| };
|
| default:
|
| assertNotReached('Unrecognized response id: ' + responseId);
|
| @@ -223,6 +283,25 @@ importer.ImportController.prototype.isCurrentDirectoryScannable_ =
|
| };
|
|
|
| /**
|
| + * @param {!importer.ScanResult} scanResult
|
| + * @return {!Promise.<boolean>} true if the files in scan will fit
|
| + * in available local storage space.
|
| + * @private
|
| + */
|
| +importer.ImportController.prototype.fitsInAvailableSpace_ =
|
| + function(scanResult) {
|
| + return this.environment_.getFreeStorageSpace().then(
|
| + /** @param {number} availableSpace In bytes. */
|
| + function(availableSpace) {
|
| + // TODO(smckay): We might want to disqualify some small amount
|
| + // storage in this calculation on the assumption that we
|
| + // don't want to completely max out storage...even though
|
| + // synced files will eventually be evicted from the cache.
|
| + return availableSpace > scanResult.getTotalBytes();
|
| + });
|
| +};
|
| +
|
| +/**
|
| * Get or create scan for the current directory or file selection.
|
| *
|
| * @return {!importer.ScanResult} A scan result object that may be
|
| @@ -274,10 +353,7 @@ importer.ImportController.prototype.getCurrentDirectoryScan_ = function() {
|
| * @private
|
| */
|
| importer.ImportController.prototype.onVolumeUnmounted_ = function(volumeId) {
|
| - // Forget all scans related to the unmounted volume volume.
|
| - if (this.cachedScans_.hasOwnProperty(volumeId)) {
|
| - delete this.cachedScans_[volumeId];
|
| - }
|
| + this.resetScanCache_();
|
| this.pushUpdate_();
|
| };
|
|
|
| @@ -320,6 +396,12 @@ importer.ControllerEnvironment.prototype.setCurrentDirectory;
|
| importer.ControllerEnvironment.prototype.isGoogleDriveMounted;
|
|
|
| /**
|
| + * Returns the available space for the local volume in bytes.
|
| + * @return {!Promise.<number>}
|
| + */
|
| +importer.ControllerEnvironment.prototype.getFreeStorageSpace;
|
| +
|
| +/**
|
| * Installs an 'unmount' listener. Listener is called with
|
| * the corresponding volume id when a volume is unmounted.
|
| * @param {function(string)} listener
|
| @@ -398,6 +480,29 @@ importer.RuntimeControllerEnvironment.prototype.addVolumeUnmountListener =
|
| };
|
|
|
| /** @override */
|
| +
|
| +importer.RuntimeControllerEnvironment.prototype.getFreeStorageSpace =
|
| + function() {
|
| + // TODO(smckay): Verify if this should be for Downloads or Drive.
|
| + var localVolumeInfo =
|
| + this.fileManager_.volumeManager.getCurrentProfileVolumeInfo(
|
| + VolumeManagerCommon.VolumeType.DOWNLOADS);
|
| + return new Promise(
|
| + function(resolve, reject) {
|
| + chrome.fileManagerPrivate.getSizeStats(
|
| + localVolumeInfo.volumeId,
|
| + /** @param {Object} stats */
|
| + function(stats) {
|
| + if (stats && !chrome.runtime.lastError) {
|
| + resolve(stats.remainingSize);
|
| + } else {
|
| + reject('Failed to ascertain available free space.');
|
| + }
|
| + });
|
| + });
|
| +};
|
| +
|
| +/** @override */
|
| importer.RuntimeControllerEnvironment.prototype.addDirectoryChangedListener =
|
| function(listener) {
|
| // TODO(smckay): remove listeners when the page is torn down.
|
|
|