- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / dial / dial_registry.cc
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 "chrome/browser/extensions/api/dial/dial_registry.h"
6
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"
19
20 using base::Time;
21 using base::TimeDelta;
22 using net::NetworkChangeNotifier;
23
24 namespace extensions {
25
26 DialRegistry::DialRegistry(Observer* dial_api,
27                            const base::TimeDelta& refresh_interval,
28                            const base::TimeDelta& expiration,
29                            const size_t max_devices)
30   : num_listeners_(0),
31     discovery_generation_(0),
32     registry_generation_(0),
33     last_event_discovery_generation_(0),
34     last_event_registry_generation_(0),
35     label_count_(0),
36     refresh_interval_delta_(refresh_interval),
37     expiration_delta_(expiration),
38     max_devices_(max_devices),
39     dial_api_(dial_api) {
40   DCHECK(max_devices_ > 0);
41   NetworkChangeNotifier::AddNetworkChangeObserver(this);
42 }
43
44 DialRegistry::~DialRegistry() {
45   DCHECK(thread_checker_.CalledOnValidThread());
46   DCHECK_EQ(0, num_listeners_);
47   NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
48 }
49
50 DialService* DialRegistry::CreateDialService() {
51   DCHECK(g_browser_process->net_log());
52   return new DialServiceImpl(g_browser_process->net_log());
53 }
54
55 void DialRegistry::ClearDialService() {
56   dial_.reset();
57 }
58
59 base::Time DialRegistry::Now() const {
60   return Time::Now();
61 }
62
63 void DialRegistry::OnListenerAdded() {
64   DCHECK(thread_checker_.CalledOnValidThread());
65   if (++num_listeners_ == 1) {
66     VLOG(2) << "Listener added; starting periodic discovery.";
67     StartPeriodicDiscovery();
68   }
69 }
70
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();
77   }
78 }
79
80 bool DialRegistry::ReadyToDiscover() {
81   if (num_listeners_ == 0) {
82     dial_api_->OnDialError(DIAL_NO_LISTENERS);
83     return false;
84   }
85   if (NetworkChangeNotifier::IsOffline()) {
86     dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
87     return false;
88   }
89   if (NetworkChangeNotifier::IsConnectionCellular(
90           NetworkChangeNotifier::GetConnectionType())) {
91     dial_api_->OnDialError(DIAL_CELLULAR_NETWORK);
92     return false;
93   }
94   return true;
95 }
96
97 bool DialRegistry::DiscoverNow() {
98   DCHECK(thread_checker_.CalledOnValidThread());
99   if (!ReadyToDiscover()) {
100     return false;
101   }
102   if (!dial_.get()) {
103     dial_api_->OnDialError(DIAL_UNKNOWN);
104     return false;
105   }
106
107   if (!dial_->HasObserver(this))
108     NOTREACHED() << "DiscoverNow() called without observing dial_";
109   discovery_generation_++;
110   return dial_->Discover();
111 }
112
113 void DialRegistry::StartPeriodicDiscovery() {
114   DCHECK(thread_checker_.CalledOnValidThread());
115   if (!ReadyToDiscover() || dial_.get())
116     return;
117
118   dial_.reset(CreateDialService());
119   dial_->AddObserver(this);
120   DoDiscovery();
121   repeating_timer_.Start(FROM_HERE,
122                          refresh_interval_delta_,
123                          this,
124                          &DialRegistry::DoDiscovery);
125 }
126
127 void DialRegistry::DoDiscovery() {
128   DCHECK(thread_checker_.CalledOnValidThread());
129   DCHECK(dial_.get());
130   discovery_generation_++;
131   VLOG(2) << "About to discover! Generation = " << discovery_generation_;
132   dial_->Discover();
133 }
134
135 void DialRegistry::StopPeriodicDiscovery() {
136   DCHECK(thread_checker_.CalledOnValidThread());
137   if (!dial_.get())
138     return;
139
140   repeating_timer_.Stop();
141   dial_->RemoveObserver(this);
142   ClearDialService();
143 }
144
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;
158     } else {
159       ++i;
160     }
161   }
162   return pruned_device;
163 }
164
165 bool DialRegistry::IsDeviceExpired(const DialDeviceData& device) const {
166   Time now = Now();
167
168   // Check against our default expiration timeout.
169   Time default_expiration_time = device.response_time() + expiration_delta_;
170   if (now > default_expiration_time)
171     return true;
172
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)
178       return true;
179   }
180   return false;
181 }
182
183 void DialRegistry::Clear() {
184   DCHECK(thread_checker_.CalledOnValidThread());
185   device_by_id_map_.clear();
186   device_by_label_map_.clear();
187   registry_generation_++;
188 }
189
190 void DialRegistry::MaybeSendEvent() {
191   DCHECK(thread_checker_.CalledOnValidThread());
192
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.
196   bool needs_event =
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;
204   if (!needs_event)
205     return;
206
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));
211   }
212   dial_api_->OnDialDeviceEvent(device_list);
213
214   // Reset watermarks.
215   last_event_discovery_generation_ = discovery_generation_;
216   last_event_registry_generation_ = registry_generation_;
217 }
218
219 std::string DialRegistry::NextLabel() {
220   DCHECK(thread_checker_.CalledOnValidThread());
221   return base::IntToString(++label_count_);
222 }
223
224 void DialRegistry::OnDiscoveryRequest(DialService* service) {
225   DCHECK(thread_checker_.CalledOnValidThread());
226   MaybeSendEvent();
227 }
228
229 void DialRegistry::OnDeviceDiscovered(DialService* service,
230                                       const DialDeviceData& device) {
231   DCHECK(thread_checker_.CalledOnValidThread());
232
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());
239
240   bool did_modify_list = false;
241   DeviceByIdMap::iterator lookup_result =
242       device_by_id_map_.find(device_data->device_id());
243
244   if (lookup_result != device_by_id_map_.end()) {
245     VLOG(2) << "Found device " << device_data->device_id() << ", merging";
246
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);
250   } else {
251     did_modify_list = MaybeAddDevice(device_data);
252   }
253
254   if (did_modify_list)
255     registry_generation_++;
256
257   VLOG(2) << "did_modify_list = " << did_modify_list
258           << ", generation = " << registry_generation_;
259 }
260
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.";
266     return false;
267   }
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();
273   return true;
274 }
275
276 void DialRegistry::OnDiscoveryFinished(DialService* service) {
277   DCHECK(thread_checker_.CalledOnValidThread());
278   if (PruneExpiredDevices())
279     registry_generation_++;
280   MaybeSendEvent();
281 }
282
283 void DialRegistry::OnError(DialService* service,
284                            const DialService::DialServiceErrorCode& code) {
285   DCHECK(thread_checker_.CalledOnValidThread());
286   switch (code) {
287     case DialService::DIAL_SERVICE_SOCKET_ERROR:
288       dial_api_->OnDialError(DIAL_SOCKET_ERROR);
289       break;
290     case DialService::DIAL_SERVICE_NO_INTERFACES:
291       dial_api_->OnDialError(DIAL_NO_INTERFACES);
292       break;
293     default:
294       NOTREACHED();
295       dial_api_->OnDialError(DIAL_UNKNOWN);
296       break;
297   }
298 }
299
300 void DialRegistry::OnNetworkChanged(
301     NetworkChangeNotifier::ConnectionType type) {
302   switch (type) {
303     case NetworkChangeNotifier::CONNECTION_NONE:
304       if (dial_.get()) {
305         VLOG(2) << "Lost connection, shutting down discovery and clearing"
306                 << " list.";
307         dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
308
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
312         // network.
313         Clear();
314         MaybeSendEvent();
315       }
316       break;
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:
323       if (!dial_.get()) {
324         VLOG(2) << "Connection detected, restarting discovery.";
325         StartPeriodicDiscovery();
326       }
327       break;
328   }
329 }
330
331 }  // namespace extensions