Index: base/files/file_path_watcher_fsevents.cc |
diff --git a/base/files/file_path_watcher_fsevents.cc b/base/files/file_path_watcher_fsevents.cc |
index ba0c1c3878ac4a7f76c2157f2088554081e49e0d..d7fa65733a315b3d596d865e766e47dc7c916179 100644 |
--- a/base/files/file_path_watcher_fsevents.cc |
+++ b/base/files/file_path_watcher_fsevents.cc |
@@ -1,116 +1,99 @@ |
-// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include "content/common/file_path_watcher/file_path_watcher.h" |
+#include "base/files/file_path_watcher_fsevents.h" |
-#include <CoreServices/CoreServices.h> |
-#include <set> |
+#include <list> |
-#include "base/file_path.h" |
+#include "base/bind.h" |
#include "base/file_util.h" |
+#include "base/lazy_instance.h" |
#include "base/logging.h" |
+#include "base/mac/libdispatch_task_runner.h" |
#include "base/mac/scoped_cftyperef.h" |
-#include "base/memory/singleton.h" |
-#include "base/message_loop.h" |
-#include "base/time.h" |
- |
-// Note to future well meaning engineers. Unless kqueue semantics have changed |
-// considerably, do NOT try to reimplement this class using kqueue. The main |
-// problem is that this class requires the ability to watch a directory |
-// and notice changes to any files within it. A kqueue on a directory can watch |
-// for creation and deletion of files, but not for modifications to files within |
-// the directory. To do this with the current kqueue semantics would require |
-// kqueueing every file in the directory, and file descriptors are a limited |
-// resource. If you have a good idea on how to get around this, the source for a |
-// reasonable implementation of this class using kqueues is attached here: |
-// http://code.google.com/p/chromium/issues/detail?id=54822#c13 |
+#include "base/message_loop/message_loop.h" |
+ |
+namespace base { |
namespace { |
// The latency parameter passed to FSEventsStreamCreate(). |
const CFAbsoluteTime kEventLatencySeconds = 0.3; |
-// Mac-specific file watcher implementation based on the FSEvents API. |
-class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, |
- public MessageLoop::DestructionObserver { |
+class FSEventsTaskRunner : public mac::LibDispatchTaskRunner { |
public: |
- FilePathWatcherImpl(); |
- |
- // Called from the FSEvents callback whenever there is a change to the paths |
- void OnFilePathChanged(); |
- |
- // (Re-)Initialize the event stream to start reporting events from |
- // |start_event|. |
- void UpdateEventStream(FSEventStreamEventId start_event); |
+ FSEventsTaskRunner() |
+ : mac::LibDispatchTaskRunner("chromium.org.FilePathWatcherFSEvents") { |
+ } |
- // FilePathWatcher::PlatformDelegate overrides. |
- virtual bool Watch(const FilePath& path, |
- FilePathWatcher::Delegate* delegate, |
- base::MessageLoopProxy* loop) OVERRIDE; |
- virtual void Cancel() OVERRIDE; |
+ protected: |
+ virtual ~FSEventsTaskRunner() {} |
+}; |
- // Deletion of the FilePathWatcher will call Cancel() to dispose of this |
- // object in the right thread. This also observes destruction of the required |
- // cleanup thread, in case it quits before Cancel() is called. |
- virtual void WillDestroyCurrentMessageLoop() OVERRIDE; |
+static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner = |
+ LAZY_INSTANCE_INITIALIZER; |
+ |
+// Resolve any symlinks in the path. |
+FilePath ResolvePath(const FilePath& path) { |
+ const unsigned kMaxLinksToResolve = 255; |
+ |
+ std::vector<FilePath::StringType> component_vector; |
+ path.GetComponents(&component_vector); |
+ std::list<FilePath::StringType> |
+ components(component_vector.begin(), component_vector.end()); |
+ |
+ FilePath result; |
+ unsigned resolve_count = 0; |
+ while (resolve_count < kMaxLinksToResolve && !components.empty()) { |
+ FilePath component(*components.begin()); |
+ components.pop_front(); |
+ |
+ FilePath current; |
+ if (component.IsAbsolute()) { |
+ current = component; |
+ } else { |
+ current = result.Append(component); |
+ } |
- scoped_refptr<base::MessageLoopProxy> run_loop_message_loop() { |
- return run_loop_message_loop_; |
+ FilePath target; |
+ if (ReadSymbolicLink(current, &target)) { |
+ if (target.IsAbsolute()) |
+ result.clear(); |
+ std::vector<FilePath::StringType> target_components; |
+ target.GetComponents(&target_components); |
+ components.insert(components.begin(), target_components.begin(), |
+ target_components.end()); |
+ resolve_count++; |
+ } else { |
+ result = current; |
+ } |
} |
- private: |
- virtual ~FilePathWatcherImpl() {} |
- |
- // Destroy the event stream. |
- void DestroyEventStream(); |
- |
- // Start observing the destruction of the |run_loop_message_loop_| thread, |
- // and watching the FSEventStream. |
- void StartObserverAndEventStream(FSEventStreamEventId start_event); |
- |
- // Cleans up and stops observing the |run_loop_message_loop_| thread. |
- void CancelOnMessageLoopThread() OVERRIDE; |
- |
- // Delegate to notify upon changes. |
- scoped_refptr<FilePathWatcher::Delegate> delegate_; |
- |
- // Target path to watch (passed to delegate). |
- FilePath target_; |
- |
- // Keep track of the last modified time of the file. We use nulltime |
- // to represent the file not existing. |
- base::Time last_modified_; |
- |
- // The time at which we processed the first notification with the |
- // |last_modified_| time stamp. |
- base::Time first_notification_; |
- |
- // Backend stream we receive event callbacks from (strong reference). |
- FSEventStreamRef fsevent_stream_; |
- |
- // Run loop for FSEventStream to run on. |
- scoped_refptr<base::MessageLoopProxy> run_loop_message_loop_; |
- |
- DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); |
-}; |
+ if (resolve_count >= kMaxLinksToResolve) |
+ result.clear(); |
+ return result; |
+} |
// The callback passed to FSEventStreamCreate(). |
void FSEventsCallback(ConstFSEventStreamRef stream, |
void* event_watcher, size_t num_events, |
void* event_paths, const FSEventStreamEventFlags flags[], |
const FSEventStreamEventId event_ids[]) { |
- FilePathWatcherImpl* watcher = |
- reinterpret_cast<FilePathWatcherImpl*>(event_watcher); |
- DCHECK(watcher->run_loop_message_loop()->BelongsToCurrentThread()); |
+ FilePathWatcherFSEvents* watcher = |
+ reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher); |
+ DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); |
- bool root_changed = false; |
+ bool root_changed = watcher->ResolveTargetPath(); |
+ std::vector<FilePath> paths; |
FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream); |
for (size_t i = 0; i < num_events; i++) { |
if (flags[i] & kFSEventStreamEventFlagRootChanged) |
root_changed = true; |
if (event_ids[i]) |
root_change_at = std::min(root_change_at, event_ids[i]); |
+ paths.push_back(FilePath( |
+ reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators()); |
} |
// Reinitialize the event stream if we find changes to the root. This is |
@@ -119,148 +102,116 @@ void FSEventsCallback(ConstFSEventStreamRef stream, |
if (root_changed) { |
// Resetting the event stream from within the callback fails (FSEvents spews |
// bad file descriptor errors), so post a task to do the reset. |
- watcher->run_loop_message_loop()->PostTask(FROM_HERE, |
- NewRunnableMethod(watcher, &FilePathWatcherImpl::UpdateEventStream, |
- root_change_at)); |
+ g_task_runner.Get().PostTask( |
+ FROM_HERE, |
+ Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher, |
+ root_change_at)); |
} |
- watcher->OnFilePathChanged(); |
+ watcher->OnFilePathsChanged(paths); |
} |
-// FilePathWatcherImpl implementation: |
+} // namespace |
-FilePathWatcherImpl::FilePathWatcherImpl() |
- : fsevent_stream_(NULL) { |
+FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) { |
} |
-void FilePathWatcherImpl::OnFilePathChanged() { |
- // Switch to the CFRunLoop based thread if necessary, so we can tear down |
- // the event stream. |
+void FilePathWatcherFSEvents::OnFilePathsChanged( |
+ const std::vector<FilePath>& paths) { |
if (!message_loop()->BelongsToCurrentThread()) { |
message_loop()->PostTask( |
FROM_HERE, |
- NewRunnableMethod(this, &FilePathWatcherImpl::OnFilePathChanged)); |
+ Bind(&FilePathWatcherFSEvents::OnFilePathsChanged, this, paths)); |
return; |
} |
DCHECK(message_loop()->BelongsToCurrentThread()); |
- DCHECK(!target_.empty()); |
- |
- base::PlatformFileInfo file_info; |
- bool file_exists = file_util::GetFileInfo(target_, &file_info); |
- if (file_exists && (last_modified_.is_null() || |
- last_modified_ != file_info.last_modified)) { |
- last_modified_ = file_info.last_modified; |
- first_notification_ = base::Time::Now(); |
- delegate_->OnFilePathChanged(target_); |
- } else if (file_exists && !first_notification_.is_null()) { |
- // The target's last modification time is equal to what's on record. This |
- // means that either an unrelated event occurred, or the target changed |
- // again (file modification times only have a resolution of 1s). Comparing |
- // file modification times against the wall clock is not reliable to find |
- // out whether the change is recent, since this code might just run too |
- // late. Moreover, there's no guarantee that file modification time and wall |
- // clock times come from the same source. |
- // |
- // Instead, the time at which the first notification carrying the current |
- // |last_notified_| time stamp is recorded. Later notifications that find |
- // the same file modification time only need to be forwarded until wall |
- // clock has advanced one second from the initial notification. After that |
- // interval, client code is guaranteed to having seen the current revision |
- // of the file. |
- if (base::Time::Now() - first_notification_ > |
- base::TimeDelta::FromSeconds(1)) { |
- // Stop further notifications for this |last_modification_| time stamp. |
- first_notification_ = base::Time(); |
+ if (resolved_target_.empty()) |
+ return; |
+ |
+ for (size_t i = 0; i < paths.size(); i++) { |
+ if (resolved_target_.IsParent(paths[i]) || resolved_target_ == paths[i]) { |
+ callback_.Run(target_, false); |
+ return; |
} |
- delegate_->OnFilePathChanged(target_); |
- } else if (!file_exists && !last_modified_.is_null()) { |
- last_modified_ = base::Time(); |
- delegate_->OnFilePathChanged(target_); |
} |
} |
-bool FilePathWatcherImpl::Watch(const FilePath& path, |
- FilePathWatcher::Delegate* delegate, |
- base::MessageLoopProxy* loop) { |
- DCHECK(target_.value().empty()); |
+bool FilePathWatcherFSEvents::Watch(const FilePath& path, |
+ bool recursive, |
+ const FilePathWatcher::Callback& callback) { |
+ DCHECK(resolved_target_.empty()); |
DCHECK(MessageLoopForIO::current()); |
+ DCHECK(!callback.is_null()); |
+ |
+ // This class could support non-recursive watches, but that is currently |
+ // left to FilePathWatcherKQueue. |
+ if (!recursive) |
+ return false; |
- set_message_loop(base::MessageLoopProxy::CreateForCurrentThread()); |
- run_loop_message_loop_ = loop; |
+ set_message_loop(MessageLoopProxy::current()); |
+ callback_ = callback; |
target_ = path; |
- delegate_ = delegate; |
FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); |
- |
- base::PlatformFileInfo file_info; |
- if (file_util::GetFileInfo(target_, &file_info)) { |
- last_modified_ = file_info.last_modified; |
- first_notification_ = base::Time::Now(); |
- } |
- |
- run_loop_message_loop()->PostTask(FROM_HERE, |
- NewRunnableMethod(this, &FilePathWatcherImpl::StartObserverAndEventStream, |
- start_event)); |
- |
+ g_task_runner.Get().PostTask( |
+ FROM_HERE, |
+ Bind(&FilePathWatcherFSEvents::StartEventStream, this, start_event)); |
return true; |
} |
-void FilePathWatcherImpl::StartObserverAndEventStream( |
- FSEventStreamEventId start_event) { |
- DCHECK(run_loop_message_loop()->BelongsToCurrentThread()); |
- MessageLoop::current()->AddDestructionObserver(this); |
- UpdateEventStream(start_event); |
-} |
- |
-void FilePathWatcherImpl::Cancel() { |
- if (!run_loop_message_loop().get()) { |
+void FilePathWatcherFSEvents::Cancel() { |
+ if (callback_.is_null()) { |
// Watch was never called, so exit. |
set_cancelled(); |
return; |
} |
- // Switch to the CFRunLoop based thread if necessary, so we can tear down |
+ // Switch to the dispatch queue thread if necessary, so we can tear down |
// the event stream. |
- if (!run_loop_message_loop()->BelongsToCurrentThread()) { |
- run_loop_message_loop()->PostTask(FROM_HERE, |
- new FilePathWatcher::CancelTask(this)); |
+ if (!g_task_runner.Get().RunsTasksOnCurrentThread()) { |
+ g_task_runner.Get().PostTask( |
+ FROM_HERE, |
+ Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this)); |
} else { |
CancelOnMessageLoopThread(); |
} |
} |
-void FilePathWatcherImpl::CancelOnMessageLoopThread() { |
+void FilePathWatcherFSEvents::CancelOnMessageLoopThread() { |
+ // For all other implementations, the "message loop thread" is the IO thread, |
+ // as returned by message_loop(). This implementation, however, needs to |
+ // cancel pending work on the Dipatch Queue thread. |
+ DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); |
+ |
set_cancelled(); |
if (fsevent_stream_) { |
DestroyEventStream(); |
- MessageLoop::current()->RemoveDestructionObserver(this); |
- delegate_ = NULL; |
+ callback_.Reset(); |
+ target_.clear(); |
+ resolved_target_.clear(); |
} |
} |
-void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { |
- CancelOnMessageLoopThread(); |
-} |
- |
-void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { |
- DCHECK(run_loop_message_loop()->BelongsToCurrentThread()); |
- DCHECK(MessageLoopForUI::current()); |
+void FilePathWatcherFSEvents::UpdateEventStream( |
+ FSEventStreamEventId start_event) { |
+ DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); |
// It can happen that the watcher gets canceled while tasks that call this |
// function are still in flight, so abort if this situation is detected. |
- if (is_cancelled()) |
+ if (is_cancelled() || resolved_target_.empty()) |
return; |
if (fsevent_stream_) |
DestroyEventStream(); |
- base::mac::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( |
- NULL, target_.value().c_str(), kCFStringEncodingMacHFS)); |
- base::mac::ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString( |
- NULL, target_.DirName().value().c_str(), kCFStringEncodingMacHFS)); |
+ ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( |
+ NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS)); |
+ ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString( |
+ NULL, resolved_target_.DirName().value().c_str(), |
+ kCFStringEncodingMacHFS)); |
CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() }; |
- base::mac::ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate( |
+ ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate( |
NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array), |
&kCFTypeArrayCallBacks)); |
@@ -276,25 +227,37 @@ void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { |
start_event, |
kEventLatencySeconds, |
kFSEventStreamCreateFlagWatchRoot); |
- FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
- kCFRunLoopDefaultMode); |
- if (!FSEventStreamStart(fsevent_stream_)) { |
- message_loop()->PostTask(FROM_HERE, |
- NewRunnableMethod(delegate_.get(), |
- &FilePathWatcher::Delegate::OnError)); |
- } |
+ FSEventStreamSetDispatchQueue(fsevent_stream_, |
+ g_task_runner.Get().GetDispatchQueue()); |
+ |
+ if (!FSEventStreamStart(fsevent_stream_)) |
+ message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true)); |
} |
-void FilePathWatcherImpl::DestroyEventStream() { |
+bool FilePathWatcherFSEvents::ResolveTargetPath() { |
+ DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); |
+ FilePath resolved = ResolvePath(target_).StripTrailingSeparators(); |
+ bool changed = resolved != resolved_target_; |
+ resolved_target_ = resolved; |
+ if (resolved_target_.empty()) |
+ message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true)); |
+ return changed; |
+} |
+ |
+void FilePathWatcherFSEvents::DestroyEventStream() { |
FSEventStreamStop(fsevent_stream_); |
- FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
- kCFRunLoopDefaultMode); |
+ FSEventStreamInvalidate(fsevent_stream_); |
FSEventStreamRelease(fsevent_stream_); |
fsevent_stream_ = NULL; |
} |
-} // namespace |
- |
-FilePathWatcher::FilePathWatcher() { |
- impl_ = new FilePathWatcherImpl(); |
+void FilePathWatcherFSEvents::StartEventStream( |
+ FSEventStreamEventId start_event) { |
+ DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread()); |
+ ResolveTargetPath(); |
+ UpdateEventStream(start_event); |
} |
+ |
+FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {} |
+ |
+} // namespace base |