| // Copyright (c) 2009 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/directory_watcher.h" |
| |
| #include <CoreServices/CoreServices.h> |
| |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/scoped_cftyperef.h" |
| |
| namespace { |
| |
| const CFAbsoluteTime kEventLatencySeconds = 0.3; |
| |
| class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate { |
| public: |
| DirectoryWatcherImpl() {} |
| ~DirectoryWatcherImpl() { |
| if (!path_.value().empty()) { |
| FSEventStreamStop(fsevent_stream_); |
| FSEventStreamInvalidate(fsevent_stream_); |
| FSEventStreamRelease(fsevent_stream_); |
| } |
| } |
| |
| virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate, |
| MessageLoop* backend_loop, bool recursive); |
| |
| void OnFSEventsCallback(const FilePath& event_path) { |
| DCHECK(!path_.value().empty()); |
| if (!recursive_) { |
| FilePath absolute_event_path = event_path; |
| if (!file_util::AbsolutePath(&absolute_event_path)) |
| return; |
| if (absolute_event_path != path_) |
| return; |
| } |
| delegate_->OnDirectoryChanged(path_); |
| } |
| |
| private: |
| // Delegate to notify upon changes. |
| DirectoryWatcher::Delegate* delegate_; |
| |
| // Path we're watching (passed to delegate). |
| FilePath path_; |
| |
| // Indicates recursive watch. |
| bool recursive_; |
| |
| // Backend stream we receive event callbacks from (strong reference). |
| FSEventStreamRef fsevent_stream_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl); |
| }; |
| |
| void FSEventsCallback(ConstFSEventStreamRef stream, |
| void* event_watcher, size_t num_events, |
| void* event_paths, const FSEventStreamEventFlags flags[], |
| const FSEventStreamEventId event_ids[]) { |
| char** paths = reinterpret_cast<char**>(event_paths); |
| DirectoryWatcherImpl* watcher = |
| reinterpret_cast<DirectoryWatcherImpl*> (event_watcher); |
| for (size_t i = 0; i < num_events; i++) { |
| watcher->OnFSEventsCallback(FilePath(paths[i])); |
| } |
| } |
| |
| bool DirectoryWatcherImpl::Watch(const FilePath& path, |
| DirectoryWatcher::Delegate* delegate, |
| MessageLoop* backend_loop, |
| bool recursive) { |
| DCHECK(path_.value().empty()); // Can only watch one path. |
| |
| DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); |
| |
| if (!file_util::PathExists(path)) |
| return false; |
| |
| path_ = path; |
| if (!file_util::AbsolutePath(&path_)) { |
| path_ = FilePath(); // Make sure we're marked as not-in-use. |
| return false; |
| } |
| delegate_ = delegate; |
| recursive_ = recursive; |
| |
| scoped_cftyperef<CFStringRef> cf_path(CFStringCreateWithCString( |
| NULL, path.value().c_str(), kCFStringEncodingMacHFS)); |
| CFStringRef path_for_array = cf_path.get(); |
| scoped_cftyperef<CFArrayRef> watched_paths(CFArrayCreate( |
| NULL, reinterpret_cast<const void**>(&path_for_array), 1, |
| &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, |
| kFSEventStreamEventIdSinceNow, |
| kEventLatencySeconds, |
| kFSEventStreamCreateFlagNone); |
| FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
| kCFRunLoopDefaultMode); |
| FSEventStreamStart(fsevent_stream_); |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| DirectoryWatcher::DirectoryWatcher() { |
| impl_ = new DirectoryWatcherImpl(); |
| } |