• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 using content::BrowserThread;
19 
20 namespace drive {
21 namespace internal {
22 
23 namespace {
24 const 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.
30 class 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 
set_delay(base::TimeDelta delay)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 
PathWatchInfodrive::internal::FileWriteWatcher::FileWriteWatcherImpl::PathWatchInfo65     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 
FileWriteWatcherImpl()80 FileWriteWatcher::FileWriteWatcherImpl::FileWriteWatcherImpl()
81     : delay_(base::TimeDelta::FromSeconds(kWriteEventDelayInSeconds)),
82       weak_ptr_factory_(this) {
83   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
84 }
85 
Destroy()86 void 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 
StartWatch(const base::FilePath & path,const StartWatchCallback & on_start_callback,const base::Closure & on_write_callback)96 void 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 
~FileWriteWatcherImpl()112 FileWriteWatcher::FileWriteWatcherImpl::~FileWriteWatcherImpl() {
113   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
114 
115   STLDeleteContainerPairSecondPointers(watchers_.begin(), watchers_.end());
116 }
117 
DestroyOnFileThread()118 void FileWriteWatcher::FileWriteWatcherImpl::DestroyOnFileThread() {
119   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
120 
121   delete this;
122 }
123 
StartWatchOnFileThread(const base::FilePath & path,const StartWatchCallback & on_start_callback,const base::Closure & on_write_callback)124 void 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 
OnWriteEvent(const base::FilePath & path,bool error)149 void 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 
InvokeCallback(const base::FilePath & path)171 void 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 
FileWriteWatcher()187 FileWriteWatcher::FileWriteWatcher()
188     : watcher_impl_(new FileWriteWatcherImpl) {
189   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 }
191 
~FileWriteWatcher()192 FileWriteWatcher::~FileWriteWatcher() {
193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194 }
195 
StartWatch(const base::FilePath & file_path,const StartWatchCallback & on_start_callback,const base::Closure & on_write_callback)196 void 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 
DisableDelayForTesting()203 void FileWriteWatcher::DisableDelayForTesting() {
204   watcher_impl_->set_delay(base::TimeDelta());
205 }
206 
207 }  // namespace internal
208 }  // namespace drive
209