- add sources.
[platform/framework/web/crosswalk.git] / src / base / files / file_path_watcher_win.cc
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/files/file_path_watcher.h"
6
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/files/file_path.h"
10 #include "base/logging.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/message_loop/message_loop_proxy.h"
13 #include "base/time/time.h"
14 #include "base/win/object_watcher.h"
15
16 namespace base {
17
18 namespace {
19
20 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
21                             public base::win::ObjectWatcher::Delegate,
22                             public MessageLoop::DestructionObserver {
23  public:
24   FilePathWatcherImpl()
25       : handle_(INVALID_HANDLE_VALUE),
26         recursive_watch_(false) {}
27
28   // FilePathWatcher::PlatformDelegate overrides.
29   virtual bool Watch(const FilePath& path,
30                      bool recursive,
31                      const FilePathWatcher::Callback& callback) OVERRIDE;
32   virtual void Cancel() OVERRIDE;
33
34   // Deletion of the FilePathWatcher will call Cancel() to dispose of this
35   // object in the right thread. This also observes destruction of the required
36   // cleanup thread, in case it quits before Cancel() is called.
37   virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
38
39   // Callback from MessageLoopForIO.
40   virtual void OnObjectSignaled(HANDLE object);
41
42  private:
43   virtual ~FilePathWatcherImpl() {}
44
45   // Setup a watch handle for directory |dir|. Set |recursive| to true to watch
46   // the directory sub trees. Returns true if no fatal error occurs. |handle|
47   // will receive the handle value if |dir| is watchable, otherwise
48   // INVALID_HANDLE_VALUE.
49   static bool SetupWatchHandle(const FilePath& dir,
50                                bool recursive,
51                                HANDLE* handle) WARN_UNUSED_RESULT;
52
53   // (Re-)Initialize the watch handle.
54   bool UpdateWatch() WARN_UNUSED_RESULT;
55
56   // Destroy the watch handle.
57   void DestroyWatch();
58
59   // Cleans up and stops observing the |message_loop_| thread.
60   void CancelOnMessageLoopThread() OVERRIDE;
61
62   // Callback to notify upon changes.
63   FilePathWatcher::Callback callback_;
64
65   // Path we're supposed to watch (passed to callback).
66   FilePath target_;
67
68   // Handle for FindFirstChangeNotification.
69   HANDLE handle_;
70
71   // ObjectWatcher to watch handle_ for events.
72   base::win::ObjectWatcher watcher_;
73
74   // Set to true to watch the sub trees of the specified directory file path.
75   bool recursive_watch_;
76
77   // Keep track of the last modified time of the file.  We use nulltime
78   // to represent the file not existing.
79   base::Time last_modified_;
80
81   // The time at which we processed the first notification with the
82   // |last_modified_| time stamp.
83   base::Time first_notification_;
84
85   DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
86 };
87
88 bool FilePathWatcherImpl::Watch(const FilePath& path,
89                                 bool recursive,
90                                 const FilePathWatcher::Callback& callback) {
91   DCHECK(target_.value().empty());  // Can only watch one path.
92
93   set_message_loop(base::MessageLoopProxy::current());
94   callback_ = callback;
95   target_ = path;
96   recursive_watch_ = recursive;
97   MessageLoop::current()->AddDestructionObserver(this);
98
99   if (!UpdateWatch())
100     return false;
101
102   watcher_.StartWatching(handle_, this);
103
104   return true;
105 }
106
107 void FilePathWatcherImpl::Cancel() {
108   if (callback_.is_null()) {
109     // Watch was never called, or the |message_loop_| has already quit.
110     set_cancelled();
111     return;
112   }
113
114   // Switch to the file thread if necessary so we can stop |watcher_|.
115   if (!message_loop()->BelongsToCurrentThread()) {
116     message_loop()->PostTask(FROM_HERE,
117                              base::Bind(&FilePathWatcher::CancelWatch,
118                                         make_scoped_refptr(this)));
119   } else {
120     CancelOnMessageLoopThread();
121   }
122 }
123
124 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
125   set_cancelled();
126
127   if (handle_ != INVALID_HANDLE_VALUE)
128     DestroyWatch();
129
130   if (!callback_.is_null()) {
131     MessageLoop::current()->RemoveDestructionObserver(this);
132     callback_.Reset();
133   }
134 }
135
136 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
137   CancelOnMessageLoopThread();
138 }
139
140 void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
141   DCHECK(object == handle_);
142   // Make sure we stay alive through the body of this function.
143   scoped_refptr<FilePathWatcherImpl> keep_alive(this);
144
145   if (!UpdateWatch()) {
146     callback_.Run(target_, true /* error */);
147     return;
148   }
149
150   // Check whether the event applies to |target_| and notify the callback.
151   base::PlatformFileInfo file_info;
152   bool file_exists = file_util::GetFileInfo(target_, &file_info);
153   if (file_exists && (last_modified_.is_null() ||
154       last_modified_ != file_info.last_modified)) {
155     last_modified_ = file_info.last_modified;
156     first_notification_ = base::Time::Now();
157     callback_.Run(target_, false);
158   } else if (file_exists && !first_notification_.is_null()) {
159     // The target's last modification time is equal to what's on record. This
160     // means that either an unrelated event occurred, or the target changed
161     // again (file modification times only have a resolution of 1s). Comparing
162     // file modification times against the wall clock is not reliable to find
163     // out whether the change is recent, since this code might just run too
164     // late. Moreover, there's no guarantee that file modification time and wall
165     // clock times come from the same source.
166     //
167     // Instead, the time at which the first notification carrying the current
168     // |last_notified_| time stamp is recorded. Later notifications that find
169     // the same file modification time only need to be forwarded until wall
170     // clock has advanced one second from the initial notification. After that
171     // interval, client code is guaranteed to having seen the current revision
172     // of the file.
173     if (base::Time::Now() - first_notification_ >
174         base::TimeDelta::FromSeconds(1)) {
175       // Stop further notifications for this |last_modification_| time stamp.
176       first_notification_ = base::Time();
177     }
178     callback_.Run(target_, false);
179   } else if (!file_exists && !last_modified_.is_null()) {
180     last_modified_ = base::Time();
181     callback_.Run(target_, false);
182   }
183
184   // The watch may have been cancelled by the callback.
185   if (handle_ != INVALID_HANDLE_VALUE)
186     watcher_.StartWatching(handle_, this);
187 }
188
189 // static
190 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
191                                            bool recursive,
192                                            HANDLE* handle) {
193   *handle = FindFirstChangeNotification(
194       dir.value().c_str(),
195       recursive,
196       FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
197       FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
198       FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
199   if (*handle != INVALID_HANDLE_VALUE) {
200     // Make sure the handle we got points to an existing directory. It seems
201     // that windows sometimes hands out watches to directories that are
202     // about to go away, but doesn't sent notifications if that happens.
203     if (!DirectoryExists(dir)) {
204       FindCloseChangeNotification(*handle);
205       *handle = INVALID_HANDLE_VALUE;
206     }
207     return true;
208   }
209
210   // If FindFirstChangeNotification failed because the target directory
211   // doesn't exist, access is denied (happens if the file is already gone but
212   // there are still handles open), or the target is not a directory, try the
213   // immediate parent directory instead.
214   DWORD error_code = GetLastError();
215   if (error_code != ERROR_FILE_NOT_FOUND &&
216       error_code != ERROR_PATH_NOT_FOUND &&
217       error_code != ERROR_ACCESS_DENIED &&
218       error_code != ERROR_SHARING_VIOLATION &&
219       error_code != ERROR_DIRECTORY) {
220     using ::operator<<; // Pick the right operator<< below.
221     DPLOG(ERROR) << "FindFirstChangeNotification failed for "
222                  << dir.value();
223     return false;
224   }
225
226   return true;
227 }
228
229 bool FilePathWatcherImpl::UpdateWatch() {
230   if (handle_ != INVALID_HANDLE_VALUE)
231     DestroyWatch();
232
233   base::PlatformFileInfo file_info;
234   if (file_util::GetFileInfo(target_, &file_info)) {
235     last_modified_ = file_info.last_modified;
236     first_notification_ = base::Time::Now();
237   }
238
239   // Start at the target and walk up the directory chain until we succesfully
240   // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
241   // directories stripped from target, in reverse order.
242   std::vector<FilePath> child_dirs;
243   FilePath watched_path(target_);
244   while (true) {
245     if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_))
246       return false;
247
248     // Break if a valid handle is returned. Try the parent directory otherwise.
249     if (handle_ != INVALID_HANDLE_VALUE)
250       break;
251
252     // Abort if we hit the root directory.
253     child_dirs.push_back(watched_path.BaseName());
254     FilePath parent(watched_path.DirName());
255     if (parent == watched_path) {
256       DLOG(ERROR) << "Reached the root directory";
257       return false;
258     }
259     watched_path = parent;
260   }
261
262   // At this point, handle_ is valid. However, the bottom-up search that the
263   // above code performs races against directory creation. So try to walk back
264   // down and see whether any children appeared in the mean time.
265   while (!child_dirs.empty()) {
266     watched_path = watched_path.Append(child_dirs.back());
267     child_dirs.pop_back();
268     HANDLE temp_handle = INVALID_HANDLE_VALUE;
269     if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle))
270       return false;
271     if (temp_handle == INVALID_HANDLE_VALUE)
272       break;
273     FindCloseChangeNotification(handle_);
274     handle_ = temp_handle;
275   }
276
277   return true;
278 }
279
280 void FilePathWatcherImpl::DestroyWatch() {
281   watcher_.StopWatching();
282   FindCloseChangeNotification(handle_);
283   handle_ = INVALID_HANDLE_VALUE;
284 }
285
286 }  // namespace
287
288 FilePathWatcher::FilePathWatcher() {
289   impl_ = new FilePathWatcherImpl();
290 }
291
292 }  // namespace base