root/chrome/browser/extensions/api/alarms/alarm_manager.cc

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

DEFINITIONS

This source file includes following definitions.
  1. kDefaultMinPollPeriod
  2. OnAlarm
  3. TimeDeltaFromDelay
  4. AlarmsFromValue
  5. AlarmsToValue
  6. delegate_
  7. AddAlarm
  8. GetAlarm
  9. GetAllAlarms
  10. RemoveAlarm
  11. RemoveAllAlarms
  12. AddAlarmWhenReady
  13. GetAlarmWhenReady
  14. GetAllAlarmsWhenReady
  15. RemoveAlarmWhenReady
  16. RemoveAllAlarmsWhenReady
  17. GetAlarmIterator
  18. SetClockForTesting
  19. GetFactoryInstance
  20. Get
  21. RemoveAlarmIterator
  22. OnAlarm
  23. AddAlarmImpl
  24. WriteToStorage
  25. ReadFromStorage
  26. SetNextPollTime
  27. ScheduleNextPoll
  28. PollAlarms
  29. RemoveAllOnUninstallCallback
  30. RunWhenReady
  31. Observe

// 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/alarms/alarm_manager.h"

#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/message_loop/message_loop.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/value_conversions.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/state_store.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/alarms.h"
#include "content/public/browser/notification_service.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_system.h"

namespace extensions {

namespace alarms = api::alarms;

namespace {

// A list of alarms that this extension has set.
const char kRegisteredAlarms[] = "alarms";
const char kAlarmGranularity[] = "granularity";

// The minimum period between polling for alarms to run.
const base::TimeDelta kDefaultMinPollPeriod() {
  return base::TimeDelta::FromDays(1);
}

class DefaultAlarmDelegate : public AlarmManager::Delegate {
 public:
  explicit DefaultAlarmDelegate(content::BrowserContext* context)
      : browser_context_(context) {}
  virtual ~DefaultAlarmDelegate() {}

  virtual void OnAlarm(const std::string& extension_id,
                       const Alarm& alarm) OVERRIDE {
    scoped_ptr<base::ListValue> args(new base::ListValue());
    args->Append(alarm.js_alarm->ToValue().release());
    scoped_ptr<Event> event(new Event(alarms::OnAlarm::kEventName,
                                      args.Pass()));
    ExtensionSystem::Get(browser_context_)
        ->event_router()
        ->DispatchEventToExtension(extension_id, event.Pass());
  }

 private:
  content::BrowserContext* browser_context_;
};

// Creates a TimeDelta from a delay as specified in the API.
base::TimeDelta TimeDeltaFromDelay(double delay_in_minutes) {
  return base::TimeDelta::FromMicroseconds(
      delay_in_minutes * base::Time::kMicrosecondsPerMinute);
}

std::vector<Alarm> AlarmsFromValue(const base::ListValue* list) {
  std::vector<Alarm> alarms;
  for (size_t i = 0; i < list->GetSize(); ++i) {
    const base::DictionaryValue* alarm_dict = NULL;
    Alarm alarm;
    if (list->GetDictionary(i, &alarm_dict) &&
        api::alarms::Alarm::Populate(*alarm_dict, alarm.js_alarm.get())) {
      const base::Value* time_value = NULL;
      if (alarm_dict->Get(kAlarmGranularity, &time_value))
        base::GetValueAsTimeDelta(*time_value, &alarm.granularity);
      alarms.push_back(alarm);
    }
  }
  return alarms;
}

scoped_ptr<base::ListValue> AlarmsToValue(const std::vector<Alarm>& alarms) {
  scoped_ptr<base::ListValue> list(new base::ListValue());
  for (size_t i = 0; i < alarms.size(); ++i) {
    scoped_ptr<base::DictionaryValue> alarm =
        alarms[i].js_alarm->ToValue().Pass();
    alarm->Set(kAlarmGranularity,
               base::CreateTimeDeltaValue(alarms[i].granularity));
    list->Append(alarm.release());
  }
  return list.Pass();
}


}  // namespace

// AlarmManager

AlarmManager::AlarmManager(content::BrowserContext* context)
    : profile_(Profile::FromBrowserContext(context)),
      clock_(new base::DefaultClock()),
      delegate_(new DefaultAlarmDelegate(context)) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
                 content::Source<Profile>(profile_));
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
                 content::Source<Profile>(profile_));

  StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
  if (storage)
    storage->RegisterKey(kRegisteredAlarms);
}

AlarmManager::~AlarmManager() {
}

