file_write_watcher.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright 2013 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 "chrome/browser/chromeos/drive/file_write_watcher.h"
6
7#include <map>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/callback.h"
12#include "base/files/file_path_watcher.h"
13#include "base/stl_util.h"
14#include "base/timer/timer.h"
15#include "chrome/browser/chromeos/drive/logging.h"
16#include "content/public/browser/browser_thread.h"
17#include "google_apis/drive/task_util.h"
18
19using content::BrowserThread;
20
21namespace drive {
22namespace internal {
23
24namespace {
25const int64 kWriteEventDelayInSeconds = 5;
26}  // namespace
27
28// base::FileWatcher needs to live in a thread that is allowed to do File IO
29// and has a TYPE_IO message loop: that is, FILE thread. This class bridges the
30// UI thread and FILE thread, and does all the main tasks in the FILE thread.
31class FileWriteWatcher::FileWriteWatcherImpl {
32 public:
33  FileWriteWatcherImpl();
34
35  // Forwards the call to DestoryOnFileThread(). This method must be used to
36  // destruct the instance.
37  void Destroy();
38
39  // Forwards the call to StartWatchOnFileThread(). |on_start_callback| is
40  // called back on the caller (UI) thread when the watch has started.
41  // |on_write_callback| is called when a write has happened to the path.
42  void StartWatch(const base::FilePath& path,
43                  const StartWatchCallback& on_start_callback,
44                  const base::Closure& on_write_callback);
45
46  void set_delay(base::TimeDelta delay) { delay_ = delay; }
47
48 private:
49  ~FileWriteWatcherImpl();
50
51  void DestroyOnFileThread();
52
53  void StartWatchOnFileThread(const base::FilePath& path,
54                              const StartWatchCallback& on_start_callback,
55                              const base::Closure& on_write_callback);
56
57  void OnWriteEvent(const base::FilePath& path, bool error);
58
59  void InvokeCallback(const base::FilePath& path);
60
61  struct PathWatchInfo {
62    std::vector<base::Closure> on_write_callbacks;
63    base::FilePathWatcher watcher;
64    base::Timer timer;
65
66    explicit PathWatchInfo(const base::Closure& on_write_callback)
67        : on_write_callbacks(1, on_write_callback),
68          timer(false /* retain_closure_on_reset */, false /* is_repeating */) {
69    }
70  };
71
72  base::TimeDelta delay_;
73  std::map<base::FilePath, PathWatchInfo*> watchers_;
74
75  // Note: This should remain the last member so it'll be destroyed and
76  // invalidate its weak pointers before any other members are destroyed.
77  base::WeakPtrFactory<FileWriteWatcherImpl> weak_ptr_factory_;
78  DISALLOW_COPY_AND_ASSIGN(FileWriteWatcherImpl);
79};
80
81FileWriteWatcher::FileWriteWatcherImpl::FileWriteWatcherImpl()
82    : delay_(base::TimeDelta::FromSeconds(kWriteEventDelayInSeconds)),
83      weak_ptr_factory_(this) {
84  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85}
86
87void FileWriteWatcher::FileWriteWatcherImpl::Destroy() {
88  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
89
90  // Just forwarding the call to FILE thread.
91  BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)->PostTask(
92      FROM_HERE,
93      base::Bind(&FileWriteWatcherImpl::DestroyOnFileThread,
94                 base::Unretained(this)));
95}
96
97void FileWriteWatcher::FileWriteWatcherImpl::StartWatch(
98    const base::FilePath& path,
99    const StartWatchCallback& on_start_callback,
100    const base::Closure& on_write_callback) {
101  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
102
103  // Forwarding the call to FILE thread and relaying the |callback|.
104  BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)->PostTask(
105      FROM_HERE,
106      base::Bind(&FileWriteWatcherImpl::StartWatchOnFileThread,
107                 base::Unretained(this),
108                 path,
109                 google_apis::CreateRelayCallback(on_start_callback),
110                 google_apis::CreateRelayCallback(on_write_callback)));
111}
112
113FileWriteWatcher::FileWriteWatcherImpl::~FileWriteWatcherImpl() {
114  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
115
116  STLDeleteContainerPairSecondPointers(watchers_.begin(), watchers_.end());
117}
118
119void FileWriteWatcher::FileWriteWatcherImpl::DestroyOnFileThread() {
120  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
121
122  delete this;
123}
124
125void FileWriteWatcher::FileWriteWatcherImpl::StartWatchOnFileThread(
126    const base::FilePath& path,
127    const StartWatchCallback& on_start_callback,
128    const base::Closure& on_write_callback) {
129  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
130  util::Log(logging::LOG_INFO, "Started watching modification to %s.",
131            path.AsUTF8Unsafe().c_str());
132
133  std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path);
134  if (it != watchers_.end()) {
135    // We are already watching the path.
136    on_start_callback.Run(true);
137    it->second->on_write_callbacks.push_back(on_write_callback);
138    return;
139  }
140
141  // Start watching |path|.
142  scoped_ptr<PathWatchInfo> info(new PathWatchInfo(on_write_callback));
143  bool ok = info->watcher.Watch(
144      path,
145      false,  // recursive
146      base::Bind(&FileWriteWatcherImpl::OnWriteEvent,
147                 weak_ptr_factory_.GetWeakPtr()));
148  watchers_[path] = info.release();
149  on_start_callback.Run(ok);
150}
151
152void FileWriteWatcher::FileWriteWatcherImpl::OnWriteEvent(
153    const base::FilePath& path,
154    bool error) {
155  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
156  util::Log(logging::LOG_INFO, "Detected modification to %s.",
157            path.AsUTF8Unsafe().c_str());
158
159  if (error)
160    return;
161
162  std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path);
163  DCHECK(it != watchers_.end());
164
165  // Heuristics for detecting the end of successive write operations.
166  // Delay running on_write_event_callback by |delay_| time, and if OnWriteEvent
167  // is called again in the period, the timer is reset. In other words, we
168  // invoke callback when |delay_| has passed after the last OnWriteEvent().
169  it->second->timer.Start(FROM_HERE,
170                          delay_,
171                          base::Bind(&FileWriteWatcherImpl::InvokeCallback,
172                                     weak_ptr_factory_.GetWeakPtr(),
173                                     path));
174}
175
176void FileWriteWatcher::FileWriteWatcherImpl::InvokeCallback(
177    const base::FilePath& path) {
178  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
179  util::Log(logging::LOG_INFO, "Finished watching modification to %s.",
180            path.AsUTF8Unsafe().c_str());
181
182  std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path);
183  DCHECK(it != watchers_.end());
184
185  std::vector<base::Closure> callbacks;
186  callbacks.swap(it->second->on_write_callbacks);
187  delete it->second;
188  watchers_.erase(it);
189
190  for (size_t i = 0; i < callbacks.size(); ++i)
191    callbacks[i].Run();
192}
193
194FileWriteWatcher::FileWriteWatcher()
195    : watcher_impl_(new FileWriteWatcherImpl) {
196  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
197}
198
199FileWriteWatcher::~FileWriteWatcher() {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201}
202
203void FileWriteWatcher::StartWatch(const base::FilePath& file_path,
204                                  const StartWatchCallback& on_start_callback,
205                                  const base::Closure& on_write_callback) {
206  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
207  watcher_impl_->StartWatch(file_path, on_start_callback, on_write_callback);
208}
209
210void FileWriteWatcher::DisableDelayForTesting() {
211  watcher_impl_->set_delay(base::TimeDelta());
212}
213
214}  // namespace internal
215}  // namespace drive
216