Chromium Code Reviews| 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 b6f99d4d09ac9c18fe272a07b5e45f4f7f58179c..578ea182588c66022e206eb187a3731a1a6c0c8f 100644 |
| --- a/ui/file_manager/file_manager/foreground/js/import_controller.js |
| +++ b/ui/file_manager/file_manager/foreground/js/import_controller.js |
| @@ -10,6 +10,7 @@ importer.ResponseId = { |
| HIDDEN: 'hidden', |
| SCANNING: 'scanning', |
| NO_MEDIA: 'no_media', |
| + NO_SPACE: 'no_spcae', |
|
Steve McKay
2015/01/26 18:08:43
INSUFFICIENT_SPACE: 'insufficient_space'
|
| EXECUTABLE: 'executable' |
| }; |
| @@ -55,9 +56,15 @@ importer.ImportController = |
| /** |
| * A cache of scans by volumeId, directory URL. |
| * Currently only scans of directories are cached. |
| - * @private {!Object.<string, !Object.<string, !importer.ScanResult>>} |
| + * @private {!importer.ScanCache} |
| + * @const |
| */ |
| - this.cachedScans_ = {}; |
| + this.scanCache_ = new importer.ScanCache(); |
| + |
| + /** |
| + * @private {number} |
| + */ |
| + this.localVolumeAvilableSize_ = 0; |
| var listener = this.onScanEvent_.bind(this); |
| this.scanner_.addObserver(listener); |
| @@ -65,8 +72,6 @@ importer.ImportController = |
| window.addEventListener('pagehide', function() { |
| this.scanner_.removeObserver(listener); |
| }.bind(this)); |
| - this.environment_.addVolumeUnmountListener( |
| - this.onVolumeUnmounted_.bind(this)); |
| }; |
| /** |
| @@ -77,17 +82,16 @@ 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]; |
| - } |
| - } |
| + if (this.scanCache_.currentCache.isInvalidated()) { |
|
Steve McKay
2015/01/26 18:08:43
Let's keep this super simple for now. When *any* s
|
| + this.scanCache_.clear(); |
| } |
| } |
| if (event === importer.ScanEvent.FINALIZED || |
| event === importer.ScanEvent.INVALIDATED) { |
| - this.updateCommands_(); |
| + this.environment_.getLocalVolumeAvailableSize().then(function(size) { |
|
Steve McKay
2015/01/26 18:08:43
Please wrap before anonymous functions. NOT wrappi
|
| + this.localVolumeAvilableSize_ = size; |
| + this.updateCommands_(); |
| + }.bind(this)); |
| } |
| }; |
| @@ -98,7 +102,7 @@ importer.ImportController.prototype.onScanEvent_ = function(event, result) { |
| */ |
| importer.ImportController.prototype.execute = function() { |
| metrics.recordEnum('CloudImport.UserAction', 'IMPORT_INITIATED'); |
| - var result = this.getScanForImport_(); |
| + var result = assert(this.getScanForImport_()); |
| var importTask = this.importRunner_.importFromScanResult(result); |
| importTask.getDestination().then( |
| @@ -120,25 +124,15 @@ 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_(); |
| + var scan = this.getScanForImport_(); |
| + if (scan) { |
| if (scan.isFinal()) { |
| if (scan.getFileEntries().length === 0) { |
| return importer.ImportController.createUpdate_( |
| importer.ResponseId.NO_MEDIA); |
| + } else if (scan.getTotalBytes() > this.localVolumeAvilableSize_) { |
| + return importer.ImportController.createUpdate_( |
| + importer.ResponseId.NO_SPACE); |
| } else { |
| return importer.ImportController.createUpdate_( |
| importer.ResponseId.EXECUTABLE, |
| @@ -186,12 +180,19 @@ importer.ImportController.createUpdate_ = |
| executable: false, |
| label: str('CLOUD_IMPORT_EMPTY_SCAN_BUTTON_LABEL') |
| }; |
| + case importer.ResponseId.NO_SPACE: |
| + return { |
| + id: responseId, |
| + visible: true, |
| + executable: false, |
| + label: str('CLOUD_IMPORT_INSUFFICIENT_SPACE_BUTTON_LABEL'), |
| + }; |
| case importer.ResponseId.EXECUTABLE: |
| return { |
| id: responseId, |
| - label: strf('CLOUD_IMPORT_BUTTON_LABEL', opt_fileCount), |
| visible: true, |
| - executable: true |
| + executable: true, |
| + label: strf('CLOUD_IMPORT_BUTTON_LABEL', opt_fileCount), |
| }; |
| default: |
| assertNotReached('Unrecognized response id: ' + responseId); |
| @@ -199,75 +200,44 @@ importer.ImportController.createUpdate_ = |
| }; |
| /** |
| - * @return {boolean} true if the current directory is scan eligible. |
| - * @private |
| - */ |
| -importer.ImportController.prototype.isCurrentDirectoryScannable_ = |
| - function() { |
| - var directory = this.environment_.getCurrentDirectory(); |
| - return !!directory && |
| - importer.isMediaDirectory(directory, this.environment_); |
| -}; |
| - |
| -/** |
| * Get or create scan for the current directory or file selection. |
| * |
| - * @return {!importer.ScanResult} A scan result object that may be |
| - * actively scanning. |
| + * @return {importer.ScanResult} A scan result object that may be |
| + * actively scanning. Can be null. |
| * @private |
| */ |
| importer.ImportController.prototype.getScanForImport_ = function() { |
|
Steve McKay
2015/01/26 18:08:43
Move this logic into ScanCache and rename that cla
|
| var entries = this.environment_.getSelection(); |
| - |
| + var targetEntries = null; |
| if (entries.length) { |
| if (entries.every( |
| importer.isEligibleEntry.bind(null, this.environment_))) { |
| - return this.scanner_.scan(entries); |
| + // 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 |
| + targetEntries = entries; |
| } |
| } else { |
| - return this.getCurrentDirectoryScan_(); |
| - } |
| -}; |
| - |
| -/** |
| - * Get or create scan for the current directory. |
| - * |
| - * @return {!importer.ScanResult} A scan result object that may be |
| - * actively scanning. |
| - * @private |
| - */ |
| -importer.ImportController.prototype.getCurrentDirectoryScan_ = function() { |
| - console.assert(this.isCurrentDirectoryScannable_()); |
| - var directory = this.environment_.getCurrentDirectory(); |
| - var volumeId = this.environment_.getVolumeInfo(directory).volumeId; |
| - |
| - // Lazily initialize the cache for volumeId. |
| - if (!this.cachedScans_.hasOwnProperty(volumeId)) { |
| - this.cachedScans_[volumeId] = {}; |
| + var directory = this.environment_.getCurrentDirectory(); |
| + if (!!directory && |
| + importer.isMediaDirectory(directory, this.environment_)) { |
| + targetEntries = [directory]; |
| + } |
| } |
| - |
| - var url = directory.toURL(); |
| - var scan = this.cachedScans_[volumeId][url]; |
| + if (!targetEntries) |
| + return null; |
| + var scan = this.scanCache_.getForEntries(targetEntries); |
|
Steve McKay
2015/01/26 18:08:43
We can't throw away directory scans. A recursive s
|
| if (!scan) { |
| - scan = this.scanner_.scan([directory]); |
| - this.cachedScans_[volumeId][url] = scan; |
| + scan = this.scanner_.scan(targetEntries); |
| + this.scanCache_.putForEntries(targetEntries, scan); |
| } |
| assert(!scan.isInvalidated()); |
| return scan; |
| }; |
| /** |
| - * @param {string} volumeId |
| - * @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]; |
| - } |
| -}; |
| - |
| -/** |
| * Interface abstracting away the concrete file manager available |
| * to commands. By hiding file manager we make it easy to test |
| * ImportController. |
| @@ -301,11 +271,9 @@ importer.ControllerEnvironment.prototype.setCurrentDirectory; |
| importer.ControllerEnvironment.prototype.isGoogleDriveMounted; |
| /** |
| - * Installs an 'unmount' listener. Listener is called with |
| - * the corresponding volume id when a volume is unmounted. |
| - * @param {function(string)} listener |
| + * @return {!Promise<number>} |
| */ |
| -importer.ControllerEnvironment.prototype.addVolumeUnmountListener; |
|
Steve McKay
2015/01/26 18:08:44
We still need to invalidate scans when a volume is
|
| +importer.ControllerEnvironment.prototype.getLocalVolumeAvailableSize; |
| /** |
| * Class providing access to various pieces of information in the |
| @@ -356,16 +324,74 @@ importer.RuntimeControllerEnvironment.prototype.isGoogleDriveMounted = |
| }; |
| /** @override */ |
| -importer.RuntimeControllerEnvironment.prototype.addVolumeUnmountListener = |
| - function(listener) { |
| - chrome.fileManagerPrivate.onMountCompleted.addListener( |
| - /** |
| - * @param {!MountCompletedEvent} event |
| - * @this {importer.RuntimeControllerEnvironment} |
| - */ |
| - function(event) { |
| - if (event.eventType === 'unmount') { |
| - listener(event.volumeMetadata.volumeId); |
| - } |
| - }); |
| +importer.RuntimeControllerEnvironment.prototype.getLocalVolumeAvailableSize = |
| + function() { |
| + var localVolumeInfo = |
| + this.fileManager_.volumeManager.getCurrentProfileVolumeInfo( |
| + VolumeManagerCommon.VolumeType.DOWNLOADS); |
| + return new Promise(function(fulfill, reject) { |
| + chrome.fileManagerPrivate.getSizeStats( |
| + localVolumeInfo.volumeId, |
| + function(stats) { |
| + if (stats && !chrome.runtime.lastError) { |
| + fulfill(stats.remainingSize); |
| + } else { |
| + reject('Failed to obtain remaining size.'); |
| + } |
| + }); |
| + }); |
| +}; |
| + |
| +/** |
| + * Cache for scan. |
| + * @constructor |
| + */ |
| +importer.ScanCache = function() { |
| + /** |
| + * Cache key |
| + * @private {string} |
| + */ |
| + this.cacheKey_ = ''; |
| + |
| + /** |
| + * Cached scan. |
| + * @public {importer.ScanResult} |
| + */ |
| + this.currentCache = null; |
| +}; |
| + |
| +/** |
| + * Creates cache key for the entries. |
| + * @param {!Array<!Entry>} entries |
| + * @return {string} |
| + * @private |
| + */ |
| +importer.ScanCache.createKey_ = function(entries) { |
| + return util.entriesToURLs(entries).sort().join('\n'); |
| +}; |
| + |
| +/** |
| + * Puts scan result for the entries into the cache. |
| + * @param {!Array<!Entry>} entries |
| + * @param {importer.ScanResult} scan |
| + */ |
| +importer.ScanCache.prototype.putForEntries = function(entries, scan) { |
| + this.cacheKey_ = importer.ScanCache.createKey_(entries); |
| + this.currentCache = scan; |
| +}; |
| + |
| +/** |
| + * Gets cached scan result for the entries. |
| + */ |
| +importer.ScanCache.prototype.getForEntries = function(entries) { |
| + var key = importer.ScanCache.createKey_(entries); |
| + return this.cacheKey_ === key ? this.currentCache : null; |
| +}; |
| + |
| +/** |
| + * Clears the cached scan result. |
| + */ |
| +importer.ScanCache.prototype.clear = function() { |
| + this.cacheKey_ = ''; |
| + this.currentCache = null; |
| }; |