• 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 "chrome/browser/chromeos/drive/logging.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "google_apis/drive/task_util.h"
18 
19 using content::BrowserThread;
20 
21 namespace drive {
22 namespace internal {
23 
24 namespace {
25 const 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.
31 class 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 
set_delay(base::TimeDelta delay)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 
PathWatchInfodrive::internal::FileWriteWatcher::FileWriteWatcherImpl::PathWatchInfo66     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 
FileWriteWatcherImpl()81 FileWriteWatcher::FileWriteWatcherImpl::FileWriteWatcherImpl()
82     : delay_(base::TimeDelta::FromSeconds(kWriteEventDelayInSeconds)),
83       weak_ptr_factory_(this) {
84   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85 }
86 
Destroy()87 void 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 
StartWatch(const base::FilePath & path,const StartWatchCallback & on_start_callback,const base::Closure & on_write_callback)97 void 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 
~FileWriteWatcherImpl()113 FileWriteWatcher::FileWriteWatcherImpl::~FileWriteWatcherImpl() {
114   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
115 
116   STLDeleteContainerPairSecondPointers(watchers_.begin(), watchers_.end());
117 }
118 
DestroyOnFileThread()119 void FileWriteWatcher::FileWriteWatcherImpl::DestroyOnFileThread() {
120   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
121 
122   delete this;
123 }
124 
StartWatchOnFileThread(const base::FilePath & path,const StartWatchCallback & on_start_callback,const base::Closure & on_write_callback)125 void 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 
OnWriteEvent(const base::FilePath & path,bool error)152 void 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 
InvokeCallback(const base::FilePath & path)176 void 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 
FileWriteWatcher()194 FileWriteWatcher::FileWriteWatcher()
195     : watcher_impl_(new FileWriteWatcherImpl) {
196   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
197 }
198 
~FileWriteWatcher()199 FileWriteWatcher::~FileWriteWatcher() {
200   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201 }
202 
StartWatch(const base::FilePath & file_path,const StartWatchCallback & on_start_callback,const base::Closure & on_write_callback)203 void 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 
DisableDelayForTesting()210 void FileWriteWatcher::DisableDelayForTesting() {
211   watcher_impl_->set_delay(base::TimeDelta());
212 }
213 
214 }  // namespace internal
215 }  // namespace drive
216