| Index: base/directory_watcher_inotify.cc
|
| diff --git a/base/directory_watcher_inotify.cc b/base/directory_watcher_inotify.cc
|
| index 86e7bd9b9a5fff53c60e53b58ae67e9ec942815d..ee421279b904d2b67d6d7a6a78c3a2f156d99b13 100644
|
| --- a/base/directory_watcher_inotify.cc
|
| +++ b/base/directory_watcher_inotify.cc
|
| @@ -6,8 +6,8 @@
|
|
|
| #include <errno.h>
|
| #include <string.h>
|
| -#include <sys/ioctl.h>
|
| #include <sys/inotify.h>
|
| +#include <sys/ioctl.h>
|
| #include <sys/select.h>
|
| #include <unistd.h>
|
|
|
| @@ -18,6 +18,7 @@
|
|
|
| #include "base/eintr_wrapper.h"
|
| #include "base/file_path.h"
|
| +#include "base/file_util.h"
|
| #include "base/hash_tables.h"
|
| #include "base/lock.h"
|
| #include "base/logging.h"
|
| @@ -26,43 +27,38 @@
|
| #include "base/singleton.h"
|
| #include "base/task.h"
|
| #include "base/thread.h"
|
| +#include "base/waitable_event.h"
|
|
|
| namespace {
|
|
|
| +class DirectoryWatcherImpl;
|
| +
|
| // Singleton to manage all inotify watches.
|
| class InotifyReader {
|
| public:
|
| typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
|
| static const Watch kInvalidWatch = -1;
|
|
|
| - // Watch |path| for changes. |delegate| will be notified on each change. Does
|
| - // not check for duplicates. If you call it n times with same |path|
|
| - // and |delegate|, it will receive n notifications for each change
|
| - // in |path|. It makes implementation of DirectoryWatcher simple.
|
| + // Watch |path| for changes. |watcher| will be notified on each change.
|
| // Returns kInvalidWatch on failure.
|
| - Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate);
|
| + Watch AddWatch(const FilePath& path, DirectoryWatcherImpl* watcher);
|
|
|
| - // Remove |watch| for |delegate|. If you had n watches for same |delegate|
|
| - // and path, after calling this function you will have n - 1.
|
| - // Returns true on success.
|
| - bool RemoveWatch(Watch watch, DirectoryWatcher::Delegate* delegate);
|
| + // Remove |watch|. Returns true on success.
|
| + bool RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher);
|
|
|
| // Callback for InotifyReaderTask.
|
| - void OnInotifyEvent(inotify_event* event);
|
| + void OnInotifyEvent(const inotify_event* event);
|
|
|
| private:
|
| friend struct DefaultSingletonTraits<InotifyReader>;
|
|
|
| - typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo;
|
| - typedef std::multiset<DelegateInfo> DelegateSet;
|
| + typedef std::set<DirectoryWatcherImpl*> WatcherSet;
|
|
|
| InotifyReader();
|
| ~InotifyReader();
|
|
|
| // We keep track of which delegates want to be notified on which watches.
|
| - // Multiset is used because there may be many DirectoryWatchers for same path
|
| - // and delegate.
|
| - base::hash_map<Watch, DelegateSet> delegates_;
|
| + base::hash_map<Watch, WatcherSet> watchers_;
|
|
|
| // For each watch we also want to know the path it's watching.
|
| base::hash_map<Watch, FilePath> paths_;
|
| @@ -85,6 +81,114 @@ class InotifyReader {
|
| DISALLOW_COPY_AND_ASSIGN(InotifyReader);
|
| };
|
|
|
| +class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
| + public:
|
| + typedef std::set<FilePath> FilePathSet;
|
| +
|
| + DirectoryWatcherImpl();
|
| + ~DirectoryWatcherImpl();
|
| +
|
| + void EnsureSetupFinished();
|
| +
|
| + // Called for each event coming from one of watches.
|
| + void OnInotifyEvent(const inotify_event* event);
|
| +
|
| + // Callback for RegisterSubtreeWatchesTask.
|
| + bool OnEnumeratedSubtree(const FilePathSet& paths);
|
| +
|
| + // Start watching |path| for changes and notify |delegate| on each change.
|
| + // If |recursive| is true, watch entire subtree.
|
| + // Returns true if watch for |path| has been added successfully. Watches
|
| + // required for |recursive| are added on a background thread and have no
|
| + // effect on the return value.
|
| + virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
| + MessageLoop* backend_loop, bool recursive);
|
| +
|
| + private:
|
| + typedef std::set<InotifyReader::Watch> WatchSet;
|
| + typedef std::set<ino_t> InodeSet;
|
| +
|
| + // Returns true if |inode| is watched by DirectoryWatcherImpl.
|
| + bool IsInodeWatched(ino_t inode) const;
|
| +
|
| + // Delegate to notify upon changes.
|
| + DirectoryWatcher::Delegate* delegate_;
|
| +
|
| + // Path we're watching (passed to delegate).
|
| + FilePath root_path_;
|
| +
|
| + // Watch returned by InotifyReader.
|
| + InotifyReader::Watch watch_;
|
| +
|
| + // Set of watched inodes.
|
| + InodeSet inodes_watched_;
|
| +
|
| + // Keep track of registered watches.
|
| + WatchSet watches_;
|
| +
|
| + // Lock to protect inodes_watched_ and watches_.
|
| + Lock lock_;
|
| +
|
| + // Flag set to true when recursively watching subtree.
|
| + bool recursive_;
|
| +
|
| + // Loop where we post directory change notifications to.
|
| + MessageLoop* loop_;
|
| +
|
| + // Event signaled when the background task finished adding initial inotify
|
| + // watches for recursive watch.
|
| + base::WaitableEvent recursive_setup_finished_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
| +};
|
| +
|
| +class RegisterSubtreeWatchesTask : public Task {
|
| + public:
|
| + RegisterSubtreeWatchesTask(DirectoryWatcherImpl* watcher,
|
| + const FilePath& path)
|
| + : watcher_(watcher),
|
| + path_(path) {
|
| + }
|
| +
|
| + virtual void Run() {
|
| + file_util::FileEnumerator dir_list(path_, true,
|
| + file_util::FileEnumerator::DIRECTORIES);
|
| +
|
| + DirectoryWatcherImpl::FilePathSet subtree;
|
| + for (FilePath subdirectory = dir_list.Next();
|
| + !subdirectory.empty();
|
| + subdirectory = dir_list.Next()) {
|
| + subtree.insert(subdirectory);
|
| + }
|
| + watcher_->OnEnumeratedSubtree(subtree);
|
| + }
|
| +
|
| + private:
|
| + DirectoryWatcherImpl* watcher_;
|
| + FilePath path_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(RegisterSubtreeWatchesTask);
|
| +};
|
| +
|
| +class DirectoryWatcherImplNotifyTask : public Task {
|
| + public:
|
| + DirectoryWatcherImplNotifyTask(DirectoryWatcher::Delegate* delegate,
|
| + const FilePath& path)
|
| + : delegate_(delegate),
|
| + path_(path) {
|
| + }
|
| +
|
| + virtual void Run() {
|
| + delegate_->OnDirectoryChanged(path_);
|
| + }
|
| +
|
| + private:
|
| + DirectoryWatcher::Delegate* delegate_;
|
| + FilePath path_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplNotifyTask);
|
| +};
|
| +
|
| class InotifyReaderTask : public Task {
|
| public:
|
| InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
|
| @@ -151,25 +255,6 @@ class InotifyReaderTask : public Task {
|
| DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
|
| };
|
|
|
| -class InotifyReaderNotifyTask : public Task {
|
| - public:
|
| - InotifyReaderNotifyTask(DirectoryWatcher::Delegate* delegate,
|
| - const FilePath& path)
|
| - : delegate_(delegate),
|
| - path_(path) {
|
| - }
|
| -
|
| - virtual void Run() {
|
| - delegate_->OnDirectoryChanged(path_);
|
| - }
|
| -
|
| - private:
|
| - DirectoryWatcher::Delegate* delegate_;
|
| - FilePath path_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(InotifyReaderNotifyTask);
|
| -};
|
| -
|
| InotifyReader::InotifyReader()
|
| : thread_("inotify_reader"),
|
| inotify_fd_(inotify_init()),
|
| @@ -199,7 +284,8 @@ InotifyReader::~InotifyReader() {
|
| }
|
|
|
| InotifyReader::Watch InotifyReader::AddWatch(
|
| - const FilePath& path, DirectoryWatcher::Delegate* delegate) {
|
| + const FilePath& path, DirectoryWatcherImpl* watcher) {
|
| +
|
| if (!valid_)
|
| return kInvalidWatch;
|
|
|
| @@ -208,19 +294,20 @@ InotifyReader::Watch InotifyReader::AddWatch(
|
| Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
|
| IN_CREATE | IN_DELETE |
|
| IN_CLOSE_WRITE | IN_MOVE);
|
| +
|
| if (watch == kInvalidWatch)
|
| return kInvalidWatch;
|
|
|
| if (paths_[watch].empty())
|
| paths_[watch] = path; // We don't yet watch this path.
|
|
|
| - delegates_[watch].insert(std::make_pair(delegate, MessageLoop::current()));
|
| + watchers_[watch].insert(watcher);
|
|
|
| return watch;
|
| }
|
|
|
| bool InotifyReader::RemoveWatch(Watch watch,
|
| - DirectoryWatcher::Delegate* delegate) {
|
| + DirectoryWatcherImpl* watcher) {
|
| if (!valid_)
|
| return false;
|
|
|
| @@ -229,89 +316,148 @@ bool InotifyReader::RemoveWatch(Watch watch,
|
| if (paths_[watch].empty())
|
| return false; // We don't recognize this watch.
|
|
|
| - // Only erase one occurrence of delegate (there may be more).
|
| - delegates_[watch].erase(
|
| - delegates_[watch].find(std::make_pair(delegate, MessageLoop::current())));
|
| + watchers_[watch].erase(watcher);
|
|
|
| - if (delegates_[watch].empty()) {
|
| + if (watchers_[watch].empty()) {
|
| paths_.erase(watch);
|
| - delegates_.erase(watch);
|
| -
|
| + watchers_.erase(watch);
|
| return (inotify_rm_watch(inotify_fd_, watch) == 0);
|
| }
|
|
|
| return true;
|
| }
|
|
|
| -void InotifyReader::OnInotifyEvent(inotify_event* event) {
|
| +void InotifyReader::OnInotifyEvent(const inotify_event* event) {
|
| if (event->mask & IN_IGNORED)
|
| return;
|
|
|
| - DelegateSet delegates_to_notify;
|
| + WatcherSet watchers_to_notify;
|
| FilePath changed_path;
|
|
|
| {
|
| AutoLock auto_lock(lock_);
|
| changed_path = paths_[event->wd];
|
| - delegates_to_notify.insert(delegates_[event->wd].begin(),
|
| - delegates_[event->wd].end());
|
| + watchers_to_notify.insert(watchers_[event->wd].begin(),
|
| + watchers_[event->wd].end());
|
| }
|
|
|
| - DelegateSet::iterator i;
|
| - for (i = delegates_to_notify.begin(); i != delegates_to_notify.end(); ++i) {
|
| - DirectoryWatcher::Delegate* delegate = i->first;
|
| - MessageLoop* loop = i->second;
|
| - loop->PostTask(FROM_HERE,
|
| - new InotifyReaderNotifyTask(delegate, changed_path));
|
| + for (WatcherSet::iterator watcher = watchers_to_notify.begin();
|
| + watcher != watchers_to_notify.end();
|
| + ++watcher) {
|
| + (*watcher)->OnInotifyEvent(event);
|
| }
|
| }
|
|
|
| -class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
| - public:
|
| - DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {}
|
| - ~DirectoryWatcherImpl();
|
| +DirectoryWatcherImpl::DirectoryWatcherImpl()
|
| + : watch_(InotifyReader::kInvalidWatch),
|
| + recursive_setup_finished_(false, false) {
|
| +}
|
|
|
| - virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
| - bool recursive);
|
| +DirectoryWatcherImpl::~DirectoryWatcherImpl() {
|
| + if (watch_ == InotifyReader::kInvalidWatch)
|
| + return;
|
|
|
| - private:
|
| - // Delegate to notify upon changes.
|
| - DirectoryWatcher::Delegate* delegate_;
|
| + if (recursive_)
|
| + recursive_setup_finished_.Wait();
|
| + for (WatchSet::iterator watch = watches_.begin();
|
| + watch != watches_.end();
|
| + ++watch) {
|
| + Singleton<InotifyReader>::get()->RemoveWatch(*watch, this);
|
| + }
|
| + watches_.clear();
|
| + inodes_watched_.clear();
|
| +}
|
|
|
| - // Path we're watching (passed to delegate).
|
| - FilePath path_;
|
| +void DirectoryWatcherImpl::OnInotifyEvent(const inotify_event* event) {
|
| + loop_->PostTask(FROM_HERE,
|
| + new DirectoryWatcherImplNotifyTask(delegate_, root_path_));
|
|
|
| - // Watch returned by InotifyReader.
|
| - InotifyReader::Watch watch_;
|
| + if (!(event->mask & IN_ISDIR))
|
| + return;
|
|
|
| - DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
| -};
|
| + if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
|
| + // TODO(phajdan.jr): add watch for this new directory.
|
| + NOTIMPLEMENTED();
|
| + } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
|
| + // TODO(phajdan.jr): remove our watch for this directory.
|
| + NOTIMPLEMENTED();
|
| + }
|
| +}
|
|
|
| -DirectoryWatcherImpl::~DirectoryWatcherImpl() {
|
| - if (watch_ != InotifyReader::kInvalidWatch)
|
| - Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_);
|
| +bool DirectoryWatcherImpl::IsInodeWatched(ino_t inode) const {
|
| + return inodes_watched_.find(inode) != inodes_watched_.end();
|
| }
|
|
|
| -bool DirectoryWatcherImpl::Watch(const FilePath& path,
|
| - DirectoryWatcher::Delegate* delegate, bool recursive) {
|
| - DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path.
|
| -
|
| - if (recursive) {
|
| - // TODO(phajdan.jr): Support recursive watches.
|
| - // Unfortunately inotify has no "native" support for them, but it can be
|
| - // emulated by walking the directory tree and setting up watches for each
|
| - // directory. Of course this is ineffective for large directory trees.
|
| - // For the algorithm, see the link below:
|
| - // http://osdir.com/ml/gnome.dashboard.devel/2004-10/msg00022.html
|
| - NOTIMPLEMENTED();
|
| +bool DirectoryWatcherImpl::OnEnumeratedSubtree(const FilePathSet& subtree) {
|
| + DCHECK(recursive_);
|
| +
|
| + if (watch_ == InotifyReader::kInvalidWatch) {
|
| + recursive_setup_finished_.Signal();
|
| return false;
|
| }
|
|
|
| + bool success = true;
|
| + AutoLock auto_lock(lock_);
|
| + for (FilePathSet::iterator subdirectory = subtree.begin();
|
| + subdirectory != subtree.end();
|
| + ++subdirectory) {
|
| + ino_t inode;
|
| + if (!file_util::GetInode(*subdirectory, &inode)) {
|
| + success = false;
|
| + continue;
|
| + }
|
| + if (IsInodeWatched(inode))
|
| + continue;
|
| + InotifyReader::Watch watch =
|
| + Singleton<InotifyReader>::get()->AddWatch(*subdirectory, this);
|
| + if (watch != InotifyReader::kInvalidWatch) {
|
| + watches_.insert(watch);
|
| + inodes_watched_.insert(inode);
|
| + }
|
| + }
|
| + recursive_setup_finished_.Signal();
|
| + return success;
|
| +}
|
| +
|
| +bool DirectoryWatcherImpl::Watch(const FilePath& path,
|
| + DirectoryWatcher::Delegate* delegate,
|
| + MessageLoop* backend_loop, bool recursive) {
|
| +
|
| + // Can only watch one path.
|
| + DCHECK(watch_ == InotifyReader::kInvalidWatch);
|
| +
|
| + ino_t inode;
|
| + if (!file_util::GetInode(path, &inode))
|
| + return false;
|
| +
|
| + InotifyReader::Watch watch =
|
| + Singleton<InotifyReader>::get()->AddWatch(path, this);
|
| + if (watch == InotifyReader::kInvalidWatch)
|
| + return false;
|
| +
|
| delegate_ = delegate;
|
| - path_ = path;
|
| - watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_);
|
| + recursive_ = recursive;
|
| + root_path_ = path;
|
| + watch_ = watch;
|
| + loop_ = MessageLoop::current();
|
| +
|
| + {
|
| + AutoLock auto_lock(lock_);
|
| + inodes_watched_.insert(inode);
|
| + watches_.insert(watch_);
|
| + }
|
| +
|
| + if (recursive_) {
|
| + Task* subtree_task = new RegisterSubtreeWatchesTask(this, root_path_);
|
| + if (backend_loop) {
|
| + backend_loop->PostTask(FROM_HERE, subtree_task);
|
| + } else {
|
| + subtree_task->Run();
|
| + delete subtree_task;
|
| + }
|
| + }
|
|
|
| - return watch_ != InotifyReader::kInvalidWatch;
|
| + return true;
|
| }
|
|
|
| } // namespace
|
| @@ -319,3 +465,4 @@ bool DirectoryWatcherImpl::Watch(const FilePath& path,
|
| DirectoryWatcher::DirectoryWatcher() {
|
| impl_ = new DirectoryWatcherImpl();
|
| }
|
| +
|
|
|