void AlarmManager::AddAlarm(const std::string& extension_id,
                            const Alarm& alarm,
                            const AddAlarmCallback& callback) {
  RunWhenReady(extension_id, base::Bind(
      &AlarmManager::AddAlarmWhenReady, AsWeakPtr(), alarm, callback));
}

void AlarmManager::GetAlarm(const std::string& extension_id,
                            const std::string& name,
                            const GetAlarmCallback& callback) {
  RunWhenReady(extension_id, base::Bind(
      &AlarmManager::GetAlarmWhenReady, AsWeakPtr(), name, callback));
}

void AlarmManager::GetAllAlarms(
    const std::string& extension_id, const GetAllAlarmsCallback& callback) {
  RunWhenReady(extension_id, base::Bind(
      &AlarmManager::GetAllAlarmsWhenReady, AsWeakPtr(), callback));
}

void AlarmManager::RemoveAlarm(const std::string& extension_id,
                               const std::string& name,
                               const RemoveAlarmCallback& callback) {
  RunWhenReady(extension_id, base::Bind(
      &AlarmManager::RemoveAlarmWhenReady, AsWeakPtr(), name, callback));
}

void AlarmManager::RemoveAllAlarms(const std::string& extension_id,
                                   const RemoveAllAlarmsCallback& callback) {
  RunWhenReady(extension_id, base::Bind(
      &AlarmManager::RemoveAllAlarmsWhenReady, AsWeakPtr(), callback));
}

void AlarmManager::AddAlarmWhenReady(const Alarm& alarm,
                                     const AddAlarmCallback& callback,
                                     const std::string& extension_id) {
  AddAlarmImpl(extension_id, alarm);
  WriteToStorage(extension_id);
  callback.Run();
}

void AlarmManager::GetAlarmWhenReady(const std::string& name,
                                     const GetAlarmCallback& callback,
                                     const std::string& extension_id) {
  AlarmIterator it = GetAlarmIterator(extension_id, name);
  callback.Run(it.first != alarms_.end() ? &*it.second : NULL);
}

void AlarmManager::GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback,
                                         const std::string& extension_id) {
  AlarmMap::iterator list = alarms_.find(extension_id);
  callback.Run(list != alarms_.end() ? &list->second : NULL);
}

void AlarmManager::RemoveAlarmWhenReady(const std::string& name,
                                        const RemoveAlarmCallback& callback,
                                        const std::string& extension_id) {
  AlarmIterator it = GetAlarmIterator(extension_id, name);
  if (it.first == alarms_.end()) {
    callback.Run(false);
    return;
  }

  RemoveAlarmIterator(it);
  WriteToStorage(extension_id);
  callback.Run(true);
}

void AlarmManager::RemoveAllAlarmsWhenReady(
    const RemoveAllAlarmsCallback& callback, const std::string& extension_id) {
  AlarmMap::iterator list = alarms_.find(extension_id);
  if (list != alarms_.end()) {
    // Note: I'm using indices rather than iterators here because
    // RemoveAlarmIterator will delete the list when it becomes empty.
    for (size_t i = 0, size = list->second.size(); i < size; ++i)
      RemoveAlarmIterator(AlarmIterator(list, list->second.begin()));

    CHECK(alarms_.find(extension_id) == alarms_.end());
    WriteToStorage(extension_id);
  }
  callback.Run();
}

AlarmManager::AlarmIterator AlarmManager::GetAlarmIterator(
    const std::string& extension_id, const std::string& name) {
  AlarmMap::iterator list = alarms_.find(extension_id);
  if (list == alarms_.end())
    return make_pair(alarms_.end(), AlarmList::iterator());

  for (AlarmList::iterator it = list->second.begin();
       it != list->second.end(); ++it) {
    if (it->js_alarm->name == name)
      return make_pair(list, it);
  }

  return make_pair(alarms_.end(), AlarmList::iterator());
}

