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