diff options
author | mnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-17 14:26:03 +0000 |
---|---|---|
committer | mnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-17 14:26:03 +0000 |
commit | 392d74d532301d0db584a1c320d32f4899d58d8f (patch) | |
tree | a3e31864beb5433f92494887f25db45ca9518686 /chrome/browser/file_path_watcher_mac.cc | |
parent | a66c64480aee2b73328961fb5ffdd074098a74fb (diff) | |
download | chromium_src-392d74d532301d0db584a1c320d32f4899d58d8f.zip chromium_src-392d74d532301d0db584a1c320d32f4899d58d8f.tar.gz chromium_src-392d74d532301d0db584a1c320d32f4899d58d8f.tar.bz2 |
Add support for watching directories to FileWatcher.
BUG=none
TEST=Unit tests in file_watcher_unittest.cc.
Review URL: http://codereview.chromium.org/3149004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56341 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/file_path_watcher_mac.cc')
-rw-r--r-- | chrome/browser/file_path_watcher_mac.cc | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/chrome/browser/file_path_watcher_mac.cc b/chrome/browser/file_path_watcher_mac.cc new file mode 100644 index 0000000..91d9c23 --- /dev/null +++ b/chrome/browser/file_path_watcher_mac.cc @@ -0,0 +1,232 @@ +// 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 "chrome/browser/file_path_watcher.h" + +#include <CoreServices/CoreServices.h> +#include <set> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_cftyperef.h" +#include "base/singleton.h" +#include "base/time.h" + +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: + FilePathWatcherImpl(); + virtual ~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); + + // FilePathWatcher::PlatformDelegate overrides. + virtual bool Watch(const FilePath& path, FilePathWatcher::Delegate* delegate); + virtual void Cancel(); + + private: + // Destroy the event stream. + void DestroyEventStream(); + + // 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_; + + // Used to detect early cancellation. + bool canceled_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); +}; + +// 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[]) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + FilePathWatcherImpl* watcher = + reinterpret_cast<FilePathWatcherImpl*>(event_watcher); + bool root_changed = false; + 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]); + } + + // 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. + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod(watcher, &FilePathWatcherImpl::UpdateEventStream, + root_change_at)); + } + + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(watcher, &FilePathWatcherImpl::OnFilePathChanged)); +} + +// FilePathWatcherImpl implementation: + +FilePathWatcherImpl::FilePathWatcherImpl() + : fsevent_stream_(NULL), + canceled_(false) { +} + +void FilePathWatcherImpl::OnFilePathChanged() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + DCHECK(!target_.empty()); + + file_util::FileInfo 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(); + } + 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) { + DCHECK(target_.value().empty()); + + target_ = path; + delegate_ = delegate; + + FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); + + file_util::FileInfo file_info; + if (file_util::GetFileInfo(target_, &file_info)) { + last_modified_ = file_info.last_modified; + first_notification_ = base::Time::Now(); + } + + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, + start_event)); + + return true; +} + +void FilePathWatcherImpl::Cancel() { + // Switch to the UI thread if necessary, so we can tear down the event stream. + if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); + return; + } + + canceled_ = true; + if (fsevent_stream_) + DestroyEventStream(); +} + +void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + // 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 (canceled_) + return; + + if (fsevent_stream_) + DestroyEventStream(); + + scoped_cftyperef<CFStringRef> cf_path(CFStringCreateWithCString( + NULL, target_.value().c_str(), kCFStringEncodingMacHFS)); + scoped_cftyperef<CFStringRef> cf_dir_path(CFStringCreateWithCString( + NULL, target_.DirName().value().c_str(), kCFStringEncodingMacHFS)); + CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() }; + scoped_cftyperef<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); + FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + if (!FSEventStreamStart(fsevent_stream_)) { + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(delegate_.get(), + &FilePathWatcher::Delegate::OnError)); + } +} + +void FilePathWatcherImpl::DestroyEventStream() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + FSEventStreamStop(fsevent_stream_); + FSEventStreamInvalidate(fsevent_stream_); + FSEventStreamRelease(fsevent_stream_); + fsevent_stream_ = NULL; +} + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} |