Index: base/directory_watcher_inotify.cc |
=================================================================== |
--- base/directory_watcher_inotify.cc (revision 15554) |
+++ 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> |
@@ -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" |
@@ -29,40 +30,34 @@ |
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 +80,113 @@ |
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, |
+ bool recursive); |
+ |
+ private: |
+ typedef std::set<InotifyReader::Watch> WatchSet; |
+ typedef std::set<ino_t> InodeSet; |
+ |
+ // Returns true if |inode| is watched by DirectoryWatcherImpl. |
+ bool is_inode_watched(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_; |
+ |
+ // Seperate thread on which we enumerate directory subtree. |
+ base::Thread path_enumerator_thread_; |
+ |
+ // Loop where we post directory change notifications to. |
+ MessageLoop* loop_; |
+ |
+ 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 +253,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()), |
@@ -199,7 +282,8 @@ |
} |
InotifyReader::Watch InotifyReader::AddWatch( |
- const FilePath& path, DirectoryWatcher::Delegate* delegate) { |
+ const FilePath& path, DirectoryWatcherImpl* watcher) { |
+ |
if (!valid_) |
return kInvalidWatch; |
@@ -208,19 +292,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; |
@@ -229,89 +314,145 @@ |
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), |
+ path_enumerator_thread_("directory_enumerator") { |
+} |
- 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_; |
+ path_enumerator_thread_.Stop(); |
+ 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::EnsureSetupFinished() { |
+ path_enumerator_thread_.Stop(); |
+} |
- // Watch returned by InotifyReader. |
- InotifyReader::Watch watch_; |
+void DirectoryWatcherImpl::OnInotifyEvent(const inotify_event* event) { |
+ loop_->PostTask(FROM_HERE, |
+ new DirectoryWatcherImplNotifyTask(delegate_, root_path_)); |
- DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl); |
-}; |
+ if (!(event->mask & IN_ISDIR)) |
+ return; |
-DirectoryWatcherImpl::~DirectoryWatcherImpl() { |
- if (watch_ != InotifyReader::kInvalidWatch) |
- Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_); |
+ if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) { |
+ NOTIMPLEMENTED(); |
+ } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) { |
+ NOTIMPLEMENTED(); |
+ } |
} |
+bool DirectoryWatcherImpl::is_inode_watched(ino_t inode) const { |
+ return inodes_watched_.find(inode) != inodes_watched_.end(); |
+} |
+ |
+bool DirectoryWatcherImpl::OnEnumeratedSubtree(const FilePathSet& subtree) { |
+ DCHECK(recursive_); |
+ |
+ if (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 (is_inode_watched(inode)) |
+ continue; |
+ InotifyReader::Watch watch = |
+ Singleton<InotifyReader>::get()->AddWatch(*subdirectory, this); |
+ if (watch != InotifyReader::kInvalidWatch) { |
+ watches_.insert(watch); |
+ inodes_watched_.insert(inode); |
+ } |
+ } |
+ return success; |
+} |
+ |
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(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(); |
- return watch_ != InotifyReader::kInvalidWatch; |
+ { |
+ AutoLock auto_lock(lock_); |
+ inodes_watched_.insert(inode); |
+ watches_.insert(watch_); |
+ } |
+ |
+ if (recursive_) { |
+ if (path_enumerator_thread_.Start()) { |
+ path_enumerator_thread_.message_loop()->PostTask( |
+ FROM_HERE, |
+ new RegisterSubtreeWatchesTask(this, root_path_)); |
+ } else { |
+ NOTREACHED(); |
+ } |
+ } |
+ |
+ return true; |
} |
} // namespace |
@@ -319,3 +460,4 @@ |
DirectoryWatcher::DirectoryWatcher() { |
impl_ = new DirectoryWatcherImpl(); |
} |
+ |