| Index: chrome/browser/devtools/devtools_file_watcher.cc
|
| diff --git a/chrome/browser/devtools/devtools_file_watcher.cc b/chrome/browser/devtools/devtools_file_watcher.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8d9d2d0d8529b4ef0aff9f4e61681adc33cd7aea
|
| --- /dev/null
|
| +++ b/chrome/browser/devtools/devtools_file_watcher.cc
|
| @@ -0,0 +1,178 @@
|
| +// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/devtools/devtools_file_watcher.h"
|
| +
|
| +#include <map>
|
| +#include <set>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/files/file_enumerator.h"
|
| +#include "base/files/file_path.h"
|
| +#include "base/files/file_path_watcher.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +
|
| +using content::BrowserThread;
|
| +
|
| +static int kDefaultThrottleTimeout = 200;
|
| +
|
| +// DevToolsFileWatcher::SharedFileWatcher --------------------------------------
|
| +
|
| +class DevToolsFileWatcher::SharedFileWatcher :
|
| + public base::RefCounted<SharedFileWatcher> {
|
| + public:
|
| + SharedFileWatcher();
|
| +
|
| + void AddListener(DevToolsFileWatcher* watcher);
|
| + void RemoveListener(DevToolsFileWatcher* watcher);
|
| + void AddWatch(const base::FilePath& path);
|
| + void RemoveWatch(const base::FilePath& path);
|
| +
|
| + private:
|
| + friend class base::RefCounted<
|
| + DevToolsFileWatcher::SharedFileWatcher>;
|
| + ~SharedFileWatcher();
|
| +
|
| + void DirectoryChanged(const base::FilePath& path, bool error);
|
| + void DispatchNotifications();
|
| +
|
| + std::vector<DevToolsFileWatcher*> listeners_;
|
| + std::map<base::FilePath, base::FilePathWatcher> watchers_;
|
| + using FilePathTimesMap = std::map<base::FilePath, base::Time>;
|
| + FilePathTimesMap file_path_times_;
|
| + std::set<base::FilePath> pending_paths_;
|
| + base::Time last_event_time_;
|
| + base::TimeDelta last_dispatch_cost_;
|
| +};
|
| +
|
| +DevToolsFileWatcher::SharedFileWatcher::SharedFileWatcher()
|
| + : last_dispatch_cost_(
|
| + base::TimeDelta::FromMilliseconds(kDefaultThrottleTimeout)) {
|
| + DevToolsFileWatcher::s_shared_watcher_ = this;
|
| +}
|
| +
|
| +DevToolsFileWatcher::SharedFileWatcher::~SharedFileWatcher() {
|
| + DevToolsFileWatcher::s_shared_watcher_ = nullptr;
|
| +}
|
| +
|
| +void DevToolsFileWatcher::SharedFileWatcher::AddListener(
|
| + DevToolsFileWatcher* watcher) {
|
| + listeners_.push_back(watcher);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::SharedFileWatcher::RemoveListener(
|
| + DevToolsFileWatcher* watcher) {
|
| + auto it = std::find(listeners_.begin(), listeners_.end(), watcher);
|
| + listeners_.erase(it);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::SharedFileWatcher::AddWatch(
|
| + const base::FilePath& path) {
|
| + if (watchers_.find(path) != watchers_.end())
|
| + return;
|
| + if (!base::FilePathWatcher::RecursiveWatchAvailable())
|
| + return;
|
| + bool success = watchers_[path].Watch(
|
| + path, true,
|
| + base::Bind(&SharedFileWatcher::DirectoryChanged, base::Unretained(this)));
|
| + if (!success)
|
| + return;
|
| +
|
| + base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
|
| + base::FilePath file_path = enumerator.Next();
|
| + while (!file_path.empty()) {
|
| + base::FileEnumerator::FileInfo file_info = enumerator.GetInfo();
|
| + file_path_times_[file_path] = file_info.GetLastModifiedTime();
|
| + file_path = enumerator.Next();
|
| + }
|
| +}
|
| +
|
| +void DevToolsFileWatcher::SharedFileWatcher::RemoveWatch(
|
| + const base::FilePath& path) {
|
| + watchers_.erase(path);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::SharedFileWatcher::DirectoryChanged(
|
| + const base::FilePath& path,
|
| + bool error) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| + pending_paths_.insert(path);
|
| + if (pending_paths_.size() > 1)
|
| + return; // PostDelayedTask is already pending.
|
| +
|
| + base::Time now = base::Time::Now();
|
| + if (now - last_event_time_ > last_dispatch_cost_) {
|
| + DispatchNotifications();
|
| + return; // Immediately dispatch single-file changes.
|
| + }
|
| +
|
| + // Throttle batches.
|
| + last_event_time_ = now;
|
| + BrowserThread::PostDelayedTask(
|
| + BrowserThread::FILE, FROM_HERE,
|
| + base::Bind(&DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications,
|
| + this), last_dispatch_cost_);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications() {
|
| + base::Time start = base::Time::Now();
|
| + std::vector<std::string> changed_paths;
|
| + for (auto path : pending_paths_) {
|
| + base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
|
| + base::FilePath file_path = enumerator.Next();
|
| + while (!file_path.empty()) {
|
| + base::FileEnumerator::FileInfo file_info = enumerator.GetInfo();
|
| + base::Time new_time = file_info.GetLastModifiedTime();
|
| + if (file_path_times_[file_path] != new_time) {
|
| + file_path_times_[file_path] = new_time;
|
| + changed_paths.push_back(file_path.AsUTF8Unsafe());
|
| + }
|
| + file_path = enumerator.Next();
|
| + }
|
| + }
|
| + pending_paths_.clear();
|
| +
|
| + for (auto watcher : listeners_) {
|
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
|
| + base::Bind(watcher->callback_, changed_paths));
|
| + }
|
| + last_dispatch_cost_ = (base::Time::Now() - start) * 2;
|
| +}
|
| +
|
| +// static
|
| +DevToolsFileWatcher::SharedFileWatcher*
|
| +DevToolsFileWatcher::s_shared_watcher_ = nullptr;
|
| +
|
| +// DevToolsFileWatcher ---------------------------------------------------------
|
| +
|
| +DevToolsFileWatcher::DevToolsFileWatcher(const WatchCallback& callback)
|
| + : callback_(callback) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
|
| + base::Bind(&DevToolsFileWatcher::InitSharedWatcher,
|
| + base::Unretained(this)));
|
| +}
|
| +
|
| +DevToolsFileWatcher::~DevToolsFileWatcher() {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| + shared_watcher_->RemoveListener(this);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::InitSharedWatcher() {
|
| + if (!DevToolsFileWatcher::s_shared_watcher_)
|
| + new SharedFileWatcher();
|
| + shared_watcher_ = DevToolsFileWatcher::s_shared_watcher_;
|
| + shared_watcher_->AddListener(this);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::AddWatch(const base::FilePath& path) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| + shared_watcher_->AddWatch(path);
|
| +}
|
| +
|
| +void DevToolsFileWatcher::RemoveWatch(const base::FilePath& path) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| + shared_watcher_->RemoveWatch(path);
|
| +}
|
|
|