summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzvorygin@chromium.org <zvorygin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-16 13:42:15 +0000
committerzvorygin@chromium.org <zvorygin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-16 13:42:15 +0000
commit9d8eab609d3ee7e68a40020805f38bb841bb9096 (patch)
tree4fff88bba42c9a6dfa909f3ed50dd28db33af1b1
parentf1c3e3dd6bedf51b70170d2cf3fce9fc9c6bf426 (diff)
downloadchromium_src-9d8eab609d3ee7e68a40020805f38bb841bb9096.zip
chromium_src-9d8eab609d3ee7e68a40020805f38bb841bb9096.tar.gz
chromium_src-9d8eab609d3ee7e68a40020805f38bb841bb9096.tar.bz2
Added refresh on filesystem change
BUG=15733 TEST= Review URL: http://codereview.chromium.org/7745051 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101485 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/chromeos/extensions/file_browser_event_router.cc92
-rw-r--r--chrome/browser/chromeos/extensions/file_browser_event_router.h48
-rw-r--r--chrome/browser/resources/file_manager/js/file_manager.js242
3 files changed, 272 insertions, 110 deletions
diff --git a/chrome/browser/chromeos/extensions/file_browser_event_router.cc b/chrome/browser/chromeos/extensions/file_browser_event_router.cc
index 52177e6..941ebd2 100644
--- a/chrome/browser/chromeos/extensions/file_browser_event_router.cc
+++ b/chrome/browser/chromeos/extensions/file_browser_event_router.cc
@@ -123,16 +123,15 @@ bool ExtensionFileBrowserEventRouter::AddFileWatch(
base::AutoLock lock(lock_);
WatcherMap::iterator iter = file_watchers_.find(local_path);
if (iter == file_watchers_.end()) {
- FileWatcherExtensions* watch = new FileWatcherExtensions(virtual_path,
- extension_id);
- file_watchers_[local_path] = watch;
- if (!watch->file_watcher->Watch(local_path, delegate_.get())) {
- delete iter->second;
- file_watchers_.erase(iter);
+ scoped_ptr<FileWatcherExtensions>
+ watch(new FileWatcherExtensions(virtual_path, extension_id));
+
+ if (watch->Watch(local_path, delegate_.get()))
+ file_watchers_[local_path] = watch.release();
+ else
return false;
- }
} else {
- iter->second->extensions.insert(extension_id);
+ iter->second->AddExtension(extension_id);
}
return true;
}
@@ -145,8 +144,8 @@ void ExtensionFileBrowserEventRouter::RemoveFileWatch(
if (iter == file_watchers_.end())
return;
// Remove the renderer process for this watch.
- iter->second->extensions.erase(extension_id);
- if (iter->second->extensions.empty()) {
+ iter->second->RemoveExtension(extension_id);
+ if (iter->second->GetRefCount() == 0) {
delete iter->second;
file_watchers_.erase(iter);
}
@@ -218,22 +217,22 @@ void ExtensionFileBrowserEventRouter::HandleFileWatchNotification(
NOTREACHED();
return;
}
- DispatchFolderChangeEvent(iter->second->virtual_path, got_error,
- iter->second->extensions);
+ DispatchFolderChangeEvent(iter->second->GetVirtualPath(), got_error,
+ iter->second->GetExtensions());
}
void ExtensionFileBrowserEventRouter::DispatchFolderChangeEvent(
const FilePath& virtual_path, bool got_error,
- const std::set<std::string>& extensions) {
+ const ExtensionFileBrowserEventRouter::ExtensionUsageRegistry& extensions) {
if (!profile_) {
NOTREACHED();
return;
}
- for (std::set<std::string>::const_iterator iter = extensions.begin();
+ for (ExtensionUsageRegistry::const_iterator iter = extensions.begin();
iter != extensions.end(); ++iter) {
GURL target_origin_url(Extension::GetBaseURLFromExtensionId(
- *iter));
+ iter->first));
GURL base_url = fileapi::GetFileSystemRootURI(target_origin_url,
fileapi::kFileSystemTypeExternal);
GURL target_file_url = GURL(base_url.spec() + virtual_path.value());
@@ -248,7 +247,7 @@ void ExtensionFileBrowserEventRouter::DispatchFolderChangeEvent(
base::JSONWriter::Write(&args, false /* pretty_print */, &args_json);
profile_->GetExtensionEventRouter()->DispatchEventToExtension(
- *iter, extension_event_names::kOnFileChanged, args_json,
+ iter->first, extension_event_names::kOnFileChanged, args_json,
NULL, GURL());
}
}
@@ -462,3 +461,64 @@ ExtensionFileBrowserEventRouter::FileWatcherDelegate::HandleFileWatchOnUIThread(
const FilePath& local_path, bool got_error) {
router_->HandleFileWatchNotification(local_path, got_error);
}
+
+
+ExtensionFileBrowserEventRouter::FileWatcherExtensions::FileWatcherExtensions(
+ const FilePath& path, const std::string& extension_id) {
+ file_watcher.reset(new base::files::FilePathWatcher());
+ virtual_path = path;
+ AddExtension(extension_id);
+}
+
+void ExtensionFileBrowserEventRouter::FileWatcherExtensions::AddExtension(
+ const std::string& extension_id) {
+ ExtensionUsageRegistry::iterator it = extensions.find(extension_id);
+ if (it != extensions.end()) {
+ it->second++;
+ } else {
+ extensions.insert(ExtensionUsageRegistry::value_type(extension_id, 1));
+ }
+
+ ref_count++;
+}
+
+void ExtensionFileBrowserEventRouter::FileWatcherExtensions::RemoveExtension(
+ const std::string& extension_id) {
+ ExtensionUsageRegistry::iterator it = extensions.find(extension_id);
+
+ if (it != extensions.end()) {
+ // If entry found - decrease it's count and remove if necessary
+ if (0 == it->second--) {
+ extensions.erase(it);
+ }
+
+ ref_count--;
+ } else {
+ // Might be reference counting problem - e.g. if some component of
+ // extension subscribes/unsubscribes correctly, but other component
+ // only unsubscribes, developer of first one might receive this message
+ LOG(FATAL) << " Extension [" << extension_id
+ << "] tries to unsubscribe from folder [" << local_path.value()
+ << "] it isn't subscribed";
+ }
+}
+
+const ExtensionFileBrowserEventRouter::ExtensionUsageRegistry&
+ExtensionFileBrowserEventRouter::FileWatcherExtensions::GetExtensions() const {
+ return extensions;
+}
+
+unsigned int
+ExtensionFileBrowserEventRouter::FileWatcherExtensions::GetRefCount() const {
+ return ref_count;
+}
+
+const FilePath&
+ExtensionFileBrowserEventRouter::FileWatcherExtensions::GetVirtualPath() const {
+ return virtual_path;
+}
+
+bool ExtensionFileBrowserEventRouter::FileWatcherExtensions::Watch
+ (const FilePath& path, FileWatcherDelegate* delegate) {
+ return file_watcher->Watch(path, delegate);
+}
diff --git a/chrome/browser/chromeos/extensions/file_browser_event_router.h b/chrome/browser/chromeos/extensions/file_browser_event_router.h
index 53cdaf9..b9d2b23 100644
--- a/chrome/browser/chromeos/extensions/file_browser_event_router.h
+++ b/chrome/browser/chromeos/extensions/file_browser_event_router.h
@@ -48,21 +48,6 @@ class ExtensionFileBrowserEventRouter
const chromeos::MountLibrary::MountPointInfo& mount_info) OVERRIDE;
private:
- typedef struct FileWatcherExtensions {
- FileWatcherExtensions(const FilePath& path,
- const std::string& extension_id) {
- file_watcher.reset(new base::files::FilePathWatcher());
- virtual_path = path;
- extensions.insert(extension_id);
- }
- ~FileWatcherExtensions() {}
- linked_ptr<base::files::FilePathWatcher> file_watcher;
- FilePath local_path;
- FilePath virtual_path;
- std::set<std::string> extensions;
- } FileWatcherProcess;
- typedef std::map<FilePath, FileWatcherExtensions*> WatcherMap;
-
// Helper class for passing through file watch notification events.
class FileWatcherDelegate : public base::files::FilePathWatcher::Delegate {
public:
@@ -78,6 +63,37 @@ class ExtensionFileBrowserEventRouter
ExtensionFileBrowserEventRouter* router_;
};
+ typedef std::map<std::string, int> ExtensionUsageRegistry;
+
+ class FileWatcherExtensions {
+ public:
+ FileWatcherExtensions(const FilePath& path,
+ const std::string& extension_id);
+
+ ~FileWatcherExtensions() {}
+
+ void AddExtension(const std::string& extension_id);
+
+ void RemoveExtension(const std::string& extension_id);
+
+ const ExtensionUsageRegistry& GetExtensions() const;
+
+ unsigned int GetRefCount() const;
+
+ const FilePath& GetVirtualPath() const;
+
+ bool Watch(const FilePath& path, FileWatcherDelegate* delegate);
+
+ private:
+ linked_ptr<base::files::FilePathWatcher> file_watcher;
+ FilePath local_path;
+ FilePath virtual_path;
+ ExtensionUsageRegistry extensions;
+ unsigned int ref_count;
+ };
+
+ typedef std::map<FilePath, FileWatcherExtensions*> WatcherMap;
+
// USB mount event handlers.
void OnDiskAdded(const chromeos::MountLibrary::Disk* disk);
void OnDiskRemoved(const chromeos::MountLibrary::Disk* disk);
@@ -95,7 +111,7 @@ class ExtensionFileBrowserEventRouter
// Sends folder change event.
void DispatchFolderChangeEvent(const FilePath& path, bool error,
- const std::set<std::string>& extensions);
+ const ExtensionUsageRegistry& extensions);
// Sends filesystem changed extension message to all renderers.
void DispatchDiskEvent(const chromeos::MountLibrary::Disk* disk, bool added);
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js
index 12d96ba..ef13bee 100644
--- a/chrome/browser/resources/file_manager/js/file_manager.js
+++ b/chrome/browser/resources/file_manager/js/file_manager.js
@@ -11,6 +11,10 @@ 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.
*
@@ -52,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_ = {};
@@ -108,6 +115,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',
@@ -119,6 +128,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
@@ -2677,6 +2689,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);
@@ -2696,108 +2728,145 @@ FileManager.prototype = {
};
/**
+ * 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 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
+
+ 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);
+ }
+ }
+ }
+
+
+ /**
* 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 {string} delayMS Delay during which next rescanDirectory calls
- * can happen.
+ *
+ * @param {function()} opt_onError Optional function to invoke when the
+ * rescan fails.
*/
+ 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;
- 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();
- }
- }
+ // Clear the table first.
+ this.dataModel_.splice(0, this.dataModel_.length);
+ this.currentList_.selectionModel.clear();
- // 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 = [];
+ this.updateBreadcrumbs_();
- if (opt_callback)
- this.rescanDirectory_.callbacks.push(opt_callback);
+ if (this.currentDirEntry_.fullPath != '/') {
+ // Add current request to pending result list
+ this.pendingRescanQueue_.push({
+ onSuccess:opt_callback,
+ onError:opt_onError
+ });
- delayMS = delayMS || 100;
+ if (this.rescanRunning_)
+ return;
- // Assumes rescanDirectoryNow_ takes less than 100 ms. If not there is
- // a possible overlap between two calls.
- if (delayMS < 100)
- delayMS = 100;
+ this.rescanRunning_ = true;
- 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);
- };
+ // 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_;
- /**
- * Rescans the current directory immediately, refreshing the list. Should NOT
- * be used in most cases. Instead use rescanDirectory_.
- *
- * @param {function()} opt_callback Optional function to invoke when the
- * rescan is complete.
- */
- FileManager.prototype.rescanDirectoryNow_ = function(opt_callback) {
- var self = this;
- var reader;
+ this.pendingRescanQueue_ = [];
- function onReadSome(entries) {
- if (entries.length == 0) {
- if (opt_callback)
- opt_callback();
- return;
- }
+ var self = this;
+ var reader;
- // 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);
+ function onError() {
+ if (self.pendingRescanQueue_.length > 0) {
+ setTimeout(self.rescanDirectory_.bind(self),
+ SIMULTANEOUS_RESCAN_INTERVAL);
+ }
- // 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) != '.';
- });
+ 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;
}
@@ -2806,7 +2875,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);
if (opt_callback)
opt_callback();
@@ -2858,6 +2927,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.
*