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/common/extensions/api/dial.h"
17 #include "components/net_log/chrome_net_log.h"
20 using base::TimeDelta
;
21 using net::NetworkChangeNotifier
;
23 namespace extensions
{
25 DialRegistry::DialRegistry(Observer
* dial_api
,
26 const base::TimeDelta
& refresh_interval
,
27 const base::TimeDelta
& expiration
,
28 const size_t max_devices
)
30 discovery_generation_(0),
31 registry_generation_(0),
32 last_event_discovery_generation_(0),
33 last_event_registry_generation_(0),
35 refresh_interval_delta_(refresh_interval
),
36 expiration_delta_(expiration
),
37 max_devices_(max_devices
),
39 DCHECK(max_devices_
> 0);
40 NetworkChangeNotifier::AddNetworkChangeObserver(this);
43 DialRegistry::~DialRegistry() {
44 DCHECK(thread_checker_
.CalledOnValidThread());
45 NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
48 DialService
* DialRegistry::CreateDialService() {
49 DCHECK(g_browser_process
->net_log());
50 return new DialServiceImpl(g_browser_process
->net_log());
53 void DialRegistry::ClearDialService() {
57 base::Time
DialRegistry::Now() const {
61 void DialRegistry::OnListenerAdded() {
62 DCHECK(thread_checker_
.CalledOnValidThread());
63 if (++num_listeners_
== 1) {
64 VLOG(2) << "Listener added; starting periodic discovery.";
65 StartPeriodicDiscovery();
69 void DialRegistry::OnListenerRemoved() {
70 DCHECK(thread_checker_
.CalledOnValidThread());
71 DCHECK(num_listeners_
> 0);
72 if (--num_listeners_
== 0) {
73 VLOG(2) << "Listeners removed; stopping periodic discovery.";
74 StopPeriodicDiscovery();
78 bool DialRegistry::ReadyToDiscover() {
79 if (num_listeners_
== 0) {
80 dial_api_
->OnDialError(DIAL_NO_LISTENERS
);
83 if (NetworkChangeNotifier::IsOffline()) {
84 dial_api_
->OnDialError(DIAL_NETWORK_DISCONNECTED
);
87 if (NetworkChangeNotifier::IsConnectionCellular(
88 NetworkChangeNotifier::GetConnectionType())) {
89 dial_api_
->OnDialError(DIAL_CELLULAR_NETWORK
);
95 bool DialRegistry::DiscoverNow() {
96 DCHECK(thread_checker_
.CalledOnValidThread());
97 if (!ReadyToDiscover()) {
101 dial_api_
->OnDialError(DIAL_UNKNOWN
);
105 if (!dial_
->HasObserver(this))
106 NOTREACHED() << "DiscoverNow() called without observing dial_";
107 discovery_generation_
++;
108 return dial_
->Discover();
111 void DialRegistry::StartPeriodicDiscovery() {
112 DCHECK(thread_checker_
.CalledOnValidThread());
113 if (!ReadyToDiscover() || dial_
.get())
116 dial_
.reset(CreateDialService());
117 dial_
->AddObserver(this);
119 repeating_timer_
.Start(FROM_HERE
,
120 refresh_interval_delta_
,
122 &DialRegistry::DoDiscovery
);
125 void DialRegistry::DoDiscovery() {
126 DCHECK(thread_checker_
.CalledOnValidThread());
128 discovery_generation_
++;
129 VLOG(2) << "About to discover! Generation = " << discovery_generation_
;
133 void DialRegistry::StopPeriodicDiscovery() {
134 DCHECK(thread_checker_
.CalledOnValidThread());
138 repeating_timer_
.Stop();
139 dial_
->RemoveObserver(this);
143 bool DialRegistry::PruneExpiredDevices() {
144 DCHECK(thread_checker_
.CalledOnValidThread());
145 bool pruned_device
= false;
146 DeviceByLabelMap::iterator i
= device_by_label_map_
.begin();
147 while (i
!= device_by_label_map_
.end()) {
148 linked_ptr
<DialDeviceData
> device
= i
->second
;
149 if (IsDeviceExpired(*device
)) {
150 VLOG(2) << "Device " << device
->label() << " expired, removing";
151 const size_t num_erased_by_id
=
152 device_by_id_map_
.erase(device
->device_id());
153 DCHECK_EQ(num_erased_by_id
, 1u);
154 device_by_label_map_
.erase(i
++);
155 pruned_device
= true;
160 return pruned_device
;
163 bool DialRegistry::IsDeviceExpired(const DialDeviceData
& device
) const {
166 // Check against our default expiration timeout.
167 Time default_expiration_time
= device
.response_time() + expiration_delta_
;
168 if (now
> default_expiration_time
)
171 // Check against the device's cache-control header, if set.
172 if (device
.has_max_age()) {
173 Time max_age_expiration_time
=
174 device
.response_time() + TimeDelta::FromSeconds(device
.max_age());
175 if (now
> max_age_expiration_time
)
181 void DialRegistry::Clear() {
182 DCHECK(thread_checker_
.CalledOnValidThread());
183 device_by_id_map_
.clear();
184 device_by_label_map_
.clear();
185 registry_generation_
++;
188 void DialRegistry::MaybeSendEvent() {
189 DCHECK(thread_checker_
.CalledOnValidThread());
191 // We need to send an event if:
192 // (1) We haven't sent one yet in this round of discovery, or
193 // (2) The device list changed since the last MaybeSendEvent.
195 (last_event_discovery_generation_
< discovery_generation_
||
196 last_event_registry_generation_
< registry_generation_
);
197 VLOG(2) << "ledg = " << last_event_discovery_generation_
<< ", dg = "
198 << discovery_generation_
199 << ", lerg = " << last_event_registry_generation_
<< ", rg = "
200 << registry_generation_
201 << ", needs_event = " << needs_event
;
205 DeviceList device_list
;
206 for (DeviceByLabelMap::const_iterator i
= device_by_label_map_
.begin();
207 i
!= device_by_label_map_
.end(); i
++) {
208 device_list
.push_back(*(i
->second
));
210 dial_api_
->OnDialDeviceEvent(device_list
);
213 last_event_discovery_generation_
= discovery_generation_
;
214 last_event_registry_generation_
= registry_generation_
;
217 std::string
DialRegistry::NextLabel() {
218 DCHECK(thread_checker_
.CalledOnValidThread());
219 return base::IntToString(++label_count_
);
222 void DialRegistry::OnDiscoveryRequest(DialService
* service
) {
223 DCHECK(thread_checker_
.CalledOnValidThread());
227 void DialRegistry::OnDeviceDiscovered(DialService
* service
,
228 const DialDeviceData
& device
) {
229 DCHECK(thread_checker_
.CalledOnValidThread());
231 // Adds |device| to our list of devices or updates an existing device, unless
232 // |device| is a duplicate. Returns true if the list was modified and
233 // increments the list generation.
234 linked_ptr
<DialDeviceData
> device_data(new DialDeviceData(device
));
235 DCHECK(!device_data
->device_id().empty());
236 DCHECK(device_data
->label().empty());
238 bool did_modify_list
= false;
239 DeviceByIdMap::iterator lookup_result
=
240 device_by_id_map_
.find(device_data
->device_id());
242 if (lookup_result
!= device_by_id_map_
.end()) {
243 VLOG(2) << "Found device " << device_data
->device_id() << ", merging";
245 // Already have previous response. Merge in data from this response and
246 // track if there were any API visible changes.
247 did_modify_list
= lookup_result
->second
->UpdateFrom(*device_data
);
249 did_modify_list
= MaybeAddDevice(device_data
);
253 registry_generation_
++;
255 VLOG(2) << "did_modify_list = " << did_modify_list
256 << ", generation = " << registry_generation_
;
259 bool DialRegistry::MaybeAddDevice(
260 const linked_ptr
<DialDeviceData
>& device_data
) {
261 DCHECK(thread_checker_
.CalledOnValidThread());
262 if (device_by_id_map_
.size() == max_devices_
) {
263 VLOG(1) << "Maximum registry size reached. Cannot add device.";
266 device_data
->set_label(NextLabel());
267 device_by_id_map_
[device_data
->device_id()] = device_data
;
268 device_by_label_map_
[device_data
->label()] = device_data
;
269 VLOG(2) << "Added device, id = " << device_data
->device_id()
270 << ", label = " << device_data
->label();
274 void DialRegistry::OnDiscoveryFinished(DialService
* service
) {
275 DCHECK(thread_checker_
.CalledOnValidThread());
276 if (PruneExpiredDevices())
277 registry_generation_
++;
281 void DialRegistry::OnError(DialService
* service
,
282 const DialService::DialServiceErrorCode
& code
) {
283 DCHECK(thread_checker_
.CalledOnValidThread());
285 case DialService::DIAL_SERVICE_SOCKET_ERROR
:
286 dial_api_
->OnDialError(DIAL_SOCKET_ERROR
);
288 case DialService::DIAL_SERVICE_NO_INTERFACES
:
289 dial_api_
->OnDialError(DIAL_NO_INTERFACES
);
293 dial_api_
->OnDialError(DIAL_UNKNOWN
);
298 void DialRegistry::OnNetworkChanged(
299 NetworkChangeNotifier::ConnectionType type
) {
301 case NetworkChangeNotifier::CONNECTION_NONE
:
303 VLOG(2) << "Lost connection, shutting down discovery and clearing"
305 dial_api_
->OnDialError(DIAL_NETWORK_DISCONNECTED
);
307 StopPeriodicDiscovery();
308 // TODO(justinlin): As an optimization, we can probably keep our device
309 // list around and restore it if we reconnected to the exact same
315 case NetworkChangeNotifier::CONNECTION_2G
:
316 case NetworkChangeNotifier::CONNECTION_3G
:
317 case NetworkChangeNotifier::CONNECTION_4G
:
318 case NetworkChangeNotifier::CONNECTION_ETHERNET
:
319 case NetworkChangeNotifier::CONNECTION_WIFI
:
320 case NetworkChangeNotifier::CONNECTION_UNKNOWN
:
321 case NetworkChangeNotifier::CONNECTION_BLUETOOTH
:
323 VLOG(2) << "Connection detected, restarting discovery.";
324 StartPeriodicDiscovery();
330 } // namespace extensions