Home | History | Annotate | Download | only in drive
      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 
     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 
     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& 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 
    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& 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 
    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 
    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 
    194 FileWriteWatcher::FileWriteWatcher()
    195     : watcher_impl_(new FileWriteWatcherImpl) {
    196   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    197 }
    198 
    199 FileWriteWatcher::~FileWriteWatcher() {
    200   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    201 }
    202 
    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 
    210 void FileWriteWatcher::DisableDelayForTesting() {
    211   watcher_impl_->set_delay(base::TimeDelta());
    212 }
    213 
    214 }  // namespace internal
    215 }  // namespace drive
    216