Commit 6d2d92a8 authored by vandebo@chromium.org's avatar vandebo@chromium.org

Use FSEvents for recursive file watch on Mac

Brings back the old FSEvents code, changing it to support recursive watches only, and use dispatch queues instead of using the CFRunLoop from the UI thread.

BUG=144491

Review URL: https://codereview.chromium.org/313083007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276289 0039d316-1c4b-4281-b951-d872f2087c98
parent d2b383ae
...@@ -184,8 +184,12 @@ component("base") { ...@@ -184,8 +184,12 @@ component("base") {
"files/file_path_constants.cc", "files/file_path_constants.cc",
"files/file_path_watcher.cc", "files/file_path_watcher.cc",
"files/file_path_watcher.h", "files/file_path_watcher.h",
"files/file_path_watcher_fsevents.cc",
"files/file_path_watcher_fsevents.h",
"files/file_path_watcher_kqueue.cc", "files/file_path_watcher_kqueue.cc",
"files/file_path_watcher_kqueue.h",
"files/file_path_watcher_linux.cc", "files/file_path_watcher_linux.cc",
"files/file_path_watcher_mac.cc",
"files/file_path_watcher_win.cc", "files/file_path_watcher_win.cc",
"files/file_proxy.cc", "files/file_proxy.cc",
"files/file_proxy.h", "files/file_proxy.h",
...@@ -836,7 +840,12 @@ component("base") { ...@@ -836,7 +840,12 @@ component("base") {
deps += [ "//third_party/mach_override" ] deps += [ "//third_party/mach_override" ]
} else { } else {
# Non-Mac. # Non-Mac.
sources -= [ "files/file_path_watcher_kqueue.cc" ] sources -= [
"files/file_path_watcher_fsevents.cc",
"files/file_path_watcher_fsevents.h",
"files/file_path_watcher_kqueue.cc",
"files/file_path_watcher_kqueue.h",
]
} }
# Linux. # Linux.
......
...@@ -194,8 +194,12 @@ ...@@ -194,8 +194,12 @@
'files/file_path_constants.cc', 'files/file_path_constants.cc',
'files/file_path_watcher.cc', 'files/file_path_watcher.cc',
'files/file_path_watcher.h', 'files/file_path_watcher.h',
'files/file_path_watcher_fsevents.cc',
'files/file_path_watcher_fsevents.h',
'files/file_path_watcher_kqueue.cc', 'files/file_path_watcher_kqueue.cc',
'files/file_path_watcher_kqueue.h',
'files/file_path_watcher_linux.cc', 'files/file_path_watcher_linux.cc',
'files/file_path_watcher_mac.cc',
'files/file_path_watcher_stub.cc', 'files/file_path_watcher_stub.cc',
'files/file_path_watcher_win.cc', 'files/file_path_watcher_win.cc',
'files/file_posix.cc', 'files/file_posix.cc',
...@@ -751,7 +755,10 @@ ...@@ -751,7 +755,10 @@
'file_util.cc', 'file_util.cc',
'file_util_posix.cc', 'file_util_posix.cc',
'files/file_enumerator_posix.cc', 'files/file_enumerator_posix.cc',
'files/file_path_watcher_fsevents.cc',
'files/file_path_watcher_fsevents.h',
'files/file_path_watcher_kqueue.cc', 'files/file_path_watcher_kqueue.cc',
'files/file_path_watcher_kqueue.h',
'files/file_proxy.cc', 'files/file_proxy.cc',
'files/file_util_proxy.cc', 'files/file_util_proxy.cc',
'memory/shared_memory_posix.cc', 'memory/shared_memory_posix.cc',
...@@ -783,7 +790,10 @@ ...@@ -783,7 +790,10 @@
['OS == "android" and >(nacl_untrusted_build)==0', { ['OS == "android" and >(nacl_untrusted_build)==0', {
'sources!': [ 'sources!': [
'base_paths_posix.cc', 'base_paths_posix.cc',
'files/file_path_watcher_fsevents.cc',
'files/file_path_watcher_fsevents.h',
'files/file_path_watcher_kqueue.cc', 'files/file_path_watcher_kqueue.cc',
'files/file_path_watcher_kqueue.h',
'files/file_path_watcher_stub.cc', 'files/file_path_watcher_stub.cc',
'power_monitor/power_monitor_device_source_posix.cc', 'power_monitor/power_monitor_device_source_posix.cc',
], ],
...@@ -848,6 +858,9 @@ ...@@ -848,6 +858,9 @@
['include', '^process/.*_ios\.(cc|mm)$'], ['include', '^process/.*_ios\.(cc|mm)$'],
['include', '^process/memory_stubs\.cc$'], ['include', '^process/memory_stubs\.cc$'],
['include', '^process/process_handle_posix\.cc$'], ['include', '^process/process_handle_posix\.cc$'],
['exclude', 'files/file_path_watcher_fsevents.cc'],
['exclude', 'files/file_path_watcher_fsevents.h'],
['include', 'files/file_path_watcher_mac.cc'],
], ],
'sources': [ 'sources': [
'process/memory_stubs.cc', 'process/memory_stubs.cc',
...@@ -863,6 +876,9 @@ ...@@ -863,6 +876,9 @@
['include', '(^|/)(cocoa|mac)/'], ['include', '(^|/)(cocoa|mac)/'],
['exclude', '_ios(_unittest)?\\.(h|cc|mm?)$'], ['exclude', '_ios(_unittest)?\\.(h|cc|mm?)$'],
['exclude', '(^|/)ios/'], ['exclude', '(^|/)ios/'],
['exclude', 'files/file_path_watcher_fsevents.cc'],
['exclude', 'files/file_path_watcher_fsevents.h'],
['include', 'files/file_path_watcher_mac.cc'],
] ]
}], }],
# For now, just test the *BSD platforms enough to exclude them. # For now, just test the *BSD platforms enough to exclude them.
...@@ -881,7 +897,10 @@ ...@@ -881,7 +897,10 @@
], ],
'sources!': [ 'sources!': [
'event_recorder_stubs.cc', 'event_recorder_stubs.cc',
'files/file_path_watcher_fsevents.cc',
'files/file_path_watcher_fsevents.h',
'files/file_path_watcher_kqueue.cc', 'files/file_path_watcher_kqueue.cc',
'files/file_path_watcher_kqueue.h',
'files/file_path_watcher_stub.cc', 'files/file_path_watcher_stub.cc',
'message_loop/message_pump_libevent.cc', 'message_loop/message_pump_libevent.cc',
'posix/file_descriptor_shuffle.cc', 'posix/file_descriptor_shuffle.cc',
...@@ -898,7 +917,10 @@ ...@@ -898,7 +917,10 @@
}], }],
['OS == "linux" and >(nacl_untrusted_build)==0', { ['OS == "linux" and >(nacl_untrusted_build)==0', {
'sources!': [ 'sources!': [
'files/file_path_watcher_fsevents.cc',
'files/file_path_watcher_fsevents.h',
'files/file_path_watcher_kqueue.cc', 'files/file_path_watcher_kqueue.cc',
'files/file_path_watcher_kqueue.h',
'files/file_path_watcher_stub.cc', 'files/file_path_watcher_stub.cc',
], ],
}], }],
......
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#if defined(OS_MACOSX) && !defined(OS_IOS)
#include "base/mac/mac_util.h"
#endif
namespace base { namespace base {
FilePathWatcher::~FilePathWatcher() { FilePathWatcher::~FilePathWatcher() {
...@@ -22,6 +26,19 @@ void FilePathWatcher::CancelWatch( ...@@ -22,6 +26,19 @@ void FilePathWatcher::CancelWatch(
delegate->CancelOnMessageLoopThread(); delegate->CancelOnMessageLoopThread();
} }
// static
bool FilePathWatcher::RecursiveWatchAvailable() {
#if defined(OS_MACOSX) && !defined(OS_IOS)
// FSEvents isn't available on iOS and is broken on OSX 10.6 and earlier.
// See http://crbug.com/54822#c31
return mac::IsOSLionOrLater();
#elif defined(OS_WIN) || defined(OS_LINUX)
return true;
#else
return false;
#endif
}
FilePathWatcher::PlatformDelegate::PlatformDelegate(): cancelled_(false) { FilePathWatcher::PlatformDelegate::PlatformDelegate(): cancelled_(false) {
} }
......
...@@ -88,12 +88,15 @@ class BASE_EXPORT FilePathWatcher { ...@@ -88,12 +88,15 @@ class BASE_EXPORT FilePathWatcher {
// shutdown. // shutdown.
static void CancelWatch(const scoped_refptr<PlatformDelegate>& delegate); static void CancelWatch(const scoped_refptr<PlatformDelegate>& delegate);
// Returns true if the platform and OS version support recursive watches.
static bool RecursiveWatchAvailable();
// Invokes |callback| whenever updates to |path| are detected. This should be // Invokes |callback| whenever updates to |path| are detected. This should be
// called at most once, and from a MessageLoop of TYPE_IO. Set |recursive| to // called at most once, and from a MessageLoop of TYPE_IO. Set |recursive| to
// true, to watch |path| and its children. The callback will be invoked on // true, to watch |path| and its children. The callback will be invoked on
// the same loop. Returns true on success. // the same loop. Returns true on success.
// //
// NOTE: Recursive watch is not supported on all platforms and file systems. // Recursive watch is not supported on all platforms and file systems.
// Watch() will return false in the case of failure. // Watch() will return false in the case of failure.
bool Watch(const FilePath& path, bool recursive, const Callback& callback); bool Watch(const FilePath& path, bool recursive, const Callback& callback);
......
...@@ -205,9 +205,8 @@ bool FilePathWatcherTest::SetupWatch(const FilePath& target, ...@@ -205,9 +205,8 @@ bool FilePathWatcherTest::SetupWatch(const FilePath& target,
bool result; bool result;
file_thread_.message_loop_proxy()->PostTask( file_thread_.message_loop_proxy()->PostTask(
FROM_HERE, FROM_HERE,
base::Bind(SetupWatchCallback, base::Bind(SetupWatchCallback, target, watcher, delegate, recursive_watch,
target, watcher, delegate, recursive_watch, &result, &result, &completion));
&completion));
completion.Wait(); completion.Wait();
return result; return result;
} }
...@@ -483,12 +482,17 @@ TEST_F(FilePathWatcherTest, MoveParent) { ...@@ -483,12 +482,17 @@ TEST_F(FilePathWatcherTest, MoveParent) {
DeleteDelegateOnFileThread(subdir_delegate.release()); DeleteDelegateOnFileThread(subdir_delegate.release());
} }
#if defined(OS_WIN) || defined(OS_LINUX)
TEST_F(FilePathWatcherTest, RecursiveWatch) { TEST_F(FilePathWatcherTest, RecursiveWatch) {
FilePathWatcher watcher; FilePathWatcher watcher;
FilePath dir(temp_dir_.path().AppendASCII("dir")); FilePath dir(temp_dir_.path().AppendASCII("dir"));
scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), true)); bool setup_result = SetupWatch(dir, &watcher, delegate.get(), true);
if (!FilePathWatcher::RecursiveWatchAvailable()) {
ASSERT_FALSE(setup_result);
DeleteDelegateOnFileThread(delegate.release());
return;
}
ASSERT_TRUE(setup_result);
// Main directory("dir") creation. // Main directory("dir") creation.
ASSERT_TRUE(base::CreateDirectory(dir)); ASSERT_TRUE(base::CreateDirectory(dir));
...@@ -536,17 +540,48 @@ TEST_F(FilePathWatcherTest, RecursiveWatch) { ...@@ -536,17 +540,48 @@ TEST_F(FilePathWatcherTest, RecursiveWatch) {
ASSERT_TRUE(WaitForEvents()); ASSERT_TRUE(WaitForEvents());
DeleteDelegateOnFileThread(delegate.release()); DeleteDelegateOnFileThread(delegate.release());
} }
#else
TEST_F(FilePathWatcherTest, RecursiveWatch) { #if defined(OS_POSIX)
TEST_F(FilePathWatcherTest, RecursiveWithSymLink) {
if (!FilePathWatcher::RecursiveWatchAvailable())
return;
FilePathWatcher watcher; FilePathWatcher watcher;
FilePath dir(temp_dir_.path().AppendASCII("dir")); FilePath test_dir(temp_dir_.path().AppendASCII("test_dir"));
ASSERT_TRUE(base::CreateDirectory(test_dir));
FilePath symlink(test_dir.AppendASCII("symlink"));
scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); scoped_ptr<TestDelegate> delegate(new TestDelegate(collector()));
// Only Windows/Linux support recursive watching. Other implementations ASSERT_TRUE(SetupWatch(symlink, &watcher, delegate.get(), true));
// should simply fail.
ASSERT_FALSE(SetupWatch(dir, &watcher, delegate.get(), true)); // Link creation.
FilePath target1(temp_dir_.path().AppendASCII("target1"));
ASSERT_TRUE(base::CreateSymbolicLink(target1, symlink));
ASSERT_TRUE(WaitForEvents());
// Target1 creation.
ASSERT_TRUE(base::CreateDirectory(target1));
ASSERT_TRUE(WaitForEvents());
// Create a file in target1.
FilePath target1_file(target1.AppendASCII("file"));
ASSERT_TRUE(WriteFile(target1_file, "content"));
ASSERT_TRUE(WaitForEvents());
// Link change.
FilePath target2(temp_dir_.path().AppendASCII("target2"));
ASSERT_TRUE(base::CreateDirectory(target2));
ASSERT_TRUE(base::DeleteFile(symlink, false));
ASSERT_TRUE(base::CreateSymbolicLink(target2, symlink));
ASSERT_TRUE(WaitForEvents());
// Create a file in target2.
FilePath target2_file(target2.AppendASCII("file"));
ASSERT_TRUE(WriteFile(target2_file, "content"));
ASSERT_TRUE(WaitForEvents());
DeleteDelegateOnFileThread(delegate.release()); DeleteDelegateOnFileThread(delegate.release());
} }
#endif #endif // OS_POSIX
TEST_F(FilePathWatcherTest, MoveChild) { TEST_F(FilePathWatcherTest, MoveChild) {
FilePathWatcher file_watcher; FilePathWatcher file_watcher;
......
// 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 "base/files/file_path_watcher_fsevents.h"
#include <list>
#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/message_loop/message_loop.h"
namespace base {
namespace {
// The latency parameter passed to FSEventsStreamCreate().
const CFAbsoluteTime kEventLatencySeconds = 0.3;
class FSEventsTaskRunner : public mac::LibDispatchTaskRunner {
public:
FSEventsTaskRunner()
: mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
}
protected:
virtual ~FSEventsTaskRunner() {}
};
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);
}
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;
}
}
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[]) {
FilePathWatcherFSEvents* watcher =
reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
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
// necessary since FSEvents doesn't report any events for the subtree after
// the directory to be watched gets created.
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.
g_task_runner.Get().PostTask(
FROM_HERE,
Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher,
root_change_at));
}
watcher->OnFilePathsChanged(paths);
}
} // namespace
FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
}
void FilePathWatcherFSEvents::OnFilePathsChanged(
const std::vector<FilePath>& paths) {
if (!message_loop()->BelongsToCurrentThread()) {
message_loop()->PostTask(
FROM_HERE,
Bind(&FilePathWatcherFSEvents::OnFilePathsChanged, this, paths));
return;
}
DCHECK(message_loop()->BelongsToCurrentThread());
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;
}
}
}
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(MessageLoopProxy::current());
callback_ = callback;
target_ = path;
FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
g_task_runner.Get().PostTask(
FROM_HERE,
Bind(&FilePathWatcherFSEvents::StartEventStream, this, start_event));
return true;
}
void FilePathWatcherFSEvents::Cancel() {
if (callback_.is_null()) {
// Watch was never called, so exit.
set_cancelled();
return;
}
// Switch to the dispatch queue thread if necessary, so we can tear down
// the event stream.
if (!g_task_runner.Get().RunsTasksOnCurrentThread()) {
g_task_runner.Get().PostTask(
FROM_HERE,
Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
} else {
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();
callback_.Reset();
target_.clear();
resolved_target_.clear();
}
}
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() || resolved_target_.empty())
return;
if (fsevent_stream_)
DestroyEventStream();
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() };
ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
&kCFTypeArrayCallBacks));
FSEventStreamContext context;
context.version = 0;
context.info = this;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;
fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
watched_paths,
start_event,
kEventLatencySeconds,
kFSEventStreamCreateFlagWatchRoot);
FSEventStreamSetDispatchQueue(fsevent_stream_,
g_task_runner.Get().GetDispatchQueue());
if (!FSEventStreamStart(fsevent_stream_))
message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true));
}
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_);
FSEventStreamInvalidate(fsevent_stream_);
FSEventStreamRelease(fsevent_stream_);
fsevent_stream_ = NULL;
}
void FilePathWatcherFSEvents::StartEventStream(
FSEventStreamEventId start_event) {
DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
ResolveTargetPath();
UpdateEventStream(start_event);
}
FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {}
} // namespace base
// 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.
#ifndef BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
#define BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
#include <CoreServices/CoreServices.h>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
namespace base {
// Mac-specific file watcher implementation based on FSEvents.
// There are trade-offs between the FSEvents implementation and a kqueue
// implementation. The biggest issues are that FSEvents on 10.6 sometimes drops
// events and kqueue does not trigger for modifications to a file in a watched
// directory. See file_path_watcher_mac.cc for the code that decides when to
// use which one.
class FilePathWatcherFSEvents : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherFSEvents();
// Called from the FSEvents callback whenever there is a change to the paths.
void OnFilePathsChanged(const std::vector<FilePath>& paths);
// (Re-)Initialize the event stream to start reporting events from
// |start_event|.
void UpdateEventStream(FSEventStreamEventId start_event);
// Returns true if resolving the target path got a different result than
// last time it was done.
bool ResolveTargetPath();
// FilePathWatcher::PlatformDelegate overrides.
virtual bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) OVERRIDE;
virtual void Cancel() OVERRIDE;
private:
virtual ~FilePathWatcherFSEvents();
// Destroy the event stream.
void DestroyEventStream();
// Start watching the FSEventStream.
void StartEventStream(FSEventStreamEventId start_event);
// Cleans up and stops the event stream.
virtual void CancelOnMessageLoopThread() OVERRIDE;
// Callback to notify upon changes.
FilePathWatcher::Callback callback_;
// Target path to watch (passed to callback).
FilePath target_;
// Target path with all symbolic links resolved.
FilePath resolved_target_;
// Backend stream we receive event callbacks from (strong reference).
FSEventStreamRef fsevent_stream_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFSEvents);
};
} // namespace base
#endif // BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
This diff is collapsed.
// 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.
#ifndef BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
#define BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
#include <sys/event.h>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
namespace base {
// Mac-specific file watcher implementation based on kqueue.
// The Linux and Windows versions are able to detect:
// - file creation/deletion/modification in a watched directory
// - file creation/deletion/modification for a watched file
// - modifications to the paths to a watched object that would affect the
// object such as renaming/attibute changes etc.
// The kqueue implementation will handle all of the items in the list above
// except for detecting modifications to files in a watched directory. It will
// detect the creation and deletion of files, just not the modification of
// files. It does however detect the attribute changes that the FSEvents impl
// would miss.
class FilePathWatcherKQueue : public FilePathWatcher::PlatformDelegate,
public MessageLoopForIO::Watcher,
public MessageLoop::DestructionObserver {
public:
FilePathWatcherKQueue();
// MessageLoopForIO::Watcher overrides.
virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
// MessageLoop::DestructionObserver overrides.
virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
// FilePathWatcher::PlatformDelegate overrides.
virtual bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) OVERRIDE;
virtual void Cancel() OVERRIDE;
protected:
virtual ~FilePathWatcherKQueue();
private:
class EventData {
public:
EventData(const FilePath& path, const FilePath::StringType& subdir)
: path_(path), subdir_(subdir) { }
FilePath path_; // Full path to this item.
FilePath::StringType subdir_; // Path to any sub item.
};
typedef std::vector<struct kevent> EventVector;
// Can only be called on |io_message_loop_|'s thread.
virtual void CancelOnMessageLoopThread() OVERRIDE;
// Returns true if the kevent values are error free.
bool AreKeventValuesValid(struct kevent* kevents, int count);
// Respond to a change of attributes of the path component represented by
// |event|. Sets |target_file_affected| to true if |target_| is affected.
// Sets |update_watches| to true if |events_| need to be updated.
void HandleAttributesChange(const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches);
// Respond to a move or deletion of the path component represented by
// |event|. Sets |target_file_affected| to true if |target_| is affected.
// Sets |update_watches| to true if |events_| need to be updated.
void HandleDeleteOrMoveChange(const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches);
// Respond to a creation of an item in the path component represented by
// |event|. Sets |target_file_affected| to true if |target_| is affected.
// Sets |update_watches| to true if |events_| need to be updated.
void HandleCreateItemChange(const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches);
// Update |events_| with the current status of the system.
// Sets |target_file_affected| to true if |target_| is affected.
// Returns false if an error occurs.
bool UpdateWatches(bool* target_file_affected);
// Fills |events| with one kevent per component in |path|.
// Returns the number of valid events created where a valid event is
// defined as one that has a ident (file descriptor) field != -1.
static int EventsForPath(FilePath path, EventVector *events);
// Release a kevent generated by EventsForPath.
static void ReleaseEvent(struct kevent& event);
// Returns a file descriptor that will not block the system from deleting
// the file it references.
static uintptr_t FileDescriptorForPath(const FilePath& path);
static const uintptr_t kNoFileDescriptor = static_cast<uintptr_t>(-1);
// Closes |*fd| and sets |*fd| to -1.
static void CloseFileDescriptor(uintptr_t* fd);
// Returns true if kevent has open file descriptor.
static bool IsKeventFileDescriptorOpen(const struct kevent& event) {
return event.ident != kNoFileDescriptor;
}
static EventData* EventDataForKevent(const struct kevent& event) {
return reinterpret_cast<EventData*>(event.udata);
}
EventVector events_;
scoped_refptr<base::MessageLoopProxy> io_message_loop_;
MessageLoopForIO::FileDescriptorWatcher kqueue_watcher_;
FilePathWatcher::Callback callback_;
FilePath target_;
int kqueue_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherKQueue);
};
} // namespace base
#endif // BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
...@@ -463,6 +463,7 @@ void FilePathWatcherImpl::Cancel() { ...@@ -463,6 +463,7 @@ void FilePathWatcherImpl::Cancel() {
} }
void FilePathWatcherImpl::CancelOnMessageLoopThread() { void FilePathWatcherImpl::CancelOnMessageLoopThread() {
DCHECK(message_loop()->BelongsToCurrentThread());
set_cancelled(); set_cancelled();
if (!callback_.is_null()) { if (!callback_.is_null()) {
......
// 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 "base/files/file_path_watcher.h"
#include "base/files/file_path_watcher_kqueue.h"
#if !defined(OS_IOS)
#include "base/files/file_path_watcher_fsevents.h"
#endif
namespace base {
namespace {
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
public:
virtual bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) OVERRIDE {
// Use kqueue for non-recursive watches and FSEvents for recursive ones.
DCHECK(!impl_.get());
if (recursive) {
if (!FilePathWatcher::RecursiveWatchAvailable())
return false;
#if !defined(OS_IOS)
impl_ = new FilePathWatcherFSEvents();
#endif // OS_IOS
} else {
impl_ = new FilePathWatcherKQueue();
}
DCHECK(impl_.get());
return impl_->Watch(path, recursive, callback);
}
virtual void Cancel() OVERRIDE {
if (impl_)
impl_->Cancel();
set_cancelled();
}
virtual void CancelOnMessageLoopThread() OVERRIDE {
if (impl_)
impl_->Cancel();
set_cancelled();
}
protected:
virtual ~FilePathWatcherImpl() {}
scoped_refptr<PlatformDelegate> impl_;
};
} // namespace
FilePathWatcher::FilePathWatcher() {
impl_ = new FilePathWatcherImpl();
}
} // namespace base
...@@ -129,6 +129,7 @@ void FilePathWatcherImpl::Cancel() { ...@@ -129,6 +129,7 @@ void FilePathWatcherImpl::Cancel() {
} }
void FilePathWatcherImpl::CancelOnMessageLoopThread() { void FilePathWatcherImpl::CancelOnMessageLoopThread() {
DCHECK(message_loop()->BelongsToCurrentThread());
set_cancelled(); set_cancelled();
if (handle_ != INVALID_HANDLE_VALUE) if (handle_ != INVALID_HANDLE_VALUE)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment