summaryrefslogtreecommitdiffstats
path: root/base/file_watcher_mac.cc
blob: 2d85133fdcc62596667e7e3fabbfb4cee93625d0 (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
// 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/file_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"
#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,
                     MessageLoop* backend_loop);

  void OnFSEventsCallback(const FilePath& event_path) {
    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++)
    watcher->OnFSEventsCallback(FilePath(paths[i]));
}

bool FileWatcherImpl::Watch(const FilePath& path,
                            FileWatcher::Delegate* delegate,
                            MessageLoop* backend_loop) {
  DCHECK(path_.value().empty());  // Can only watch one path.
  DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);

  FilePath parent_dir = path.DirName();
  if (!file_util::AbsolutePath(&parent_dir))
    return false;

  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();
}