blob: e5af5a5f198df8f249f0c90fead55bb3a152d088 [file] [log] [blame]
// 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 <algorithm>
#include <map>
#include <memory>
#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 kFirstThrottleTimeout = 10;
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, std::unique_ptr<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;
watchers_[path].reset(new base::FilePathWatcher());
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();
// Quickly dispatch first chunk.
base::TimeDelta shedule_for =
now - last_event_time_ > last_dispatch_cost_ ?
base::TimeDelta::FromMilliseconds(kFirstThrottleTimeout) :
last_dispatch_cost_ * 2;
BrowserThread::PostDelayedTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications,
this), shedule_for);
last_event_time_ = now;
}
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;
}
// 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);
}