root/chrome/browser/extensions/api/dial/dial_registry.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dial_api_
  2. CreateDialService
  3. ClearDialService
  4. Now
  5. OnListenerAdded
  6. OnListenerRemoved
  7. ReadyToDiscover
  8. DiscoverNow
  9. StartPeriodicDiscovery
  10. DoDiscovery
  11. StopPeriodicDiscovery
  12. PruneExpiredDevices
  13. IsDeviceExpired
  14. Clear
  15. MaybeSendEvent
  16. NextLabel
  17. OnDiscoveryRequest
  18. OnDeviceDiscovered
  19. MaybeAddDevice
  20. OnDiscoveryFinished
  21. OnError
  22. OnNetworkChanged

// 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/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

/* [<][>][^][v][top][bottom][index][help] */