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 2d4ce96d91439929c58de6dd8d948de9297d2c36..895c7fa03e32de81d2f64365d061bd047c1e8f78 100644 |
--- a/ui/file_manager/file_manager/background/js/media_scanner.js |
+++ b/ui/file_manager/file_manager/background/js/media_scanner.js |
@@ -45,6 +45,11 @@ importer.ScanResult = function() {}; |
importer.ScanResult.prototype.isFinal; |
/** |
+ * @return {boolean} true if scanning is invalidated. |
+ */ |
+importer.ScanResult.prototype.isInvalidated; |
+ |
+/** |
* Returns all files entries discovered so far. The list will be |
* complete only after scanning has completed and {@code isFinal} |
* returns {@code true}. |
@@ -85,12 +90,14 @@ importer.ScanResult.prototype.whenFinal; |
* |
* @param {function(!FileEntry): !Promise.<string>} hashGenerator |
* @param {!importer.HistoryLoader} historyLoader |
+ * @param {!importer.DirectoryWatcherFactory} watcherFactory |
*/ |
-importer.DefaultMediaScanner = function(hashGenerator, historyLoader) { |
+importer.DefaultMediaScanner = function( |
+ hashGenerator, historyLoader, watcherFactory) { |
/** |
* A little factory for DefaultScanResults which allows us to forgo |
* the saving it's dependencies in our fields. |
- * @private {function(): !importer.DefaultScanResult} |
+ * @return {!importer.DefaultScanResult} |
*/ |
this.createScanResult_ = function() { |
return new importer.DefaultScanResult(hashGenerator, historyLoader); |
@@ -98,6 +105,12 @@ importer.DefaultMediaScanner = function(hashGenerator, historyLoader) { |
/** @private {!Array.<!importer.ScanObserver>} */ |
this.observers_ = []; |
+ |
+ /** |
+ * @private {!importer.DirectoryWatcherFactory} |
+ * @const |
+ */ |
+ this.watcherFactory_ = watcherFactory; |
}; |
/** @override */ |
@@ -122,7 +135,16 @@ importer.DefaultMediaScanner.prototype.scan = function(entries) { |
} |
var scanResult = this.createScanResult_(); |
- var scanPromises = entries.map(this.scanEntry_.bind(this, scanResult)); |
+ var watcher = this.watcherFactory_(function() { |
+ scanResult.invalidateScan(); |
+ this.observers_.forEach( |
+ /** @param {!importer.ScanObserver} observer */ |
+ function(observer) { |
+ observer(importer.ScanEvent.INVALIDATED, scanResult); |
+ }); |
+ }.bind(this)); |
+ var scanPromises = entries.map( |
+ this.scanEntry_.bind(this, scanResult, watcher)); |
Promise.all(scanPromises) |
.then(scanResult.resolveScan.bind(scanResult)) |
@@ -155,45 +177,50 @@ importer.DefaultMediaScanner.prototype.onScanFinished_ = function(result) { |
* Resolves the entry to a list of {@code FileEntry}. |
* |
* @param {!importer.DefaultScanResult} result |
+ * @param {!importer.DirectoryWatcher} watcher |
* @param {!Entry} entry |
* @return {!Promise} |
* @private |
*/ |
importer.DefaultMediaScanner.prototype.scanEntry_ = |
- function(result, entry) { |
+ function(result, watcher, entry) { |
return entry.isFile ? |
result.onFileEntryFound(/** @type {!FileEntry} */ (entry)) : |
- this.scanDirectory_(result, /** @type {!DirectoryEntry} */ (entry)); |
+ this.scanDirectory_( |
+ result, watcher, /** @type {!DirectoryEntry} */ (entry)); |
}; |
/** |
* Finds all files beneath directory. |
* |
* @param {!importer.DefaultScanResult} result |
+ * @param {!importer.DirectoryWatcher} watcher |
* @param {!DirectoryEntry} entry |
* @return {!Promise} |
* @private |
*/ |
importer.DefaultMediaScanner.prototype.scanDirectory_ = |
- function(result, entry) { |
- return new Promise( |
- function(resolve, reject) { |
- // Collect promises for all files being added to results. |
- // The directory scan promise can't resolve until all |
- // file entries are completely promised. |
- var promises = []; |
- fileOperationUtil.findFilesRecursively( |
- entry, |
- /** @param {!FileEntry} fileEntry */ |
- function(fileEntry) { |
- promises.push(result.onFileEntryFound(fileEntry)); |
- }) |
- .then( |
- /** @this {importer.DefaultScanResult} */ |
- function() { |
- Promise.all(promises).then(resolve).catch(reject); |
- }); |
- }); |
+ function(result, 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. |
+ var promises = []; |
+ |
+ return fileOperationUtil.findEntriesRecursively( |
+ entry, |
+ /** @param {!Entry} entry */ |
+ function(entry) { |
+ if (watcher.triggered) { |
+ return; |
+ } |
+ if (entry.isDirectory) { |
+ watcher.addDirectory(/** @type {!DirectoryEntry} */(entry)); |
+ } else { |
+ promises.push( |
+ result.onFileEntryFound(/** @type {!FileEntry} */(entry))); |
+ } |
+ }) |
+ .then(Promise.all.bind(Promise, promises)); |
}; |
/** |
@@ -225,6 +252,11 @@ 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. |
@@ -276,6 +308,10 @@ importer.DefaultScanResult.prototype.isFinal = function() { |
return this.settled_; |
}; |
+importer.DefaultScanResult.prototype.isInvalidated = function() { |
+ return this.invalidated_; |
+}; |
+ |
/** @override */ |
importer.DefaultScanResult.prototype.getFileEntries = function() { |
return this.fileEntries_; |
@@ -297,6 +333,13 @@ importer.DefaultScanResult.prototype.whenFinal = function() { |
}; |
/** |
+ * Invalidates this scan. |
+ */ |
+importer.DefaultScanResult.prototype.invalidateScan = function() { |
+ this.invalidated_ = true; |
+}; |
+ |
+/** |
* Handles files discovered during scanning. |
* |
* @param {!FileEntry} entry |
@@ -385,3 +428,80 @@ importer.DefaultScanResult.prototype.addFileEntry_ = function(entry) { |
}.bind(this)); |
}; |
+/** |
+ * Watcher for directories. |
+ * @interface |
+ */ |
+importer.DirectoryWatcher = function() {}; |
+ |
+/** |
+ * Registers new directory to be watched. |
+ * @param {!DirectoryEntry} entry |
+ */ |
+importer.DirectoryWatcher.prototype.addDirectory = function(entry) {}; |
+ |
+/** |
+ * @typedef {function()} |
+ */ |
+importer.DirectoryWatcherFactoryCallback; |
+ |
+/** |
+ * @typedef {function(importer.DirectoryWatcherFactoryCallback): |
+ * !importer.DirectoryWatcher} |
+ */ |
+importer.DirectoryWatcherFactory; |
+ |
+/** |
+ * Watcher for directories. |
+ * @param {function()} callback Callback to be invoked when one of watched |
+ * directories is changed. |
+ * @implements {importer.DirectoryWatcher} |
+ * @constructor |
+ */ |
+importer.DefaultDirectoryWatcher = function(callback) { |
+ this.callback_ = callback; |
+ this.watchedDirectories_ = {}; |
+ this.triggered = false; |
+ this.listener_ = null; |
+}; |
+ |
+/** |
+ * Creates new directory watcher. |
+ * @param {function()} callback Callback to be invoked when one of watched |
+ * directories is changed. |
+ * @return {!importer.DirectoryWatcher} |
+ */ |
+importer.DefaultDirectoryWatcher.create = function(callback) { |
+ return new importer.DefaultDirectoryWatcher(callback); |
+}; |
+ |
+/** |
+ * Registers new directory to be watched. |
+ * @param {!DirectoryEntry} entry |
+ */ |
+importer.DefaultDirectoryWatcher.prototype.addDirectory = function(entry) { |
+ if (!this.listener_) { |
+ this.listener_ = this.onWatchedDirectoryModified_.bind(this); |
+ chrome.fileManagerPrivate.onDirectoryChanged.addListener( |
+ assert(this.listener_)); |
+ } |
+ this.watchedDirectories_[entry.toURL()] = true; |
+ chrome.fileManagerPrivate.addFileWatch(entry.toURL(), function() {}); |
+}; |
+ |
+/** |
+ * @param {FileWatchEvent} event |
+ * @private |
+ */ |
+importer.DefaultDirectoryWatcher.prototype.onWatchedDirectoryModified_ = |
+ function(event) { |
+ if (!this.watchedDirectories_[event.entry.toURL()]) |
+ return; |
+ this.triggered = true; |
+ for (var url in this.watchedDirectories_) { |
+ chrome.fileManagerPrivate.removeFileWatch(url, function() {}); |
+ } |
+ chrome.fileManagerPrivate.onDirectoryChanged.removeListener( |
+ assert(this.listener_)); |
+ this.callback_(); |
+}; |