diff options
author | zvorygin@chromium.org <zvorygin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-16 13:42:15 +0000 |
---|---|---|
committer | zvorygin@chromium.org <zvorygin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-16 13:42:15 +0000 |
commit | 9d8eab609d3ee7e68a40020805f38bb841bb9096 (patch) | |
tree | 4fff88bba42c9a6dfa909f3ed50dd28db33af1b1 | |
parent | f1c3e3dd6bedf51b70170d2cf3fce9fc9c6bf426 (diff) | |
download | chromium_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
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. * |