blob: 07560855027653a9f772e17563c26dd12371d245 [file] [log] [blame]
// Copyright (c) 2012 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 "remoting/host/config_file_watcher.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path_watcher.h"
#include "base/files/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/timer/timer.h"
namespace remoting {
// The name of the command-line switch used to specify the host configuration
// file to use.
const char kHostConfigSwitchName[] = "host-config";
const base::FilePath::CharType kDefaultHostConfigFile[] =
FILE_PATH_LITERAL("host.json");
#if defined(OS_WIN)
// Maximum number of times to try reading the configuration file before
// reporting an error.
const int kMaxRetries = 3;
#endif // defined(OS_WIN)
class ConfigFileWatcherImpl
: public base::RefCountedThreadSafe<ConfigFileWatcherImpl> {
public:
// Creates a configuration file watcher that lives on the |io_task_runner|
// thread but posts config file updates on on |main_task_runner|.
ConfigFileWatcherImpl(
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
const base::FilePath& config_path);
// Notify |delegate| of config changes.
void Watch(ConfigWatcher::Delegate* delegate);
// Stops watching the configuration file.
void StopWatching();
private:
friend class base::RefCountedThreadSafe<ConfigFileWatcherImpl>;
virtual ~ConfigFileWatcherImpl();
void FinishStopping();
void WatchOnIoThread();
// Called every time the host configuration file is updated.
void OnConfigUpdated(const base::FilePath& path, bool error);
// Called to notify the delegate of updates/errors in the main thread.
void NotifyUpdate(const std::string& config);
void NotifyError();
// Reads the configuration file and passes it to the delegate.
void ReloadConfig();
std::string config_;
base::FilePath config_path_;
scoped_ptr<base::DelayTimer<ConfigFileWatcherImpl> > config_updated_timer_;
// Number of times an attempt to read the configuration file failed.
int retries_;
// Monitors the host configuration file.
scoped_ptr<base::FilePathWatcher> config_watcher_;
ConfigWatcher::Delegate* delegate_;
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
base::WeakPtrFactory<ConfigFileWatcherImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ConfigFileWatcherImpl);
};
ConfigFileWatcher::ConfigFileWatcher(
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
const base::FilePath& config_path)
: impl_(new ConfigFileWatcherImpl(main_task_runner,
io_task_runner, config_path)) {
}
ConfigFileWatcher::~ConfigFileWatcher() {
impl_->StopWatching();
impl_ = nullptr;
}
void ConfigFileWatcher::Watch(ConfigWatcher::Delegate* delegate) {
impl_->Watch(delegate);
}
ConfigFileWatcherImpl::ConfigFileWatcherImpl(
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
const base::FilePath& config_path)
: config_path_(config_path),
retries_(0),
delegate_(nullptr),
main_task_runner_(main_task_runner),
io_task_runner_(io_task_runner),
weak_factory_(this) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
}
void ConfigFileWatcherImpl::Watch(ConfigWatcher::Delegate* delegate) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
DCHECK(!delegate_);
delegate_ = delegate;
io_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ConfigFileWatcherImpl::WatchOnIoThread, this));
}
void ConfigFileWatcherImpl::WatchOnIoThread() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
DCHECK(!config_updated_timer_);
DCHECK(!config_watcher_);
// Create the timer that will be used for delayed-reading the configuration
// file.
config_updated_timer_.reset(new base::DelayTimer<ConfigFileWatcherImpl>(
FROM_HERE, base::TimeDelta::FromSeconds(2), this,
&ConfigFileWatcherImpl::ReloadConfig));
// Start watching the configuration file.
config_watcher_.reset(new base::FilePathWatcher());
if (!config_watcher_->Watch(
config_path_, false,
base::Bind(&ConfigFileWatcherImpl::OnConfigUpdated, this))) {
PLOG(ERROR) << "Couldn't watch file '" << config_path_.value() << "'";
main_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ConfigFileWatcherImpl::NotifyError,
weak_factory_.GetWeakPtr()));
return;
}
// Force reloading of the configuration file at least once.
ReloadConfig();
}
void ConfigFileWatcherImpl::StopWatching() {
DCHECK(main_task_runner_->BelongsToCurrentThread());
weak_factory_.InvalidateWeakPtrs();
io_task_runner_->PostTask(
FROM_HERE, base::Bind(&ConfigFileWatcherImpl::FinishStopping, this));
}
ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
DCHECK(!config_updated_timer_);
DCHECK(!config_watcher_);
}
void ConfigFileWatcherImpl::FinishStopping() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
config_updated_timer_.reset();
config_watcher_.reset();
}
void ConfigFileWatcherImpl::OnConfigUpdated(const base::FilePath& path,
bool error) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
// Call ReloadConfig() after a short delay, so that we will not try to read
// the updated configuration file before it has been completely written.
// If the writer moves the new configuration file into place atomically,
// this delay may not be necessary.
if (!error && config_path_ == path)
config_updated_timer_->Reset();
}
void ConfigFileWatcherImpl::NotifyError() {
DCHECK(main_task_runner_->BelongsToCurrentThread());
delegate_->OnConfigWatcherError();
}
void ConfigFileWatcherImpl::NotifyUpdate(const std::string& config) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
delegate_->OnConfigUpdated(config_);
}
void ConfigFileWatcherImpl::ReloadConfig() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
std::string config;
if (!base::ReadFileToString(config_path_, &config)) {
#if defined(OS_WIN)
// EACCESS may indicate a locking or sharing violation. Retry a few times
// before reporting an error.
if (errno == EACCES && retries_ < kMaxRetries) {
PLOG(WARNING) << "Failed to read '" << config_path_.value() << "'";
retries_ += 1;
config_updated_timer_->Reset();
return;
}
#endif // defined(OS_WIN)
PLOG(ERROR) << "Failed to read '" << config_path_.value() << "'";
main_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ConfigFileWatcherImpl::NotifyError,
weak_factory_.GetWeakPtr()));
return;
}
retries_ = 0;
// Post an updated configuration only if it has actually changed.
if (config_ != config) {
config_ = config;
main_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ConfigFileWatcherImpl::NotifyUpdate,
weak_factory_.GetWeakPtr(), config_));
}
}
} // namespace remoting