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 9 #include "base/bind.h" 10 #include "base/callback.h" 11 #include "base/files/file_path_watcher.h" 12 #include "base/stl_util.h" 13 #include "base/timer/timer.h" 14 #include "chrome/browser/chromeos/drive/file_system/operation_observer.h" 15 #include "chrome/browser/chromeos/drive/logging.h" 16 #include "chrome/browser/google_apis/task_util.h" 17 #include "content/public/browser/browser_thread.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(). |callback| is called back 40 // on the caller (UI) thread when the watch has started. 41 // |on_write_event_callback| is called when a write has happened to the path. 42 void StartWatch(const base::FilePath& path, 43 const StartWatchCallback& callback, 44 const base::Closure& on_write_event_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& callback, 55 const base::Closure& on_write_event_callback); 56 57 void OnWriteEvent(const base::FilePath& path, bool error); 58 59 void InvokeCallback(const base::FilePath& path); 60 61 struct PathWatchInfo { 62 base::Closure on_write_event_callback; 63 base::FilePathWatcher watcher; 64 base::Timer timer; 65 66 explicit PathWatchInfo(const base::Closure& callback) 67 : on_write_event_callback(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 81 FileWriteWatcher::FileWriteWatcherImpl::FileWriteWatcherImpl() 82 : delay_(base::TimeDelta::FromSeconds(kWriteEventDelayInSeconds)), 83 weak_ptr_factory_(this) { 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 85 } 86 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 97 void FileWriteWatcher::FileWriteWatcherImpl::StartWatch( 98 const base::FilePath& path, 99 const StartWatchCallback& callback, 100 const base::Closure& on_write_event_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(callback), 110 google_apis::CreateRelayCallback(on_write_event_callback))); 111 } 112 113 FileWriteWatcher::FileWriteWatcherImpl::~FileWriteWatcherImpl() { 114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 115 116 STLDeleteContainerPairSecondPointers(watchers_.begin(), watchers_.end()); 117 } 118 119 void FileWriteWatcher::FileWriteWatcherImpl::DestroyOnFileThread() { 120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 121 122 delete this; 123 } 124 125 void FileWriteWatcher::FileWriteWatcherImpl::StartWatchOnFileThread( 126 const base::FilePath& path, 127 const StartWatchCallback& callback, 128 const base::Closure& on_write_event_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 // Do nothing if we are already watching the path. 136 callback.Run(true); 137 return; 138 } 139 140 // Start watching |path|. 141 scoped_ptr<PathWatchInfo> info(new PathWatchInfo(on_write_event_callback)); 142 bool ok = info->watcher.Watch( 143 path, 144 false, // recursive 145 base::Bind(&FileWriteWatcherImpl::OnWriteEvent, 146 weak_ptr_factory_.GetWeakPtr())); 147 watchers_[path] = info.release(); 148 callback.Run(ok); 149 } 150 151 void FileWriteWatcher::FileWriteWatcherImpl::OnWriteEvent( 152 const base::FilePath& path, 153 bool error) { 154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 155 util::Log(logging::LOG_INFO, "Detected modification to %s.", 156 path.AsUTF8Unsafe().c_str()); 157 158 if (error) 159 return; 160 161 std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path); 162 DCHECK(it != watchers_.end()); 163 164 // Heuristics for detecting the end of successive write operations. 165 // Delay running on_write_event_callback by |delay_| time, and if OnWriteEvent 166 // is called again in the period, the timer is reset. In other words, we 167 // invoke callback when |delay_| has passed after the last OnWriteEvent(). 168 it->second->timer.Start(FROM_HERE, 169 delay_, 170 base::Bind(&FileWriteWatcherImpl::InvokeCallback, 171 weak_ptr_factory_.GetWeakPtr(), 172 path)); 173 } 174 175 void FileWriteWatcher::FileWriteWatcherImpl::InvokeCallback( 176 const base::FilePath& path) { 177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 178 util::Log(logging::LOG_INFO, "Finished watching modification to %s.", 179 path.AsUTF8Unsafe().c_str()); 180 181 std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path); 182 DCHECK(it != watchers_.end()); 183 184 base::Closure callback = it->second->on_write_event_callback; 185 delete it->second; 186 watchers_.erase(it); 187 188 callback.Run(); 189 } 190 191 FileWriteWatcher::FileWriteWatcher(file_system::OperationObserver* observer) 192 : watcher_impl_(new FileWriteWatcherImpl), 193 operation_observer_(observer), 194 weak_ptr_factory_(this) { 195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 196 } 197 198 FileWriteWatcher::~FileWriteWatcher() { 199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 200 } 201 202 void FileWriteWatcher::StartWatch(const base::FilePath& file_path, 203 const std::string& resource_id, 204 const StartWatchCallback& callback) { 205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 206 207 watcher_impl_->StartWatch(file_path, 208 callback, 209 base::Bind(&FileWriteWatcher::OnWriteEvent, 210 weak_ptr_factory_.GetWeakPtr(), 211 resource_id)); 212 } 213 214 void FileWriteWatcher::DisableDelayForTesting() { 215 watcher_impl_->set_delay(base::TimeDelta()); 216 } 217 218 void FileWriteWatcher::OnWriteEvent(const std::string& resource_id) { 219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 220 221 operation_observer_->OnCacheFileUploadNeededByOperation(resource_id); 222 } 223 224 } // namespace internal 225 } // namespace drive 226