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 "chrome/browser/extensions/api/dial/dial_registry.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/time/time.h"
11 #include "base/values.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/extensions/api/dial/dial_api.h"
14 #include "chrome/browser/extensions/api/dial/dial_device_data.h"
15 #include "chrome/browser/extensions/api/dial/dial_service.h"
16 #include "chrome/browser/extensions/event_names.h"
17 #include "chrome/browser/net/chrome_net_log.h"
18 #include "chrome/common/extensions/api/dial.h"
21 using base::TimeDelta;
22 using net::NetworkChangeNotifier;
24 namespace extensions {
26 DialRegistry::DialRegistry(Observer* dial_api,
27 const base::TimeDelta& refresh_interval,
28 const base::TimeDelta& expiration,
29 const size_t max_devices)
31 discovery_generation_(0),
32 registry_generation_(0),
33 last_event_discovery_generation_(0),
34 last_event_registry_generation_(0),
36 refresh_interval_delta_(refresh_interval),
37 expiration_delta_(expiration),
38 max_devices_(max_devices),
40 DCHECK(max_devices_ > 0);
41 NetworkChangeNotifier::AddNetworkChangeObserver(this);
44 DialRegistry::~DialRegistry() {
45 DCHECK(thread_checker_.CalledOnValidThread());
46 DCHECK_EQ(0, num_listeners_);
47 NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
50 DialService* DialRegistry::CreateDialService() {
51 DCHECK(g_browser_process->net_log());
52 return new DialServiceImpl(g_browser_process->net_log());
55 void DialRegistry::ClearDialService() {
59 base::Time DialRegistry::Now() const {
63 void DialRegistry::OnListenerAdded() {
64 DCHECK(thread_checker_.CalledOnValidThread());
65 if (++num_listeners_ == 1) {
66 VLOG(2) << "Listener added; starting periodic discovery.";
67 StartPeriodicDiscovery();
71 void DialRegistry::OnListenerRemoved() {
72 DCHECK(thread_checker_.CalledOnValidThread());
73 DCHECK(num_listeners_ > 0);
74 if (--num_listeners_ == 0) {
75 VLOG(2) << "Listeners removed; stopping periodic discovery.";
76 StopPeriodicDiscovery();
80 bool DialRegistry::ReadyToDiscover() {
81 if (num_listeners_ == 0) {
82 dial_api_->OnDialError(DIAL_NO_LISTENERS);
85 if (NetworkChangeNotifier::IsOffline()) {
86 dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
89 if (NetworkChangeNotifier::IsConnectionCellular(
90 NetworkChangeNotifier::GetConnectionType())) {
91 dial_api_->OnDialError(DIAL_CELLULAR_NETWORK);
97 bool DialRegistry::DiscoverNow() {
98 DCHECK(thread_checker_.CalledOnValidThread());
99 if (!ReadyToDiscover()) {
103 dial_api_->OnDialError(DIAL_UNKNOWN);
107 if (!dial_->HasObserver(this))
108 NOTREACHED() << "DiscoverNow() called without observing dial_";
109 discovery_generation_++;
110 return dial_->Discover();
113 void DialRegistry::StartPeriodicDiscovery() {
114 DCHECK(thread_checker_.CalledOnValidThread());
115 if (!ReadyToDiscover() || dial_.get())
118 dial_.reset(CreateDialService());
119 dial_->AddObserver(this);
121 repeating_timer_.Start(FROM_HERE,
122 refresh_interval_delta_,
124 &DialRegistry::DoDiscovery);
127 void DialRegistry::DoDiscovery() {
128 DCHECK(thread_checker_.CalledOnValidThread());
130 discovery_generation_++;
131 VLOG(2) << "About to discover! Generation = " << discovery_generation_;
135 void DialRegistry::StopPeriodicDiscovery() {
136 DCHECK(thread_checker_.CalledOnValidThread());
140 repeating_timer_.Stop();
141 dial_->RemoveObserver(this);
145 bool DialRegistry::PruneExpiredDevices() {
146 DCHECK(thread_checker_.CalledOnValidThread());
147 bool pruned_device = false;
148 DeviceByLabelMap::iterator i = device_by_label_map_.begin();
149 while (i != device_by_label_map_.end()) {
150 linked_ptr<DialDeviceData> device = i->second;
151 if (IsDeviceExpired(*device)) {
152 VLOG(2) << "Device " << device->label() << " expired, removing";
153 const size_t num_erased_by_id =
154 device_by_id_map_.erase(device->device_id());
155 DCHECK_EQ(num_erased_by_id, 1u);
156 device_by_label_map_.erase(i++);
157 pruned_device = true;
162 return pruned_device;
165 bool DialRegistry::IsDeviceExpired(const DialDeviceData& device) const {
168 // Check against our default expiration timeout.
169 Time default_expiration_time = device.response_time() + expiration_delta_;
170 if (now > default_expiration_time)
173 // Check against the device's cache-control header, if set.
174 if (device.has_max_age()) {
175 Time max_age_expiration_time =
176 device.response_time() + TimeDelta::FromSeconds(device.max_age());
177 if (now > max_age_expiration_time)
183 void DialRegistry::Clear() {
184 DCHECK(thread_checker_.CalledOnValidThread());
185 device_by_id_map_.clear();
186 device_by_label_map_.clear();
187 registry_generation_++;
190 void DialRegistry::MaybeSendEvent() {
191 DCHECK(thread_checker_.CalledOnValidThread());
193 // We need to send an event if:
194 // (1) We haven't sent one yet in this round of discovery, or
195 // (2) The device list changed since the last MaybeSendEvent.
197 (last_event_discovery_generation_ < discovery_generation_ ||
198 last_event_registry_generation_ < registry_generation_);
199 VLOG(2) << "ledg = " << last_event_discovery_generation_ << ", dg = "
200 << discovery_generation_
201 << ", lerg = " << last_event_registry_generation_ << ", rg = "
202 << registry_generation_
203 << ", needs_event = " << needs_event;
207 DeviceList device_list;
208 for (DeviceByLabelMap::const_iterator i = device_by_label_map_.begin();
209 i != device_by_label_map_.end(); i++) {
210 device_list.push_back(*(i->second));
212 dial_api_->OnDialDeviceEvent(device_list);
215 last_event_discovery_generation_ = discovery_generation_;
216 last_event_registry_generation_ = registry_generation_;
219 std::string DialRegistry::NextLabel() {
220 DCHECK(thread_checker_.CalledOnValidThread());
221 return base::IntToString(++label_count_);
224 void DialRegistry::OnDiscoveryRequest(DialService* service) {
225 DCHECK(thread_checker_.CalledOnValidThread());
229 void DialRegistry::OnDeviceDiscovered(DialService* service,
230 const DialDeviceData& device) {
231 DCHECK(thread_checker_.CalledOnValidThread());
233 // Adds |device| to our list of devices or updates an existing device, unless
234 // |device| is a duplicate. Returns true if the list was modified and
235 // increments the list generation.
236 linked_ptr<DialDeviceData> device_data(new DialDeviceData(device));
237 DCHECK(!device_data->device_id().empty());
238 DCHECK(device_data->label().empty());
240 bool did_modify_list = false;
241 DeviceByIdMap::iterator lookup_result =
242 device_by_id_map_.find(device_data->device_id());
244 if (lookup_result != device_by_id_map_.end()) {
245 VLOG(2) << "Found device " << device_data->device_id() << ", merging";
247 // Already have previous response. Merge in data from this response and
248 // track if there were any API visible changes.
249 did_modify_list = lookup_result->second->UpdateFrom(*device_data);
251 did_modify_list = MaybeAddDevice(device_data);
255 registry_generation_++;
257 VLOG(2) << "did_modify_list = " << did_modify_list
258 << ", generation = " << registry_generation_;
261 bool DialRegistry::MaybeAddDevice(
262 const linked_ptr<DialDeviceData>& device_data) {
263 DCHECK(thread_checker_.CalledOnValidThread());
264 if (device_by_id_map_.size() == max_devices_) {
265 DLOG(WARNING) << "Maximum registry size reached. Cannot add device.";
268 device_data->set_label(NextLabel());
269 device_by_id_map_[device_data->device_id()] = device_data;
270 device_by_label_map_[device_data->label()] = device_data;
271 VLOG(2) << "Added device, id = " << device_data->device_id()
272 << ", label = " << device_data->label();
276 void DialRegistry::OnDiscoveryFinished(DialService* service) {
277 DCHECK(thread_checker_.CalledOnValidThread());
278 if (PruneExpiredDevices())
279 registry_generation_++;
283 void DialRegistry::OnError(DialService* service,
284 const DialService::DialServiceErrorCode& code) {
285 DCHECK(thread_checker_.CalledOnValidThread());
287 case DialService::DIAL_SERVICE_SOCKET_ERROR:
288 dial_api_->OnDialError(DIAL_SOCKET_ERROR);
290 case DialService::DIAL_SERVICE_NO_INTERFACES:
291 dial_api_->OnDialError(DIAL_NO_INTERFACES);
295 dial_api_->OnDialError(DIAL_UNKNOWN);
300 void DialRegistry::OnNetworkChanged(
301 NetworkChangeNotifier::ConnectionType type) {
303 case NetworkChangeNotifier::CONNECTION_NONE:
305 VLOG(2) << "Lost connection, shutting down discovery and clearing"
307 dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
309 StopPeriodicDiscovery();
310 // TODO(justinlin): As an optimization, we can probably keep our device
311 // list around and restore it if we reconnected to the exact same
317 case NetworkChangeNotifier::CONNECTION_2G:
318 case NetworkChangeNotifier::CONNECTION_3G:
319 case NetworkChangeNotifier::CONNECTION_4G:
320 case NetworkChangeNotifier::CONNECTION_ETHERNET:
321 case NetworkChangeNotifier::CONNECTION_WIFI:
322 case NetworkChangeNotifier::CONNECTION_UNKNOWN:
324 VLOG(2) << "Connection detected, restarting discovery.";
325 StartPeriodicDiscovery();
331 } // namespace extensions