Chromium Code Reviews| Index: ui/file_manager/file_manager/background/js/media_scanner.js |
| diff --git a/ui/file_manager/file_manager/background/js/media_scanner.js b/ui/file_manager/file_manager/background/js/media_scanner.js |
| index 84c0b21f3b3c9228fa1da66608463c381a7ee71c..1d6fce5c054a6e9769fd31e7431c91835b95f468 100644 |
| --- a/ui/file_manager/file_manager/background/js/media_scanner.js |
| +++ b/ui/file_manager/file_manager/background/js/media_scanner.js |
| @@ -94,13 +94,20 @@ importer.ScanResult.prototype.whenFinal; |
| */ |
| importer.DefaultMediaScanner = function( |
| hashGenerator, historyLoader, watcherFactory) { |
| + |
| + /** @private {!importer.HistoryLoader} */ |
| + this.historyLoader_ = historyLoader; |
| + |
| + /** @private {function(!FileEntry): !Promise.<string>} */ |
| + this.createHashcode_ = hashGenerator; |
| + |
| /** |
| * A little factory for DefaultScanResults which allows us to forgo |
| * the saving it's dependencies in our fields. |
| * @return {!importer.DefaultScanResult} |
| */ |
| this.createScanResult_ = function() { |
| - return new importer.DefaultScanResult(hashGenerator, historyLoader); |
| + return new importer.DefaultScanResult(); |
| }; |
| /** @private {!Array.<!importer.ScanObserver>} */ |
| @@ -139,11 +146,7 @@ importer.DefaultMediaScanner.prototype.scan = function(entries) { |
| /** @this {importer.DefaultMediaScanner} */ |
| function() { |
| scanResult.invalidateScan(); |
| - this.observers_.forEach( |
| - /** @param {!importer.ScanObserver} observer */ |
| - function(observer) { |
| - observer(importer.ScanEvent.INVALIDATED, scanResult); |
| - }); |
| + this.notify_(importer.ScanEvent.INVALIDATED, scanResult); |
| }.bind(this)); |
| var scanPromises = entries.map( |
| this.scanEntry_.bind(this, scanResult, watcher)); |
| @@ -154,55 +157,66 @@ importer.DefaultMediaScanner.prototype.scan = function(entries) { |
| scanResult.whenFinal() |
| .then( |
| + /** @this {importer.DefaultMediaScanner} */ |
| function() { |
| - this.onScanFinished_(scanResult); |
| + this.notify_(importer.ScanEvent.FINALIZED, scanResult); |
| }.bind(this)); |
| return scanResult; |
| }; |
| /** |
| - * Called when a scan is finished. |
| + * Notifies all listeners at some point in the near future. |
| * |
| + * @param {!importer.ScanEvent} event |
| * @param {!importer.DefaultScanResult} result |
| * @private |
| */ |
| -importer.DefaultMediaScanner.prototype.onScanFinished_ = function(result) { |
| +importer.DefaultMediaScanner.prototype.notify_ = function(event, result) { |
| this.observers_.forEach( |
| /** @param {!importer.ScanObserver} observer */ |
| function(observer) { |
| - observer(importer.ScanEvent.FINALIZED, result); |
| + observer(event, result); |
| }); |
| }; |
| /** |
| - * Resolves the entry to a list of {@code FileEntry}. |
| + * Resolves the entry by either: |
| + * a) recursing on it (when a directory) |
| + * b) adding it to the results (when a media type file) |
| + * c) ignoring it, if neither a or b |
| * |
| - * @param {!importer.DefaultScanResult} result |
| + * @param {!importer.DefaultScanResult} scan |
| * @param {!importer.DirectoryWatcher} watcher |
| * @param {!Entry} entry |
| + * |
| * @return {!Promise} |
| * @private |
| */ |
| importer.DefaultMediaScanner.prototype.scanEntry_ = |
| - function(result, watcher, entry) { |
| - return entry.isFile ? |
| - result.onFileEntryFound(/** @type {!FileEntry} */ (entry)) : |
| - this.scanDirectory_( |
| - result, watcher, /** @type {!DirectoryEntry} */ (entry)); |
| + function(scan, watcher, entry) { |
| + if (entry.isDirectory) { |
| + return this.scanDirectory_( |
| + scan, |
| + watcher, |
| + /** @type {!DirectoryEntry} */ (entry)); |
| + } |
| + |
| + console.assert(entry.isFile); |
|
Ben Kwa
2015/02/05 14:54:26
Suggestion: include an explanatory string in the a
Steve McKay
2015/02/05 15:46:46
Removed.
|
| + return this.onFileEntryFound_(scan, /** @type {!FileEntry} */ (entry)); |
| }; |
| /** |
| * Finds all files beneath directory. |
| * |
| - * @param {!importer.DefaultScanResult} result |
| + * @param {!importer.DefaultScanResult} scan |
| * @param {!importer.DirectoryWatcher} watcher |
| * @param {!DirectoryEntry} entry |
| * @return {!Promise} |
| * @private |
| */ |
| importer.DefaultMediaScanner.prototype.scanDirectory_ = |
| - function(result, watcher, entry) { |
| + function(scan, watcher, entry) { |
| // Collect promises for all files being added to results. |
| // The directory scan promise can't resolve until all |
| // file entries are completely promised. |
| @@ -210,22 +224,104 @@ importer.DefaultMediaScanner.prototype.scanDirectory_ = |
| return fileOperationUtil.findEntriesRecursively( |
| entry, |
| - /** @param {!Entry} entry */ |
| + /** |
| + * @param {!Entry} entry |
| + * @this {importer.DefaultMediaScanner} |
| + */ |
| function(entry) { |
| if (watcher.triggered) { |
| + console.log('Skipping file entry...watched directory was modified.'); |
|
hirono
2015/02/05 10:51:43
Is it intentionally left here?
Steve McKay
2015/02/05 15:46:46
It was, but on second thought...removed it.
|
| return; |
| } |
| + |
| if (entry.isDirectory) { |
| + // Note, there is no need for us to recurse, the utility |
| + // funciton findEntriesRecursively does that. So we |
|
Ben Kwa
2015/02/05 14:54:26
function
Steve McKay
2015/02/05 15:46:46
Done.
|
| + // just watch the directory for modifications, and that's it. |
| watcher.addDirectory(/** @type {!DirectoryEntry} */(entry)); |
| - } else { |
| - promises.push( |
| - result.onFileEntryFound(/** @type {!FileEntry} */(entry))); |
| + return; |
| } |
| - }) |
| + |
| + promises.push( |
| + this.onFileEntryFound_(scan, /** @type {!FileEntry} */(entry))); |
| + |
| + }.bind(this)) |
| .then(Promise.all.bind(Promise, promises)); |
| }; |
| /** |
| + * Finds all files beneath directory. |
| + * |
| + * @param {!importer.DefaultScanResult} scan |
| + * @param {!FileEntry} entry |
| + * @return {!Promise} |
| + * @private |
| + */ |
| +importer.DefaultMediaScanner.prototype.onFileEntryFound_ = |
| + function(scan, entry) { |
| + |
| + if (!FileType.isImageOrVideo(entry)) { |
| + return Promise.resolve(false); |
|
Ben Kwa
2015/02/05 14:54:26
I think this should just be Promise.resolve(). Th
Steve McKay
2015/02/05 15:46:46
Done.
|
| + } |
| + return this.hasHistoryDuplicate_(entry).then( |
|
Ben Kwa
2015/02/05 14:54:26
nit: line break before .then
Steve McKay
2015/02/05 15:46:46
Done.
|
| + /** |
| + * @param {boolean} duplicate |
| + * @return {!Promise} |
| + * @this {importer.DefaultMediaScanner} |
| + */ |
| + function(duplicate) { |
| + if (duplicate) { |
| + return false; |
| + } else { |
| + return this.createHashcode_(entry) |
| + .then( |
| + function(hashcode) { |
| + return scan.addFileEntry(entry, hashcode); |
| + }); |
| + } |
| + }.bind(this)).then( |
|
Ben Kwa
2015/02/05 14:54:26
nit: line break before .then
Steve McKay
2015/02/05 15:46:46
Done.
|
| + /** |
| + * @param {boolean} added |
| + * @this {importer.DefaultMediaScanner} |
| + */ |
| + function(added) { |
| + if (added) { |
| + this.notify_(importer.ScanEvent.UPDATED, scan); |
| + } |
| + }.bind(this)); |
| +}; |
| + |
| +/** |
| + * @param {!FileEntry} entry |
| + * @return {!Promise.<boolean>} True if there is a history-entry-duplicate |
| + * for the file. |
| + * @private |
| + */ |
| +importer.DefaultMediaScanner.prototype.hasHistoryDuplicate_ = function(entry) { |
| + return this.historyLoader_.getHistory() |
| + .then( |
| + /** |
| + * @param {!importer.ImportHistory} history |
| + * @return {!Promise} |
| + * @this {importer.DefaultMediaScanner} |
| + */ |
| + function(history) { |
| + return Promise.all([ |
| + history.wasCopied(entry, importer.Destination.GOOGLE_DRIVE), |
| + history.wasImported(entry, importer.Destination.GOOGLE_DRIVE) |
| + ]).then( |
| + /** |
| + * @param {!Array.<boolean>} results |
| + * @return {!Promise} |
| + * @this {importer.DefaultMediaScanner} |
| + */ |
| + function(results) { |
| + return results[0] || results[1]; |
| + }.bind(this)); |
| + }.bind(this)); |
| +}; |
| + |
| +/** |
| * Results of a scan operation. The object is "live" in that data can and |
| * will change as the scan operation discovers files. |
| * |
| @@ -235,17 +331,8 @@ importer.DefaultMediaScanner.prototype.scanDirectory_ = |
| * @constructor |
| * @struct |
| * @implements {importer.ScanResult} |
| - * |
| - * @param {function(!FileEntry): !Promise.<string>} hashGenerator |
| - * @param {!importer.HistoryLoader} historyLoader |
| */ |
| -importer.DefaultScanResult = function(hashGenerator, historyLoader) { |
| - |
| - /** @private {function(!FileEntry): !Promise.<string>} */ |
| - this.createHashcode_ = hashGenerator; |
| - |
| - /** @private {!importer.HistoryLoader} */ |
| - this.historyLoader_ = historyLoader; |
| +importer.DefaultScanResult = function() { |
| /** |
| * List of file entries found while scanning. |
| @@ -254,11 +341,6 @@ importer.DefaultScanResult = function(hashGenerator, historyLoader) { |
| this.fileEntries_ = []; |
| /** |
| - * @private {boolean} |
| - */ |
| - this.invalidated_ = false; |
| - |
| - /** |
| * Hashcodes of all files included captured by this result object so-far. |
| * Used to dedupe newly discovered files against other files withing |
| * the ScanResult. |
| @@ -281,6 +363,11 @@ importer.DefaultScanResult = function(hashGenerator, historyLoader) { |
| */ |
| this.lastScanActivity_ = this.scanStarted_; |
| + /** |
| + * @private {boolean} |
| + */ |
| + this.invalidated_ = false; |
| + |
| /** @private {!importer.Resolver.<!importer.ScanResult>} */ |
| this.resolver_ = new importer.Resolver(); |
| }; |
| @@ -332,92 +419,38 @@ importer.DefaultScanResult.prototype.invalidateScan = function() { |
| }; |
| /** |
| - * Handles files discovered during scanning. |
| + * Adds a file to results. |
| * |
| * @param {!FileEntry} entry |
| - * @return {!Promise} Resolves once file entry has been processed |
| - * and is represented in results. |
| + * @param {string} hashcode |
| + * @return {!Promise.<boolean>} True if the file as added, false if it was |
| + * rejected as a dupe. |
| */ |
| -importer.DefaultScanResult.prototype.onFileEntryFound = function(entry) { |
| - this.lastScanActivity_ = new Date(); |
| - |
| - if (!FileType.isImageOrVideo(entry)) { |
| - return Promise.resolve(); |
| - } |
| +importer.DefaultScanResult.prototype.addFileEntry = function(entry, hashcode) { |
| + return new Promise(entry.getMetadata.bind(entry)).then( |
| + /** |
| + * @param {!Metadata} metadata |
| + * @this {importer.DefaultScanResult} |
| + */ |
| + function(metadata) { |
| + console.assert( |
| + 'size' in metadata, |
| + 'size attribute missing from metadata.'); |
| + this.lastScanActivity_ = new Date(); |
| + |
| + // We wait to check the hashcode until after all |
|
Ben Kwa
2015/02/05 14:54:26
I find this comment confusing. Which async data a
Steve McKay
2015/02/05 15:46:46
Rewrote to make the thought clearer.
|
| + // async data is loaded. This avoids a possible race. |
| + if (hashcode in this.fileHashcodes_) { |
| + return false; |
| + } |
| - return this.historyLoader_.getHistory() |
| - .then( |
| - /** |
| - * @param {!importer.ImportHistory} history |
| - * @return {!Promise} |
| - * @this {importer.DefaultScanResult} |
| - */ |
| - function(history) { |
| - return Promise.all([ |
| - history.wasCopied(entry, importer.Destination.GOOGLE_DRIVE), |
| - history.wasImported(entry, importer.Destination.GOOGLE_DRIVE) |
| - ]).then( |
| - /** |
| - * @param {!Array.<boolean>} results |
| - * @return {!Promise} |
| - * @this {importer.DefaultScanResult} |
| - */ |
| - function(results) { |
| - return results[0] || results[1] ? |
| - Promise.resolve() : |
| - this.addFileEntry_(entry); |
| - }.bind(this)); |
| - }.bind(this)); |
| -}; |
| + entry.size = metadata.size; |
| + this.totalBytes_ += metadata['size']; |
| + this.fileHashcodes_[hashcode] = entry; |
| + this.fileEntries_.push(entry); |
| + return true; |
| -/** |
| - * Adds a file to results. |
| - * |
| - * @param {!FileEntry} entry |
| - * @return {!Promise} Resolves once file entry has been processed |
| - * and is represented in results. |
| - * @private |
| - */ |
| -importer.DefaultScanResult.prototype.addFileEntry_ = function(entry) { |
| - return new Promise( |
| - function(resolve, reject) { |
| - this.createHashcode_(entry).then( |
| - /** |
| - * @param {string} hashcode |
| - * @this {importer.DefaultScanResult} |
| - */ |
| - function(hashcode) { |
| - // Ignore the entry if it is a duplicate. |
| - if (hashcode in this.fileHashcodes_) { |
| - resolve(); |
| - return; |
| - } |
| - |
| - entry.getMetadata( |
| - /** |
| - * @param {!Metadata} metadata |
| - * @this {importer.DefaultScanResult} |
| - */ |
| - function(metadata) { |
| - console.assert( |
| - 'size' in metadata, |
| - 'size attribute missing from metadata.'); |
| - this.lastScanActivity_ = new Date(); |
| - |
| - // Double check that a dupe entry wasn't added while we were |
| - // busy looking up metadata. |
| - if (hashcode in this.fileHashcodes_) { |
| - resolve(); |
| - return; |
| - } |
| - entry.size = metadata.size; |
| - this.totalBytes_ += metadata['size']; |
| - this.fileHashcodes_[hashcode] = entry; |
| - this.fileEntries_.push(entry); |
| - resolve(); |
| - }.bind(this)); |
| - }.bind(this)); |
| - }.bind(this)); |
| + }.bind(this)); |
| }; |
| /** |