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/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.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");
27 // Maximum number of times to try reading the configuration file before
28 // reporting an error.
29 const int kMaxRetries
= 3;
31 class ConfigFileWatcherImpl
32 : public base::RefCountedThreadSafe
<ConfigFileWatcherImpl
> {
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
);
41 // Starts watching |config_path|.
42 void Watch(const base::FilePath
& config_path
);
44 // Stops watching the configuration file.
48 friend class base::RefCountedThreadSafe
<ConfigFileWatcherImpl
>;
49 virtual ~ConfigFileWatcherImpl();
51 void FinishStopping();
53 // Called every time the host configuration file is updated.
54 void OnConfigUpdated(const base::FilePath
& path
, bool error
);
56 // Reads the configuration file and passes it to the delegate.
60 base::FilePath config_path_
;
62 scoped_ptr
<base::DelayTimer
<ConfigFileWatcherImpl
> > config_updated_timer_
;
64 // Number of times an attempt to read the configuration file failed.
67 // Monitors the host configuration file.
68 scoped_ptr
<base::FilePathWatcher
> config_watcher_
;
70 base::WeakPtrFactory
<ConfigFileWatcher::Delegate
> delegate_weak_factory_
;
71 base::WeakPtr
<ConfigFileWatcher::Delegate
> delegate_
;
73 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner_
;
74 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner_
;
76 DISALLOW_COPY_AND_ASSIGN(ConfigFileWatcherImpl
);
79 ConfigFileWatcher::Delegate::~Delegate() {
82 ConfigFileWatcher::ConfigFileWatcher(
83 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
84 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
86 : impl_(new ConfigFileWatcherImpl(main_task_runner
,
87 io_task_runner
, delegate
)) {
90 ConfigFileWatcher::~ConfigFileWatcher() {
91 impl_
->StopWatching();
95 void ConfigFileWatcher::Watch(const base::FilePath
& config_path
) {
96 impl_
->Watch(config_path
);
99 ConfigFileWatcherImpl::ConfigFileWatcherImpl(
100 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
101 scoped_refptr
<base::SingleThreadTaskRunner
> io_task_runner
,
102 ConfigFileWatcher::Delegate
* delegate
)
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());
111 void ConfigFileWatcherImpl::Watch(const base::FilePath
& config_path
) {
112 if (!io_task_runner_
->BelongsToCurrentThread()) {
113 io_task_runner_
->PostTask(
115 base::Bind(&ConfigFileWatcherImpl::Watch
, this, config_path
));
119 DCHECK(config_path_
.empty());
120 DCHECK(!config_updated_timer_
);
121 DCHECK(!config_watcher_
);
123 // Create the timer that will be used for delayed-reading the configuration
125 config_updated_timer_
.reset(new base::DelayTimer
<ConfigFileWatcherImpl
>(
126 FROM_HERE
, base::TimeDelta::FromSeconds(2), this,
127 &ConfigFileWatcherImpl::ReloadConfig
));
129 // Start watching the configuration file.
130 config_watcher_
.reset(new base::FilePathWatcher());
131 config_path_
= config_path
;
132 if (!config_watcher_
->Watch(
134 base::Bind(&ConfigFileWatcherImpl::OnConfigUpdated
, this))) {
135 PLOG(ERROR
) << "Couldn't watch file '" << config_path_
.value() << "'";
136 main_task_runner_
->PostTask(
138 base::Bind(&ConfigFileWatcher::Delegate::OnConfigWatcherError
,
143 // Force reloading of the configuration file at least once.
147 void ConfigFileWatcherImpl::StopWatching() {
148 DCHECK(main_task_runner_
->BelongsToCurrentThread());
150 delegate_weak_factory_
.InvalidateWeakPtrs();
151 io_task_runner_
->PostTask(
152 FROM_HERE
, base::Bind(&ConfigFileWatcherImpl::FinishStopping
, this));
155 ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
156 DCHECK(!config_updated_timer_
);
157 DCHECK(!config_watcher_
);
160 void ConfigFileWatcherImpl::FinishStopping() {
161 DCHECK(io_task_runner_
->BelongsToCurrentThread());
163 config_updated_timer_
.reset();
164 config_watcher_
.reset();
167 void ConfigFileWatcherImpl::OnConfigUpdated(const base::FilePath
& path
,
169 DCHECK(io_task_runner_
->BelongsToCurrentThread());
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();
179 void ConfigFileWatcherImpl::ReloadConfig() {
180 DCHECK(io_task_runner_
->BelongsToCurrentThread());
183 if (!file_util::ReadFileToString(config_path_
, &config
)) {
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() << "'";
191 config_updated_timer_
->Reset();
194 #endif // defined(OS_WIN)
196 PLOG(ERROR
) << "Failed to read '" << config_path_
.value() << "'";
197 main_task_runner_
->PostTask(
199 base::Bind(&ConfigFileWatcher::Delegate::OnConfigWatcherError
,
206 // Post an updated configuration only if it has actually changed.
207 if (config_
!= config
) {
209 main_task_runner_
->PostTask(
211 base::Bind(&ConfigFileWatcher::Delegate::OnConfigUpdated
, delegate_
,
216 } // namespace remoting