Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(38)

Unified Diff: ui/file_manager/file_manager/foreground/js/import_controller.js

Issue 888693002: Correctly handle out of storage space and "actively importing" states in controller. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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.

Powered by Google App Engine
This is Rietveld 408576698