/*
* Copyright 2014 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*
* Authors:
* Charles Kerr
*/
#include
#include
#include
namespace ayatana {
namespace indicator {
namespace datetime {
/***
****
***/
class SimpleAlarmQueue::Impl
{
public:
Impl(const std::shared_ptr& clock,
const std::shared_ptr& planner,
const std::shared_ptr& timer):
m_clock{clock},
m_planner{planner},
m_timer{timer},
m_datetime{clock->localtime()}
{
m_planner->appointments().changed().connect([this](const std::vector&){
g_debug("AlarmQueue %p calling requeue() due to appointments changed", this);
requeue();
});
m_clock->minute_changed.connect([this]{
const auto now = m_clock->localtime();
constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC;
const bool clock_jumped = std::abs(now - m_datetime) > skew_threshold_usec;
m_datetime = now;
if (clock_jumped) {
g_debug("AlarmQueue %p calling requeue() due to clock skew", this);
requeue();
}
});
m_timer->timeout().connect([this](){
g_debug("AlarmQueue %p calling requeue() due to timeout", this);
requeue();
});
requeue();
}
~Impl()
{
}
core::Signal& alarm_reached()
{
return m_alarm_reached;
}
private:
void requeue()
{
const auto appointments = m_planner->appointments().get();
const Alarm* alarm;
// kick any current alarms
for (const auto& appointment : appointments)
{
if ((alarm = appointment_get_current_alarm(appointment)))
{
m_triggered.insert(std::make_pair(appointment.uid, alarm->time));
m_alarm_reached(appointment, *alarm);
}
}
// idle until the next alarm
if ((alarm = find_next_alarm(appointments)))
{
g_debug ("setting timer to wake up for next appointment '%s' at %s",
alarm->text.c_str(),
alarm->time.format("%F %T").c_str());
m_timer->set_wakeup_time(alarm->time);
}
}
bool already_triggered (const Appointment& appt, const Alarm& alarm) const
{
const std::pair key{appt.uid, alarm.time};
return m_triggered.count(key) != 0;
}
// return the next Alarm (if any) that will kick now or in the future
const Alarm* find_next_alarm(const std::vector& appointments) const
{
const Alarm* best {};
const auto now = m_clock->localtime();
const auto beginning_of_minute = now.start_of_minute();
g_debug ("planner has %zu appointments in it", (size_t)appointments.size());
for(const auto& appointment : appointments)
{
for(const auto& alarm : appointment.alarms)
{
if (already_triggered(appointment, alarm))
continue;
if (alarm.time < beginning_of_minute) // has this one already passed?
continue;
if (best && (best->time < alarm.time)) // do we already have a better match?
continue;
best = &alarm;
}
}
return best;
}
// return the Appointment's current Alarm (if any)
const Alarm* appointment_get_current_alarm(const Appointment& appointment) const
{
const auto now = m_clock->localtime();
for (const auto& alarm : appointment.alarms)
if (!already_triggered(appointment, alarm) && DateTime::is_same_minute(now, alarm.time))
return &alarm;
return nullptr;
}
std::set> m_triggered;
const std::shared_ptr m_clock;
const std::shared_ptr m_planner;
const std::shared_ptr m_timer;
core::Signal m_alarm_reached;
DateTime m_datetime;
};
/***
**** Public API
***/
SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr& clock,
const std::shared_ptr& planner,
const std::shared_ptr& timer):
impl{new Impl{clock, planner, timer}}
{
}
SimpleAlarmQueue::~SimpleAlarmQueue()
{
}
core::Signal&
SimpleAlarmQueue::alarm_reached()
{
return impl->alarm_reached();
}
/***
****
***/
} // namespace datetime
} // namespace indicator
} // namespace ayatana