Index: chrome/browser/resources/file_manager/js/file_manager.js |
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js |
index 41fd6f3c47a007ec15af898b2d2df861eb3554ae..7c10fa3eee56a85f165af7093b6b4d4431c9367b 100644 |
--- a/chrome/browser/resources/file_manager/js/file_manager.js |
+++ b/chrome/browser/resources/file_manager/js/file_manager.js |
@@ -9,6 +9,12 @@ const EMPTY_IMAGE_URI = 'data:image/gif;base64,' |
var g_slideshow_data = null; |
+const GALLERY_ENABLED = true; |
+ |
+// If directory files changes too often, don't rescan directory more than once |
+// per specified interval |
+const SIMULTANEOUS_RESCAN_INTERVAL = 1000; |
+ |
/** |
* FileManager constructor. |
* |
@@ -50,6 +56,9 @@ function FileManager(dialogDom, filesystem, rootEntries) { |
// True if we should filter out files that start with a dot. |
this.filterFiles_ = true; |
+ this.subscribedOnDirectoryChanges_ = false; |
+ this.pendingRescanQueue_ = []; |
+ this.rescanRunning_ = false; |
this.commands_ = {}; |
@@ -111,6 +120,8 @@ function FileManager(dialogDom, filesystem, rootEntries) { |
this.onCopyProgress_.bind(this)); |
window.addEventListener('popstate', this.onPopState_.bind(this)); |
+ window.addEventListener('unload', this.onUnload_.bind(this)); |
+ |
this.addEventListener('directory-changed', |
this.onDirectoryChanged_.bind(this)); |
this.addEventListener('selection-summarized', |
@@ -122,6 +133,9 @@ function FileManager(dialogDom, filesystem, rootEntries) { |
chrome.fileBrowserPrivate.onMountCompleted.addListener( |
this.onMountCompleted_.bind(this)); |
+ chrome.fileBrowserPrivate.onFileChanged.addListener( |
+ this.onFileChanged_.bind(this)); |
+ |
var self = this; |
// The list of callbacks to be invoked during the directory rescan after |
@@ -2853,6 +2867,26 @@ FileManager.prototype = { |
this.document_.title = this.currentDirEntry_.fullPath; |
var self = this; |
+ |
+ if (this.subscribedOnDirectoryChanges_) { |
+ chrome.fileBrowserPrivate.removeFileWatch(event.previousDirEntry.toURL(), |
+ function(result) { |
+ if (!result) { |
+ console.log('Failed to remove file watch'); |
+ } |
+ }); |
+ } |
+ |
+ if (event.newDirEntry.fullPath != '/') { |
+ this.subscribedOnDirectoryChanges_ = true; |
+ chrome.fileBrowserPrivate.addFileWatch(event.newDirEntry.toURL(), |
+ function(result) { |
+ if (!result) { |
+ console.log('Failed to add file watch'); |
+ } |
+ }); |
+ } |
+ |
this.rescanDirectory_(function() { |
if (event.selectedEntry) |
self.selectEntry(event.selectedEntry); |
@@ -2872,117 +2906,145 @@ FileManager.prototype = { |
}; |
/** |
- * Rescans the current directory, refreshing the list. It decreases the |
- * probability that two such calls are pending simultaneously. |
+ * Rescans directory later. |
+ * This method should be used if we just want rescan but not actually now. |
+ * This helps us not to flood queue with rescan requests. |
* |
- * @param {function()} opt_callback Optional function to invoke when the |
- * rescan is complete. |
- * @param {string} delayMS Delay during which next rescanDirectory calls |
- * can happen. |
+ * @param opt_callback |
+ * @param opt_onError |
*/ |
+ FileManager.prototype.rescanDirectoryLater_ = function(opt_callback, |
+ opt_onError) { |
+ // It might be massive change, so let's note somehow, that we need |
+ // rescanning and then wait some time |
- FileManager.prototype.rescanDirectory_ = function(opt_callback, delayMS) { |
- var self = this; |
- function done(count) { |
- // Variable count is introduced because we only want to do callbacks, that |
- // were in the queue at the moment of rescanDirectoryNow invocation. |
- while (count--) { |
- var callback = self.rescanDirectory_.callbacks.shift(); |
- callback(); |
+ if (this.pendingRescanQueue_.length == 0) { |
+ this.pendingRescanQueue_.push({onSuccess:opt_callback, |
+ onError:opt_onError}); |
+ |
+ // If rescan isn't going to run without |
+ // our interruption, then say that we need to run it |
+ if (!this.rescanRunning_) { |
+ setTimeout(this.rescanDirectory_.bind(this), |
+ SIMULTANEOUS_RESCAN_INTERVAL); |
} |
} |
+ } |
- // callbacks is a queue of callbacks that need to be called after rescaning |
- // directory is done. We push callback to the end and pop form the front. |
- // When we get to actually call rescanDirectoryNow_ we need to remember how |
- // many callbacks were in a queue at the time, not to call callbacks that |
- // arrived in the middle of execution (they may depend on rescaning being |
- // done after they called rescanDirectory_). |
- if (!this.rescanDirectory_.callbacks) |
- this.rescanDirectory_.callbacks = []; |
- |
- if (opt_callback) |
- this.rescanDirectory_.callbacks.push(opt_callback); |
- |
- delayMS = delayMS || 100; |
- |
- // Assumes rescanDirectoryNow_ takes less than 100 ms. If not there is |
- // a possible overlap between two calls. |
- if (delayMS < 100) |
- delayMS = 100; |
- |
- if (this.rescanDirectory_.handle) |
- clearTimeout(this.rescanDirectory_.handle); |
- var currentQueueLength = self.rescanDirectory_.callbacks.length; |
- this.rescanDirectory_.handle = setTimeout(function () { |
- self.rescanDirectoryNow_(function() { |
- done(currentQueueLength); |
- }); |
- }, delayMS); |
- }; |
/** |
- * Rescans the current directory immediately, refreshing the list. Should NOT |
- * be used in most cases. Instead use rescanDirectory_. |
+ * Rescans the current directory, refreshing the list. It decreases the |
+ * probability that two such calls are pending simultaneously. |
+ * |
+ * This method tries to queue request if rescan is already running, and |
+ * processes this request later. Anyway callback would be called after |
+ * processing. |
+ * |
+ * If no rescan is running, then method starts rescanning immediately. |
* |
* @param {function()} opt_callback Optional function to invoke when the |
* rescan is complete. |
+ * |
+ * @param {function()} opt_onError Optional function to invoke when the |
+ * rescan fails. |
*/ |
- FileManager.prototype.rescanDirectoryNow_ = function(opt_callback) { |
- var self = this; |
- var reader; |
+ FileManager.prototype.rescanDirectory_ = function(opt_callback, opt_onError) { |
+ // Updated when a user clicks on the label of a file, used to detect |
+ // when a click is eligible to trigger a rename. Can be null, or |
+ // an object with 'path' and 'date' properties. |
+ this.lastLabelClick_ = null; |
- function rescanDone() { |
- metrics.recordTime('ScanDirectory'); |
- if (self.currentDirEntry_.fullPath == DOWNLOADS_DIRECTORY) |
- metrics.reportCount("DownloadsCount", self.dataModel_.length); |
- if (opt_callback) |
- opt_callback(); |
- } |
+ // Clear the table first. |
+ this.dataModel_.splice(0, this.dataModel_.length); |
+ this.currentList_.selectionModel.clear(); |
- metrics.startInterval('ScanDirectory'); |
+ this.updateBreadcrumbs_(); |
- function onReadSome(entries) { |
- if (entries.length == 0) { |
- rescanDone(); |
+ if (this.currentDirEntry_.fullPath != '/') { |
+ // Add current request to pending result list |
+ this.pendingRescanQueue_.push({ |
+ onSuccess:opt_callback, |
+ onError:opt_onError |
+ }); |
+ |
+ if (this.rescanRunning_) |
return; |
- } |
- // Splice takes the to-be-spliced-in array as individual parameters, |
- // rather than as an array, so we need to perform some acrobatics... |
- var spliceArgs = [].slice.call(entries); |
+ this.rescanRunning_ = true; |
- // Hide files that start with a dot ('.'). |
- // TODO(rginda): User should be able to override this. Support for other |
- // commonly hidden patterns might be nice too. |
- if (self.filterFiles_) { |
- spliceArgs = spliceArgs.filter(function(e) { |
- return e.name.substr(0, 1) != '.'; |
- }); |
+ // The current list of callbacks is saved and reset. Subsequent |
+ // calls to rescanDirectory_ while we're still pending will be |
+ // saved and will cause an additional rescan to happen after a delay. |
+ var callbacks = this.pendingRescanQueue_; |
+ |
+ this.pendingRescanQueue_ = []; |
+ |
+ var self = this; |
+ var reader; |
+ |
+ function onError() { |
+ if (self.pendingRescanQueue_.length > 0) { |
+ setTimeout(self.rescanDirectory_.bind(self), |
+ SIMULTANEOUS_RESCAN_INTERVAL); |
+ } |
+ |
+ self.rescanRunning_ = false; |
+ |
+ for (var i= 0; i < callbacks.length; i++) { |
+ if (callbacks[i].onError) |
+ try { |
+ callbacks[i].onError(); |
+ } catch (ex) { |
+ console.error('Caught exception while notifying about error: ' + |
+ name, ex); |
+ } |
+ } |
} |
- spliceArgs.unshift(0, 0); // index, deleteCount |
- self.dataModel_.splice.apply(self.dataModel_, spliceArgs); |
+ function onReadSome(entries) { |
+ if (entries.length == 0) { |
+ if (self.pendingRescanQueue_.length > 0) { |
+ setTimeout(self.rescanDirectory_.bind(self), |
+ SIMULTANEOUS_RESCAN_INTERVAL); |
+ } |
- // Keep reading until entries.length is 0. |
- reader.readEntries(onReadSome); |
- }; |
+ self.rescanRunning_ = false; |
+ for (var i= 0; i < callbacks.length; i++) { |
+ if (callbacks[i].onSuccess) |
+ try { |
+ callbacks[i].onSuccess(); |
+ } catch (ex) { |
+ console.error('Caught exception while notifying about error: ' + |
+ name, ex); |
+ } |
+ } |
- // Updated when a user clicks on the label of a file, used to detect |
- // when a click is eligible to trigger a rename. Can be null, or |
- // an object with 'path' and 'date' properties. |
- this.lastLabelClick_ = null; |
+ return; |
+ } |
- // Clear the table first. |
- this.dataModel_.splice(0, this.dataModel_.length); |
- this.currentList_.selectionModel.clear(); |
+ // Splice takes the to-be-spliced-in array as individual parameters, |
+ // rather than as an array, so we need to perform some acrobatics... |
+ var spliceArgs = [].slice.call(entries); |
+ |
+ // Hide files that start with a dot ('.'). |
+ // TODO(rginda): User should be able to override this. Support for other |
+ // commonly hidden patterns might be nice too. |
+ if (self.filterFiles_) { |
+ spliceArgs = spliceArgs.filter(function(e) { |
+ return e.name.substr(0, 1) != '.'; |
+ }); |
+ } |
- this.updateBreadcrumbs_(); |
+ spliceArgs.unshift(0, 0); // index, deleteCount |
+ self.dataModel_.splice.apply(self.dataModel_, spliceArgs); |
+ |
+ // Keep reading until entries.length is 0. |
+ reader.readEntries(onReadSome, onError); |
+ }; |
- if (this.currentDirEntry_.fullPath != '/') { |
// If not the root directory, just read the contents. |
reader = this.currentDirEntry_.createReader(); |
- reader.readEntries(onReadSome); |
+ reader.readEntries(onReadSome, onError); |
return; |
} |
@@ -2991,7 +3053,7 @@ FileManager.prototype = { |
// harness) can't be enumerated yet. |
var spliceArgs = [].slice.call(this.rootEntries_); |
spliceArgs.unshift(0, 0); // index, deleteCount |
- self.dataModel_.splice.apply(self.dataModel_, spliceArgs); |
+ this.dataModel_.splice.apply(this.dataModel_, spliceArgs); |
rescanDone(); |
}; |
@@ -3042,6 +3104,23 @@ FileManager.prototype = { |
} |
}; |
+ FileManager.prototype.onUnload_ = function(event) { |
+ if (this.subscribedOnDirectoryChanges_) { |
+ chrome.fileBrowserPrivate.removeFileWatch(event.previousDirEntry.toURL(), |
+ function(result) { |
+ if (!result) { |
+ console.log('Failed to remove file watch'); |
+ } |
+ }); |
+ } |
+ }; |
+ |
+ FileManager.prototype.onFileChanged_ = function(event) { |
+ // We receive a lot of events even in folders we are not interested in. |
+ if (event.fileUrl == this.currentDirEntry_.toURL()) |
+ this.rescanDirectoryLater_(); |
+ }; |
+ |
/** |
* Determine whether or not a click should initiate a rename. |
* |