| 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.
|
| *
|
|
|