void AlarmManager::SetClockForTesting(base::Clock* clock) {
  clock_.reset(clock);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<AlarmManager> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<AlarmManager>*
AlarmManager::GetFactoryInstance() {
  return g_factory.Pointer();
}

// static
AlarmManager* AlarmManager::Get(Profile* profile) {
  return BrowserContextKeyedAPIFactory<AlarmManager>::Get(profile);
}

void AlarmManager::RemoveAlarmIterator(const AlarmIterator& iter) {
  AlarmList& list = iter.first->second;
  list.erase(iter.second);
  if (list.empty())
    alarms_.erase(iter.first);

  // Cancel the timer if there are no more alarms.
  // We don't need to reschedule the poll otherwise, because in
  // the worst case we would just poll one extra time.
  if (alarms_.empty()) {
    timer_.Stop();
    next_poll_time_ = base::Time();
  }
}

void AlarmManager::OnAlarm(AlarmIterator it) {
  CHECK(it.first != alarms_.end());
  Alarm& alarm = *it.second;
  std::string extension_id_copy(it.first->first);
  delegate_->OnAlarm(extension_id_copy, alarm);

  // Update our scheduled time for the next alarm.
  if (double* period_in_minutes =
      alarm.js_alarm->period_in_minutes.get()) {
    // Get the timer's delay in JS time (i.e., convert it from minutes to
    // milliseconds).
    double period_in_js_time =
        *period_in_minutes * base::Time::kMicrosecondsPerMinute /
        base::Time::kMicrosecondsPerMillisecond;
    // Find out how many periods have transpired since the alarm last went off
    // (it's possible that we missed some).
    int transpired_periods =
        (last_poll_time_.ToJsTime() - alarm.js_alarm->scheduled_time) /
        period_in_js_time;
    // Schedule the alarm for the next period that is in-line with the original
    // scheduling.
    alarm.js_alarm->scheduled_time +=
        period_in_js_time * (transpired_periods + 1);
  } else {
    RemoveAlarmIterator(it);
  }
  WriteToStorage(extension_id_copy);
}

void AlarmManager::AddAlarmImpl(const std::string& extension_id,
                                const Alarm& alarm) {
  // Override any old alarm with the same name.
  AlarmIterator old_alarm = GetAlarmIterator(extension_id,
                                             alarm.js_alarm->name);
  if (old_alarm.first != alarms_.end())
    RemoveAlarmIterator(old_alarm);

  alarms_[extension_id].push_back(alarm);
  base::Time alarm_time =
      base::Time::FromJsTime(alarm.js_alarm->scheduled_time);
  if (next_poll_time_.is_null() || alarm_time < next_poll_time_)
    SetNextPollTime(alarm_time);
}

void AlarmManager::WriteToStorage(const std::string& extension_id) {
  StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
  if (!storage)
    return;

  scoped_ptr<base::Value> alarms;
  AlarmMap::iterator list = alarms_.find(extension_id);
  if (list != alarms_.end())
    alarms.reset(AlarmsToValue(list->second).release());
  else
    alarms.reset(AlarmsToValue(std::vector<Alarm>()).release());
  storage->SetExtensionValue(extension_id, kRegisteredAlarms, alarms.Pass());
}

void AlarmManager::ReadFromStorage(const std::string& extension_id,
                                   scoped_ptr<base::Value> value) {
  base::ListValue* list = NULL;
  if (value.get() && value->GetAsList(&list)) {
    std::vector<Alarm> alarm_states = AlarmsFromValue(list);
    for (size_t i = 0; i < alarm_states.size(); ++i)
      AddAlarmImpl(extension_id, alarm_states[i]);
  }

  ReadyQueue& extension_ready_queue = ready_actions_[extension_id];
  while (!extension_ready_queue.empty()) {
    extension_ready_queue.front().Run(extension_id);
    extension_ready_queue.pop();
  }
  ready_actions_.erase(extension_id);
}

void AlarmManager::SetNextPollTime(const base::Time& time) {
  next_poll_time_ = time;
  timer_.Start(FROM_HERE,
               std::max(base::TimeDelta::FromSeconds(0), time - clock_->Now()),
               this,
               &AlarmManager::PollAlarms);
}

void AlarmManager::ScheduleNextPoll() {
  // If there are no alarms, stop the timer.
  if (alarms_.empty()) {
    timer_.Stop();
    next_poll_time_ = base::Time();
    return;
  }

  // Find the soonest alarm that is scheduled to run and the smallest
  // granularity of any alarm.
  // alarms_ guarantees that none of its contained lists are empty.
  base::Time soonest_alarm_time = base::Time::FromJsTime(
      alarms_.begin()->second.begin()->js_alarm->scheduled_time);
  base::TimeDelta min_granularity = kDefaultMinPollPeriod();
  for (AlarmMap::const_iterator m_it = alarms_.begin(), m_end = alarms_.end();
       m_it != m_end; ++m_it) {
    for (AlarmList::const_iterator l_it = m_it->second.begin();
         l_it != m_it->second.end(); ++l_it) {
      base::Time cur_alarm_time =
          base::Time::FromJsTime(l_it->js_alarm->scheduled_time);
      if (cur_alarm_time < soonest_alarm_time)
        soonest_alarm_time = cur_alarm_time;
      if (l_it->granularity < min_granularity)
        min_granularity = l_it->granularity;
      base::TimeDelta cur_alarm_delta = cur_alarm_time - last_poll_time_;
      if (cur_alarm_delta < l_it->minimum_granularity)
        cur_alarm_delta = l_it->minimum_granularity;
      if (cur_alarm_delta < min_granularity)
        min_granularity = cur_alarm_delta;
    }
  }

  base::Time next_poll(last_poll_time_ + min_granularity);
  // If the next alarm is more than min_granularity in the future, wait for it.
  // Otherwise, only poll as often as min_granularity.
  // As a special case, if we've never checked for an alarm before
  // (e.g. during startup), let alarms fire asap.
  if (last_poll_time_.is_null() || next_poll < soonest_alarm_time)
    next_poll = soonest_alarm_time;

  // Schedule the poll.
  SetNextPollTime(next_poll);
}

void AlarmManager::PollAlarms() {
  last_poll_time_ = clock_->Now();

  // Run any alarms scheduled in the past. OnAlarm uses vector::erase to remove
  // elements from the AlarmList, and map::erase to remove AlarmLists from the
  // AlarmMap.
  for (AlarmMap::iterator m_it = alarms_.begin(), m_end = alarms_.end();
       m_it != m_end;) {
    AlarmMap::iterator cur_extension = m_it++;

    // Iterate (a) backwards so that removing elements doesn't affect
    // upcoming iterations, and (b) with indices so that if the last
    // iteration destroys the AlarmList, I'm not about to use the end
    // iterator that the destruction invalidates.
    for (size_t i = cur_extension->second.size(); i > 0; --i) {
      AlarmList::iterator cur_alarm = cur_extension->second.begin() + i - 1;
      if (base::Time::FromJsTime(cur_alarm->js_alarm->scheduled_time) <=
          last_poll_time_) {
        OnAlarm(make_pair(cur_extension, cur_alarm));
      }
    }
  }

  ScheduleNextPoll();
}

static void RemoveAllOnUninstallCallback() {}

void AlarmManager::RunWhenReady(
    const std::string& extension_id, const ReadyAction& action) {
  ReadyMap::iterator it = ready_actions_.find(extension_id);

  if (it == ready_actions_.end())
    action.Run(extension_id);
  else
    it->second.push(action);
}

void AlarmManager::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_EXTENSION_LOADED: {
      const Extension* extension =
          content::Details<const Extension>(details).ptr();
      StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
      if (storage) {
        ready_actions_.insert(
            ReadyMap::value_type(extension->id(), ReadyQueue()));
        storage->GetExtensionValue(extension->id(), kRegisteredAlarms,
            base::Bind(&AlarmManager::ReadFromStorage,
                       AsWeakPtr(), extension->id()));
      }
      break;
    }
    case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
      const Extension* extension =
          content::Details<const Extension>(details).ptr();
      RemoveAllAlarms(
          extension->id(), base::Bind(RemoveAllOnUninstallCallback));
      break;
    }
    default:
      NOTREACHED();
      break;
  }
}

