config_file_watcher.cc revision 58537e28ecd584eab876aee8be7156509866d23a
1// Copyright (c) 2012 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 "remoting/host/config_file_watcher.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/file_util.h"
12#include "base/files/file_path_watcher.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/memory/weak_ptr.h"
15#include "base/single_thread_task_runner.h"
16#include "base/timer/timer.h"
17
18namespace remoting {
19
20// The name of the command-line switch used to specify the host configuration
21// file to use.
22const char kHostConfigSwitchName[] = "host-config";
23
24const base::FilePath::CharType kDefaultHostConfigFile[] =
25    FILE_PATH_LITERAL("host.json");
26
27// Maximum number of times to try reading the configuration file before
28// reporting an error.
29const int kMaxRetries = 3;
30
31class ConfigFileWatcherImpl
32    : public base::RefCountedThreadSafe<ConfigFileWatcherImpl> {
33 public:
34  // Creates a configuration file watcher that lives on the |io_task_runner|
35  // thread but posts config file updates on on |main_task_runner|.
36  ConfigFileWatcherImpl(
37      scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
38      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
39      ConfigFileWatcher::Delegate* delegate);
40
41  // Starts watching |config_path|.
42  void Watch(const base::FilePath& config_path);
43
44  // Stops watching the configuration file.
45  void StopWatching();
46
47 private:
48  friend class base::RefCountedThreadSafe<ConfigFileWatcherImpl>;
49  virtual ~ConfigFileWatcherImpl();
50
51  void FinishStopping();
52
53  // Called every time the host configuration file is updated.
54  void OnConfigUpdated(const base::FilePath& path, bool error);
55
56  // Reads the configuration file and passes it to the delegate.
57  void ReloadConfig();
58
59  std::string config_;
60  base::FilePath config_path_;
61
62  scoped_ptr<base::DelayTimer<ConfigFileWatcherImpl> > config_updated_timer_;
63
64  // Number of times an attempt to read the configuration file failed.
65  int retries_;
66
67  // Monitors the host configuration file.
68  scoped_ptr<base::FilePathWatcher> config_watcher_;
69
70  base::WeakPtrFactory<ConfigFileWatcher::Delegate> delegate_weak_factory_;
71  base::WeakPtr<ConfigFileWatcher::Delegate> delegate_;
72
73  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
74  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
75
76  DISALLOW_COPY_AND_ASSIGN(ConfigFileWatcherImpl);
77};
78
79ConfigFileWatcher::Delegate::~Delegate() {
80}
81
82ConfigFileWatcher::ConfigFileWatcher(
83    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
84    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
85    Delegate* delegate)
86    : impl_(new ConfigFileWatcherImpl(main_task_runner,
87                                      io_task_runner, delegate)) {
88}
89
90ConfigFileWatcher::~ConfigFileWatcher() {
91  impl_->StopWatching();
92  impl_ = NULL;
93}
94
95void ConfigFileWatcher::Watch(const base::FilePath& config_path) {
96  impl_->Watch(config_path);
97}
98
99ConfigFileWatcherImpl::ConfigFileWatcherImpl(
100    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
101    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
102    ConfigFileWatcher::Delegate* delegate)
103    : retries_(0),
104      delegate_weak_factory_(delegate),
105      delegate_(delegate_weak_factory_.GetWeakPtr()),
106      main_task_runner_(main_task_runner),
107      io_task_runner_(io_task_runner) {
108  DCHECK(main_task_runner_->BelongsToCurrentThread());
109}
110
111void ConfigFileWatcherImpl::Watch(const base::FilePath& config_path) {
112  if (!io_task_runner_->BelongsToCurrentThread()) {
113    io_task_runner_->PostTask(
114        FROM_HERE,
115        base::Bind(&ConfigFileWatcherImpl::Watch, this, config_path));
116    return;
117  }
118
119  DCHECK(config_path_.empty());
120  DCHECK(!config_updated_timer_);
121  DCHECK(!config_watcher_);
122
123  // Create the timer that will be used for delayed-reading the configuration
124  // file.
125  config_updated_timer_.reset(new base::DelayTimer<ConfigFileWatcherImpl>(
126      FROM_HERE, base::TimeDelta::FromSeconds(2), this,
127      &ConfigFileWatcherImpl::ReloadConfig));
128
129  // Start watching the configuration file.
130  config_watcher_.reset(new base::FilePathWatcher());
131  config_path_ = config_path;
132  if (!config_watcher_->Watch(
133          config_path_, false,
134          base::Bind(&ConfigFileWatcherImpl::OnConfigUpdated, this))) {
135    PLOG(ERROR) << "Couldn't watch file '" << config_path_.value() << "'";
136    main_task_runner_->PostTask(
137        FROM_HERE,
138        base::Bind(&ConfigFileWatcher::Delegate::OnConfigWatcherError,
139                   delegate_));
140    return;
141  }
142
143  // Force reloading of the configuration file at least once.
144  ReloadConfig();
145}
146
147void ConfigFileWatcherImpl::StopWatching() {
148  DCHECK(main_task_runner_->BelongsToCurrentThread());
149
150  delegate_weak_factory_.InvalidateWeakPtrs();
151  io_task_runner_->PostTask(
152      FROM_HERE, base::Bind(&ConfigFileWatcherImpl::FinishStopping, this));
153}
154
155ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
156  DCHECK(!config_updated_timer_);
157  DCHECK(!config_watcher_);
158}
159
160void ConfigFileWatcherImpl::FinishStopping() {
161  DCHECK(io_task_runner_->BelongsToCurrentThread());
162
163  config_updated_timer_.reset();
164  config_watcher_.reset();
165}
166
167void ConfigFileWatcherImpl::OnConfigUpdated(const base::FilePath& path,
168                                            bool error) {
169  DCHECK(io_task_runner_->BelongsToCurrentThread());
170
171  // Call ReloadConfig() after a short delay, so that we will not try to read
172  // the updated configuration file before it has been completely written.
173  // If the writer moves the new configuration file into place atomically,
174  // this delay may not be necessary.
175  if (!error && config_path_ == path)
176    config_updated_timer_->Reset();
177}
178
179void ConfigFileWatcherImpl::ReloadConfig() {
180  DCHECK(io_task_runner_->BelongsToCurrentThread());
181
182  std::string config;
183  if (!base::ReadFileToString(config_path_, &config)) {
184#if defined(OS_WIN)
185    // EACCESS may indicate a locking or sharing violation. Retry a few times
186    // before reporting an error.
187    if (errno == EACCES && retries_ < kMaxRetries) {
188      PLOG(WARNING) << "Failed to read '" << config_path_.value() << "'";
189
190      retries_ += 1;
191      config_updated_timer_->Reset();
192      return;
193    }
194#endif  // defined(OS_WIN)
195
196    PLOG(ERROR) << "Failed to read '" << config_path_.value() << "'";
197    main_task_runner_->PostTask(
198        FROM_HERE,
199        base::Bind(&ConfigFileWatcher::Delegate::OnConfigWatcherError,
200                   delegate_));
201    return;
202  }
203
204  retries_ = 0;
205
206  // Post an updated configuration only if it has actually changed.
207  if (config_ != config) {
208    config_ = config;
209    main_task_runner_->PostTask(
210        FROM_HERE,
211        base::Bind(&ConfigFileWatcher::Delegate::OnConfigUpdated, delegate_,
212                   config_));
213  }
214}
215
216}  // namespace remoting
217