summaryrefslogtreecommitdiffstats
path: root/chrome/browser/file_path_watcher_mac.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/file_path_watcher_mac.cc')
-rw-r--r--chrome/browser/file_path_watcher_mac.cc232
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..b95992c
--- /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());
+
+ 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();
+ }
+ 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();
+
+ base::PlatformFileInfo 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();
+}