| Index: base/directory_watcher_inotify.cc
|
| ===================================================================
|
| --- base/directory_watcher_inotify.cc (revision 14201)
|
| +++ base/directory_watcher_inotify.cc (working copy)
|
| @@ -6,8 +6,8 @@
|
|
|
| #include <errno.h>
|
| #include <string.h>
|
| +#include <sys/inotify.h>
|
| #include <sys/ioctl.h>
|
| -#include <sys/inotify.h>
|
| #include <sys/select.h>
|
| #include <unistd.h>
|
|
|
| @@ -17,6 +17,7 @@
|
| #include <vector>
|
|
|
| #include "base/file_path.h"
|
| +#include "base/file_util.h"
|
| #include "base/hash_tables.h"
|
| #include "base/lock.h"
|
| #include "base/logging.h"
|
| @@ -28,23 +29,20 @@
|
|
|
| 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);
|
| @@ -52,16 +50,13 @@
|
| 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_;
|
| @@ -84,6 +79,115 @@
|
| DISALLOW_COPY_AND_ASSIGN(InotifyReader);
|
| };
|
|
|
| +class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
| + public:
|
| +
|
| + typedef std::set<FilePath> FilePathSet;
|
| +
|
| + DirectoryWatcherImpl();
|
| + ~DirectoryWatcherImpl();
|
| +
|
| + // Called for each event coming from one of watches.
|
| + void HandleInotifyEvent(inotify_event* event);
|
| +
|
| + // Callback for DirectoryWatcherImplEnumeratePathTask.
|
| + void OnEnumeratedPath(const std::set<FilePath>& subtree);
|
| +
|
| + // Start watching |path| for changes and notify |delegate| on each change.
|
| + // Watch entire subtree, when |recursive| is true.
|
| + // Returns true if watch for |path| has been added successfuly. 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,
|
| + bool recursive);
|
| +
|
| + private:
|
| + typedef std::set<InotifyReader::Watch> WatchSet;
|
| +
|
| + // Returns true if |inode| is watched by DirectoryWatcherImpl.
|
| + bool isPathWatched(ino_t inode) const;
|
| +
|
| + // Return true if watching subtree is success.
|
| + bool watchSubtree(const FilePathSet& subtree);
|
| +
|
| + // Lock to protect attributes.
|
| + Lock lock_;
|
| +
|
| + // Delegate to notify upon changes.
|
| + DirectoryWatcher::Delegate* delegate_;
|
| +
|
| + // Path we're watching (passed to delegate).
|
| + FilePath root_path_;
|
| +
|
| + // Watch returned by InotifyReader.
|
| + InotifyReader::Watch root_watch_;
|
| +
|
| + typedef std::set<ino_t> InodeSet;
|
| +
|
| + // Hold inode for paths that are watched.
|
| + InodeSet path_watched_;
|
| +
|
| + // We need to know who is watching what inode,
|
| + // for cleaning up and handling events.
|
| + base::hash_map<ino_t, DirectoryWatcherImpl*> path_watcher_;
|
| +
|
| + // Flag set to true when recursively watching subtree.
|
| + bool recursive_;
|
| +
|
| + // Flag set to true when thread startup was successful.
|
| + bool path_enumerator_thread_started_;
|
| +
|
| + // Seperate thread on which we enumerate directory subtree.
|
| + base::Thread path_enumerator_thread_;
|
| +
|
| + MessageLoop* loop_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
| +};
|
| +
|
| +class DirectoryWatcherImplEnumeratePathTask : public Task {
|
| + public:
|
| + DirectoryWatcherImplEnumeratePathTask(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_->OnEnumeratedPath(subtree);
|
| + }
|
| +
|
| + private:
|
| + DirectoryWatcherImpl* watcher_;
|
| + FilePath path_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplEnumeratePathTask);
|
| +};
|
| +
|
| +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)
|
| @@ -152,25 +256,6 @@
|
| 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()),
|
| @@ -205,7 +290,8 @@
|
| }
|
|
|
| InotifyReader::Watch InotifyReader::AddWatch(
|
| - const FilePath& path, DirectoryWatcher::Delegate* delegate) {
|
| + const FilePath& path, DirectoryWatcherImpl* watcher) {
|
| +
|
| if (!valid_)
|
| return kInvalidWatch;
|
|
|
| @@ -214,19 +300,20 @@
|
| 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;
|
|
|
| @@ -235,14 +322,11 @@
|
| 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);
|
| }
|
|
|
| @@ -253,73 +337,138 @@
|
| 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)->HandleInotifyEvent(event);
|
| }
|
| }
|
|
|
| -class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
| - public:
|
| - DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {}
|
| - ~DirectoryWatcherImpl();
|
| +DirectoryWatcherImpl::DirectoryWatcherImpl()
|
| + : root_watch_(InotifyReader::kInvalidWatch),
|
| + path_enumerator_thread_started_(false),
|
| + path_enumerator_thread_("directory_enumerator"),
|
| + loop_(MessageLoop::current()) {}
|
|
|
| - virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
| - bool recursive);
|
| +DirectoryWatcherImpl::~DirectoryWatcherImpl() {
|
| + if (root_watch_ == InotifyReader::kInvalidWatch)
|
| + return;
|
| + for (InodeSet::iterator i = path_watched_.begin();
|
| + i != path_watched_.end();
|
| + ++i) {
|
| + DirectoryWatcherImpl* delete_me = path_watcher_[*i];
|
| + if (Singleton<InotifyReader>::get()->RemoveWatch(delete_me->root_watch_,
|
| + delete_me)) {
|
| + delete_me->root_watch_ = InotifyReader::kInvalidWatch;
|
| + }
|
| + }
|
| + path_watcher_.clear();
|
| + path_watched_.clear();
|
| + if (path_enumerator_thread_started_)
|
| + path_enumerator_thread_.Stop();
|
| +}
|
|
|
| - private:
|
| - // Delegate to notify upon changes.
|
| - DirectoryWatcher::Delegate* delegate_;
|
| +void DirectoryWatcherImpl::HandleInotifyEvent(inotify_event* event) {
|
| + loop_->PostTask(FROM_HERE,
|
| + new DirectoryWatcherImplNotifyTask(delegate_, root_path_));
|
|
|
| - // Path we're watching (passed to delegate).
|
| - FilePath path_;
|
| + if (!(event->mask & IN_ISDIR))
|
| + return;
|
|
|
| - // Watch returned by InotifyReader.
|
| - InotifyReader::Watch watch_;
|
| + if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
|
| + NOTIMPLEMENTED();
|
| + } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
|
| + NOTIMPLEMENTED();
|
| + }
|
| +}
|
|
|
| - DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
| -};
|
| +bool DirectoryWatcherImpl::isPathWatched(ino_t inode) const {
|
| + return path_watched_.find(inode) != path_watched_.end();
|
| +}
|
|
|
| -DirectoryWatcherImpl::~DirectoryWatcherImpl() {
|
| - if (watch_ != InotifyReader::kInvalidWatch)
|
| - Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_);
|
| +void DirectoryWatcherImpl::OnEnumeratedPath(const FilePathSet& subtree) {
|
| + DCHECK(recursive_);
|
| + watchSubtree(subtree);
|
| }
|
|
|
| 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();
|
| + // Can only watch one path.
|
| + DCHECK(root_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;
|
| + root_watch_ = watch;
|
|
|
| - return watch_ != InotifyReader::kInvalidWatch;
|
| + {
|
| + AutoLock auto_lock(lock_);
|
| + path_watched_.insert(inode);
|
| + path_watcher_[inode] = this;
|
| + }
|
| +
|
| + if (recursive_) {
|
| + path_enumerator_thread_started_ = path_enumerator_thread_.Start();
|
| + if (path_enumerator_thread_started_) {
|
| + path_enumerator_thread_.message_loop()->PostTask(
|
| + FROM_HERE,
|
| + new DirectoryWatcherImplEnumeratePathTask(this, root_path_));
|
| + }
|
| + }
|
| +
|
| + return true;
|
| }
|
|
|
| +bool DirectoryWatcherImpl::watchSubtree(const FilePathSet& subtree) {
|
| + DCHECK(recursive_);
|
| +
|
| + if (root_watch_ == InotifyReader::kInvalidWatch)
|
| + 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 (isPathWatched(inode))
|
| + continue;
|
| + DirectoryWatcherImpl* child = new DirectoryWatcherImpl();
|
| + child->loop_ = loop_;
|
| + if (!child->Watch(*subdirectory, delegate_, false)) {
|
| + scoped_refptr<DirectoryWatcherImpl> delete_me = child;
|
| + } else {
|
| + path_watcher_[inode] = child;
|
| + path_watched_.insert(inode);
|
| + }
|
| + }
|
| + return success;
|
| +}
|
| +
|
| } // namespace
|
|
|
| DirectoryWatcher::DirectoryWatcher() {
|
|
|