// AlarmManager::Alarm

Alarm::Alarm()
    : js_alarm(new api::alarms::Alarm()) {
}

Alarm::Alarm(const std::string& name,
             const api::alarms::AlarmCreateInfo& create_info,
             base::TimeDelta min_granularity,
             base::Time now)
    : js_alarm(new api::alarms::Alarm()) {
  js_alarm->name = name;
  minimum_granularity = min_granularity;

  if (create_info.when.get()) {
    // Absolute scheduling.
    js_alarm->scheduled_time = *create_info.when;
    granularity = base::Time::FromJsTime(js_alarm->scheduled_time) - now;
  } else {
    // Relative scheduling.
    double* delay_in_minutes = create_info.delay_in_minutes.get();
    if (delay_in_minutes == NULL)
      delay_in_minutes = create_info.period_in_minutes.get();
    CHECK(delay_in_minutes != NULL)
        << "ValidateAlarmCreateInfo in alarms_api.cc should have "
        << "prevented this call.";
    base::TimeDelta delay = TimeDeltaFromDelay(*delay_in_minutes);
    js_alarm->scheduled_time = (now + delay).ToJsTime();
    granularity = delay;
  }

  if (granularity < min_granularity)
    granularity = min_granularity;

  // Check for repetition.
  if (create_info.period_in_minutes.get()) {
    js_alarm->period_in_minutes.reset(
        new double(*create_info.period_in_minutes));
  }
}

Alarm::~Alarm() {
}

}  // namespace extensions

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