| 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..b311fc6a51750ee11f09e5d433a0faa588a11240 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,17 @@ importer.ScanResult.prototype.whenFinal;
|
| */
|
| importer.DefaultMediaScanner = function(
|
| hashGenerator, historyLoader, watcherFactory) {
|
| +
|
| + /** @private {!importer.HistoryLoader} */
|
| + this.historyLoader_ = historyLoader;
|
| +
|
| /**
|
| * 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(hashGenerator);
|
| };
|
|
|
| /** @private {!Array.<!importer.ScanObserver>} */
|
| @@ -134,75 +138,85 @@ importer.DefaultMediaScanner.prototype.scan = function(entries) {
|
| throw new Error('Cannot scan empty list of entries.');
|
| }
|
|
|
| - var scanResult = this.createScanResult_();
|
| + var scan = this.createScanResult_();
|
| var watcher = this.watcherFactory_(
|
| /** @this {importer.DefaultMediaScanner} */
|
| function() {
|
| - scanResult.invalidateScan();
|
| - this.observers_.forEach(
|
| - /** @param {!importer.ScanObserver} observer */
|
| - function(observer) {
|
| - observer(importer.ScanEvent.INVALIDATED, scanResult);
|
| - });
|
| + scan.invalidateScan();
|
| + this.notify_(importer.ScanEvent.INVALIDATED, scan);
|
| }.bind(this));
|
| +
|
| var scanPromises = entries.map(
|
| - this.scanEntry_.bind(this, scanResult, watcher));
|
| + this.scanEntry_.bind(this, scan, watcher));
|
|
|
| Promise.all(scanPromises)
|
| - .then(scanResult.resolve)
|
| - .catch(scanResult.reject);
|
| + .then(scan.resolve)
|
| + .catch(scan.reject);
|
|
|
| - scanResult.whenFinal()
|
| + scan.whenFinal()
|
| .then(
|
| + /** @this {importer.DefaultMediaScanner} */
|
| function() {
|
| - this.onScanFinished_(scanResult);
|
| + this.notify_(importer.ScanEvent.FINALIZED, scan);
|
| }.bind(this));
|
|
|
| - return scanResult;
|
| + return scan;
|
| };
|
|
|
| /**
|
| - * 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));
|
| + }
|
| +
|
| + // Since this entry is by client code (and presumably the user)
|
| + // we add it directly (skipping over the history dupe check).
|
| + return this.onUniqueFileFound_(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,113 @@ importer.DefaultMediaScanner.prototype.scanDirectory_ =
|
|
|
| return fileOperationUtil.findEntriesRecursively(
|
| entry,
|
| - /** @param {!Entry} entry */
|
| + /**
|
| + * @param {!Entry} entry
|
| + * @this {importer.DefaultMediaScanner}
|
| + */
|
| function(entry) {
|
| if (watcher.triggered) {
|
| return;
|
| }
|
| +
|
| if (entry.isDirectory) {
|
| + // Note, there is no need for us to recurse, the utility
|
| + // function findEntriesRecursively does that. So we
|
| + // 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) {
|
| + return this.hasHistoryDuplicate_(entry)
|
| + .then(
|
| + /**
|
| + * @param {boolean} duplicate
|
| + * @return {!Promise}
|
| + * @this {importer.DefaultMediaScanner}
|
| + */
|
| + function(duplicate) {
|
| + if (!duplicate) {
|
| + return this.onUniqueFileFound_(scan, entry);
|
| + }
|
| + }.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * Finds all files beneath directory.
|
| + *
|
| + * @param {!importer.DefaultScanResult} scan
|
| + * @param {!FileEntry} entry
|
| + * @return {!Promise}
|
| + * @private
|
| + */
|
| +importer.DefaultMediaScanner.prototype.onUniqueFileFound_ =
|
| + function(scan, entry) {
|
| +
|
| + if (!FileType.isImageOrVideo(entry)) {
|
| + return Promise.resolve();
|
| + }
|
| +
|
| + return scan.addFileEntry(entry)
|
| + .then(
|
| + /**
|
| + * @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.
|
| *
|
| @@ -236,17 +341,14 @@ importer.DefaultMediaScanner.prototype.scanDirectory_ =
|
| * @struct
|
| * @implements {importer.ScanResult}
|
| *
|
| - * @param {function(!FileEntry): !Promise.<string>} hashGenerator
|
| - * @param {!importer.HistoryLoader} historyLoader
|
| + * @param {function(!FileEntry): !Promise.<string>} hashGenerator Hash-code
|
| + * generator used to dedupe within the scan results itself.
|
| */
|
| -importer.DefaultScanResult = function(hashGenerator, historyLoader) {
|
| +importer.DefaultScanResult = function(hashGenerator) {
|
|
|
| /** @private {function(!FileEntry): !Promise.<string>} */
|
| this.createHashcode_ = hashGenerator;
|
|
|
| - /** @private {!importer.HistoryLoader} */
|
| - this.historyLoader_ = historyLoader;
|
| -
|
| /**
|
| * List of file entries found while scanning.
|
| * @private {!Array.<!FileEntry>}
|
| @@ -254,11 +356,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 +378,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 +434,44 @@ 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.
|
| + * @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();
|
| - }
|
| -
|
| - 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(
|
| +importer.DefaultScanResult.prototype.addFileEntry = function(entry) {
|
| + 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.');
|
| +
|
| + return this.createHashcode_(entry)
|
| + .then(
|
| /**
|
| - * @param {!Array.<boolean>} results
|
| - * @return {!Promise}
|
| + * @param {string} hashcode
|
| * @this {importer.DefaultScanResult}
|
| */
|
| - function(results) {
|
| - return results[0] || results[1] ?
|
| - Promise.resolve() :
|
| - this.addFileEntry_(entry);
|
| + function(hashcode) {
|
| + this.lastScanActivity_ = new Date();
|
| +
|
| + if (hashcode in this.fileHashcodes_) {
|
| + return false;
|
| + }
|
| +
|
| + entry.size = metadata.size;
|
| + this.totalBytes_ += metadata['size'];
|
| + this.fileHashcodes_[hashcode] = entry;
|
| + this.fileEntries_.push(entry);
|
| + return true;
|
| }.bind(this));
|
| - }.bind(this));
|
| -};
|
|
|
| -/**
|
| - * 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));
|
| };
|
|
|
| /**
|
|
|