blob: 638654ce8e73b26b110dacb2c45e42f5d646d38e [file] [log] [blame]
// 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_watcher.h"
#include <CoreServices/CoreServices.h>
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/scoped_cftyperef.h"
#include "base/time.h"
namespace {
const CFAbsoluteTime kEventLatencySeconds = 0.3;
class FileWatcherImpl : public FileWatcher::PlatformDelegate {
public:
FileWatcherImpl() {}
~FileWatcherImpl() {
if (!path_.value().empty()) {
FSEventStreamStop(fsevent_stream_);
FSEventStreamInvalidate(fsevent_stream_);
FSEventStreamRelease(fsevent_stream_);
}
}
virtual bool Watch(const FilePath& path, FileWatcher::Delegate* delegate) {
FilePath parent_dir = path.DirName();
if (!file_util::AbsolutePath(&parent_dir))
return false;
// Jump back to the UI thread because FSEventStreamScheduleWithRunLoop
// requires a UI thread.
if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) {
ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
NewRunnableMethod(this, &FileWatcherImpl::WatchImpl, path, delegate));
} else {
LOG(INFO) << "Adding FileWatcher watch.";
// During unittests, there is only one thread and it is both the UI
// thread and the file thread.
WatchImpl(path, delegate);
}
return true;
}
bool WatchImpl(const FilePath& path, FileWatcher::Delegate* delegate);
void OnFSEventsCallback(const FilePath& event_path) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
DCHECK(!path_.value().empty());
FilePath absolute_event_path = event_path;
if (!file_util::AbsolutePath(&absolute_event_path))
return;
file_util::FileInfo file_info;
bool file_exists = file_util::GetFileInfo(path_, &file_info);
if (file_exists && (last_modified_.is_null() ||
last_modified_ != file_info.last_modified)) {
last_modified_ = file_info.last_modified;
delegate_->OnFileChanged(path_);
} else if (file_exists && (base::Time::Now() - last_modified_ <
base::TimeDelta::FromSeconds(2))) {
// Since we only have a resolution of 1s, if we get a callback within
// 2s of the file having changed, go ahead and notify our observer. This
// might be from a different file change, but it's better to notify too
// much rather than miss a notification.
delegate_->OnFileChanged(path_);
} else if (!file_exists && !last_modified_.is_null()) {
last_modified_ = base::Time();
delegate_->OnFileChanged(path_);
}
}
private:
// Delegate to notify upon changes.
FileWatcher::Delegate* delegate_;
// Path we're watching (passed to delegate).
FilePath path_;
// Backend stream we receive event callbacks from (strong reference).
FSEventStreamRef fsevent_stream_;
// Keep track of the last modified time of the file. We use nulltime
// to represent the file not existing.
base::Time last_modified_;
DISALLOW_COPY_AND_ASSIGN(FileWatcherImpl);
};
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);
FileWatcherImpl* watcher =
reinterpret_cast<FileWatcherImpl*>(event_watcher);
for (size_t i = 0; i < num_events; i++) {
if (!ChromeThread::CurrentlyOn(ChromeThread::FILE)) {
ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
NewRunnableMethod(watcher, &FileWatcherImpl::OnFSEventsCallback,
FilePath(paths[i])));
} else {
LOG(INFO) << "FileWatcher event callback for " << paths[i];
// During unittests, there is only one thread and it is both the UI
// thread and the file thread.
watcher->OnFSEventsCallback(FilePath(paths[i]));
}
}
}
bool FileWatcherImpl::WatchImpl(const FilePath& path,
FileWatcher::Delegate* delegate) {
DCHECK(path_.value().empty()); // Can only watch one path.
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
file_util::FileInfo file_info;
if (file_util::GetFileInfo(path, &file_info))
last_modified_ = file_info.last_modified;
path_ = path;
delegate_ = delegate;
scoped_cftyperef<CFStringRef> cf_path(CFStringCreateWithCString(
NULL, path.DirName().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
FileWatcher::FileWatcher() {
impl_ = new FileWatcherImpl();
}