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)); |
}; |
/** |