| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/dial/dial_registry.h" |
| |
| #include "base/memory/scoped_ptr.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/api/dial/dial_api.h" |
| #include "chrome/browser/extensions/api/dial/dial_device_data.h" |
| #include "chrome/browser/extensions/api/dial/dial_service.h" |
| #include "chrome/browser/extensions/event_names.h" |
| #include "chrome/browser/net/chrome_net_log.h" |
| #include "chrome/common/extensions/api/dial.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| using net::NetworkChangeNotifier; |
| |
| namespace extensions { |
| |
| DialRegistry::DialRegistry(Observer* dial_api, |
| const base::TimeDelta& refresh_interval, |
| const base::TimeDelta& expiration, |
| const size_t max_devices) |
| : num_listeners_(0), |
| discovery_generation_(0), |
| registry_generation_(0), |
| last_event_discovery_generation_(0), |
| last_event_registry_generation_(0), |
| label_count_(0), |
| refresh_interval_delta_(refresh_interval), |
| expiration_delta_(expiration), |
| max_devices_(max_devices), |
| dial_api_(dial_api) { |
| DCHECK(max_devices_ > 0); |
| NetworkChangeNotifier::AddNetworkChangeObserver(this); |
| } |
| |
| DialRegistry::~DialRegistry() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_EQ(0, num_listeners_); |
| NetworkChangeNotifier::RemoveNetworkChangeObserver(this); |
| } |
| |
| DialService* DialRegistry::CreateDialService() { |
| DCHECK(g_browser_process->net_log()); |
| return new DialServiceImpl(g_browser_process->net_log()); |
| } |
| |
| void DialRegistry::ClearDialService() { |
| dial_.reset(); |
| } |
| |
| base::Time DialRegistry::Now() const { |
| return Time::Now(); |
| } |
| |
| void DialRegistry::OnListenerAdded() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (++num_listeners_ == 1) { |
| VLOG(2) << "Listener added; starting periodic discovery."; |
| StartPeriodicDiscovery(); |
| } |
| } |
| |
| void DialRegistry::OnListenerRemoved() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(num_listeners_ > 0); |
| if (--num_listeners_ == 0) { |
| VLOG(2) << "Listeners removed; stopping periodic discovery."; |
| StopPeriodicDiscovery(); |
| } |
| } |
| |
| bool DialRegistry::ReadyToDiscover() { |
| if (num_listeners_ == 0) { |
| dial_api_->OnDialError(DIAL_NO_LISTENERS); |
| return false; |
| } |
| if (NetworkChangeNotifier::IsOffline()) { |
| dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED); |
| return false; |
| } |
| if (NetworkChangeNotifier::IsConnectionCellular( |
| NetworkChangeNotifier::GetConnectionType())) { |
| dial_api_->OnDialError(DIAL_CELLULAR_NETWORK); |
| return false; |
| } |
| return true; |
| } |
| |
| bool DialRegistry::DiscoverNow() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!ReadyToDiscover()) { |
| return false; |
| } |
| if (!dial_.get()) { |
| dial_api_->OnDialError(DIAL_UNKNOWN); |
| return false; |
| } |
| |
| if (!dial_->HasObserver(this)) |
| NOTREACHED() << "DiscoverNow() called without observing dial_"; |
| discovery_generation_++; |
| return dial_->Discover(); |
| } |
| |
| void DialRegistry::StartPeriodicDiscovery() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!ReadyToDiscover() || dial_.get()) |
| return; |
| |
| dial_.reset(CreateDialService()); |
| dial_->AddObserver(this); |
| DoDiscovery(); |
| repeating_timer_.Start(FROM_HERE, |
| refresh_interval_delta_, |
| this, |
| &DialRegistry::DoDiscovery); |
| } |
| |
| void DialRegistry::DoDiscovery() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(dial_.get()); |
| discovery_generation_++; |
| VLOG(2) << "About to discover! Generation = " << discovery_generation_; |
| dial_->Discover(); |
| } |
| |
| void DialRegistry::StopPeriodicDiscovery() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!dial_.get()) |
| return; |
| |
| repeating_timer_.Stop(); |
| dial_->RemoveObserver(this); |
| ClearDialService(); |
| } |
| |
| bool DialRegistry::PruneExpiredDevices() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| bool pruned_device = false; |
| DeviceByLabelMap::iterator i = device_by_label_map_.begin(); |
| while (i != device_by_label_map_.end()) { |
| linked_ptr<DialDeviceData> device = i->second; |
| if (IsDeviceExpired(*device)) { |
| VLOG(2) << "Device " << device->label() << " expired, removing"; |
| const size_t num_erased_by_id = |
| device_by_id_map_.erase(device->device_id()); |
| DCHECK_EQ(num_erased_by_id, 1u); |
| device_by_label_map_.erase(i++); |
| pruned_device = true; |
| } else { |
| ++i; |
| } |
| } |
| return pruned_device; |
| } |
| |
| bool DialRegistry::IsDeviceExpired(const DialDeviceData& device) const { |
| Time now = Now(); |
| |
| // Check against our default expiration timeout. |
| Time default_expiration_time = device.response_time() + expiration_delta_; |
| if (now > default_expiration_time) |
| return true; |
| |
| // Check against the device's cache-control header, if set. |
| if (device.has_max_age()) { |
| Time max_age_expiration_time = |
| device.response_time() + TimeDelta::FromSeconds(device.max_age()); |
| if (now > max_age_expiration_time) |
| return true; |
| } |
| return false; |
| } |
| |
| void DialRegistry::Clear() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| device_by_id_map_.clear(); |
| device_by_label_map_.clear(); |
| registry_generation_++; |
| } |
| |
| void DialRegistry::MaybeSendEvent() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // We need to send an event if: |
| // (1) We haven't sent one yet in this round of discovery, or |
| // (2) The device list changed since the last MaybeSendEvent. |
| bool needs_event = |
| (last_event_discovery_generation_ < discovery_generation_ || |
| last_event_registry_generation_ < registry_generation_); |
| VLOG(2) << "ledg = " << last_event_discovery_generation_ << ", dg = " |
| << discovery_generation_ |
| << ", lerg = " << last_event_registry_generation_ << ", rg = " |
| << registry_generation_ |
| << ", needs_event = " << needs_event; |
| if (!needs_event) |
| return; |
| |
| DeviceList device_list; |
| for (DeviceByLabelMap::const_iterator i = device_by_label_map_.begin(); |
| i != device_by_label_map_.end(); i++) { |
| device_list.push_back(*(i->second)); |
| } |
| dial_api_->OnDialDeviceEvent(device_list); |
| |
| // Reset watermarks. |
| last_event_discovery_generation_ = discovery_generation_; |
| last_event_registry_generation_ = registry_generation_; |
| } |
| |
| std::string DialRegistry::NextLabel() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return base::IntToString(++label_count_); |
| } |
| |
| void DialRegistry::OnDiscoveryRequest(DialService* service) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| MaybeSendEvent(); |
| } |
| |
| void DialRegistry::OnDeviceDiscovered(DialService* service, |
| const DialDeviceData& device) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Adds |device| to our list of devices or updates an existing device, unless |
| // |device| is a duplicate. Returns true if the list was modified and |
| // increments the list generation. |
| linked_ptr<DialDeviceData> device_data(new DialDeviceData(device)); |
| DCHECK(!device_data->device_id().empty()); |
| DCHECK(device_data->label().empty()); |
| |
| bool did_modify_list = false; |
| DeviceByIdMap::iterator lookup_result = |
| device_by_id_map_.find(device_data->device_id()); |
| |
| if (lookup_result != device_by_id_map_.end()) { |
| VLOG(2) << "Found device " << device_data->device_id() << ", merging"; |
| |
| // Already have previous response. Merge in data from this response and |
| // track if there were any API visible changes. |
| did_modify_list = lookup_result->second->UpdateFrom(*device_data); |
| } else { |
| did_modify_list = MaybeAddDevice(device_data); |
| } |
| |
| if (did_modify_list) |
| registry_generation_++; |
| |
| VLOG(2) << "did_modify_list = " << did_modify_list |
| << ", generation = " << registry_generation_; |
| } |
| |
| bool DialRegistry::MaybeAddDevice( |
| const linked_ptr<DialDeviceData>& device_data) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (device_by_id_map_.size() == max_devices_) { |
| DLOG(WARNING) << "Maximum registry size reached. Cannot add device."; |
| return false; |
| } |
| device_data->set_label(NextLabel()); |
| device_by_id_map_[device_data->device_id()] = device_data; |
| device_by_label_map_[device_data->label()] = device_data; |
| VLOG(2) << "Added device, id = " << device_data->device_id() |
| << ", label = " << device_data->label(); |
| return true; |
| } |
| |
| void DialRegistry::OnDiscoveryFinished(DialService* service) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (PruneExpiredDevices()) |
| registry_generation_++; |
| MaybeSendEvent(); |
| } |
| |
| void DialRegistry::OnError(DialService* service, |
| const DialService::DialServiceErrorCode& code) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| switch (code) { |
| case DialService::DIAL_SERVICE_SOCKET_ERROR: |
| dial_api_->OnDialError(DIAL_SOCKET_ERROR); |
| break; |
| case DialService::DIAL_SERVICE_NO_INTERFACES: |
| dial_api_->OnDialError(DIAL_NO_INTERFACES); |
| break; |
| default: |
| NOTREACHED(); |
| dial_api_->OnDialError(DIAL_UNKNOWN); |
| break; |
| } |
| } |
| |
| void DialRegistry::OnNetworkChanged( |
| NetworkChangeNotifier::ConnectionType type) { |
| switch (type) { |
| case NetworkChangeNotifier::CONNECTION_NONE: |
| if (dial_.get()) { |
| VLOG(2) << "Lost connection, shutting down discovery and clearing" |
| << " list."; |
| dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED); |
| |
| StopPeriodicDiscovery(); |
| // TODO(justinlin): As an optimization, we can probably keep our device |
| // list around and restore it if we reconnected to the exact same |
| // network. |
| Clear(); |
| MaybeSendEvent(); |
| } |
| break; |
| case NetworkChangeNotifier::CONNECTION_2G: |
| case NetworkChangeNotifier::CONNECTION_3G: |
| case NetworkChangeNotifier::CONNECTION_4G: |
| case NetworkChangeNotifier::CONNECTION_ETHERNET: |
| case NetworkChangeNotifier::CONNECTION_WIFI: |
| case NetworkChangeNotifier::CONNECTION_UNKNOWN: |
| if (!dial_.get()) { |
| VLOG(2) << "Connection detected, restarting discovery."; |
| StartPeriodicDiscovery(); |
| } |
| break; |
| } |
| } |
| |
| } // namespace extensions |