summaryrefslogtreecommitdiffstats
path: root/chrome/browser/file_watcher_mac.cc
blob: 638654ce8e73b26b110dacb2c45e42f5d646d38e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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();
}