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.
5 #include "remoting/host/config_file_watcher.h"
10 #include "base/bind_helpers.h"
11 #include "base/files/file_path_watcher.h"
12 #include "base/files/file_util.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"
20 // The name of the command-line switch used to specify the host configuration
22 const char kHostConfigSwitchName
[] = "host-config";
24 const base::FilePath::CharType kDefaultHostConfigFile
[] =
25 FILE_PATH_LITERAL("host.json");
28 // Maximum number of times to try reading the configuration file before
29 // reporting an error.
30 const int kMaxRetries
= 3;
31 #endif // defined(OS_WIN)
33 class ConfigFileWatcherImpl
34 : public base::RefCountedThreadSafe
<ConfigFileWatcherImpl
> {
36 // Creates a configuration file watcher that lives on the |io_task_runner|
37 // thread but posts config file updates on on |main_task_runner|.
38 ConfigFileWatcherImpl(
39 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
40 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
41 const base::FilePath
& config_path
);
44 // Notify |delegate| of config changes.
45 void Watch(ConfigWatcher::Delegate
* delegate
);
47 // Stops watching the configuration file.
51 friend class base::RefCountedThreadSafe
<ConfigFileWatcherImpl
>;
52 virtual ~ConfigFileWatcherImpl();
54 void FinishStopping();
56 void WatchOnIoThread();
58 // Called every time the host configuration file is updated.
59 void OnConfigUpdated(const base::FilePath
& path
, bool error
);
61 // Called to notify the delegate of updates/errors in the main thread.
62 void NotifyUpdate(const std::string
& config
);
65 // Reads the configuration file and passes it to the delegate.
69 base::FilePath config_path_
;
71 scoped_ptr
<base::DelayTimer
<ConfigFileWatcherImpl
> > config_updated_timer_
;
73 // Number of times an attempt to read the configuration file failed.
76 // Monitors the host configuration file.
77 scoped_ptr
<base::FilePathWatcher
> config_watcher_
;
79 ConfigWatcher::Delegate
* delegate_
;
81 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner_
;
82 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner_
;
84 base::WeakPtrFactory
<ConfigFileWatcherImpl
> weak_factory_
;
86 DISALLOW_COPY_AND_ASSIGN(ConfigFileWatcherImpl
);
89 ConfigFileWatcher::ConfigFileWatcher(
90 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
91 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
92 const base::FilePath
& config_path
)
93 : impl_(new ConfigFileWatcherImpl(main_task_runner
,
94 io_task_runner
, config_path
)) {
97 ConfigFileWatcher::~ConfigFileWatcher() {
98 impl_
->StopWatching();
102 void ConfigFileWatcher::Watch(ConfigWatcher::Delegate
* delegate
) {
103 impl_
->Watch(delegate
);
106 ConfigFileWatcherImpl::ConfigFileWatcherImpl(
107 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
108 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
109 const base::FilePath
& config_path
)
110 : config_path_(config_path
),
113 main_task_runner_(main_task_runner
),
114 io_task_runner_(io_task_runner
),
115 weak_factory_(this) {
116 DCHECK(main_task_runner_
->BelongsToCurrentThread());
119 void ConfigFileWatcherImpl::Watch(ConfigWatcher::Delegate
* delegate
) {
120 DCHECK(main_task_runner_
->BelongsToCurrentThread());
123 delegate_
= delegate
;
125 io_task_runner_
->PostTask(
127 base::Bind(&ConfigFileWatcherImpl::WatchOnIoThread
, this));
130 void ConfigFileWatcherImpl::WatchOnIoThread() {
131 DCHECK(io_task_runner_
->BelongsToCurrentThread());
132 DCHECK(!config_updated_timer_
);
133 DCHECK(!config_watcher_
);
135 // Create the timer that will be used for delayed-reading the configuration
137 config_updated_timer_
.reset(new base::DelayTimer
<ConfigFileWatcherImpl
>(
138 FROM_HERE
, base::TimeDelta::FromSeconds(2), this,
139 &ConfigFileWatcherImpl::ReloadConfig
));
141 // Start watching the configuration file.
142 config_watcher_
.reset(new base::FilePathWatcher());
143 if (!config_watcher_
->Watch(
145 base::Bind(&ConfigFileWatcherImpl::OnConfigUpdated
, this))) {
146 PLOG(ERROR
) << "Couldn't watch file '" << config_path_
.value() << "'";
147 main_task_runner_
->PostTask(
149 base::Bind(&ConfigFileWatcherImpl::NotifyError
,
150 weak_factory_
.GetWeakPtr()));
154 // Force reloading of the configuration file at least once.
158 void ConfigFileWatcherImpl::StopWatching() {
159 DCHECK(main_task_runner_
->BelongsToCurrentThread());
161 weak_factory_
.InvalidateWeakPtrs();
162 io_task_runner_
->PostTask(
163 FROM_HERE
, base::Bind(&ConfigFileWatcherImpl::FinishStopping
, this));
166 ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
167 DCHECK(!config_updated_timer_
);
168 DCHECK(!config_watcher_
);
171 void ConfigFileWatcherImpl::FinishStopping() {
172 DCHECK(io_task_runner_
->BelongsToCurrentThread());
174 config_updated_timer_
.reset();
175 config_watcher_
.reset();
178 void ConfigFileWatcherImpl::OnConfigUpdated(const base::FilePath
& path
,
180 DCHECK(io_task_runner_
->BelongsToCurrentThread());
182 // Call ReloadConfig() after a short delay, so that we will not try to read
183 // the updated configuration file before it has been completely written.
184 // If the writer moves the new configuration file into place atomically,
185 // this delay may not be necessary.
186 if (!error
&& config_path_
== path
)
187 config_updated_timer_
->Reset();
190 void ConfigFileWatcherImpl::NotifyError() {
191 DCHECK(main_task_runner_
->BelongsToCurrentThread());
193 delegate_
->OnConfigWatcherError();
196 void ConfigFileWatcherImpl::NotifyUpdate(const std::string
& config
) {
197 DCHECK(main_task_runner_
->BelongsToCurrentThread());
199 delegate_
->OnConfigUpdated(config_
);
202 void ConfigFileWatcherImpl::ReloadConfig() {
203 DCHECK(io_task_runner_
->BelongsToCurrentThread());
206 if (!base::ReadFileToString(config_path_
, &config
)) {
208 // EACCESS may indicate a locking or sharing violation. Retry a few times
209 // before reporting an error.
210 if (errno
== EACCES
&& retries_
< kMaxRetries
) {
211 PLOG(WARNING
) << "Failed to read '" << config_path_
.value() << "'";
214 config_updated_timer_
->Reset();
217 #endif // defined(OS_WIN)
219 PLOG(ERROR
) << "Failed to read '" << config_path_
.value() << "'";
221 main_task_runner_
->PostTask(
223 base::Bind(&ConfigFileWatcherImpl::NotifyError
,
224 weak_factory_
.GetWeakPtr()));
230 // Post an updated configuration only if it has actually changed.
231 if (config_
!= config
) {
233 main_task_runner_
->PostTask(
235 base::Bind(&ConfigFileWatcherImpl::NotifyUpdate
,
236 weak_factory_
.GetWeakPtr(), config_
));
240 } // namespace remoting