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() { |