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. |